mirror of
https://github.com/meshtastic/python.git
synced 2026-02-23 02:06:13 -05:00
Merge pull request #607 from geeksville/pr-powermon
PowerMon/StructuredLogging support
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -13,7 +13,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
@@ -56,7 +55,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
|
||||
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]
|
||||
|
||||
|
||||
80
.vscode/launch.json
vendored
80
.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,7 +30,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic tunnel",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic set chan",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic listen",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -62,7 +62,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug getPref",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug getPref telemetry",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug info",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -94,7 +94,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug set region",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -102,7 +102,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug set bluetooth fixed pin",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -110,7 +110,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug get bluetooth fixed pin",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -118,15 +118,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 +134,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug setPref telemetry.environment_screen_enabled",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -142,7 +142,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic debug setPref telemetry",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -150,7 +150,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic setpref",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -158,7 +158,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic --ch-set",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
{
|
||||
"name": "meshtastic seturl",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -176,15 +176,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-out", "default", "--power-ppk2-meter", "--power-stress", "--power-voltage", "3.3", "--seriallog"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic test",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -192,7 +216,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic settime",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -200,7 +224,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic sendtext",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
@@ -208,7 +232,7 @@
|
||||
},
|
||||
{
|
||||
"name": "meshtastic showNodes",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
|
||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,8 +1,16 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"bitmask",
|
||||
"boardid",
|
||||
"Meshtastic",
|
||||
"milliwatt",
|
||||
"portnums",
|
||||
"powermon",
|
||||
"POWERSTRESS",
|
||||
"pyarrow",
|
||||
"TORADIO",
|
||||
"Vids"
|
||||
],
|
||||
"python.pythonPath": "/usr/bin/python3"
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
"flake8.enabled" : false // 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,
|
||||
@@ -228,6 +230,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,13 +1,17 @@
|
||||
#!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 yaml
|
||||
@@ -22,6 +26,10 @@ from meshtastic import remote_hardware, BROADCAST_ADDR
|
||||
from meshtastic.version import get_active_version
|
||||
from meshtastic.ble_interface import BLEInterface
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
from meshtastic.powermon import RidenPowerSupply, PPK2PowerSupply, SimPowerSupply, PowerStress, PowerMeter
|
||||
from meshtastic.slog import LogSet
|
||||
|
||||
meter: Optional[PowerMeter] = None
|
||||
|
||||
def onReceive(packet, interface):
|
||||
"""Callback invoked when a packet arrives"""
|
||||
@@ -847,6 +855,15 @@ def onConnected(interface):
|
||||
qr = pyqrcode.create(url)
|
||||
print(qr.terminal())
|
||||
|
||||
if args.slog or args.power_stress:
|
||||
# Setup loggers
|
||||
global meter # pylint: disable=global-variable-not-assigned
|
||||
LogSet(interface, args.slog if args.slog != 'default' else None, meter)
|
||||
|
||||
if args.power_stress:
|
||||
stress = PowerStress(interface)
|
||||
stress.run()
|
||||
|
||||
if args.listen:
|
||||
closeNow = False
|
||||
|
||||
@@ -984,9 +1001,32 @@ def export_config(interface):
|
||||
print(config)
|
||||
return config
|
||||
|
||||
def create_power_meter():
|
||||
"""Setup the power meter."""
|
||||
|
||||
global meter # pylint: disable=global-statement
|
||||
args = mt_config.args
|
||||
if args.power_riden:
|
||||
meter = RidenPowerSupply(args.power_riden)
|
||||
elif args.power_ppk2_supply or args.power_ppk2_meter:
|
||||
meter = PPK2PowerSupply()
|
||||
meter.setIsSupply(args.power_ppk2_supply)
|
||||
elif args.power_sim:
|
||||
meter = SimPowerSupply()
|
||||
|
||||
if meter and args.power_voltage:
|
||||
v = float(args.power_voltage)
|
||||
if v < 0.5 or v >5.0:
|
||||
meshtastic.util.our_exit("Voltage must be between 1.0 and 5.0")
|
||||
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...")
|
||||
|
||||
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
|
||||
@@ -1003,6 +1043,8 @@ def common():
|
||||
meshtastic.util.support_info()
|
||||
meshtastic.util.our_exit("", 0)
|
||||
|
||||
create_power_meter()
|
||||
|
||||
if args.ch_index is not None:
|
||||
channelIndex = int(args.ch_index)
|
||||
mt_config.channel_index = channelIndex
|
||||
@@ -1084,7 +1126,6 @@ def common():
|
||||
f"Error connecting to localhost:{ex}", 1
|
||||
)
|
||||
|
||||
|
||||
# We assume client is fully connected now
|
||||
onConnected(client)
|
||||
|
||||
@@ -1092,8 +1133,11 @@ def common():
|
||||
if (
|
||||
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)
|
||||
@@ -1498,6 +1542,58 @@ 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",
|
||||
|
||||
1
meshtastic/analysis/__init__.py
Normal file
1
meshtastic/analysis/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Post-run analysis tools for meshtastic."""
|
||||
@@ -7,7 +7,6 @@ import struct
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import List, Optional
|
||||
import print_color # type: ignore[import-untyped]
|
||||
|
||||
from bleak import BleakClient, BleakScanner, BLEDevice
|
||||
from bleak.exc import BleakDBusError, BleakError
|
||||
@@ -98,30 +97,11 @@ class BLEInterface(MeshInterface):
|
||||
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)
|
||||
self._handleLogLine(message)
|
||||
|
||||
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]:
|
||||
|
||||
@@ -16,6 +16,7 @@ from typing import Any, Callable, Dict, List, Optional, Union
|
||||
import google.protobuf.json_format
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
from tabulate import tabulate
|
||||
import print_color # type: ignore[import-untyped]
|
||||
|
||||
import meshtastic.node
|
||||
|
||||
@@ -31,7 +32,7 @@ from meshtastic import (
|
||||
NODELESS_WANT_CONFIG_ID,
|
||||
ResponseHandler,
|
||||
protocols,
|
||||
publishingThread,
|
||||
publishingThread
|
||||
)
|
||||
from meshtastic.util import (
|
||||
Acknowledgment,
|
||||
@@ -43,7 +44,6 @@ from meshtastic.util import (
|
||||
message_to_json,
|
||||
)
|
||||
|
||||
|
||||
def _timeago(delta_secs: int) -> str:
|
||||
"""Convert a number of seconds in the past into a short, friendly string
|
||||
e.g. "now", "30 sec ago", "1 hour ago"
|
||||
@@ -117,6 +117,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 +142,33 @@ 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."""
|
||||
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()})"
|
||||
@@ -924,7 +957,8 @@ 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)
|
||||
|
||||
|
||||
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)."""
|
||||
156
meshtastic/powermon/ppk2.py
Normal file
156
meshtastic/powermon/ppk2.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""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
|
||||
|
||||
# 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
|
||||
|
||||
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:
|
||||
# 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:
|
||||
self.current_min = samples[
|
||||
0
|
||||
] # we need at least one sample to get an initial min
|
||||
self.current_max = max(self.current_max, max(samples))
|
||||
self.current_min = min(self.current_min, min(samples))
|
||||
self.current_sum += sum(samples)
|
||||
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))
|
||||
|
||||
time.sleep(0.01) # FIXME figure out correct sleep duration
|
||||
|
||||
def get_min_current_mA(self):
|
||||
"""Returns max current in mA (since last call to this method)."""
|
||||
return self.current_min / 1000
|
||||
|
||||
def get_max_current_mA(self):
|
||||
"""Returns max current in mA (since last call to this method)."""
|
||||
return self.current_max / 1000
|
||||
|
||||
def get_average_current_mA(self):
|
||||
"""Returns average current in mA (since last call to this method)."""
|
||||
if self.current_num_samples == 0:
|
||||
return 0
|
||||
else:
|
||||
return (
|
||||
self.current_sum / self.current_num_samples / 1000
|
||||
) # measurements are in microamperes, divide by 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_max = 0
|
||||
self.current_min = 0
|
||||
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
|
||||
|
||||
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, s: bool):
|
||||
"""If in supply mode we will provide power ourself, otherwise we are just an amp meter."""
|
||||
|
||||
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 s
|
||||
): # 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()))
|
||||
81
meshtastic/powermon/stress.py
Normal file
81
meshtastic/powermon/stress.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""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,
|
||||
)
|
||||
|
||||
|
||||
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."""
|
||||
# Send the power stress command
|
||||
gotAck = False
|
||||
|
||||
def onResponse(packet: dict): # pylint: disable=unused-argument
|
||||
nonlocal gotAck
|
||||
gotAck = True
|
||||
|
||||
logging.info("Starting power stress test, attempting to contact UUT...")
|
||||
self.client.sendPowerStress(
|
||||
powermon_pb2.PowerStressMessage.PRINT_INFO, onResponse=onResponse
|
||||
)
|
||||
|
||||
# Wait for the response
|
||||
while not gotAck:
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.info("Power stress test complete.")
|
||||
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
|
||||
"""
|
||||
|
||||
@@ -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
@@ -307,6 +307,23 @@ 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
|
||||
"""
|
||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -598,6 +615,23 @@ 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
|
||||
"""
|
||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2058,6 +2092,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,
|
||||
@@ -2145,6 +2180,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,
|
||||
*,
|
||||
@@ -2162,13 +2203,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):
|
||||
"""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -13,7 +13,7 @@ _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\x42\x63\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
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)
|
||||
@@ -25,4 +25,8 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
_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)
|
||||
|
||||
@@ -95,3 +95,127 @@ class PowerMon(google.protobuf.message.Message):
|
||||
) -> 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
|
||||
|
||||
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
|
||||
79
meshtastic/slog/arrow.py
Normal file
79
meshtastic/slog/arrow.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Utilities for Apache Arrow serialization."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
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: pa.Schema | None = None # haven't yet learned the schema
|
||||
self.writer: pa.RecordBatchFileWriter | None = None
|
||||
|
||||
def close(self):
|
||||
"""Close the stream and writes the file as needed."""
|
||||
self._write()
|
||||
if self.writer:
|
||||
self.writer.close()
|
||||
self.sink.close()
|
||||
|
||||
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.schema = pa.Table.from_pylist([self.new_rows[0]]).schema
|
||||
self.writer = pa.ipc.new_stream(self.sink, self.schema)
|
||||
|
||||
self.writer.write_batch(pa.RecordBatch.from_pylist(self.new_rows))
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
208
meshtastic/slog/slog.py
Normal file
208
meshtastic/slog/slog.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""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 typing import Optional
|
||||
|
||||
import parse # type: ignore[import-untyped]
|
||||
import platformdirs
|
||||
from pubsub import pub # type: ignore[import-untyped]
|
||||
|
||||
from meshtastic.mesh_interface import MeshInterface
|
||||
from meshtastic.powermon import PowerMeter
|
||||
|
||||
from .arrow import FeatherWriter
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class LogDef:
|
||||
"""Log definition."""
|
||||
|
||||
code: str # i.e. PM or B or whatever... see meshtastic slog documentation
|
||||
format: parse.Parser # A format string that can be used to parse the arguments
|
||||
|
||||
def __init__(self, code: str, fmt: str) -> None:
|
||||
"""Initialize the LogDef object.
|
||||
|
||||
code (str): The code.
|
||||
format (str): The format.
|
||||
"""
|
||||
self.code = code
|
||||
self.format = parse.compile(fmt)
|
||||
|
||||
|
||||
"""A dictionary mapping from logdef code to logdef"""
|
||||
log_defs = {
|
||||
d.code: d
|
||||
for d in [
|
||||
LogDef("B", "{boardid:d},{version}"),
|
||||
LogDef("PM", "{bitmask:d},{reason}"),
|
||||
]
|
||||
}
|
||||
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.2) -> 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 _logging_thread(self) -> None:
|
||||
"""Background thread for logging the current watts reading."""
|
||||
while self.is_logging:
|
||||
d = {
|
||||
"time": datetime.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)
|
||||
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) -> None:
|
||||
"""Initialize the StructuredLogger object.
|
||||
|
||||
client (MeshInterface): The MeshInterface object to monitor.
|
||||
"""
|
||||
self.client = client
|
||||
self.writer = FeatherWriter(f"{dir_path}/slog")
|
||||
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.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
|
||||
"""
|
||||
m = log_regex.match(line)
|
||||
if m:
|
||||
src = m.group(1)
|
||||
args = m.group(2)
|
||||
args += " " # append a space so that if the last arg is an empty str it will still be accepted as a match
|
||||
logging.debug(f"SLog {src}, reason: {args}")
|
||||
if src != "PM":
|
||||
logging.warning(f"Not yet handling structured log {src} (FIXME)")
|
||||
else:
|
||||
d = log_defs.get(src)
|
||||
if d:
|
||||
r = d.format.parse(args) # get the values with the correct types
|
||||
if r:
|
||||
di = r.named
|
||||
di["time"] = datetime.now()
|
||||
self.writer.add_row(di)
|
||||
else:
|
||||
logging.warning(f"Failed to parse slog {line} with {d.format}")
|
||||
else:
|
||||
logging.warning(f"Unknown Structured Log: {line}")
|
||||
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_name = "meshtastic"
|
||||
app_author = "meshtastic"
|
||||
app_dir = platformdirs.user_data_dir(app_name, app_author)
|
||||
dir_name = f"{app_dir}/slogs/{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}/slogs/latest"):
|
||||
os.unlink(f"{app_dir}/slogs/latest")
|
||||
os.symlink(dir_name, f"{app_dir}/slogs/latest", target_is_directory=True)
|
||||
|
||||
self.dir_name = dir_name
|
||||
|
||||
logging.info(f"Writing slogs to {dir_name}")
|
||||
|
||||
self.slog_logger: Optional[StructuredLogger] = StructuredLogger(
|
||||
client, self.dir_name
|
||||
)
|
||||
self.power_logger: Optional[PowerLogger] = (
|
||||
None
|
||||
if not power_meter
|
||||
else PowerLogger(power_meter, f"{self.dir_name}/power")
|
||||
)
|
||||
|
||||
# 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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
2456
poetry.lock
generated
2456
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
Submodule protobufs updated: 4da558d0f7...1198b7dbab
@@ -7,7 +7,7 @@ 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"
|
||||
@@ -21,6 +21,11 @@ pyyaml = "^6.0.1"
|
||||
pypubsub = "^4.0.3"
|
||||
bleak = "^0.21.1"
|
||||
packaging = "^24.0"
|
||||
riden = { git = "https://github.com/geeksville/riden.git#1.2.1" }
|
||||
parse = "^1.20.2"
|
||||
ppk2-api = "^0.9.2"
|
||||
pyarrow = "^16.1.0"
|
||||
platformdirs = "^4.2.2"
|
||||
print-color = "^0.4.6"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
@@ -39,6 +44,26 @@ 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"
|
||||
|
||||
# If you are doing power analysis you probably want these extra devtools
|
||||
[tool.poetry.group.analysis]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.analysis.dependencies]
|
||||
jupyterlab = "^4.2.2"
|
||||
mypy = "^1.10.0"
|
||||
mypy-protobuf = "^3.6.0"
|
||||
types-protobuf = "^5.26.0.20240422"
|
||||
types-tabulate = "^0.9.0.20240106"
|
||||
types-requests = "^2.31.0.20240406"
|
||||
types-setuptools = "^69.5.0.20240423"
|
||||
types-pyyaml = "^6.0.12.20240311"
|
||||
matplotlib = "^3.9.0"
|
||||
ipympl = "^0.9.4"
|
||||
ipywidgets = "^8.1.3"
|
||||
jupyterlab-widgets = "^3.0.11"
|
||||
dash = "^2.17.1"
|
||||
|
||||
[tool.poetry.extras]
|
||||
tunnel = ["pytap2"]
|
||||
@@ -47,6 +72,13 @@ tunnel = ["pytap2"]
|
||||
meshtastic = "meshtastic.__main__:main"
|
||||
mesh-tunnel = "meshtastic.__main__:tunnelMain [tunnel]"
|
||||
|
||||
# "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"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
Reference in New Issue
Block a user