From b41cb7d8dfeee9f5a9e7ebad3a37dd6722f2ba3f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 10:34:16 -0700 Subject: [PATCH 01/57] Merge branch 'pr-poetry' into powermon --- .vscode/launch.json | 12 +- meshtastic/__main__.py | 16 +++ meshtastic/mesh_interface.py | 9 +- meshtastic/observable.py | 35 +++++ meshtastic/power_mon.py | 143 +++++++++++++++++++++ meshtastic/powermon_pb2.py | 28 ++++ meshtastic/powermon_pb2.pyi | 84 ++++++++++++ meshtastic/stream_interface.py | 29 ++++- poetry.lock | 226 ++++++++++++++++++++++++++++++++- protobufs | 2 +- pyproject.toml | 4 +- 11 files changed, 575 insertions(+), 13 deletions(-) create mode 100644 meshtastic/observable.py create mode 100644 meshtastic/power_mon.py create mode 100644 meshtastic/powermon_pb2.py create mode 100644 meshtastic/powermon_pb2.pyi diff --git a/.vscode/launch.json b/.vscode/launch.json index aca86dde..b0937863 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -106,7 +106,7 @@ "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", @@ -164,7 +164,15 @@ "request": "launch", "module": "meshtastic", "justMyCode": true, - "args": ["--debug", "--seriallog", "stdout"] + "args": ["--noproto", "--seriallog", "stdout"] + }, + { + "name": "meshtastic powermon", + "type": "python", + "request": "launch", + "module": "meshtastic", + "justMyCode": false, + "args": ["--power-mon", "/dev/ttyUSB1", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index fa71adcc..3db80ba7 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -21,6 +21,7 @@ from meshtastic.version import get_active_version from meshtastic.ble_interface import BLEInterface from meshtastic.mesh_interface import MeshInterface +from meshtastic.power_mon import PowerMonClient def onReceive(packet, interface): """Callback invoked when a packet arrives""" @@ -1089,6 +1090,10 @@ def common(): # We assume client is fully connected now onConnected(client) + if args.power_mon: + PowerMonClient(args.power_mon, client) + + have_tunnel = platform.system() == "Linux" if ( args.noproto or args.reply or (have_tunnel and args.tunnel) or args.listen @@ -1500,6 +1505,17 @@ def initParser(): action="store_true", ) + group.add_argument( + "--power-mon", + help="Capture any power monitor records. You must use --power-mon /dev/ttyUSBxxx to specify which port the power supply is on", + ) + + 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", + ) + group.add_argument( "--ble-scan", help="Scan for Meshtastic BLE devices", diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 5b3403cc..0bc67ba9 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -29,7 +29,7 @@ NODELESS_WANT_CONFIG_ID, ResponseHandler, protocols, - publishingThread, + publishingThread ) from meshtastic.util import ( Acknowledgment, @@ -40,7 +40,7 @@ stripnl, message_to_json, ) - +from meshtastic.observable import Observable class MeshInterface: # pylint: disable=R0902 """Interface class for meshtastic devices @@ -71,6 +71,7 @@ def __init__(self, debugOut=None, noProto: bool=False, noNodes: bool=False) -> N self.nodes: Optional[Dict[str,Dict]] = None # FIXME self.isConnected: threading.Event = threading.Event() self.noProto: bool = noProto + self.onLogMessage = Observable() 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 @@ -111,6 +112,10 @@ def __exit__(self, exc_type, exc_value, traceback): logging.error(f"Traceback: {traceback}") self.close() + def _handleLogLine(self, line): + """Handle a line of log output from the device.""" + self.onLogMessage.fire(message=line) + def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613 """Show human readable summary about this object""" owner = f"Owner: {self.getLongName()} ({self.getShortName()})" diff --git a/meshtastic/observable.py b/meshtastic/observable.py new file mode 100644 index 00000000..2bd2a5bc --- /dev/null +++ b/meshtastic/observable.py @@ -0,0 +1,35 @@ + + +class Event(object): + """A simple event class.""" + +class Observable(object): + """A class that represents an observable object. + + To publish an event call fire(type="progress", percent=50) or whatever. It will call + """ + + def __init__(self): + """Initialize the Observable object.""" + self.callbacks = [] + + def subscribe(self, callback): + """Subscribe to the observable object. + + Args: + callback (function): The callback function to be called when the event is fired. + """ + self.callbacks.append(callback) + + def fire(self, **attrs): + """Fire the event. + + Args: + **attrs: Arbitrary keyword arguments to be passed to the callback functions. + """ + e = Event() + e.source = self + for k, v in attrs.items(): + setattr(e, k, v) + for fn in self.callbacks: + fn(e) \ No newline at end of file diff --git a/meshtastic/power_mon.py b/meshtastic/power_mon.py new file mode 100644 index 00000000..377ba333 --- /dev/null +++ b/meshtastic/power_mon.py @@ -0,0 +1,143 @@ +"""code logging power consumption of meshtastic devices.""" + +import logging +import re +import atexit +from datetime import datetime + +import pandas as pd +from riden import Riden + +from meshtastic.mesh_interface import MeshInterface +from meshtastic.observable import Event + + +class PowerSupply: + """Interface for talking to programmable bench-top power supplies. + Currently only the Riden supplies are supported (RD6006 tested) + """ + + def __init__(self, portName: str = "/dev/ttyUSB0"): + """Initialize the PowerSupply object.""" + 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()) + + def powerOn(self): + """Power on the supply, with reasonable defaults for meshtastic devices.""" + self.r.set_i_set( + 0.300 + ) # Set current limit to 300mA - hopefully enough to power any board but not break things if there is a short circuit + + # self.r.set_v_set(3.7) # default to a nominal LiPo voltage + self.r.set_v_set(3.3) # my WM1110 devboard header is directly connected to the 3.3V rail + self.r.set_output(1) + + """Get current watts out. + But for most applications you probably want getWattHour() instead (to prevent integration errors from accumulating). + """ + return self.r.get_p_out() + + def getWattHour(self): + """Get current Wh out, since power was turned on.""" + # FIXME: Individual reads seem busted in the riden lib. So for now I just read everything. + self.r.update() + return self.r.wh + # return self.r.get_wh() + + def clearWattHour(self): + """Clear the watt-hour counter FIXME.""" + + +"""Used to match power mon log lines: +INFO | ??:??:?? 7 [Blink] S:PM:0x00000080,reason +""" +logRegex = re.compile(".*S:PM:0x([0-9A-Fa-f]+),(.*)") + + +class PowerMonClient: + """Client for monitoring power consumption of meshtastic devices.""" + + def __init__(self, portName: str, client: MeshInterface) -> None: + """Initialize the PowerMonClient object. + + Args: + client (MeshInterface): The MeshInterface object to monitor. + + """ + self.client = client + self.state = 0 # The current power mon state bitfields + self.columns = ["time", "power", "reason", "bitmask"] + self.rawData = pd.DataFrame(columns=self.columns) # use time as the index + + # for efficiency reasons we keep new data in a list - only adding to rawData when needfed + self.newData = [] + + self.power = power = PowerSupply(portName) + power.powerOn() + + # Used to calculate watts over an interval + self.prevPowerTime = datetime.now() + self.prevWattHour = power.getWattHour() + atexit.register(self._exitHandler) + client.onLogMessage.subscribe(self._onLogMessage) + + def getRawData(self) -> pd.DataFrame: + """Get the raw data. + + Returns: + pd.DataFrame: The raw data. + + """ + df = pd.DataFrame(self.newData, columns=self.columns) + self.rawData = pd.concat([self.rawData, df], ignore_index=True) + self.newData = [] + + return self.rawData + + def _exitHandler(self) -> None: + """Exit handler.""" + fn = "/tmp/powermon.csv" # Find a better place + logging.info(f"Storing PowerMon raw data in {fn}") + self.getRawData().to_csv(fn) + + def _onLogMessage(self, ev: Event) -> None: + """Callback function for handling log messages. + + Args: + message (str): The log message. + + """ + m = logRegex.match(ev.message) + if m: + mask = int(m.group(1), 16) + reason = m.group(2) + logging.debug(f"PowerMon state: 0x{mask:x}, reason: {reason}") + if mask != self.state: + self._storeRecord(mask, reason) + + def _storeRecord(self, mask: int, reason: str) -> None: + """Store a power mon record. + + Args: + mask (int): The power mon state bitfields. + reason (str): The reason for the power mon state change. + + """ + + now = datetime.now() + nowWattHour = self.power.getWattHour() + watts = ( + (nowWattHour - self.prevWattHour) + / (now - self.prevPowerTime).total_seconds() + * 3600 + ) + self.prevPowerTime = now + self.prevWattHour = nowWattHour + self.state = mask + + self.newData.append( + {"time": now, "power": watts, "reason": reason, "bitmask": mask}) + # self.getRawData() diff --git a/meshtastic/powermon_pb2.py b/meshtastic/powermon_pb2.py new file mode 100644 index 00000000..bd91da4f --- /dev/null +++ b/meshtastic/powermon_pb2.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: meshtastic/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\x19meshtastic/powermon.proto\x12\x13meshtastic.PowerMon\"\'\n\x05\x45vent\x12\x13\n\x06states\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_states*\xfb\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\r\n\tCPU_Sleep\x10\x02\x12\r\n\tCPU_Awake\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\x11\n\x0cGPS_LowPower\x10\x80\x10\x12\x14\n\x0fGPS_MediumPower\x10\x80 \x12\x12\n\rGPS_HighPower\x10\x80@Bc\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.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['_STATE']._serialized_start=92 + _globals['_STATE']._serialized_end=343 + _globals['_EVENT']._serialized_start=50 + _globals['_EVENT']._serialized_end=89 +# @@protoc_insertion_point(module_scope) diff --git a/meshtastic/powermon_pb2.pyi b/meshtastic/powermon_pb2.pyi new file mode 100644 index 00000000..df6971a2 --- /dev/null +++ b/meshtastic/powermon_pb2.pyi @@ -0,0 +1,84 @@ +""" +@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 + +class _State: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _StateEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_State.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + CPU_DeepSleep: _State.ValueType # 1 + CPU_Sleep: _State.ValueType # 2 + CPU_Awake: _State.ValueType # 4 + Lora_RXOn: _State.ValueType # 8 + Lora_TXOn: _State.ValueType # 16 + Lora_RXActive: _State.ValueType # 32 + BT_On: _State.ValueType # 64 + LED_On: _State.ValueType # 128 + Screen_On: _State.ValueType # 256 + Screen_Drawing: _State.ValueType # 512 + Wifi_On: _State.ValueType # 1024 + GPS_LowPower: _State.ValueType # 2048 + GPS_MediumPower: _State.ValueType # 4096 + GPS_HighPower: _State.ValueType # 8192 + +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: State.ValueType # 1 +CPU_Sleep: State.ValueType # 2 +CPU_Awake: State.ValueType # 4 +Lora_RXOn: State.ValueType # 8 +Lora_TXOn: State.ValueType # 16 +Lora_RXActive: State.ValueType # 32 +BT_On: State.ValueType # 64 +LED_On: State.ValueType # 128 +Screen_On: State.ValueType # 256 +Screen_Drawing: State.ValueType # 512 +Wifi_On: State.ValueType # 1024 +GPS_LowPower: State.ValueType # 2048 +GPS_MediumPower: State.ValueType # 4096 +GPS_HighPower: State.ValueType # 8192 +global___State = State + +@typing.final +class Event(google.protobuf.message.Message): + """ + the log messages will be short and complete (see PowerMon.Event in the protobufs for details). + something like "PwrMon,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) + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STATES_FIELD_NUMBER: builtins.int + states: builtins.int + """Bitwise-OR of States""" + def __init__( + self, + *, + states: builtins.int | None = ..., + ) -> None: ... + def HasField(self, field_name: typing.Literal["_states", b"_states", "states", b"states"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["_states", b"_states", "states", b"states"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["_states", b"_states"]) -> typing.Literal["states"] | None: ... + +global___Event = Event diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index dea69233..c05a3188 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -39,6 +39,7 @@ def __init__(self, debugOut=None, noProto=False, connectNow=True, noNodes=False) 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) @@ -124,6 +125,26 @@ def close(self): 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 + + if self.debugOut is not None: + self.debugOut.write(utf) + def __reader(self): """The reader thread that reads bytes from our stream""" logging.debug("in __reader()") @@ -146,11 +167,9 @@ def __reader(self): 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: diff --git a/poetry.lock b/poetry.lock index e7286ba6..a045de44 100644 --- a/poetry.lock +++ b/poetry.lock @@ -227,6 +227,20 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -607,6 +621,20 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "modbus-tk" +version = "1.1.3" +description = "Implementation of modbus protocol in python" +optional = false +python-versions = "*" +files = [ + {file = "modbus_tk-1.1.3-py3-none-any.whl", hash = "sha256:2b7afca05292a58371e7a7e4ec2931f2bb9b8a1a7c0295ada758740e72985aef"}, + {file = "modbus_tk-1.1.3.tar.gz", hash = "sha256:690fa7bb86ea978992465d2d61c8b5acc639ce0e8b833a0aa96d4dd172c5644a"}, +] + +[package.dependencies] +pyserial = ">=3.1" + [[package]] name = "mypy" version = "1.10.0" @@ -680,6 +708,60 @@ files = [ protobuf = ">=4.25.3" types-protobuf = ">=4.24" +[[package]] +name = "numpy" +version = "2.0.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-2.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238"}, + {file = "numpy-2.0.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196"}, + {file = "numpy-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc"}, + {file = "numpy-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787"}, + {file = "numpy-2.0.0-cp310-cp310-win32.whl", hash = "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98"}, + {file = "numpy-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609"}, + {file = "numpy-2.0.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4"}, + {file = "numpy-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995"}, + {file = "numpy-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f"}, + {file = "numpy-2.0.0-cp311-cp311-win32.whl", hash = "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f"}, + {file = "numpy-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2"}, + {file = "numpy-2.0.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2"}, + {file = "numpy-2.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95"}, + {file = "numpy-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9"}, + {file = "numpy-2.0.0-cp312-cp312-win32.whl", hash = "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54"}, + {file = "numpy-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f"}, + {file = "numpy-2.0.0-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a"}, + {file = "numpy-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4"}, + {file = "numpy-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44"}, + {file = "numpy-2.0.0-cp39-cp39-win32.whl", hash = "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275"}, + {file = "numpy-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad"}, + {file = "numpy-2.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9"}, + {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, +] + [[package]] name = "packaging" version = "24.1" @@ -691,6 +773,79 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pandas" +version = "2.2.2" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, + {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, + {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, + {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, + {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, + {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, + {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, + {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, + {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, + {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, + {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, + {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, + {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, + {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, + {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, + {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + [[package]] name = "pdoc3" version = "0.10.0" @@ -1062,6 +1217,31 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.2" @@ -1153,6 +1333,26 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "riden" +version = "1.2.0" +description = "A python library for Riden RD power supplies" +optional = false +python-versions = ">=3.7,<4.0" +files = [] +develop = false + +[package.dependencies] +click = "^8.0.3" +modbus_tk = "^1.1.2" +pyserial = "^3.5" + +[package.source] +type = "git" +url = "https://github.com/geeksville/riden.git#ecbda543cf566346dd28558d957b4aa7fe116a83" +reference = "HEAD" +resolved_reference = "ecbda543cf566346dd28558d957b4aa7fe116a83" + [[package]] name = "setuptools" version = "70.1.0" @@ -1168,6 +1368,17 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -1294,6 +1505,17 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + [[package]] name = "urllib3" version = "2.2.2" @@ -1560,5 +1782,5 @@ tunnel = [] [metadata] lock-version = "2.0" -python-versions = "^3.8,<3.13" -content-hash = "2ee78b53bdbdb0d560d2092d28d4379ec0ca18377e9d698f152d708318f9ca23" +python-versions = "^3.9,<3.13" +content-hash = "b893063a1dfe925f901dc45b01d68e255e2bd0ba68bda54de83c875c17b964d8" diff --git a/protobufs b/protobufs index a641c5ce..bfde27a4 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a641c5ce4fca158d18ca3cffc92ac7a10f9b6a04 +Subproject commit bfde27a4ea5cfd0f60ffe961b5ce249aaf54c182 diff --git a/pyproject.toml b/pyproject.toml index 94cbcd25..b2dc9a01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" # was 3.7 for production but, 3.8 is needed for pytap2, 3.9 is needed for pandas, bleak requires a max of 3.13 for some reason pyserial = "^3.5" protobuf = ">=5.26.0" dotmap = "^1.3.30" @@ -29,6 +29,8 @@ types-requests = "^2.31.0.20240406" types-setuptools = "^69.5.0.20240423" types-pyyaml = "^6.0.12.20240311" packaging = "^24.0" +riden = {git = "https://github.com/geeksville/riden.git#ecbda543cf566346dd28558d957b4aa7fe116a83"} +pandas = "^2.2.2" [tool.poetry.group.dev.dependencies] hypothesis = "^6.103.2" From a3462e02094d33ce0d736b9d60e4a7f9490aede5 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 10:20:11 -0700 Subject: [PATCH 02/57] didn't mean to check in built protobufs --- meshtastic/powermon_pb2.py | 28 ------------- meshtastic/powermon_pb2.pyi | 84 ------------------------------------- 2 files changed, 112 deletions(-) delete mode 100644 meshtastic/powermon_pb2.py delete mode 100644 meshtastic/powermon_pb2.pyi diff --git a/meshtastic/powermon_pb2.py b/meshtastic/powermon_pb2.py deleted file mode 100644 index bd91da4f..00000000 --- a/meshtastic/powermon_pb2.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: meshtastic/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\x19meshtastic/powermon.proto\x12\x13meshtastic.PowerMon\"\'\n\x05\x45vent\x12\x13\n\x06states\x18\x01 \x01(\x04H\x00\x88\x01\x01\x42\t\n\x07_states*\xfb\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\r\n\tCPU_Sleep\x10\x02\x12\r\n\tCPU_Awake\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\x11\n\x0cGPS_LowPower\x10\x80\x10\x12\x14\n\x0fGPS_MediumPower\x10\x80 \x12\x12\n\rGPS_HighPower\x10\x80@Bc\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.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['_STATE']._serialized_start=92 - _globals['_STATE']._serialized_end=343 - _globals['_EVENT']._serialized_start=50 - _globals['_EVENT']._serialized_end=89 -# @@protoc_insertion_point(module_scope) diff --git a/meshtastic/powermon_pb2.pyi b/meshtastic/powermon_pb2.pyi deleted file mode 100644 index df6971a2..00000000 --- a/meshtastic/powermon_pb2.pyi +++ /dev/null @@ -1,84 +0,0 @@ -""" -@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 - -class _State: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _StateEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_State.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - CPU_DeepSleep: _State.ValueType # 1 - CPU_Sleep: _State.ValueType # 2 - CPU_Awake: _State.ValueType # 4 - Lora_RXOn: _State.ValueType # 8 - Lora_TXOn: _State.ValueType # 16 - Lora_RXActive: _State.ValueType # 32 - BT_On: _State.ValueType # 64 - LED_On: _State.ValueType # 128 - Screen_On: _State.ValueType # 256 - Screen_Drawing: _State.ValueType # 512 - Wifi_On: _State.ValueType # 1024 - GPS_LowPower: _State.ValueType # 2048 - GPS_MediumPower: _State.ValueType # 4096 - GPS_HighPower: _State.ValueType # 8192 - -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: State.ValueType # 1 -CPU_Sleep: State.ValueType # 2 -CPU_Awake: State.ValueType # 4 -Lora_RXOn: State.ValueType # 8 -Lora_TXOn: State.ValueType # 16 -Lora_RXActive: State.ValueType # 32 -BT_On: State.ValueType # 64 -LED_On: State.ValueType # 128 -Screen_On: State.ValueType # 256 -Screen_Drawing: State.ValueType # 512 -Wifi_On: State.ValueType # 1024 -GPS_LowPower: State.ValueType # 2048 -GPS_MediumPower: State.ValueType # 4096 -GPS_HighPower: State.ValueType # 8192 -global___State = State - -@typing.final -class Event(google.protobuf.message.Message): - """ - the log messages will be short and complete (see PowerMon.Event in the protobufs for details). - something like "PwrMon,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) - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STATES_FIELD_NUMBER: builtins.int - states: builtins.int - """Bitwise-OR of States""" - def __init__( - self, - *, - states: builtins.int | None = ..., - ) -> None: ... - def HasField(self, field_name: typing.Literal["_states", b"_states", "states", b"states"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["_states", b"_states", "states", b"states"]) -> None: ... - def WhichOneof(self, oneof_group: typing.Literal["_states", b"_states"]) -> typing.Literal["states"] | None: ... - -global___Event = Event From 338f00a64a46dc3453c23d53c8fbf43d12649680 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 10:43:33 -0700 Subject: [PATCH 03/57] minor cleanup on observable --- meshtastic/observable.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/meshtastic/observable.py b/meshtastic/observable.py index 2bd2a5bc..a980d411 100644 --- a/meshtastic/observable.py +++ b/meshtastic/observable.py @@ -1,9 +1,12 @@ +"""A basic implementation of the observer pattern.""" - -class Event(object): +class Event: """A simple event class.""" -class Observable(object): + def __init__(self, source) -> None: + self.source = source + +class Observable: """A class that represents an observable object. To publish an event call fire(type="progress", percent=50) or whatever. It will call @@ -27,9 +30,8 @@ def fire(self, **attrs): Args: **attrs: Arbitrary keyword arguments to be passed to the callback functions. """ - e = Event() - e.source = self + e = Event(self) for k, v in attrs.items(): setattr(e, k, v) for fn in self.callbacks: - fn(e) \ No newline at end of file + fn(e) From 21e5601b2319bad1d735c00b91955cd1f020a34b Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 10:43:50 -0700 Subject: [PATCH 04/57] run linter as part of prerelease tests --- bin/prerelease-tests.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/prerelease-tests.sh b/bin/prerelease-tests.sh index 8f89548c..ee27cc86 100755 --- a/bin/prerelease-tests.sh +++ b/bin/prerelease-tests.sh @@ -2,6 +2,10 @@ 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 "Running (crude) prerelease tests to verify sanity" # Use the python environment created by poetry From a1f86a351aaccca47fcc3c50eef3035d7c20300b Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 11:24:24 -0700 Subject: [PATCH 05/57] add typing hints --- bin/prerelease-tests.sh | 4 +++- meshtastic/observable.py | 6 ++++++ meshtastic/power_mon.py | 2 +- poetry.lock | 39 ++++++++++++++++++++++++++++++++++----- pyproject.toml | 3 ++- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/bin/prerelease-tests.sh b/bin/prerelease-tests.sh index ee27cc86..27a5a31b 100755 --- a/bin/prerelease-tests.sh +++ b/bin/prerelease-tests.sh @@ -3,9 +3,11 @@ 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 diff --git a/meshtastic/observable.py b/meshtastic/observable.py index a980d411..0d06a735 100644 --- a/meshtastic/observable.py +++ b/meshtastic/observable.py @@ -1,11 +1,17 @@ """A basic implementation of the observer pattern.""" +import typing + class Event: """A simple event class.""" def __init__(self, source) -> None: self.source = source + def __getattr__(self, name: str) -> typing.Any: + """We dynamically add attributes to this class, so stub out __getattr__ so that mypy doesn't complain.""" + pass + class Observable: """A class that represents an observable object. diff --git a/meshtastic/power_mon.py b/meshtastic/power_mon.py index 377ba333..e2bab351 100644 --- a/meshtastic/power_mon.py +++ b/meshtastic/power_mon.py @@ -73,7 +73,7 @@ def __init__(self, portName: str, client: MeshInterface) -> None: self.rawData = pd.DataFrame(columns=self.columns) # use time as the index # for efficiency reasons we keep new data in a list - only adding to rawData when needfed - self.newData = [] + self.newData: list[dict] = [] self.power = power = PowerSupply(portName) power.powerOn() diff --git a/poetry.lock b/poetry.lock index a045de44..0c6851f8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -814,8 +814,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -846,6 +846,24 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "pandas-stubs" +version = "2.2.2.240603" +description = "Type annotations for pandas" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pandas_stubs-2.2.2.240603-py3-none-any.whl", hash = "sha256:e08ce7f602a4da2bff5a67475ba881c39f2a4d4f7fccc1cba57c6f35a379c6c0"}, + {file = "pandas_stubs-2.2.2.240603.tar.gz", hash = "sha256:2dcc86e8fa6ea41535a4561c1f08b3942ba5267b464eff2e99caeee66f9e4cd1"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.23.5", markers = "python_version >= \"3.9\" and python_version < \"3.12\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\" and python_version < \"3.13\""}, +] +types-pytz = ">=2022.1.1" + [[package]] name = "pdoc3" version = "0.10.0" @@ -1026,8 +1044,8 @@ astroid = ">=3.2.2,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -1349,9 +1367,9 @@ pyserial = "^3.5" [package.source] type = "git" -url = "https://github.com/geeksville/riden.git#ecbda543cf566346dd28558d957b4aa7fe116a83" +url = "https://github.com/geeksville/riden.git#1.2.1" reference = "HEAD" -resolved_reference = "ecbda543cf566346dd28558d957b4aa7fe116a83" +resolved_reference = "a1fbecd54c3346f573277a06489b42957a6fd520" [[package]] name = "setuptools" @@ -1447,6 +1465,17 @@ files = [ {file = "types_protobuf-5.26.0.20240422-py3-none-any.whl", hash = "sha256:e4dc2554d342501d5aebc3c71203868b51118340e105fc190e3a64ca1be43831"}, ] +[[package]] +name = "types-pytz" +version = "2024.1.0.20240417" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981"}, + {file = "types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"}, +] + [[package]] name = "types-pyyaml" version = "6.0.12.20240311" @@ -1783,4 +1812,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "b893063a1dfe925f901dc45b01d68e255e2bd0ba68bda54de83c875c17b964d8" +content-hash = "8263f20a372fbae7bf88b74a40931cb9e8b6b49d12942d91d960c12142b5a979" diff --git a/pyproject.toml b/pyproject.toml index b2dc9a01..622473a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ types-requests = "^2.31.0.20240406" types-setuptools = "^69.5.0.20240423" types-pyyaml = "^6.0.12.20240311" packaging = "^24.0" -riden = {git = "https://github.com/geeksville/riden.git#ecbda543cf566346dd28558d957b4aa7fe116a83"} +riden = {git = "https://github.com/geeksville/riden.git#1.2.1"} pandas = "^2.2.2" [tool.poetry.group.dev.dependencies] @@ -41,6 +41,7 @@ autopep8 = "^2.1.0" pylint = "^3.2.3" pytap2 = "^2.3.0" pyinstaller = "^6.8.0" +pandas-stubs = "^2.2.2.240603" [tool.poetry.extras] tunnel = ["pytap2"] From cc60f3ebc0fbc55966e90a53ad9343e923019b72 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 12:09:41 -0700 Subject: [PATCH 06/57] begin support for multiple power meter types --- .vscode/launch.json | 2 +- meshtastic/__main__.py | 18 +++++--- meshtastic/powermon/__init__.py | 1 + meshtastic/powermon/riden.py | 58 +++++++++++++++++++++++++ meshtastic/slog/__init__.py | 0 meshtastic/{ => slog}/power_mon.py | 68 +++++------------------------- 6 files changed, 84 insertions(+), 63 deletions(-) create mode 100644 meshtastic/powermon/__init__.py create mode 100644 meshtastic/powermon/riden.py create mode 100644 meshtastic/slog/__init__.py rename meshtastic/{ => slog}/power_mon.py (57%) diff --git a/.vscode/launch.json b/.vscode/launch.json index b0937863..3da59236 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -172,7 +172,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-mon", "/dev/ttyUSB1", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] + "args": ["--power-riden", "/dev/ttyUSB0", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 3db80ba7..3593edf7 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -21,7 +21,8 @@ from meshtastic.version import get_active_version from meshtastic.ble_interface import BLEInterface from meshtastic.mesh_interface import MeshInterface -from meshtastic.power_mon import PowerMonClient +from meshtastic.powermon import RidenPowerSupply +from meshtastic.slog.power_mon import PowerMonClient def onReceive(packet, interface): """Callback invoked when a packet arrives""" @@ -1090,8 +1091,9 @@ def common(): # We assume client is fully connected now onConnected(client) - if args.power_mon: - PowerMonClient(args.power_mon, client) + if args.power_riden: + meter = RidenPowerSupply(args.power_riden) + PowerMonClient(meter, client) have_tunnel = platform.system() == "Linux" @@ -1506,8 +1508,14 @@ def initParser(): ) group.add_argument( - "--power-mon", - help="Capture any power monitor records. You must use --power-mon /dev/ttyUSBxxx to specify which port the power supply is on", + "--power-riden", + help="Talk to a Riden power-supply. You must specify the device path, i.e. /dev/ttyUSBxxx", + ) + + group.add_argument( + "--power-ppk2", + help="Talk to a Nordic Power Profiler Kit 2", + action="store_true", ) group.add_argument( diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py new file mode 100644 index 00000000..d83d4965 --- /dev/null +++ b/meshtastic/powermon/__init__.py @@ -0,0 +1 @@ +from .riden import * \ No newline at end of file diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py new file mode 100644 index 00000000..41b82cd3 --- /dev/null +++ b/meshtastic/powermon/riden.py @@ -0,0 +1,58 @@ +"""code logging power consumption of meshtastic devices.""" + +import logging + +from datetime import datetime + +from riden import Riden + +class PowerMeter: + """Abstract class for power meters.""" + + def getWattHour(self) -> float: + """Get the current watt-hour reading.""" + + + +class PowerSupply(PowerMeter): + """Abstract class for power supplies.""" + + def setMaxCurrent(self, i: float): + """Set the maximum current the supply will provide.""" + + def powerOn(self, v: float): + """Turn on the power supply.""" + + + +class RidenPowerSupply(PowerSupply): + """Interface for talking to programmable bench-top power supplies. + Currently only the Riden supplies are supported (RD6006 tested) + """ + + def __init__(self, portName: str = "/dev/ttyUSB0"): + """Initialize the RidenPowerSupply object. + + Args: + 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()) + + def setMaxCurrent(self, i: float): + """Set the maximum current the supply will provide.""" + self.r.set_i_set(i) + + def powerOn(self, v: float): + """Power on the supply, with reasonable defaults for meshtastic devices.""" + self.r.set_v_set(v) # my WM1110 devboard header is directly connected to the 3.3V rail + self.r.set_output(1) + + def getWattHour(self) -> float: + """Get the current watt-hour reading.""" + self.r.update() + return self.r.wh + diff --git a/meshtastic/slog/__init__.py b/meshtastic/slog/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/meshtastic/power_mon.py b/meshtastic/slog/power_mon.py similarity index 57% rename from meshtastic/power_mon.py rename to meshtastic/slog/power_mon.py index e2bab351..c1fd12e3 100644 --- a/meshtastic/power_mon.py +++ b/meshtastic/slog/power_mon.py @@ -6,77 +6,35 @@ from datetime import datetime import pandas as pd -from riden import Riden from meshtastic.mesh_interface import MeshInterface from meshtastic.observable import Event +from meshtastic.powermon import PowerSupply - -class PowerSupply: - """Interface for talking to programmable bench-top power supplies. - Currently only the Riden supplies are supported (RD6006 tested) - """ - - def __init__(self, portName: str = "/dev/ttyUSB0"): - """Initialize the PowerSupply object.""" - 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()) - - def powerOn(self): - """Power on the supply, with reasonable defaults for meshtastic devices.""" - self.r.set_i_set( - 0.300 - ) # Set current limit to 300mA - hopefully enough to power any board but not break things if there is a short circuit - - # self.r.set_v_set(3.7) # default to a nominal LiPo voltage - self.r.set_v_set(3.3) # my WM1110 devboard header is directly connected to the 3.3V rail - self.r.set_output(1) - - """Get current watts out. - But for most applications you probably want getWattHour() instead (to prevent integration errors from accumulating). - """ - return self.r.get_p_out() - - def getWattHour(self): - """Get current Wh out, since power was turned on.""" - # FIXME: Individual reads seem busted in the riden lib. So for now I just read everything. - self.r.update() - return self.r.wh - # return self.r.get_wh() - - def clearWattHour(self): - """Clear the watt-hour counter FIXME.""" - - -"""Used to match power mon log lines: -INFO | ??:??:?? 7 [Blink] S:PM:0x00000080,reason -""" logRegex = re.compile(".*S:PM:0x([0-9A-Fa-f]+),(.*)") class PowerMonClient: """Client for monitoring power consumption of meshtastic devices.""" - def __init__(self, portName: str, client: MeshInterface) -> None: + def __init__(self, power: PowerSupply, client: MeshInterface) -> None: """Initialize the PowerMonClient object. Args: + power (PowerSupply): The power supply object. client (MeshInterface): The MeshInterface object to monitor. - """ self.client = client self.state = 0 # The current power mon state bitfields self.columns = ["time", "power", "reason", "bitmask"] - self.rawData = pd.DataFrame(columns=self.columns) # use time as the index + self.rawData = pd.DataFrame(columns=self.columns) # use time as the index - # for efficiency reasons we keep new data in a list - only adding to rawData when needfed + # for efficiency reasons we keep new data in a list - only adding to rawData when needed self.newData: list[dict] = [] - self.power = power = PowerSupply(portName) - power.powerOn() + self.power = power + power.setMaxCurrent(0.300) # Set current limit to 300mA - hopefully enough to power any board but not break things if there is a short circuit + power.powerOn(3.3) # Used to calculate watts over an interval self.prevPowerTime = datetime.now() @@ -89,7 +47,6 @@ def getRawData(self) -> pd.DataFrame: Returns: pd.DataFrame: The raw data. - """ df = pd.DataFrame(self.newData, columns=self.columns) self.rawData = pd.concat([self.rawData, df], ignore_index=True) @@ -107,8 +64,7 @@ def _onLogMessage(self, ev: Event) -> None: """Callback function for handling log messages. Args: - message (str): The log message. - + ev (Event): The log event. """ m = logRegex.match(ev.message) if m: @@ -124,9 +80,7 @@ def _storeRecord(self, mask: int, reason: str) -> None: Args: mask (int): The power mon state bitfields. reason (str): The reason for the power mon state change. - """ - now = datetime.now() nowWattHour = self.power.getWattHour() watts = ( @@ -139,5 +93,5 @@ def _storeRecord(self, mask: int, reason: str) -> None: self.state = mask self.newData.append( - {"time": now, "power": watts, "reason": reason, "bitmask": mask}) - # self.getRawData() + {"time": now, "power": watts, "reason": reason, "bitmask": mask} + ) From 362c1f3d2a93de30c5a18f346a2dd6e164675190 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 12:09:49 -0700 Subject: [PATCH 07/57] remove python 3.8 --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39f8992f..82d1c996 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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" From be74c3eea0be7f96758957a3af67935237775f01 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 12:19:06 -0700 Subject: [PATCH 08/57] fix linter warnings --- meshtastic/observable.py | 2 +- meshtastic/powermon/__init__.py | 4 +++- meshtastic/powermon/riden.py | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/meshtastic/observable.py b/meshtastic/observable.py index 0d06a735..d57330c7 100644 --- a/meshtastic/observable.py +++ b/meshtastic/observable.py @@ -10,7 +10,7 @@ def __init__(self, source) -> None: def __getattr__(self, name: str) -> typing.Any: """We dynamically add attributes to this class, so stub out __getattr__ so that mypy doesn't complain.""" - pass + class Observable: """A class that represents an observable object. diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py index d83d4965..d8b4104f 100644 --- a/meshtastic/powermon/__init__.py +++ b/meshtastic/powermon/__init__.py @@ -1 +1,3 @@ -from .riden import * \ No newline at end of file +"""Support for logging from power meters/supplies.""" + +from .riden import * diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py index 41b82cd3..8d7c1316 100644 --- a/meshtastic/powermon/riden.py +++ b/meshtastic/powermon/riden.py @@ -55,4 +55,3 @@ def getWattHour(self) -> float: """Get the current watt-hour reading.""" self.r.update() return self.r.wh - From 0e45637f2c3efd483b40b2b17ec93c5d00b5ecf8 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 14:46:08 -0700 Subject: [PATCH 09/57] generalize the powermon stuff to become structured logging --- .vscode/settings.json | 3 + meshtastic/__main__.py | 13 +++-- meshtastic/powermon/riden.py | 29 ++++++++-- meshtastic/slog/__init__.py | 3 + meshtastic/slog/power_mon.py | 97 --------------------------------- meshtastic/slog/slog.py | 103 +++++++++++++++++++++++++++++++++++ poetry.lock | 13 ++++- pyproject.toml | 1 + 8 files changed, 154 insertions(+), 108 deletions(-) delete mode 100644 meshtastic/slog/power_mon.py create mode 100644 meshtastic/slog/slog.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 5deb0628..a1fd5743 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "cSpell.words": [ + "bitmask", + "boardid", "Meshtastic", + "powermon", "TORADIO", "Vids" ], diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 3593edf7..4a21e859 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -22,7 +22,7 @@ from meshtastic.ble_interface import BLEInterface from meshtastic.mesh_interface import MeshInterface from meshtastic.powermon import RidenPowerSupply -from meshtastic.slog.power_mon import PowerMonClient +from meshtastic.slog import StructuredLogger def onReceive(packet, interface): """Callback invoked when a packet arrives""" @@ -1087,21 +1087,24 @@ def common(): f"Error connecting to localhost:{ex}", 1 ) - # We assume client is fully connected now onConnected(client) + meter = None # assume no power meter if args.power_riden: meter = RidenPowerSupply(args.power_riden) - PowerMonClient(meter, client) + StructuredLogger(client, meter) have_tunnel = platform.system() == "Linux" 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) diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py index 8d7c1316..1dd65bc2 100644 --- a/meshtastic/powermon/riden.py +++ b/meshtastic/powermon/riden.py @@ -1,7 +1,7 @@ """code logging power consumption of meshtastic devices.""" import logging - +import math from datetime import datetime from riden import Riden @@ -9,8 +9,27 @@ class PowerMeter: """Abstract class for power meters.""" - def getWattHour(self) -> float: - """Get the current watt-hour reading.""" + def __init__(self): + """Initialize the PowerMeter object.""" + self.prevPowerTime = datetime.now() + self.prevWattHour = self._getRawWattHour() + + def getWatts(self) -> float: + """Get the total amount of power that has been consumed since the previous call of 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 + + def _getRawWattHour(self) -> float: + """Get the current watt-hour reading (without any offset correction).""" + return math.nan @@ -33,7 +52,6 @@ class RidenPowerSupply(PowerSupply): def __init__(self, portName: str = "/dev/ttyUSB0"): """Initialize the RidenPowerSupply object. - Args: portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyUSB0". """ self.r = r = Riden(port=portName, baudrate=115200, address=1) @@ -41,6 +59,7 @@ def __init__(self, portName: str = "/dev/ttyUSB0"): f"Connected to Riden power supply: model {r.type}, sn {r.sn}, firmware {r.fw}. Date/time updated." ) r.set_date_time(datetime.now()) + 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.""" @@ -51,7 +70,7 @@ def powerOn(self, v: float): self.r.set_v_set(v) # my WM1110 devboard header is directly connected to the 3.3V rail self.r.set_output(1) - def getWattHour(self) -> float: + def _getRawWattHour(self) -> float: """Get the current watt-hour reading.""" self.r.update() return self.r.wh diff --git a/meshtastic/slog/__init__.py b/meshtastic/slog/__init__.py index e69de29b..a96ae8eb 100644 --- a/meshtastic/slog/__init__.py +++ b/meshtastic/slog/__init__.py @@ -0,0 +1,3 @@ +"""Structured logging framework (see dev docs for more info)""" + +from .slog import StructuredLogger diff --git a/meshtastic/slog/power_mon.py b/meshtastic/slog/power_mon.py deleted file mode 100644 index c1fd12e3..00000000 --- a/meshtastic/slog/power_mon.py +++ /dev/null @@ -1,97 +0,0 @@ -"""code logging power consumption of meshtastic devices.""" - -import logging -import re -import atexit -from datetime import datetime - -import pandas as pd - -from meshtastic.mesh_interface import MeshInterface -from meshtastic.observable import Event -from meshtastic.powermon import PowerSupply - -logRegex = re.compile(".*S:PM:0x([0-9A-Fa-f]+),(.*)") - - -class PowerMonClient: - """Client for monitoring power consumption of meshtastic devices.""" - - def __init__(self, power: PowerSupply, client: MeshInterface) -> None: - """Initialize the PowerMonClient object. - - Args: - power (PowerSupply): The power supply object. - client (MeshInterface): The MeshInterface object to monitor. - """ - self.client = client - self.state = 0 # The current power mon state bitfields - self.columns = ["time", "power", "reason", "bitmask"] - self.rawData = pd.DataFrame(columns=self.columns) # use time as the index - - # for efficiency reasons we keep new data in a list - only adding to rawData when needed - self.newData: list[dict] = [] - - self.power = power - power.setMaxCurrent(0.300) # Set current limit to 300mA - hopefully enough to power any board but not break things if there is a short circuit - power.powerOn(3.3) - - # Used to calculate watts over an interval - self.prevPowerTime = datetime.now() - self.prevWattHour = power.getWattHour() - atexit.register(self._exitHandler) - client.onLogMessage.subscribe(self._onLogMessage) - - def getRawData(self) -> pd.DataFrame: - """Get the raw data. - - Returns: - pd.DataFrame: The raw data. - """ - df = pd.DataFrame(self.newData, columns=self.columns) - self.rawData = pd.concat([self.rawData, df], ignore_index=True) - self.newData = [] - - return self.rawData - - def _exitHandler(self) -> None: - """Exit handler.""" - fn = "/tmp/powermon.csv" # Find a better place - logging.info(f"Storing PowerMon raw data in {fn}") - self.getRawData().to_csv(fn) - - def _onLogMessage(self, ev: Event) -> None: - """Callback function for handling log messages. - - Args: - ev (Event): The log event. - """ - m = logRegex.match(ev.message) - if m: - mask = int(m.group(1), 16) - reason = m.group(2) - logging.debug(f"PowerMon state: 0x{mask:x}, reason: {reason}") - if mask != self.state: - self._storeRecord(mask, reason) - - def _storeRecord(self, mask: int, reason: str) -> None: - """Store a power mon record. - - Args: - mask (int): The power mon state bitfields. - reason (str): The reason for the power mon state change. - """ - now = datetime.now() - nowWattHour = self.power.getWattHour() - watts = ( - (nowWattHour - self.prevWattHour) - / (now - self.prevPowerTime).total_seconds() - * 3600 - ) - self.prevPowerTime = now - self.prevWattHour = nowWattHour - self.state = mask - - self.newData.append( - {"time": now, "power": watts, "reason": reason, "bitmask": mask} - ) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py new file mode 100644 index 00000000..17910e7b --- /dev/null +++ b/meshtastic/slog/slog.py @@ -0,0 +1,103 @@ +"""code logging power consumption of meshtastic devices.""" + +import logging +import re +import atexit +from datetime import datetime +from dataclasses import dataclass + +import parse +import pandas as pd + +from meshtastic.mesh_interface import MeshInterface +from meshtastic.observable import Event +from meshtastic.powermon import PowerMeter + + +@dataclass(init=False) +class LogDef: + """Log definition.""" + code: str # i.e. PM or B or whatever... see meshtastic slog documentation + format: str # A format string that can be used to parse the arguments + + def __init__(self, code: str, format: str) -> None: + """Initialize the LogDef object. + + code (str): The code. + format (str): The format. + """ + self.code = code + self.format = parse.compile(format) + +"""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 StructuredLogger: + """Sniffs device logs for structured log messages, extracts those into pandas/CSV format.""" + + def __init__(self, client: MeshInterface, pMeter: PowerMeter = None) -> None: + """Initialize the PowerMonClient object. + + power (PowerSupply): The power supply object. + client (MeshInterface): The MeshInterface object to monitor. + """ + self.client = client + self.pMeter = pMeter + self.columns = ["time", "power"] + self.rawData = pd.DataFrame(columns=self.columns) # use time as the index + + # for efficiency reasons we keep new data in a list - only adding to rawData when needed + self.newData: list[dict] = [] + + atexit.register(self._exitHandler) + client.onLogMessage.subscribe(self._onLogMessage) + + def getRawData(self) -> pd.DataFrame: + """Get the raw data. + + Returns + ------- + pd.DataFrame: The raw data. + """ + df = pd.DataFrame(self.newData, columns=self.columns) + self.rawData = pd.concat([self.rawData, df], ignore_index=True) + self.newData = [] + + return self.rawData + + def _exitHandler(self) -> None: + """Exit handler.""" + fn = "/tmp/powermon.slog" # Find a better place + logging.info(f"Storing slog in {fn}") + self.getRawData().to_csv(fn) + + def _onLogMessage(self, ev: Event) -> None: + """Handle log messages. + + ev (Event): The log event. + """ + m = log_regex.match(ev.message) + 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}") + 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() + if self.pMeter: # if we have a power meter include a fresh power reading + di["power"] = self.pMeter.getWatts() + self.newData.append(di) + else: + logging.warning(f"Failed to parse slog {ev.message} with {d.format}") + else: + logging.warning(f"Unknown Structured Log: {ev.message}") diff --git a/poetry.lock b/poetry.lock index 0c6851f8..b2ea4c6e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -864,6 +864,17 @@ numpy = [ ] types-pytz = ">=2022.1.1" +[[package]] +name = "parse" +version = "1.20.2" +description = "parse() is the opposite of format()" +optional = false +python-versions = "*" +files = [ + {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}, + {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, +] + [[package]] name = "pdoc3" version = "0.10.0" @@ -1812,4 +1823,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "8263f20a372fbae7bf88b74a40931cb9e8b6b49d12942d91d960c12142b5a979" +content-hash = "109915f24859629e5ec50dd761be8fcaa72fb327d724da8f18f266e9f2a3e4db" diff --git a/pyproject.toml b/pyproject.toml index 622473a4..2f539540 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ types-pyyaml = "^6.0.12.20240311" packaging = "^24.0" riden = {git = "https://github.com/geeksville/riden.git#1.2.1"} pandas = "^2.2.2" +parse = "^1.20.2" [tool.poetry.group.dev.dependencies] hypothesis = "^6.103.2" From 79c65c17062f9b400dd9bbd14f57b208b42092c9 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 16:02:59 -0700 Subject: [PATCH 10/57] make slog nicely add new rows --- meshtastic/slog/slog.py | 16 ++++++++++++++-- protobufs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 17910e7b..951ae864 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -50,6 +50,7 @@ def __init__(self, client: MeshInterface, pMeter: PowerMeter = None) -> None: self.pMeter = pMeter self.columns = ["time", "power"] self.rawData = pd.DataFrame(columns=self.columns) # use time as the index + # self.rawData.set_index("time", inplace=True) # for efficiency reasons we keep new data in a list - only adding to rawData when needed self.newData: list[dict] = [] @@ -64,8 +65,18 @@ def getRawData(self) -> pd.DataFrame: ------- pd.DataFrame: The raw data. """ - df = pd.DataFrame(self.newData, columns=self.columns) - self.rawData = pd.concat([self.rawData, df], ignore_index=True) + + df = pd.DataFrame(self.newData) + + # We prefer some columns to be integers + intcols = [ "bitmask" ] + for c in intcols: + if c in df: + df[c] = df[c].astype('Int64') + + # df.set_index("time") + # Add new data, creating new columns as needed (an outer join) + self.rawData = pd.concat([self.rawData, df], axis=0, ignore_index=True) self.newData = [] return self.rawData @@ -97,6 +108,7 @@ def _onLogMessage(self, ev: Event) -> None: if self.pMeter: # if we have a power meter include a fresh power reading di["power"] = self.pMeter.getWatts() self.newData.append(di) + self.getRawData() else: logging.warning(f"Failed to parse slog {ev.message} with {d.format}") else: diff --git a/protobufs b/protobufs index bfde27a4..a82df223 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bfde27a4ea5cfd0f60ffe961b5ce249aaf54c182 +Subproject commit a82df2239a9dc4b9d43debeb4e87135b13784ca5 From 9c657c6c8a2024a31c5acb5e3bf0cd4f0ed87759 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 23:16:05 -0700 Subject: [PATCH 11/57] switch to latest protobufs --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index a82df223..4da558d0 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a82df2239a9dc4b9d43debeb4e87135b13784ca5 +Subproject commit 4da558d0f73c46ef91b74431facee73c09affbfc From 51c6c2cae135f04c184d3cc04a97e51ad7c92c8b Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 22 Jun 2024 23:16:43 -0700 Subject: [PATCH 12/57] The github action for building protobufs is using 0.4.6 so we should match --- bin/regen-protobufs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/regen-protobufs.sh b/bin/regen-protobufs.sh index 1f2f23c0..aff4ff62 100755 --- a/bin/regen-protobufs.sh +++ b/bin/regen-protobufs.sh @@ -4,8 +4,8 @@ #gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/* #gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/* -./nanopb-0.4.7/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto -./nanopb-0.4.7/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto +./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto +./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto # workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628 From 67e1e7c3184aea49c9b7ec9a54d766a6b6595899 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 10:23:34 -0700 Subject: [PATCH 13/57] move mypy and type info into dev-time only dependencies thx @njh # Conflicts: # poetry.lock # pyproject.toml --- poetry.lock | 227 +++++++++++++++++++++---------------------------- pyproject.toml | 15 ++-- 2 files changed, 106 insertions(+), 136 deletions(-) diff --git a/poetry.lock b/poetry.lock index b2ea4c6e..464c3eb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -57,13 +57,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "autopep8" -version = "2.3.0" +version = "2.3.1" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" optional = false python-versions = ">=3.8" files = [ - {file = "autopep8-2.3.0-py2.py3-none-any.whl", hash = "sha256:b716efa70cbafbf4a2c9c5ec1cabfa037a68f9e30b04c74ffa5864dd49b8f7d2"}, - {file = "autopep8-2.3.0.tar.gz", hash = "sha256:5cfe45eb3bef8662f6a3c7e28b7c0310c7310d340074b7f0f28f9810b44b7ef4"}, + {file = "autopep8-2.3.1-py2.py3-none-any.whl", hash = "sha256:a203fe0fcad7939987422140ab17a930f684763bf7335bdb6709991dd7ef6c2d"}, + {file = "autopep8-2.3.1.tar.gz", hash = "sha256:8d6c87eba648fdcfc83e29b788910b8643171c395d9c4bcf115ece035b9c9dda"}, ] [package.dependencies] @@ -254,63 +254,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.3" +version = "7.5.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, - {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, - {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, - {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, - {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, - {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, - {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, - {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, - {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, - {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, - {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, - {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, - {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, - {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, - {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, - {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, - {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, - {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, - {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, - {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, - {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, - {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, - {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, - {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, - {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, - {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, - {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, - {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"}, + {file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"}, + {file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"}, + {file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"}, + {file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"}, + {file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"}, + {file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"}, + {file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"}, + {file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"}, + {file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"}, + {file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"}, + {file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"}, + {file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"}, + {file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"}, + {file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"}, + {file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"}, + {file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"}, + {file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"}, + {file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"}, + {file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"}, + {file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"}, + {file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"}, + {file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"}, + {file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"}, + {file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"}, + {file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"}, + {file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"}, + {file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"}, ] [package.dependencies] @@ -404,13 +404,13 @@ test = ["pytest (>=6)"] [[package]] name = "hypothesis" -version = "6.103.2" +version = "6.104.0" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.103.2-py3-none-any.whl", hash = "sha256:629b7cdeca8c225933739f99879caba21949000d2c919c8b4585e01048b3bc73"}, - {file = "hypothesis-6.103.2.tar.gz", hash = "sha256:83504e31e90a0d7d6e8eb93e51525dc1a48d79c932a50ad6035e29f8295328cd"}, + {file = "hypothesis-6.104.0-py3-none-any.whl", hash = "sha256:4aaa38b57625abae0d377b2460b80a5847504dcce22985e3381e4373079d45d0"}, + {file = "hypothesis-6.104.0.tar.gz", hash = "sha256:f3f376491380aab841d706c562034c0118616dca7ce23e07b1a744c99f38d26b"}, ] [package.dependencies] @@ -419,10 +419,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} sortedcontainers = ">=2.1.0,<3.0.0" [package.extras] -all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.54)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.55)", "django (>=3.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.4)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.17.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.1)"] cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] codemods = ["libcst (>=0.3.16)"] -crosshair = ["crosshair-tool (>=0.0.54)", "hypothesis-crosshair (>=0.0.4)"] +crosshair = ["crosshair-tool (>=0.0.55)", "hypothesis-crosshair (>=0.0.4)"] dateutil = ["python-dateutil (>=1.4)"] django = ["django (>=3.2)"] dpcontracts = ["dpcontracts (>=0.4)"] @@ -448,13 +448,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.2.0" +version = "7.2.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.2.0-py3-none-any.whl", hash = "sha256:04e4aad329b8b948a5711d394fa8759cb80f009225441b4f2a02bd4d8e5f426c"}, - {file = "importlib_metadata-7.2.0.tar.gz", hash = "sha256:3ff4519071ed42740522d494d04819b666541b9752c43012f85afb2cc220fcc6"}, + {file = "importlib_metadata-7.2.1-py3-none-any.whl", hash = "sha256:ffef94b0b66046dd8ea2d619b701fe978d9264d38f3998bc4c27ec3b146a87c8"}, + {file = "importlib_metadata-7.2.1.tar.gz", hash = "sha256:509ecb2ab77071db5137c655e24ceb3eee66e7bbc6574165d0d114d9fc4bbe68"}, ] [package.dependencies] @@ -637,38 +637,38 @@ pyserial = ">=3.1" [[package]] name = "mypy" -version = "1.10.0" +version = "1.10.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, - {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, - {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, - {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, - {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, - {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, - {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, - {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, - {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, - {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, - {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, - {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, - {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, - {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, - {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, - {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, - {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, - {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, - {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, - {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, - {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, - {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, - {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, ] [package.dependencies] @@ -814,8 +814,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -846,24 +846,6 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] -[[package]] -name = "pandas-stubs" -version = "2.2.2.240603" -description = "Type annotations for pandas" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas_stubs-2.2.2.240603-py3-none-any.whl", hash = "sha256:e08ce7f602a4da2bff5a67475ba881c39f2a4d4f7fccc1cba57c6f35a379c6c0"}, - {file = "pandas_stubs-2.2.2.240603.tar.gz", hash = "sha256:2dcc86e8fa6ea41535a4561c1f08b3942ba5267b464eff2e99caeee66f9e4cd1"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.5", markers = "python_version >= \"3.9\" and python_version < \"3.12\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\" and python_version < \"3.13\""}, -] -types-pytz = ">=2022.1.1" - [[package]] name = "parse" version = "1.20.2" @@ -1055,8 +1037,8 @@ astroid = ">=3.2.2,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -1380,17 +1362,17 @@ pyserial = "^3.5" type = "git" url = "https://github.com/geeksville/riden.git#1.2.1" reference = "HEAD" -resolved_reference = "a1fbecd54c3346f573277a06489b42957a6fd520" +resolved_reference = "27fd58f069a089676dcaaea2ccb8dc8d24e4c6d9" [[package]] name = "setuptools" -version = "70.1.0" +version = "70.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-70.1.0-py3-none-any.whl", hash = "sha256:d9b8b771455a97c8a9f3ab3448ebe0b29b5e105f1228bba41028be116985a267"}, - {file = "setuptools-70.1.0.tar.gz", hash = "sha256:01a1e793faa5bd89abc851fa15d0a0db26f160890c7102cd8dce643e886b47f5"}, + {file = "setuptools-70.1.1-py3-none-any.whl", hash = "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95"}, + {file = "setuptools-70.1.1.tar.gz", hash = "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650"}, ] [package.extras] @@ -1476,17 +1458,6 @@ files = [ {file = "types_protobuf-5.26.0.20240422-py3-none-any.whl", hash = "sha256:e4dc2554d342501d5aebc3c71203868b51118340e105fc190e3a64ca1be43831"}, ] -[[package]] -name = "types-pytz" -version = "2024.1.0.20240417" -description = "Typing stubs for pytz" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981"}, - {file = "types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"}, -] - [[package]] name = "types-pyyaml" version = "6.0.12.20240311" @@ -1823,4 +1794,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "109915f24859629e5ec50dd761be8fcaa72fb327d724da8f18f266e9f2a3e4db" +content-hash = "cc81bcd3e4671bde3375f2f08334d6a3b87b38c97f2676d7266cab980082659e" diff --git a/pyproject.toml b/pyproject.toml index 2f539540..c5c4b8a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,13 +21,6 @@ pyparsing = "^3.1.2" pyyaml = "^6.0.1" pypubsub = "^4.0.3" bleak = "^0.21.1" -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" packaging = "^24.0" riden = {git = "https://github.com/geeksville/riden.git#1.2.1"} pandas = "^2.2.2" @@ -42,7 +35,13 @@ autopep8 = "^2.1.0" pylint = "^3.2.3" pytap2 = "^2.3.0" pyinstaller = "^6.8.0" -pandas-stubs = "^2.2.2.240603" +mypy = "^1.10.0" +mypy-protobuf = "^3.6.0" +types-protobuf = "^5.26.0.20240422" +types-tabulate = "^0.9.0.20240106" +types-requests = "^2.31.0.20240406" +types-setuptools = "^69.5.0.20240423" +types-pyyaml = "^6.0.12.20240311" [tool.poetry.extras] tunnel = ["pytap2"] From 46edd78f92383eb75c8d68dc4f1c3d743b4b5ad5 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 10:25:52 -0700 Subject: [PATCH 14/57] For poetry change: need to put venv in our path so mypy protobuf plugin works # Conflicts: # bin/regen-protobufs.sh --- .github/workflows/update_protobufs.yml | 5 +++++ bin/regen-protobufs.sh | 3 +++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 86049b75..38ff1d3e 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -22,6 +22,11 @@ jobs: tar xvzf nanopb-0.4.6-linux-x86.tar.gz mv nanopb-0.4.6-linux-x86 nanopb-0.4.6 + - name: Install poetry (needed by regen-protobufs.sh) + run: | + python -m pip install --upgrade pip + pip3 install poetry + - name: Re-generate protocol buffers run: | ./bin/regen-protobufs.sh diff --git a/bin/regen-protobufs.sh b/bin/regen-protobufs.sh index aff4ff62..ac56608e 100755 --- a/bin/regen-protobufs.sh +++ b/bin/regen-protobufs.sh @@ -4,6 +4,9 @@ #gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/* #gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/* +# protoc looks for mypy plugin in the python path +source $(poetry env info --path)/bin/activate + ./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto ./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto From 7b18fd599c32bc3e16294d226ec0dad8504cd88c Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 08:17:54 -0700 Subject: [PATCH 15/57] remove observable - switch because we are already using pubsub elsewhere --- meshtastic/__init__.py | 1 + meshtastic/mesh_interface.py | 17 ++++++++++---- meshtastic/observable.py | 43 ---------------------------------- meshtastic/slog/slog.py | 18 +++++++------- meshtastic/stream_interface.py | 3 --- 5 files changed, 23 insertions(+), 59 deletions(-) delete mode 100644 meshtastic/observable.py diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index a5075bc2..4745d2dd 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -31,6 +31,7 @@ - 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 diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 0bc67ba9..60a26ab9 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -40,7 +40,6 @@ stripnl, message_to_json, ) -from meshtastic.observable import Observable class MeshInterface: # pylint: disable=R0902 """Interface class for meshtastic devices @@ -71,7 +70,6 @@ def __init__(self, debugOut=None, noProto: bool=False, noNodes: bool=False) -> N self.nodes: Optional[Dict[str,Dict]] = None # FIXME self.isConnected: threading.Event = threading.Event() self.noProto: bool = noProto - self.onLogMessage = Observable() 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 @@ -93,6 +91,12 @@ def __init__(self, debugOut=None, noProto: bool=False, noNodes: bool=False) -> N 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: @@ -112,9 +116,14 @@ def __exit__(self, exc_type, exc_value, traceback): logging.error(f"Traceback: {traceback}") self.close() - def _handleLogLine(self, line): + @staticmethod + def _printLogLine(line, interface): + """Print a line of log output""" + interface.debugOut.write(line + "\n") + + def _handleLogLine(self, line: str) -> None: """Handle a line of log output from the device.""" - self.onLogMessage.fire(message=line) + pub.sendMessage("meshtastic.log.line", line=line, interface=self) def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613 """Show human readable summary about this object""" diff --git a/meshtastic/observable.py b/meshtastic/observable.py deleted file mode 100644 index d57330c7..00000000 --- a/meshtastic/observable.py +++ /dev/null @@ -1,43 +0,0 @@ -"""A basic implementation of the observer pattern.""" - -import typing - -class Event: - """A simple event class.""" - - def __init__(self, source) -> None: - self.source = source - - def __getattr__(self, name: str) -> typing.Any: - """We dynamically add attributes to this class, so stub out __getattr__ so that mypy doesn't complain.""" - - -class Observable: - """A class that represents an observable object. - - To publish an event call fire(type="progress", percent=50) or whatever. It will call - """ - - def __init__(self): - """Initialize the Observable object.""" - self.callbacks = [] - - def subscribe(self, callback): - """Subscribe to the observable object. - - Args: - callback (function): The callback function to be called when the event is fired. - """ - self.callbacks.append(callback) - - def fire(self, **attrs): - """Fire the event. - - Args: - **attrs: Arbitrary keyword arguments to be passed to the callback functions. - """ - e = Event(self) - for k, v in attrs.items(): - setattr(e, k, v) - for fn in self.callbacks: - fn(e) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 951ae864..ab474145 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -8,9 +8,9 @@ import parse import pandas as pd +from pubsub import pub # type: ignore[import-untyped] from meshtastic.mesh_interface import MeshInterface -from meshtastic.observable import Event from meshtastic.powermon import PowerMeter @@ -20,14 +20,14 @@ class LogDef: code: str # i.e. PM or B or whatever... see meshtastic slog documentation format: str # A format string that can be used to parse the arguments - def __init__(self, code: str, format: str) -> None: + 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(format) + self.format = parse.compile(fmt) """A dictionary mapping from logdef code to logdef""" log_defs = {d.code: d for d in [ @@ -56,7 +56,7 @@ def __init__(self, client: MeshInterface, pMeter: PowerMeter = None) -> None: self.newData: list[dict] = [] atexit.register(self._exitHandler) - client.onLogMessage.subscribe(self._onLogMessage) + pub.subscribe(self._onLogMessage, "meshtastic.log.line") def getRawData(self) -> pd.DataFrame: """Get the raw data. @@ -87,12 +87,12 @@ def _exitHandler(self) -> None: logging.info(f"Storing slog in {fn}") self.getRawData().to_csv(fn) - def _onLogMessage(self, ev: Event) -> None: + def _onLogMessage(self, line: str, interface: MeshInterface) -> None: # pylint: disable=unused-argument """Handle log messages. - ev (Event): The log event. + line (str): the line of log output """ - m = log_regex.match(ev.message) + m = log_regex.match(line) if m: src = m.group(1) args = m.group(2) @@ -110,6 +110,6 @@ def _onLogMessage(self, ev: Event) -> None: self.newData.append(di) self.getRawData() else: - logging.warning(f"Failed to parse slog {ev.message} with {d.format}") + logging.warning(f"Failed to parse slog {line} with {d.format}") else: - logging.warning(f"Unknown Structured Log: {ev.message}") + logging.warning(f"Unknown Structured Log: {line}") diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index c05a3188..18d6e9f3 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -142,9 +142,6 @@ def _handleLogByte(self, b): else: self.cur_log_line += utf - if self.debugOut is not None: - self.debugOut.write(utf) - def __reader(self): """The reader thread that reads bytes from our stream""" logging.debug("in __reader()") From b7f7a40192e23944415c228e67637bf9684d6897 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 08:18:16 -0700 Subject: [PATCH 16/57] document why using python 3.9 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c5c4b8a5..368c2651 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "GPL-3.0-only" readme = "README.md" [tool.poetry.dependencies] -python = "^3.9,<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" From 6c0e978470dd5e8704d9676adfe9b9bf9df8d733 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 08:18:27 -0700 Subject: [PATCH 17/57] debugging config tweaks --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3da59236..49a3d71f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -163,7 +163,7 @@ "type": "python", "request": "launch", "module": "meshtastic", - "justMyCode": true, + "justMyCode": false, "args": ["--noproto", "--seriallog", "stdout"] }, { From 8b781d32453e5a3329c7c26ea61009e59dd776e0 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 08:26:20 -0700 Subject: [PATCH 18/57] fix #610: bump nanopb to 0.4.8 Including in the Poetry changes because it touches the same lines and I want to avoid hand merging ;-) --- .github/workflows/update_protobufs.yml | 6 +++--- bin/regen-protobufs.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 38ff1d3e..5cec898d 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -18,9 +18,9 @@ jobs: - name: Download nanopb run: | - wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.6-linux-x86.tar.gz - tar xvzf nanopb-0.4.6-linux-x86.tar.gz - mv nanopb-0.4.6-linux-x86 nanopb-0.4.6 + wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz + tar xvzf nanopb-0.4.8-linux-x86.tar.gz + mv nanopb-0.4.8-linux-x86 nanopb-0.4.8 - name: Install poetry (needed by regen-protobufs.sh) run: | diff --git a/bin/regen-protobufs.sh b/bin/regen-protobufs.sh index ac56608e..6b993c86 100755 --- a/bin/regen-protobufs.sh +++ b/bin/regen-protobufs.sh @@ -7,8 +7,8 @@ # protoc looks for mypy plugin in the python path source $(poetry env info --path)/bin/activate -./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto -./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto +./nanopb-0.4.8/generator-bin/protoc -I=protobufs --python_out ./ --mypy_out ./ ./protobufs/meshtastic/*.proto +./nanopb-0.4.8/generator-bin/protoc -I=protobufs --python_out ./meshtastic/ --mypy_out ./meshtastic/ ./protobufs/nanopb.proto # workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628 From 1add2934148226111129687dff65a00990cc9385 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 19 Jun 2024 14:45:59 -0700 Subject: [PATCH 19/57] Add a whitelist of known meshtastic USB VIDs to use a default serial ports. Initially only RAK4631 and heltec tracker are listed --- meshtastic/util.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index 14f6a544..86de1e90 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -24,8 +24,14 @@ from meshtastic.supported_device import supported_devices from meshtastic.version import get_active_version -"""Some devices such as a seger jlink we never want to accidentally open""" -blacklistVids = dict.fromkeys([0x1366]) +"""Some devices such as a seger jlink or st-link we never want to accidentally open""" +blacklistVids = dict.fromkeys([0x1366, 0x0483]) + +"""Some devices are highly likely to be meshtastic. +0x239a RAK4631 +0x303a Heltec tracker""" +whitelistVids = dict.fromkeys([0x239a, 0x303a]) + def quoteBooleans(a_string): """Quote booleans @@ -130,19 +136,35 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]: Returns: list -- a list of device paths """ - l = list( + all_ports = serial.tools.list_ports.comports() + + # look for 'likely' meshtastic devices + ports = list( map( lambda port: port.device, filter( - lambda port: port.vid is not None and port.vid not in blacklistVids, - serial.tools.list_ports.comports(), + lambda port: port.vid is not None and port.vid in whitelistVids, + all_ports, ), ) ) - l.sort() + + # if no likely devices, just list everything not blacklisted + if len(ports) == 0: + ports = list( + map( + lambda port: port.device, + filter( + lambda port: port.vid is not None and port.vid not in blacklistVids, + all_ports, + ), + ) + ) + + ports.sort() if eliminate_duplicates: - l = eliminate_duplicate_port(l) - return l + ports = eliminate_duplicate_port(ports) + return ports class dotdict(dict): From 5ff4025ed615146605c981539b97fa4e75b03501 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 17:18:04 -0700 Subject: [PATCH 20/57] add NordicSemi Power Profiler Kit 2 device to the USB blacklist --- meshtastic/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index 86de1e90..31938029 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -24,8 +24,10 @@ 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""" -blacklistVids = dict.fromkeys([0x1366, 0x0483]) +"""Some devices such as a seger jlink or st-link we never want to accidentally open +0x1915 NordicSemi (PPK2) +""" +blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915]) """Some devices are highly likely to be meshtastic. 0x239a RAK4631 From ea18057c1f8d39cc1b5571150bb62dcbe4940b10 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 18:42:15 -0700 Subject: [PATCH 21/57] Add support for NRF PPK2 power testing board. --- .vscode/launch.json | 2 +- meshtastic/__main__.py | 17 +++++++++- meshtastic/powermon/__init__.py | 4 ++- meshtastic/powermon/power_supply.py | 49 +++++++++++++++++++++++++++++ meshtastic/powermon/ppk2.py | 47 +++++++++++++++++++++++++++ meshtastic/powermon/riden.py | 48 +++------------------------- poetry.lock | 16 +++++++++- pyproject.toml | 1 + 8 files changed, 137 insertions(+), 47 deletions(-) create mode 100644 meshtastic/powermon/power_supply.py create mode 100644 meshtastic/powermon/ppk2.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 49a3d71f..f833f3a4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -172,7 +172,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-riden", "/dev/ttyUSB0", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"] + "args": ["--power-ppk", "--power-voltage", "3.3", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 4a21e859..323d13e1 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -21,7 +21,7 @@ from meshtastic.version import get_active_version from meshtastic.ble_interface import BLEInterface from meshtastic.mesh_interface import MeshInterface -from meshtastic.powermon import RidenPowerSupply +from meshtastic.powermon import RidenPowerSupply, PPK2PowerSupply from meshtastic.slog import StructuredLogger def onReceive(packet, interface): @@ -1093,6 +1093,16 @@ def common(): meter = None # assume no power meter if args.power_riden: meter = RidenPowerSupply(args.power_riden) + elif args.power_ppk2: + meter = PPK2PowerSupply() + + if meter and args.power_voltage: + v = float(args.power_voltage) + if v < 1.0 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() StructuredLogger(client, meter) @@ -1521,6 +1531,11 @@ def initParser(): action="store_true", ) + group.add_argument( + "--power-voltage", + help="Set the specified voltage on the power-supply. Be VERY careful, you can burn things up.", + ) + group.add_argument( "--power-stress", help="Perform power monitor stress testing, to capture a power consumption profile for the device (also requires --power-mon)", diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py index d8b4104f..c4aa654e 100644 --- a/meshtastic/powermon/__init__.py +++ b/meshtastic/powermon/__init__.py @@ -1,3 +1,5 @@ """Support for logging from power meters/supplies.""" -from .riden import * +from .power_supply import PowerMeter, PowerSupply, PowerError +from .riden import RidenPowerSupply +from .ppk2 import PPK2PowerSupply \ No newline at end of file diff --git a/meshtastic/powermon/power_supply.py b/meshtastic/powermon/power_supply.py new file mode 100644 index 00000000..f87a4b8d --- /dev/null +++ b/meshtastic/powermon/power_supply.py @@ -0,0 +1,49 @@ +"""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() + self.prevWattHour = self._getRawWattHour() + + def getWatts(self) -> float: + """Get the total amount of power that has been consumed since the previous call of 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 + + def _getRawWattHour(self) -> float: + """Get the current watt-hour reading (without any offset correction).""" + return math.nan + + + +class PowerSupply(PowerMeter): + """Abstract class for power supplies.""" + + def __init__(self): + """Initialize the PowerSupply object.""" + super().__init__() + self.v = 3.3 + + def powerOn(self): + """Turn on the power supply (using the voltage set in self.v).""" diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py new file mode 100644 index 00000000..4fe4a88c --- /dev/null +++ b/meshtastic/powermon/ppk2.py @@ -0,0 +1,47 @@ +"""code logging power consumption of meshtastic devices.""" + +import logging +from typing import * + +from ppk2_api import ppk2_api +from .power_supply import PowerSupply, PowerError + + + +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.r = r = ppk2_api.PPK2_MP(portName) # serial port will be different for you + r.get_modifiers() + + logging.info("Connected to PPK2 power supply") + + super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works + + def powerOn(self): + """Power on the supply, with reasonable defaults for meshtastic devices.""" + self.r.use_source_meter() # set source meter mode + self.r.set_source_voltage(self.v * 1000) # set source voltage in mV + self.r.toggle_DUT_power("ON") + self.r.start_measuring() # start measuring + + + def _getRawWattHour(self) -> float: + """Get the current watt-hour reading.""" + return 4 # FIXME diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py index 1dd65bc2..17d5c025 100644 --- a/meshtastic/powermon/riden.py +++ b/meshtastic/powermon/riden.py @@ -1,52 +1,14 @@ """code logging power consumption of meshtastic devices.""" import logging -import math from datetime import datetime from riden import Riden - -class PowerMeter: - """Abstract class for power meters.""" - - def __init__(self): - """Initialize the PowerMeter object.""" - self.prevPowerTime = datetime.now() - self.prevWattHour = self._getRawWattHour() - - def getWatts(self) -> float: - """Get the total amount of power that has been consumed since the previous call of 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 - - def _getRawWattHour(self) -> float: - """Get the current watt-hour reading (without any offset correction).""" - return math.nan - - - -class PowerSupply(PowerMeter): - """Abstract class for power supplies.""" - - def setMaxCurrent(self, i: float): - """Set the maximum current the supply will provide.""" - - def powerOn(self, v: float): - """Turn on the power supply.""" - - +from .power_supply import PowerSupply class RidenPowerSupply(PowerSupply): - """Interface for talking to programmable bench-top power supplies. - Currently only the Riden supplies are supported (RD6006 tested) + """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"): @@ -65,9 +27,9 @@ def setMaxCurrent(self, i: float): """Set the maximum current the supply will provide.""" self.r.set_i_set(i) - def powerOn(self, v: float): + def powerOn(self): """Power on the supply, with reasonable defaults for meshtastic devices.""" - self.r.set_v_set(v) # my WM1110 devboard header is directly connected to the 3.3V rail + 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 _getRawWattHour(self) -> float: diff --git a/poetry.lock b/poetry.lock index 464c3eb3..c18bdfb3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -928,6 +928,20 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "ppk2-api" +version = "0.9.2" +description = "API for Nordic Semiconductor's Power Profiler Kit II (PPK 2)." +optional = false +python-versions = "*" +files = [ + {file = "ppk2-api-0.9.2.tar.gz", hash = "sha256:e8fb29f782ba6e5bd2e0286079163c495cfffce336f9b149430348c043b25300"}, + {file = "ppk2_api-0.9.2-py3-none-any.whl", hash = "sha256:b7fb02156f87d8430bbce0006876d38c8309ada671fbcd15848173b431198803"}, +] + +[package.dependencies] +pyserial = "*" + [[package]] name = "protobuf" version = "5.27.1" @@ -1794,4 +1808,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "cc81bcd3e4671bde3375f2f08334d6a3b87b38c97f2676d7266cab980082659e" +content-hash = "7719c01f112a7864acdec0a82e7131aa33a646d84459cc0cc6db0eadf94933c7" diff --git a/pyproject.toml b/pyproject.toml index 368c2651..c767dd3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ packaging = "^24.0" riden = {git = "https://github.com/geeksville/riden.git#1.2.1"} pandas = "^2.2.2" parse = "^1.20.2" +ppk2-api = "^0.9.2" [tool.poetry.group.dev.dependencies] hypothesis = "^6.103.2" From 26a672ed587fd2f5f7ca2d0f3225e532c36eb067 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 24 Jun 2024 09:57:46 -0700 Subject: [PATCH 22/57] ppk2 tweaks --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f833f3a4..a652316d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -172,7 +172,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-ppk", "--power-voltage", "3.3", "--port", "/dev/ttyACM0", "--noproto", "--seriallog", "stdout"] + "args": ["--power-ppk", "--power-voltage", "0.6", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", From dc8348b99ef60bce8bff3bde46d53d508bd975e6 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 24 Jun 2024 09:59:31 -0700 Subject: [PATCH 23/57] add (optional) poe tool config for easy running of external commands --- pyproject.toml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c767dd3b..525bf979 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "GPL-3.0-only" readme = "README.md" [tool.poetry.dependencies] -python = "^3.9,<3.13" # 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,7 +22,7 @@ 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"} +riden = { git = "https://github.com/geeksville/riden.git#1.2.1" } pandas = "^2.2.2" parse = "^1.20.2" ppk2-api = "^0.9.2" @@ -51,6 +51,12 @@ 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 ." + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From 43e1f65a75c1a6409a6b6f0eed1f62bf985fd194 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 24 Jun 2024 15:25:14 -0700 Subject: [PATCH 24/57] "python" is deprecated vscode now wants "pydebug" --- .vscode/launch.json | 62 +++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a652316d..a9ae9a29 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "configurations": [ { "name": "meshtastic BLE", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": false, @@ -14,7 +14,7 @@ }, { "name": "meshtastic admin", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -22,7 +22,7 @@ }, { "name": "meshtastic tunnel", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -30,7 +30,7 @@ }, { "name": "meshtastic set chan", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -38,7 +38,7 @@ }, { "name": "meshtastic debug", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -46,7 +46,7 @@ }, { "name": "meshtastic listen", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -54,7 +54,7 @@ }, { "name": "meshtastic debug getPref", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -62,7 +62,7 @@ }, { "name": "meshtastic debug getPref telemetry", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -70,7 +70,7 @@ }, { "name": "meshtastic debug info", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -78,7 +78,7 @@ }, { "name": "meshtastic debug set region", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -86,7 +86,7 @@ }, { "name": "meshtastic debug set bluetooth fixed pin", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -94,7 +94,7 @@ }, { "name": "meshtastic debug get bluetooth fixed pin", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -102,7 +102,7 @@ }, { "name": "meshtastic debug setPref", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -110,7 +110,7 @@ }, { "name": "meshtastic debug setPref telemetry.environment_measurement_enabled", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -118,7 +118,7 @@ }, { "name": "meshtastic debug setPref telemetry.environment_screen_enabled", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -126,7 +126,7 @@ }, { "name": "meshtastic debug setPref telemetry", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -134,7 +134,7 @@ }, { "name": "meshtastic setpref", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -142,7 +142,7 @@ }, { "name": "meshtastic --ch-set", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -151,7 +151,7 @@ { "name": "meshtastic seturl", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -160,23 +160,31 @@ }, { "name": "meshtastic shell", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": false, "args": ["--noproto", "--seriallog", "stdout"] }, { - "name": "meshtastic powermon", - "type": "python", + "name": "meshtastic powermon sim", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-ppk", "--power-voltage", "0.6", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] + "args": ["--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": ["--power-ppk2-meter", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -184,7 +192,7 @@ }, { "name": "meshtastic settime", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -192,7 +200,7 @@ }, { "name": "meshtastic sendtext", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, @@ -200,7 +208,7 @@ }, { "name": "meshtastic showNodes", - "type": "python", + "type": "debugpy", "request": "launch", "module": "meshtastic", "justMyCode": true, From 7ce7d73e89dead1b1be1710f13de73114e79c043 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 24 Jun 2024 15:26:05 -0700 Subject: [PATCH 25/57] Switch from pandas to apache arrow for live data logging (better streaming) --- .vscode/settings.json | 1 + meshtastic/__main__.py | 37 ++++++--- meshtastic/slog/__init__.py | 4 +- meshtastic/slog/arrow.py | 42 ++++++++++ meshtastic/slog/slog.py | 157 ++++++++++++++++++++++-------------- 5 files changed, 170 insertions(+), 71 deletions(-) create mode 100644 meshtastic/slog/arrow.py diff --git a/.vscode/settings.json b/.vscode/settings.json index a1fd5743..ba9060af 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "boardid", "Meshtastic", "powermon", + "pyarrow", "TORADIO", "Vids" ], diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 323d13e1..1287f4fd 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -21,8 +21,8 @@ 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 -from meshtastic.slog import StructuredLogger +from meshtastic.powermon import RidenPowerSupply, PPK2PowerSupply, SimPowerSupply +from meshtastic.slog import LogSet def onReceive(packet, interface): """Callback invoked when a packet arrives""" @@ -1090,21 +1090,26 @@ def common(): # We assume client is fully connected now onConnected(client) + # Setup power meters meter = None # assume no power meter if args.power_riden: meter = RidenPowerSupply(args.power_riden) - elif args.power_ppk2: + 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 < 1.0 or v >5.0: + 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() - StructuredLogger(client, meter) + # Setup loggers + LogSet(client, meter) have_tunnel = platform.system() == "Linux" if ( @@ -1520,14 +1525,28 @@ def initParser(): action="store_true", ) - group.add_argument( + power_supply_group = 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", ) - group.add_argument( - "--power-ppk2", - help="Talk to a Nordic Power Profiler Kit 2", + 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", ) diff --git a/meshtastic/slog/__init__.py b/meshtastic/slog/__init__.py index a96ae8eb..acd5d211 100644 --- a/meshtastic/slog/__init__.py +++ b/meshtastic/slog/__init__.py @@ -1,3 +1,3 @@ -"""Structured logging framework (see dev docs for more info)""" +"""Structured logging framework (see dev docs for more info).""" -from .slog import StructuredLogger +from .slog import LogSet diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py new file mode 100644 index 00000000..2342c9b8 --- /dev/null +++ b/meshtastic/slog/arrow.py @@ -0,0 +1,42 @@ +import pyarrow as pa + +chunk_size = 10 # 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") + 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: + self.schema = pa.Table.from_pylist(self.new_rows).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() diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index ab474145..0c480e74 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -1,96 +1,104 @@ """code logging power consumption of meshtastic devices.""" +import atexit import logging import re -import atexit -from datetime import datetime +import threading +import time from dataclasses import dataclass +from datetime import datetime import parse -import pandas as pd -from pubsub import pub # type: ignore[import-untyped] +from pubsub import pub # type: ignore[import-untyped] from meshtastic.mesh_interface import MeshInterface from meshtastic.powermon import PowerMeter +from .arrow import ArrowWriter + @dataclass(init=False) class LogDef: """Log definition.""" - code: str # i.e. PM or B or whatever... see meshtastic slog documentation - format: str # A format string that can be used to parse the arguments + + code: str # i.e. PM or B or whatever... see meshtastic slog documentation + format: str # 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. + 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_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 = ArrowWriter(file_path) + self.interval = interval + self.is_logging = True + self.thread = threading.Thread(target=self._logging_thread, name="PowerLogger") + self.thread.start() + + def _logging_thread(self) -> None: + """Background thread for logging the current watts reading.""" + while self.is_logging: + watts = self.pMeter.getWatts() + d = {"time": datetime.now(), "watts": watts} + self.writer.add_row(d) + time.sleep(self.interval) + + def close(self) -> None: + """Close the PowerLogger and stop logging.""" + if self.is_logging: + 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 pandas/CSV format.""" - def __init__(self, client: MeshInterface, pMeter: PowerMeter = None) -> None: + def __init__(self, client: MeshInterface, file_path: str) -> None: """Initialize the PowerMonClient object. - power (PowerSupply): The power supply object. - client (MeshInterface): The MeshInterface object to monitor. + power (PowerSupply): The power supply object. + client (MeshInterface): The MeshInterface object to monitor. """ self.client = client - self.pMeter = pMeter - self.columns = ["time", "power"] - self.rawData = pd.DataFrame(columns=self.columns) # use time as the index - # self.rawData.set_index("time", inplace=True) - - # for efficiency reasons we keep new data in a list - only adding to rawData when needed - self.newData: list[dict] = [] - - atexit.register(self._exitHandler) - pub.subscribe(self._onLogMessage, "meshtastic.log.line") - - def getRawData(self) -> pd.DataFrame: - """Get the raw data. - - Returns - ------- - pd.DataFrame: The raw data. - """ - - df = pd.DataFrame(self.newData) - - # We prefer some columns to be integers - intcols = [ "bitmask" ] - for c in intcols: - if c in df: - df[c] = df[c].astype('Int64') + self.writer = ArrowWriter(file_path) + self.listener = pub.subscribe(self._onLogMessage, TOPIC_MESHTASTIC_LOG_LINE) - # df.set_index("time") - # Add new data, creating new columns as needed (an outer join) - self.rawData = pd.concat([self.rawData, df], axis=0, ignore_index=True) - self.newData = [] + def close(self) -> None: + """Stop logging.""" + pub.unsubscribe(self.listener, TOPIC_MESHTASTIC_LOG_LINE) + self.writer.close() - return self.rawData - - def _exitHandler(self) -> None: - """Exit handler.""" - fn = "/tmp/powermon.slog" # Find a better place - logging.info(f"Storing slog in {fn}") - self.getRawData().to_csv(fn) - - def _onLogMessage(self, line: str, interface: MeshInterface) -> None: # pylint: disable=unused-argument + def _onLogMessage( + self, line: str, interface: MeshInterface + ) -> None: # pylint: disable=unused-argument """Handle log messages. - line (str): the line of log output + line (str): the line of log output """ m = log_regex.match(line) if m: @@ -101,15 +109,44 @@ def _onLogMessage(self, line: str, interface: MeshInterface) -> None: # pylint: logging.debug(f"SLog {src}, reason: {args}") d = log_defs.get(src) if d: - r = d.format.parse(args) # get the values with the correct types + r = d.format.parse(args) # get the values with the correct types if r: di = r.named di["time"] = datetime.now() - if self.pMeter: # if we have a power meter include a fresh power reading - di["power"] = self.pMeter.getWatts() - self.newData.append(di) - self.getRawData() + 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}") + + +class LogSet: + """A complete set of meshtastic log/metadata for a particular run.""" + + def __init__(self, client: MeshInterface, power_meter: PowerMeter = None) -> None: + """Initialize the PowerMonClient object. + + power (PowerSupply): The power supply object. + client (MeshInterface): The MeshInterface object to monitor. + """ + self.dir_name = "/tmp" # FIXME + + self.slog_logger = StructuredLogger(client, f"{self.dir_name}/slog.arrow") + if power_meter: + self.power_logger = PowerLogger(power_meter, f"{self.dir_name}/power.arrow") + else: + self.power_logger = None + + atexit.register(self._exitHandler) + + def close(self) -> None: + """Close the log set.""" + + logging.info(f"Storing slog in {self.dir_name}") + self.slog_logger.close() + if self.power_logger: + self.power_logger.close() + + def _exitHandler(self) -> None: + """Exit handler.""" + self.close() From 91066f6aed48d191e670556ca987bf1079f02e1d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 08:57:37 -0700 Subject: [PATCH 26/57] add powermon_sim support --- meshtastic/powermon/__init__.py | 3 ++- meshtastic/powermon/power_supply.py | 7 +++--- meshtastic/powermon/ppk2.py | 33 +++++++++++++++++++++-------- meshtastic/powermon/riden.py | 10 ++++++--- meshtastic/powermon/sim.py | 17 +++++++++++++++ pyproject.toml | 3 ++- 6 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 meshtastic/powermon/sim.py diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py index c4aa654e..9db55448 100644 --- a/meshtastic/powermon/__init__.py +++ b/meshtastic/powermon/__init__.py @@ -2,4 +2,5 @@ from .power_supply import PowerMeter, PowerSupply, PowerError from .riden import RidenPowerSupply -from .ppk2 import PPK2PowerSupply \ No newline at end of file +from .ppk2 import PPK2PowerSupply +from .sim import SimPowerSupply \ No newline at end of file diff --git a/meshtastic/powermon/power_supply.py b/meshtastic/powermon/power_supply.py index f87a4b8d..18463a75 100644 --- a/meshtastic/powermon/power_supply.py +++ b/meshtastic/powermon/power_supply.py @@ -3,8 +3,10 @@ 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) @@ -19,7 +21,7 @@ def __init__(self): self.prevWattHour = self._getRawWattHour() def getWatts(self) -> float: - """Get the total amount of power that has been consumed since the previous call of this method""" + """Get the total amount of power that is currently being consumed.""" now = datetime.now() nowWattHour = self._getRawWattHour() watts = ( @@ -36,14 +38,13 @@ def _getRawWattHour(self) -> float: return math.nan - class PowerSupply(PowerMeter): """Abstract class for power supplies.""" def __init__(self): """Initialize the PowerSupply object.""" super().__init__() - self.v = 3.3 + self.v = 0.0 def powerOn(self): """Turn on the power supply (using the voltage set in self.v).""" diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 4fe4a88c..db906351 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -4,8 +4,8 @@ from typing import * from ppk2_api import ppk2_api -from .power_supply import PowerSupply, PowerError +from .power_supply import PowerError, PowerSupply class PPK2PowerSupply(PowerSupply): @@ -16,32 +16,47 @@ class PPK2PowerSupply(PowerSupply): 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". + 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") + raise PowerError( + "Multiple PPK2 devices found, please specify the portName" + ) else: portName = devs[0] self.r = r = ppk2_api.PPK2_MP(portName) # serial port will be different for you r.get_modifiers() + self.r.start_measuring() # start measuring logging.info("Connected to PPK2 power supply") - super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works + super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works + + def setIsSupply(self, s: bool): + """If in supply mode we will provide power ourself, otherwise we are just an amp meter.""" + 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.set_source_voltage( + int(self.v * 1000) + ) # set source voltage in mV BEFORE setting source mode + self.r.use_source_meter() # set source meter mode def powerOn(self): - """Power on the supply, with reasonable defaults for meshtastic devices.""" - self.r.use_source_meter() # set source meter mode - self.r.set_source_voltage(self.v * 1000) # set source voltage in mV + """Power on the supply.""" self.r.toggle_DUT_power("ON") - self.r.start_measuring() # start measuring + def powerOff(self): + """Power off the supply.""" + self.r.toggle_DUT_power("OFF") def _getRawWattHour(self) -> float: """Get the current watt-hour reading.""" - return 4 # FIXME + return 4 # FIXME diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py index 17d5c025..c425855a 100644 --- a/meshtastic/powermon/riden.py +++ b/meshtastic/powermon/riden.py @@ -4,8 +4,10 @@ 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. @@ -14,14 +16,14 @@ class RidenPowerSupply(PowerSupply): 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". + 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()) - super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works + 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.""" @@ -29,7 +31,9 @@ def setMaxCurrent(self, i: float): 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_v_set( + self.v + ) # my WM1110 devboard header is directly connected to the 3.3V rail self.r.set_output(1) def _getRawWattHour(self) -> float: diff --git a/meshtastic/powermon/sim.py b/meshtastic/powermon/sim.py new file mode 100644 index 00000000..9b6ea125 --- /dev/null +++ b/meshtastic/powermon/sim.py @@ -0,0 +1,17 @@ +"""code logging power consumption of meshtastic devices.""" + +import math +import time +from typing import * + +from .power_supply import PowerError, PowerSupply + + +class SimPowerSupply(PowerSupply): + """A simulated power supply for testing.""" + + def getWatts(self) -> float: + """Get the total amount of power that is currently being consumed.""" + + # Sim a 20mW load that varies sinusoidally + return (20 + 5 * math.sin(time.time())) / 1000 diff --git a/pyproject.toml b/pyproject.toml index 525bf979..3eee1fe0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,9 +23,10 @@ pypubsub = "^4.0.3" bleak = "^0.21.1" packaging = "^24.0" riden = { git = "https://github.com/geeksville/riden.git#1.2.1" } -pandas = "^2.2.2" parse = "^1.20.2" ppk2-api = "^0.9.2" +pyarrow = "^16.1.0" +pyarrow-stubs = "^10.0.1.7" [tool.poetry.group.dev.dependencies] hypothesis = "^6.103.2" From 9cdfde47ec1843aae408be4be1398b5e0a2d96a0 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 10:02:07 -0700 Subject: [PATCH 27/57] store slogs in correct default directory (OS dependent) --- .vscode/launch.json | 4 +- meshtastic/__main__.py | 9 +- meshtastic/powermon/power_supply.py | 4 +- meshtastic/powermon/ppk2.py | 4 +- meshtastic/powermon/sim.py | 2 +- meshtastic/slog/arrow.py | 2 +- meshtastic/slog/slog.py | 55 ++++++--- poetry.lock | 180 +++++++++------------------- pyproject.toml | 1 + 9 files changed, 112 insertions(+), 149 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a9ae9a29..5add7d97 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -172,7 +172,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-sim", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] + "args": ["--slog-out", "default", "--power-sim", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic powermon ppk2", @@ -180,7 +180,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--power-ppk2-meter", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] + "args": ["--slog-out", "default", "--power-ppk2-meter", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 1287f4fd..793f2d79 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -1108,8 +1108,9 @@ def common(): meter.v = v meter.powerOn() - # Setup loggers - LogSet(client, meter) + if args.slog_out: + # Setup loggers + LogSet(client, args.slog_out if args.slog_out != 'default' else None, meter) have_tunnel = platform.system() == "Linux" if ( @@ -1561,6 +1562,10 @@ def initParser(): action="store_true", ) + group.add_argument( + "--slog-out", + help="A directory to store structured logging to, or 'default' for automatically selected.", + ) group.add_argument( "--ble-scan", help="Scan for Meshtastic BLE devices", diff --git a/meshtastic/powermon/power_supply.py b/meshtastic/powermon/power_supply.py index 18463a75..cb4d0441 100644 --- a/meshtastic/powermon/power_supply.py +++ b/meshtastic/powermon/power_supply.py @@ -20,8 +20,8 @@ def __init__(self): self.prevPowerTime = datetime.now() self.prevWattHour = self._getRawWattHour() - def getWatts(self) -> float: - """Get the total amount of power that is currently being consumed.""" + def getAverageWatts(self) -> float: + """Get watts consumed since last call to this method.""" now = datetime.now() nowWattHour = self._getRawWattHour() watts = ( diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index db906351..79f87ff4 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -57,6 +57,4 @@ def powerOff(self): """Power off the supply.""" self.r.toggle_DUT_power("OFF") - def _getRawWattHour(self) -> float: - """Get the current watt-hour reading.""" - return 4 # FIXME + diff --git a/meshtastic/powermon/sim.py b/meshtastic/powermon/sim.py index 9b6ea125..c66c5b37 100644 --- a/meshtastic/powermon/sim.py +++ b/meshtastic/powermon/sim.py @@ -10,7 +10,7 @@ class SimPowerSupply(PowerSupply): """A simulated power supply for testing.""" - def getWatts(self) -> float: + def getAverageWatts(self) -> float: """Get the total amount of power that is currently being consumed.""" # Sim a 20mW load that varies sinusoidally diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py index 2342c9b8..157f581c 100644 --- a/meshtastic/slog/arrow.py +++ b/meshtastic/slog/arrow.py @@ -1,6 +1,6 @@ import pyarrow as pa -chunk_size = 10 # disk writes are batched based on this number of rows +chunk_size = 1000 # disk writes are batched based on this number of rows class ArrowWriter: diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 0c480e74..d2cc41f6 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -5,16 +5,20 @@ import re import threading import time +import os from dataclasses import dataclass from datetime import datetime +from typing import Optional import parse +import platformdirs from pubsub import pub # type: ignore[import-untyped] from meshtastic.mesh_interface import MeshInterface from meshtastic.powermon import PowerMeter from .arrow import ArrowWriter +import os @dataclass(init=False) @@ -54,13 +58,13 @@ def __init__(self, pMeter: PowerMeter, file_path: str, interval=0.2) -> None: self.writer = ArrowWriter(file_path) self.interval = interval self.is_logging = True - self.thread = threading.Thread(target=self._logging_thread, name="PowerLogger") + 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: - watts = self.pMeter.getWatts() + watts = self.pMeter.getAverageWatts() d = {"time": datetime.now(), "watts": watts} self.writer.add_row(d) time.sleep(self.interval) @@ -72,26 +76,33 @@ def close(self) -> None: 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 pandas/CSV format.""" + """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, file_path: str) -> None: - """Initialize the PowerMonClient object. + def __init__(self, client: MeshInterface, dir_path: str) -> None: + """Initialize the StructuredLogger object. - power (PowerSupply): The power supply object. client (MeshInterface): The MeshInterface object to monitor. """ self.client = client - self.writer = ArrowWriter(file_path) + self.writer = ArrowWriter(f"{dir_path}/slog.arrow") + # trunk-ignore(pylint/R1732) + self.raw_file = open( + f"{dir_path}/raw.txt", "w", encoding="utf8" + ) self.listener = pub.subscribe(self._onLogMessage, TOPIC_MESHTASTIC_LOG_LINE) def close(self) -> None: """Stop logging.""" pub.unsubscribe(self.listener, TOPIC_MESHTASTIC_LOG_LINE) self.writer.close() + self.raw_file.close() # Close the raw.txt file def _onLogMessage( self, line: str, interface: MeshInterface @@ -104,7 +115,6 @@ def _onLogMessage( 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}") d = log_defs.get(src) @@ -118,35 +128,46 @@ def _onLogMessage( logging.warning(f"Failed to parse slog {line} with {d.format}") else: logging.warning(f"Unknown Structured Log: {line}") + 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, power_meter: PowerMeter = None) -> None: + def __init__( + self, + client: MeshInterface, + dir_name: Optional[str] = None, + power_meter: PowerMeter = None, + ) -> None: """Initialize the PowerMonClient object. power (PowerSupply): The power supply object. client (MeshInterface): The MeshInterface object to monitor. """ - self.dir_name = "/tmp" # FIXME - self.slog_logger = StructuredLogger(client, f"{self.dir_name}/slog.arrow") + 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) + self.dir_name = dir_name + + logging.info(f"Writing slogs to {dir_name}") + + self.slog_logger = StructuredLogger(client, self.dir_name) if power_meter: self.power_logger = PowerLogger(power_meter, f"{self.dir_name}/power.arrow") else: self.power_logger = None - atexit.register(self._exitHandler) + atexit.register(self.close) def close(self) -> None: """Close the log set.""" - logging.info(f"Storing slog in {self.dir_name}") + logging.info(f"Closing slogs in {self.dir_name}") self.slog_logger.close() if self.power_logger: self.power_logger.close() - - def _exitHandler(self) -> None: - """Exit handler.""" - self.close() diff --git a/poetry.lock b/poetry.lock index c18bdfb3..b872b405 100644 --- a/poetry.lock +++ b/poetry.lock @@ -773,79 +773,6 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -[[package]] -name = "pandas" -version = "2.2.2" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - [[package]] name = "parse" version = "1.20.2" @@ -973,6 +900,64 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +[[package]] +name = "pyarrow" +version = "16.1.0" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyarrow-16.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:17e23b9a65a70cc733d8b738baa6ad3722298fa0c81d88f63ff94bf25eaa77b9"}, + {file = "pyarrow-16.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4740cc41e2ba5d641071d0ab5e9ef9b5e6e8c7611351a5cb7c1d175eaf43674a"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98100e0268d04e0eec47b73f20b39c45b4006f3c4233719c3848aa27a03c1aef"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68f409e7b283c085f2da014f9ef81e885d90dcd733bd648cfba3ef265961848"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a8914cd176f448e09746037b0c6b3a9d7688cef451ec5735094055116857580c"}, + {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:48be160782c0556156d91adbdd5a4a7e719f8d407cb46ae3bb4eaee09b3111bd"}, + {file = "pyarrow-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cf389d444b0f41d9fe1444b70650fea31e9d52cfcb5f818b7888b91b586efff"}, + {file = "pyarrow-16.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d0ebea336b535b37eee9eee31761813086d33ed06de9ab6fc6aaa0bace7b250c"}, + {file = "pyarrow-16.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e73cfc4a99e796727919c5541c65bb88b973377501e39b9842ea71401ca6c1c"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9251264247ecfe93e5f5a0cd43b8ae834f1e61d1abca22da55b20c788417f6"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf5aace92d520d3d2a20031d8b0ec27b4395cab9f74e07cc95edf42a5cc0147"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:25233642583bf658f629eb230b9bb79d9af4d9f9229890b3c878699c82f7d11e"}, + {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a33a64576fddfbec0a44112eaf844c20853647ca833e9a647bfae0582b2ff94b"}, + {file = "pyarrow-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:185d121b50836379fe012753cf15c4ba9638bda9645183ab36246923875f8d1b"}, + {file = "pyarrow-16.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:2e51ca1d6ed7f2e9d5c3c83decf27b0d17bb207a7dea986e8dc3e24f80ff7d6f"}, + {file = "pyarrow-16.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06ebccb6f8cb7357de85f60d5da50e83507954af617d7b05f48af1621d331c9a"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04707f1979815f5e49824ce52d1dceb46e2f12909a48a6a753fe7cafbc44a0c"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d32000693deff8dc5df444b032b5985a48592c0697cb6e3071a5d59888714e2"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8785bb10d5d6fd5e15d718ee1d1f914fe768bf8b4d1e5e9bf253de8a26cb1628"}, + {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e1369af39587b794873b8a307cc6623a3b1194e69399af0efd05bb202195a5a7"}, + {file = "pyarrow-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:febde33305f1498f6df85e8020bca496d0e9ebf2093bab9e0f65e2b4ae2b3444"}, + {file = "pyarrow-16.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b5f5705ab977947a43ac83b52ade3b881eb6e95fcc02d76f501d549a210ba77f"}, + {file = "pyarrow-16.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d27bf89dfc2576f6206e9cd6cf7a107c9c06dc13d53bbc25b0bd4556f19cf5f"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d07de3ee730647a600037bc1d7b7994067ed64d0eba797ac74b2bc77384f4c2"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbef391b63f708e103df99fbaa3acf9f671d77a183a07546ba2f2c297b361e83"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19741c4dbbbc986d38856ee7ddfdd6a00fc3b0fc2d928795b95410d38bb97d15"}, + {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f2c5fb249caa17b94e2b9278b36a05ce03d3180e6da0c4c3b3ce5b2788f30eed"}, + {file = "pyarrow-16.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:e6b6d3cd35fbb93b70ade1336022cc1147b95ec6af7d36906ca7fe432eb09710"}, + {file = "pyarrow-16.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:18da9b76a36a954665ccca8aa6bd9f46c1145f79c0bb8f4f244f5f8e799bca55"}, + {file = "pyarrow-16.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99f7549779b6e434467d2aa43ab2b7224dd9e41bdde486020bae198978c9e05e"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f07fdffe4fd5b15f5ec15c8b64584868d063bc22b86b46c9695624ca3505b7b4"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfe389a08ea374972bd4065d5f25d14e36b43ebc22fc75f7b951f24378bf0b5"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b20bd67c94b3a2ea0a749d2a5712fc845a69cb5d52e78e6449bbd295611f3aa"}, + {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ba8ac20693c0bb0bf4b238751d4409e62852004a8cf031c73b0e0962b03e45e3"}, + {file = "pyarrow-16.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:31a1851751433d89a986616015841977e0a188662fcffd1a5677453f1df2de0a"}, + {file = "pyarrow-16.1.0.tar.gz", hash = "sha256:15fbb22ea96d11f0b5768504a3f961edab25eaf4197c341720c4a387f6c60315"}, +] + +[package.dependencies] +numpy = ">=1.16.6" + +[[package]] +name = "pyarrow-stubs" +version = "10.0.1.7" +description = "Type annotations for pyarrow" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pyarrow_stubs-10.0.1.7-py3-none-any.whl", hash = "sha256:cccc7a46eddeea4e3cb85330eb8972c116a615da6188b8ae1f7a44cb724b21ac"}, +] + [[package]] name = "pycodestyle" version = "2.12.0" @@ -1242,31 +1227,6 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, -] - [[package]] name = "pywin32-ctypes" version = "0.2.2" @@ -1393,17 +1353,6 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - [[package]] name = "sortedcontainers" version = "2.4.0" @@ -1530,17 +1479,6 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - [[package]] name = "urllib3" version = "2.2.2" @@ -1808,4 +1746,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "7719c01f112a7864acdec0a82e7131aa33a646d84459cc0cc6db0eadf94933c7" +content-hash = "b42cdca565b7cbe0ba16405328144b06cd8394d64cb41dac4def2c68d1f1e4b4" diff --git a/pyproject.toml b/pyproject.toml index 3eee1fe0..ca9070ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ parse = "^1.20.2" ppk2-api = "^0.9.2" pyarrow = "^16.1.0" pyarrow-stubs = "^10.0.1.7" +platformdirs = "^4.2.2" [tool.poetry.group.dev.dependencies] hypothesis = "^6.103.2" From c6561713dbcff538c6d857e936dd1f165c43d2ef Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 10:09:08 -0700 Subject: [PATCH 28/57] don't let daemon keep process alive --- meshtastic/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/stream_interface.py b/meshtastic/stream_interface.py index 18d6e9f3..55c1691f 100644 --- a/meshtastic/stream_interface.py +++ b/meshtastic/stream_interface.py @@ -42,7 +42,7 @@ def __init__(self, debugOut=None, noProto=False, connectNow=True, noNodes=False) 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) From 07fc991f4e2738ab1216976fbc55855eda86a876 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 10:39:44 -0700 Subject: [PATCH 29/57] clean up slog closing --- meshtastic/__main__.py | 6 +++++- meshtastic/slog/slog.py | 13 +++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 793f2d79..bbc4bb70 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -1108,9 +1108,10 @@ def common(): meter.v = v meter.powerOn() + log_set = None if args.slog_out: # Setup loggers - LogSet(client, args.slog_out if args.slog_out != 'default' else None, meter) + log_set = LogSet(client, args.slog_out if args.slog_out != 'default' else None, meter) have_tunnel = platform.system() == "Linux" if ( @@ -1122,6 +1123,9 @@ def common(): except KeyboardInterrupt: logging.info("Exiting due to keyboard interrupt") + if log_set: + log_set.close() + # don't call exit, background threads might be running still # sys.exit(0) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index d2cc41f6..9bee5922 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -162,12 +162,17 @@ def __init__( else: self.power_logger = None + # Store a lambda so we can find it again to unregister + self.atexit_handler = lambda: self.close() atexit.register(self.close) def close(self) -> None: """Close the log set.""" - logging.info(f"Closing slogs in {self.dir_name}") - self.slog_logger.close() - if self.power_logger: - self.power_logger.close() + 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 From 1b045bec884662d8628930d68b0a48ec4857bbe6 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 11:02:24 -0700 Subject: [PATCH 30/57] fix linter warnings --- meshtastic/powermon/__init__.py | 6 +++--- meshtastic/powermon/ppk2.py | 4 +--- meshtastic/powermon/sim.py | 3 +-- meshtastic/slog/arrow.py | 2 ++ meshtastic/slog/slog.py | 17 +++++++++-------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py index 9db55448..a8f578f4 100644 --- a/meshtastic/powermon/__init__.py +++ b/meshtastic/powermon/__init__.py @@ -1,6 +1,6 @@ """Support for logging from power meters/supplies.""" -from .power_supply import PowerMeter, PowerSupply, PowerError -from .riden import RidenPowerSupply +from .power_supply import PowerError, PowerMeter, PowerSupply from .ppk2 import PPK2PowerSupply -from .sim import SimPowerSupply \ No newline at end of file +from .riden import RidenPowerSupply +from .sim import SimPowerSupply diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 79f87ff4..3f8c67f0 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -1,7 +1,7 @@ """code logging power consumption of meshtastic devices.""" import logging -from typing import * +from typing import Optional from ppk2_api import ppk2_api @@ -56,5 +56,3 @@ def powerOn(self): def powerOff(self): """Power off the supply.""" self.r.toggle_DUT_power("OFF") - - diff --git a/meshtastic/powermon/sim.py b/meshtastic/powermon/sim.py index c66c5b37..e7e84842 100644 --- a/meshtastic/powermon/sim.py +++ b/meshtastic/powermon/sim.py @@ -2,9 +2,8 @@ import math import time -from typing import * -from .power_supply import PowerError, PowerSupply +from .power_supply import PowerSupply class SimPowerSupply(PowerSupply): diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py index 157f581c..71703c11 100644 --- a/meshtastic/slog/arrow.py +++ b/meshtastic/slog/arrow.py @@ -1,3 +1,5 @@ +"""Utilities for Apache Arrow serialization.""" + import pyarrow as pa chunk_size = 1000 # disk writes are batched based on this number of rows diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 9bee5922..720886a2 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -2,10 +2,10 @@ import atexit import logging +import os import re import threading import time -import os from dataclasses import dataclass from datetime import datetime from typing import Optional @@ -18,7 +18,6 @@ from meshtastic.powermon import PowerMeter from .arrow import ArrowWriter -import os @dataclass(init=False) @@ -58,7 +57,9 @@ def __init__(self, pMeter: PowerMeter, file_path: str, interval=0.2) -> None: self.writer = ArrowWriter(file_path) self.interval = interval self.is_logging = True - self.thread = threading.Thread(target=self._logging_thread, name="PowerLogger", daemon=True) + self.thread = threading.Thread( + target=self._logging_thread, name="PowerLogger", daemon=True + ) self.thread.start() def _logging_thread(self) -> None: @@ -92,10 +93,9 @@ def __init__(self, client: MeshInterface, dir_path: str) -> None: """ self.client = client self.writer = ArrowWriter(f"{dir_path}/slog.arrow") - # trunk-ignore(pylint/R1732) self.raw_file = open( f"{dir_path}/raw.txt", "w", encoding="utf8" - ) + ) # pylint: disable=consider-using-with self.listener = pub.subscribe(self._onLogMessage, TOPIC_MESHTASTIC_LOG_LINE) def close(self) -> None: @@ -163,15 +163,16 @@ def __init__( self.power_logger = None # Store a lambda so we can find it again to unregister - self.atexit_handler = lambda: self.close() - atexit.register(self.close) + 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 + 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() From 8d94458e559368f4813568837cc84169f1114852 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 11:02:58 -0700 Subject: [PATCH 31/57] flake8 has different settings than trunk, don't confict in vscode --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ba9060af..f393e653 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "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 } \ No newline at end of file From 9b61f11c88bc3ab8c422a1dbb414e29713943391 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 11:08:35 -0700 Subject: [PATCH 32/57] temporarily suppress warning about main.py being too long --- meshtastic/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index bbc4bb70..0014ab35 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -1,7 +1,10 @@ -#!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 From 220241448f6456b041b1d9332b477c7c75acdac7 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 11:08:47 -0700 Subject: [PATCH 33/57] more fighting with trunk --- meshtastic/slog/slog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 720886a2..1eac5e70 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -93,9 +93,9 @@ def __init__(self, client: MeshInterface, dir_path: str) -> None: """ self.client = client self.writer = ArrowWriter(f"{dir_path}/slog.arrow") - self.raw_file = open( + self.raw_file = open( # pylint: disable=consider-using-with f"{dir_path}/raw.txt", "w", encoding="utf8" - ) # pylint: disable=consider-using-with + ) self.listener = pub.subscribe(self._onLogMessage, TOPIC_MESHTASTIC_LOG_LINE) def close(self) -> None: @@ -105,8 +105,8 @@ def close(self) -> None: self.raw_file.close() # Close the raw.txt file def _onLogMessage( - self, line: str, interface: MeshInterface - ) -> None: # pylint: disable=unused-argument + self, line: str, interface: MeshInterface # pylint: disable=unused-argument + ) -> None: """Handle log messages. line (str): the line of log output From 402622f4271dd26447679c29eff866568cb0a644 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 11:25:07 -0700 Subject: [PATCH 34/57] fix type warnings --- meshtastic/powermon/ppk2.py | 4 ++-- meshtastic/slog/arrow.py | 2 +- meshtastic/slog/slog.py | 17 +++++++++-------- poetry.lock | 2 +- pyproject.toml | 2 +- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 3f8c67f0..bec4c1d0 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -1,9 +1,9 @@ -"""code logging power consumption of meshtastic devices.""" +"""Classes for logging power consumption of meshtastic devices.""" import logging from typing import Optional -from ppk2_api import ppk2_api +from ppk2_api import ppk2_api # type: ignore[import-untyped] from .power_supply import PowerError, PowerSupply diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py index 71703c11..225d3ce1 100644 --- a/meshtastic/slog/arrow.py +++ b/meshtastic/slog/arrow.py @@ -13,7 +13,7 @@ def __init__(self, file_name: str): file_name (str): The name of the file to write to. """ - self.sink = pa.OSFile(file_name, "wb") + 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 diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 1eac5e70..0dd0690a 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -10,7 +10,7 @@ from datetime import datetime from typing import Optional -import parse +import parse # type: ignore[import-untyped] import platformdirs from pubsub import pub # type: ignore[import-untyped] @@ -25,7 +25,7 @@ class LogDef: """Log definition.""" code: str # i.e. PM or B or whatever... see meshtastic slog documentation - format: str # A format string that can be used to parse the arguments + 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. @@ -138,7 +138,7 @@ def __init__( self, client: MeshInterface, dir_name: Optional[str] = None, - power_meter: PowerMeter = None, + power_meter: Optional[PowerMeter] = None, ) -> None: """Initialize the PowerMonClient object. @@ -156,11 +156,12 @@ def __init__( logging.info(f"Writing slogs to {dir_name}") - self.slog_logger = StructuredLogger(client, self.dir_name) - if power_meter: - self.power_logger = PowerLogger(power_meter, f"{self.dir_name}/power.arrow") - else: - self.power_logger = None + 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.arrow") + ) # Store a lambda so we can find it again to unregister self.atexit_handler = lambda: self.close() # pylint: disable=unnecessary-lambda diff --git a/poetry.lock b/poetry.lock index b872b405..af2ca49a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1746,4 +1746,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "b42cdca565b7cbe0ba16405328144b06cd8394d64cb41dac4def2c68d1f1e4b4" +content-hash = "ad12848e1311886733ea3205795e49e405c2012e9dcff2df6e926ef1cd1dd4b0" diff --git a/pyproject.toml b/pyproject.toml index ca9070ff..e0b99b4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ riden = { git = "https://github.com/geeksville/riden.git#1.2.1" } parse = "^1.20.2" ppk2-api = "^0.9.2" pyarrow = "^16.1.0" -pyarrow-stubs = "^10.0.1.7" platformdirs = "^4.2.2" [tool.poetry.group.dev.dependencies] @@ -45,6 +44,7 @@ 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" [tool.poetry.extras] tunnel = ["pytap2"] From d448ea5767fde975db048391febfd8c9fddbbaaf Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 12:03:35 -0700 Subject: [PATCH 35/57] keep a symbolic link "latest" that points to the latest slog dir --- meshtastic/slog/slog.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 0dd0690a..bc8df62f 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -152,6 +152,13 @@ def __init__( 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}") From d1aadf0c8ea7aa6b9ee9ee4d0c77a3be63795e2e Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 12:22:47 -0700 Subject: [PATCH 36/57] close power meter gracefully --- meshtastic/powermon/power_supply.py | 3 +++ meshtastic/powermon/ppk2.py | 5 +++++ meshtastic/slog/slog.py | 1 + 3 files changed, 9 insertions(+) diff --git a/meshtastic/powermon/power_supply.py b/meshtastic/powermon/power_supply.py index cb4d0441..50a3a837 100644 --- a/meshtastic/powermon/power_supply.py +++ b/meshtastic/powermon/power_supply.py @@ -20,6 +20,9 @@ def __init__(self): self.prevPowerTime = datetime.now() self.prevWattHour = self._getRawWattHour() + def close(self) -> None: + """Close the power meter.""" + def getAverageWatts(self) -> float: """Get watts consumed since last call to this method.""" now = datetime.now() diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index bec4c1d0..3ebf44ff 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -37,6 +37,11 @@ def __init__(self, portName: Optional[str] = None): super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works + def close(self) -> None: + """Close the power meter.""" + self.r.stop_measuring() + super().close() + def setIsSupply(self, s: bool): """If in supply mode we will provide power ourself, otherwise we are just an amp meter.""" if ( diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index bc8df62f..31e3a14b 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -73,6 +73,7 @@ def _logging_thread(self) -> None: 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() From f8ad4fef7c1e1faaf9ada59ab808827926a770ce Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 12:23:38 -0700 Subject: [PATCH 37/57] deferred execution thread should be named and marked as daemon --- meshtastic/util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index 31938029..ef12344a 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -266,9 +266,10 @@ def reset(self): 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() From ff20ad5d05aa3e8208edc03e38402ec2c6faee24 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 13:48:02 -0700 Subject: [PATCH 38/57] group power options in --help. add --power-wait to support some boards --- .vscode/launch.json | 2 +- meshtastic/__main__.py | 63 +++++++++++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5add7d97..cef11f42 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -180,7 +180,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--slog-out", "default", "--power-ppk2-meter", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"] + "args": ["--slog-out", "default", "--power-ppk2-meter", "--power-wait", "--power-voltage", "3.3", "--noproto", "--seriallog", "stdout"] }, { "name": "meshtastic test", diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 0014ab35..ba4e6ecc 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -985,9 +985,33 @@ def export_config(interface): print(config) return config +def create_power_meter(): + """Setup the power meter.""" + + args = mt_config.args + meter = None # assume no power meter + 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...") + return meter 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 @@ -1004,6 +1028,8 @@ def common(): meshtastic.util.support_info() meshtastic.util.our_exit("", 0) + meter = create_power_meter() + if args.ch_index is not None: channelIndex = int(args.ch_index) mt_config.channel_index = channelIndex @@ -1093,24 +1119,6 @@ def common(): # We assume client is fully connected now onConnected(client) - # Setup power meters - meter = None # assume no power meter - 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() - log_set = None if args.slog_out: # Setup loggers @@ -1533,7 +1541,9 @@ def initParser(): action="store_true", ) - power_supply_group = group.add_mutually_exclusive_group() + 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", @@ -1558,21 +1568,28 @@ def initParser(): action="store_true", ) - group.add_argument( + power_group.add_argument( "--power-voltage", help="Set the specified voltage on the power-supply. Be VERY careful, you can burn things up.", ) - group.add_argument( + 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", ) - group.add_argument( + 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-out", help="A directory to store structured logging to, or 'default' for automatically selected.", ) + group.add_argument( "--ble-scan", help="Scan for Meshtastic BLE devices", From 231bc25255ab79fa0135305a1967590c306bd167 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 15:19:21 -0700 Subject: [PATCH 39/57] PPK2 based power measurements seem to approximately work --- .vscode/settings.json | 1 + meshtastic/powermon/power_supply.py | 31 ++++++++-------- meshtastic/powermon/ppk2.py | 57 ++++++++++++++++++++++++++--- meshtastic/powermon/riden.py | 15 ++++++++ meshtastic/powermon/sim.py | 6 +-- meshtastic/slog/slog.py | 13 +++++-- 6 files changed, 95 insertions(+), 28 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f393e653..9b0d16e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "bitmask", "boardid", "Meshtastic", + "milliwatt", "powermon", "pyarrow", "TORADIO", diff --git a/meshtastic/powermon/power_supply.py b/meshtastic/powermon/power_supply.py index 50a3a837..89e61b0f 100644 --- a/meshtastic/powermon/power_supply.py +++ b/meshtastic/powermon/power_supply.py @@ -18,28 +18,27 @@ class PowerMeter: def __init__(self): """Initialize the PowerMeter object.""" self.prevPowerTime = datetime.now() - self.prevWattHour = self._getRawWattHour() def close(self) -> None: """Close the power meter.""" - def getAverageWatts(self) -> float: - """Get watts consumed 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 - - def _getRawWattHour(self) -> float: - """Get the current watt-hour reading (without any offset correction).""" + 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.""" diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 3ebf44ff..53285fa6 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -1,6 +1,8 @@ """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] @@ -31,27 +33,70 @@ def __init__(self, portName: Optional[str] = None): self.r = r = ppk2_api.PPK2_MP(portName) # serial port will be different for you r.get_modifiers() - self.r.start_measuring() # start measuring - logging.info("Connected to PPK2 power supply") + self.r.start_measuring() # send command to ppk2 + self.current_measurements = [0.0] # reset current measurements to 0mA + self.measuring = True + + self.measurement_thread = threading.Thread( + target=self.measurement_loop, daemon=True, name="ppk2 measurement" + ) + self.measurement_thread.start() + + 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: + read_data = self.r.get_data() + if read_data != b"": + samples, _ = self.r.get_samples(read_data) + self.current_measurements += samples + time.sleep(0.001) # FIXME figure out correct sleep duration + + def get_min_current_mA(self): + """Returns max current in mA (since last call to this method).""" + return min(self.current_measurements) / 1000 + + def get_max_current_mA(self): + """Returns max current in mA (since last call to this method).""" + return max(self.current_measurements) / 1000 + + def get_average_current_mA(self): + """Returns average current in mA (since last call to this method).""" + average_current_mA = ( + sum(self.current_measurements) / len(self.current_measurements) + ) / 1000 # measurements are in microamperes, divide by 1000 + + return average_current_mA + + 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_measurements = [ self.current_measurements[-1] ] + def close(self) -> None: """Close the power meter.""" - self.r.stop_measuring() + 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 + 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.set_source_voltage( - int(self.v * 1000) - ) # set source voltage in mV BEFORE setting source mode self.r.use_source_meter() # set source meter mode def powerOn(self): diff --git a/meshtastic/powermon/riden.py b/meshtastic/powermon/riden.py index c425855a..fd84b3b4 100644 --- a/meshtastic/powermon/riden.py +++ b/meshtastic/powermon/riden.py @@ -23,6 +23,8 @@ def __init__(self, portName: str = "/dev/ttyUSB0"): 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): @@ -36,6 +38,19 @@ def powerOn(self): ) # 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() diff --git a/meshtastic/powermon/sim.py b/meshtastic/powermon/sim.py index e7e84842..796065bb 100644 --- a/meshtastic/powermon/sim.py +++ b/meshtastic/powermon/sim.py @@ -9,8 +9,8 @@ class SimPowerSupply(PowerSupply): """A simulated power supply for testing.""" - def getAverageWatts(self) -> float: - """Get the total amount of power that is currently being consumed.""" + 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 + 5 * math.sin(time.time())) / 1000 + return (20.0 + 5 * math.sin(time.time())) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 31e3a14b..49ed62cf 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -65,8 +65,13 @@ def __init__(self, pMeter: PowerMeter, file_path: str, interval=0.2) -> None: def _logging_thread(self) -> None: """Background thread for logging the current watts reading.""" while self.is_logging: - watts = self.pMeter.getAverageWatts() - d = {"time": datetime.now(), "watts": watts} + 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) @@ -164,7 +169,9 @@ def __init__( logging.info(f"Writing slogs to {dir_name}") - self.slog_logger: Optional[StructuredLogger] = StructuredLogger(client, self.dir_name) + self.slog_logger: Optional[StructuredLogger] = StructuredLogger( + client, self.dir_name + ) self.power_logger: Optional[PowerLogger] = ( None if not power_meter From 4ca9aa29c2b352aa9e2bc992c7f9c1dc94748158 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 18:10:47 -0700 Subject: [PATCH 40/57] beginnings of meshtastic.analysis --- meshtastic/analysis/__init__.py | 1 + poetry.lock | 1683 ++++++++++++++++++++++++++++++- pyproject.toml | 11 + 3 files changed, 1656 insertions(+), 39 deletions(-) create mode 100644 meshtastic/analysis/__init__.py diff --git a/meshtastic/analysis/__init__.py b/meshtastic/analysis/__init__.py new file mode 100644 index 00000000..c6fda103 --- /dev/null +++ b/meshtastic/analysis/__init__.py @@ -0,0 +1 @@ +"""Post-run analysis tools for meshtastic.""" diff --git a/poetry.lock b/poetry.lock index af2ca49a..23ce5367 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,6 +11,115 @@ files = [ {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, ] +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, + {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["argon2-cffi[tests,typing]", "tox (>4)"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] +tests = ["hypothesis", "pytest"] +typing = ["mypy"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + +[[package]] +name = "arrow" +version = "1.3.0" +description = "Better dates & times for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, + {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +types-python-dateutil = ">=2.8.10" + +[package.extras] +doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] +test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] + [[package]] name = "astroid" version = "3.2.2" @@ -25,6 +134,38 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "async-lru" +version = "2.0.4" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, + {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + [[package]] name = "async-timeout" version = "4.0.3" @@ -70,6 +211,59 @@ files = [ pycodestyle = ">=2.12.0" tomli = {version = "*", markers = "python_version < \"3.11\""} +[[package]] +name = "babel" +version = "2.15.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "6.1.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"}, + {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.3)"] + [[package]] name = "bleak" version = "0.21.1" @@ -128,6 +322,70 @@ files = [ {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -252,6 +510,23 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + [[package]] name = "coverage" version = "7.5.4" @@ -362,6 +637,59 @@ files = [ {file = "dbus_fast-2.21.3.tar.gz", hash = "sha256:8d0f0f61d007c1316ce79cde35ed52c0ce8ce229fd0f0bf8c9af2013ab4516a7"}, ] +[[package]] +name = "debugpy" +version = "1.8.2" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, + {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, + {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, + {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, + {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, + {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, + {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, + {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, + {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, + {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, + {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, + {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, + {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, + {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, + {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, + {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, + {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, + {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, + {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, + {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, + {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, + {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + [[package]] name = "dill" version = "0.3.8" @@ -402,15 +730,110 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "fastjsonschema" +version = "2.20.0" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"}, + {file = "fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "fqdn" +version = "1.5.1" +description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +optional = false +python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "hypothesis" -version = "6.104.0" +version = "6.104.1" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.104.0-py3-none-any.whl", hash = "sha256:4aaa38b57625abae0d377b2460b80a5847504dcce22985e3381e4373079d45d0"}, - {file = "hypothesis-6.104.0.tar.gz", hash = "sha256:f3f376491380aab841d706c562034c0118616dca7ce23e07b1a744c99f38d26b"}, + {file = "hypothesis-6.104.1-py3-none-any.whl", hash = "sha256:a0a898fa78ecaefe76ad248901dc274e598f29198c6015b3053f7f7827670e0e"}, + {file = "hypothesis-6.104.1.tar.gz", hash = "sha256:4033898019a6149823d2feeb8d214921b4ac2d342a05d6b02e40a3ca4be07eea"}, ] [package.dependencies] @@ -448,13 +871,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.2.1" +version = "8.0.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.2.1-py3-none-any.whl", hash = "sha256:ffef94b0b66046dd8ea2d619b701fe978d9264d38f3998bc4c27ec3b146a87c8"}, - {file = "importlib_metadata-7.2.1.tar.gz", hash = "sha256:509ecb2ab77071db5137c655e24ceb3eee66e7bbc6574165d0d114d9fc4bbe68"}, + {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, + {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, ] [package.dependencies] @@ -476,6 +899,90 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "ipykernel" +version = "6.29.4" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.4-py3-none-any.whl", hash = "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da"}, + {file = "ipykernel-6.29.4.tar.gz", hash = "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.18.1" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, + {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] + +[[package]] +name = "isoduration" +version = "20.11.0" +description = "Operations with ISO 8601 durations" +optional = false +python-versions = ">=3.7" +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[package.dependencies] +arrow = ">=0.15.0" + [[package]] name = "isort" version = "5.13.2" @@ -490,6 +997,317 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "json5" +version = "0.9.25" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = ">=3.8" +files = [ + {file = "json5-0.9.25-py3-none-any.whl", hash = "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f"}, + {file = "json5-0.9.25.tar.gz", hash = "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae"}, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, + {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, +] + +[[package]] +name = "jsonschema" +version = "4.22.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.22.0-py3-none-any.whl", hash = "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802"}, + {file = "jsonschema-4.22.0.tar.gz", hash = "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +fqdn = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +idna = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +isoduration = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format-nongpl\""} +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} +rpds-py = ">=0.7.1" +uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format-nongpl\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "jupyter-client" +version = "8.6.2" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-events" +version = "0.10.0" +description = "Jupyter Event System library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_events-0.10.0-py3-none-any.whl", hash = "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960"}, + {file = "jupyter_events-0.10.0.tar.gz", hash = "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22"}, +] + +[package.dependencies] +jsonschema = {version = ">=4.18.0", extras = ["format-nongpl"]} +python-json-logger = ">=2.0.4" +pyyaml = ">=5.3" +referencing = "*" +rfc3339-validator = "*" +rfc3986-validator = ">=0.1.1" +traitlets = ">=5.3" + +[package.extras] +cli = ["click", "rich"] +docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme", "sphinxcontrib-spelling"] +test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "rich"] + +[[package]] +name = "jupyter-lsp" +version = "2.2.5" +description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001"}, + {file = "jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jupyter-server = ">=1.1.2" + +[[package]] +name = "jupyter-server" +version = "2.14.1" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server-2.14.1-py3-none-any.whl", hash = "sha256:16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5"}, + {file = "jupyter_server-2.14.1.tar.gz", hash = "sha256:12558d158ec7a0653bf96cc272bc7ad79e0127d503b982ed144399346694f726"}, +] + +[package.dependencies] +anyio = ">=3.1.0" +argon2-cffi = ">=21.1" +jinja2 = ">=3.0.3" +jupyter-client = ">=7.4.4" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-events = ">=0.9.0" +jupyter-server-terminals = ">=0.4.4" +nbconvert = ">=6.4.4" +nbformat = ">=5.3.0" +overrides = ">=5.0" +packaging = ">=22.0" +prometheus-client = ">=0.9" +pywinpty = {version = ">=2.0.1", markers = "os_name == \"nt\""} +pyzmq = ">=24" +send2trash = ">=1.8.2" +terminado = ">=0.8.3" +tornado = ">=6.2.0" +traitlets = ">=5.6.0" +websocket-client = ">=1.7" + +[package.extras] +docs = ["ipykernel", "jinja2", "jupyter-client", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi (>=0.8.0)", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] +test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0,<9)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.7)", "pytest-timeout", "requests"] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +description = "A Jupyter Server Extension Providing Terminals." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, + {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"}, +] + +[package.dependencies] +pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} +terminado = ">=0.8.3" + +[package.extras] +docs = ["jinja2", "jupyter-server", "mistune (<4.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] + +[[package]] +name = "jupyterlab" +version = "4.2.2" +description = "JupyterLab computational environment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab-4.2.2-py3-none-any.whl", hash = "sha256:59ee9b839f43308c3dfd55d72d1f1a299ed42a7f91f2d1afe9c12a783f9e525f"}, + {file = "jupyterlab-4.2.2.tar.gz", hash = "sha256:a534b6a25719a92a40d514fb133a9fe8f0d9981b0bbce5d8a5fcaa33344a3038"}, +] + +[package.dependencies] +async-lru = ">=1.0.0" +httpx = ">=0.25.0" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +ipykernel = ">=6.5.0" +jinja2 = ">=3.0.3" +jupyter-core = "*" +jupyter-lsp = ">=2.0.0" +jupyter-server = ">=2.4.0,<3" +jupyterlab-server = ">=2.27.1,<3" +notebook-shim = ">=0.2" +packaging = "*" +setuptools = ">=40.1.0" +tomli = {version = ">=1.2.2", markers = "python_version < \"3.11\""} +tornado = ">=6.2.0" +traitlets = "*" + +[package.extras] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.3.5)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<7.3.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.3.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.2)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.1.post2)", "matplotlib (==3.8.3)", "nbconvert (>=7.0.0)", "pandas (==2.2.1)", "scipy (==1.12.0)", "vega-datasets (==0.9.0)"] +test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] +upgrade-extension = ["copier (>=8,<10)", "jinja2-time (<0.3)", "pydantic (<2.0)", "pyyaml-include (<2.0)", "tomli-w (<2.0)"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.2" +description = "A set of server components for JupyterLab and JupyterLab like applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab_server-2.27.2-py3-none-any.whl", hash = "sha256:54aa2d64fd86383b5438d9f0c032f043c4d8c0264b8af9f60bd061157466ea43"}, + {file = "jupyterlab_server-2.27.2.tar.gz", hash = "sha256:15cbb349dc45e954e09bacf81b9f9bcb10815ff660fb2034ecd7417db3a7ea27"}, +] + +[package.dependencies] +babel = ">=2.10" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.18.0" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.31" + +[package.extras] +docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] +openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0,<8)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + [[package]] name = "macholib" version = "1.16.3" @@ -610,6 +1428,20 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + [[package]] name = "mccabe" version = "0.7.0" @@ -621,6 +1453,17 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mistune" +version = "3.0.2" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, + {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, +] + [[package]] name = "modbus-tk" version = "1.1.3" @@ -672,41 +1515,150 @@ files = [ ] [package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "mypy-protobuf" +version = "3.6.0" +description = "Generate mypy stub files from protobuf specs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-protobuf-3.6.0.tar.gz", hash = "sha256:02f242eb3409f66889f2b1a3aa58356ec4d909cdd0f93115622e9e70366eca3c"}, + {file = "mypy_protobuf-3.6.0-py3-none-any.whl", hash = "sha256:56176e4d569070e7350ea620262478b49b7efceba4103d468448f1d21492fd6c"}, +] + +[package.dependencies] +protobuf = ">=4.25.3" +types-protobuf = ">=4.24" + +[[package]] +name = "nbclient" +version = "0.10.0" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"}, + {file = "nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.4" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] +test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.16.4" +description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, + {file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "!=5.0.0" +defusedxml = "*" +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.1" [package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] +all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["pyqtwebengine (>=5.15)"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] +webpdf = ["playwright"] [[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." +name = "nbformat" +version = "5.10.4" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, + {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, +] + +[package.dependencies] +fastjsonschema = ">=2.15" +jsonschema = ">=2.6" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] [[package]] -name = "mypy-protobuf" -version = "3.6.0" -description = "Generate mypy stub files from protobuf specs" +name = "notebook-shim" +version = "0.2.4" +description = "A shim layer for notebook traits and config" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "mypy-protobuf-3.6.0.tar.gz", hash = "sha256:02f242eb3409f66889f2b1a3aa58356ec4d909cdd0f93115622e9e70366eca3c"}, - {file = "mypy_protobuf-3.6.0-py3-none-any.whl", hash = "sha256:56176e4d569070e7350ea620262478b49b7efceba4103d468448f1d21492fd6c"}, + {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, + {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, ] [package.dependencies] -protobuf = ">=4.25.3" -types-protobuf = ">=4.24" +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] [[package]] name = "numpy" @@ -762,6 +1714,17 @@ files = [ {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, ] +[[package]] +name = "overrides" +version = "7.7.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = false +python-versions = ">=3.6" +files = [ + {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, + {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, +] + [[package]] name = "packaging" version = "24.1" @@ -773,6 +1736,17 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pandocfilters" +version = "1.5.1" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, +] + [[package]] name = "parse" version = "1.20.2" @@ -784,6 +1758,21 @@ files = [ {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, ] +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + [[package]] name = "pdoc3" version = "0.10.0" @@ -869,26 +1858,83 @@ files = [ [package.dependencies] pyserial = "*" +[[package]] +name = "prometheus-client" +version = "0.20.0" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.8" +files = [ + {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"}, + {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, +] + +[package.dependencies] +wcwidth = "*" + [[package]] name = "protobuf" -version = "5.27.1" +version = "5.27.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-5.27.1-cp310-abi3-win32.whl", hash = "sha256:3adc15ec0ff35c5b2d0992f9345b04a540c1e73bfee3ff1643db43cc1d734333"}, - {file = "protobuf-5.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:25236b69ab4ce1bec413fd4b68a15ef8141794427e0b4dc173e9d5d9dffc3bcd"}, - {file = "protobuf-5.27.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4e38fc29d7df32e01a41cf118b5a968b1efd46b9c41ff515234e794011c78b17"}, - {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:917ed03c3eb8a2d51c3496359f5b53b4e4b7e40edfbdd3d3f34336e0eef6825a"}, - {file = "protobuf-5.27.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:ee52874a9e69a30271649be88ecbe69d374232e8fd0b4e4b0aaaa87f429f1631"}, - {file = "protobuf-5.27.1-cp38-cp38-win32.whl", hash = "sha256:7a97b9c5aed86b9ca289eb5148df6c208ab5bb6906930590961e08f097258107"}, - {file = "protobuf-5.27.1-cp38-cp38-win_amd64.whl", hash = "sha256:f6abd0f69968792da7460d3c2cfa7d94fd74e1c21df321eb6345b963f9ec3d8d"}, - {file = "protobuf-5.27.1-cp39-cp39-win32.whl", hash = "sha256:dfddb7537f789002cc4eb00752c92e67885badcc7005566f2c5de9d969d3282d"}, - {file = "protobuf-5.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:39309898b912ca6febb0084ea912e976482834f401be35840a008da12d189340"}, - {file = "protobuf-5.27.1-py3-none-any.whl", hash = "sha256:4ac7249a1530a2ed50e24201d6630125ced04b30619262f06224616e0030b6cf"}, - {file = "protobuf-5.27.1.tar.gz", hash = "sha256:df5e5b8e39b7d1c25b186ffdf9f44f40f810bbcc9d2b71d9d3156fee5a9adf15"}, + {file = "protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38"}, + {file = "protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505"}, + {file = "protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e"}, + {file = "protobuf-5.27.2-cp38-cp38-win32.whl", hash = "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863"}, + {file = "protobuf-5.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6"}, + {file = "protobuf-5.27.2-cp39-cp39-win32.whl", hash = "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca"}, + {file = "protobuf-5.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce"}, + {file = "protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470"}, + {file = "protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714"}, +] + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, ] +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + [[package]] name = "ptyprocess" version = "0.7.0" @@ -900,6 +1946,20 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pyarrow" version = "16.1.0" @@ -969,6 +2029,31 @@ files = [ {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pyinstaller" version = "6.8.0" @@ -1227,6 +2312,54 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-json-logger" +version = "2.0.7" +description = "A python library adding a json log formatter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, + {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + [[package]] name = "pywin32-ctypes" version = "0.2.2" @@ -1238,6 +2371,21 @@ files = [ {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, ] +[[package]] +name = "pywinpty" +version = "2.0.13" +description = "Pseudo terminal support for Windows from Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pywinpty-2.0.13-cp310-none-win_amd64.whl", hash = "sha256:697bff211fb5a6508fee2dc6ff174ce03f34a9a233df9d8b5fe9c8ce4d5eaf56"}, + {file = "pywinpty-2.0.13-cp311-none-win_amd64.whl", hash = "sha256:b96fb14698db1284db84ca38c79f15b4cfdc3172065b5137383910567591fa99"}, + {file = "pywinpty-2.0.13-cp312-none-win_amd64.whl", hash = "sha256:2fd876b82ca750bb1333236ce98488c1be96b08f4f7647cfdf4129dfad83c2d4"}, + {file = "pywinpty-2.0.13-cp38-none-win_amd64.whl", hash = "sha256:61d420c2116c0212808d31625611b51caf621fe67f8a6377e2e8b617ea1c1f7d"}, + {file = "pywinpty-2.0.13-cp39-none-win_amd64.whl", hash = "sha256:71cb613a9ee24174730ac7ae439fd179ca34ccb8c5349e8d7b72ab5dea2c6f4b"}, + {file = "pywinpty-2.0.13.tar.gz", hash = "sha256:c34e32351a3313ddd0d7da23d27f835c860d32fe4ac814d372a3ea9594f41dde"}, +] + [[package]] name = "pyyaml" version = "6.0.1" @@ -1297,6 +2445,121 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "pyzmq" +version = "26.0.3" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, + {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, + {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, + {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, + {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, + {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, + {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, + {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "referencing" +version = "0.35.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + [[package]] name = "requests" version = "2.32.3" @@ -1318,6 +2581,31 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +description = "A pure python RFC3339 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +description = "Pure python rfc3986 validator" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, + {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, +] + [[package]] name = "riden" version = "1.2.0" @@ -1338,6 +2626,130 @@ url = "https://github.com/geeksville/riden.git#1.2.1" reference = "HEAD" resolved_reference = "27fd58f069a089676dcaaea2ccb8dc8d24e4c6d9" +[[package]] +name = "rpds-py" +version = "0.18.1" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53"}, + {file = "rpds_py-0.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0"}, + {file = "rpds_py-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d"}, + {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60"}, + {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da"}, + {file = "rpds_py-0.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1"}, + {file = "rpds_py-0.18.1-cp310-none-win32.whl", hash = "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333"}, + {file = "rpds_py-0.18.1-cp310-none-win_amd64.whl", hash = "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a"}, + {file = "rpds_py-0.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8"}, + {file = "rpds_py-0.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100"}, + {file = "rpds_py-0.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8"}, + {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7"}, + {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e"}, + {file = "rpds_py-0.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88"}, + {file = "rpds_py-0.18.1-cp311-none-win32.whl", hash = "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb"}, + {file = "rpds_py-0.18.1-cp311-none-win_amd64.whl", hash = "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2"}, + {file = "rpds_py-0.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3"}, + {file = "rpds_py-0.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8"}, + {file = "rpds_py-0.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac"}, + {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c"}, + {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac"}, + {file = "rpds_py-0.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a"}, + {file = "rpds_py-0.18.1-cp312-none-win32.whl", hash = "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6"}, + {file = "rpds_py-0.18.1-cp312-none-win_amd64.whl", hash = "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72"}, + {file = "rpds_py-0.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74"}, + {file = "rpds_py-0.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5"}, + {file = "rpds_py-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0"}, + {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d"}, + {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e"}, + {file = "rpds_py-0.18.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc"}, + {file = "rpds_py-0.18.1-cp38-none-win32.whl", hash = "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9"}, + {file = "rpds_py-0.18.1-cp38-none-win_amd64.whl", hash = "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2"}, + {file = "rpds_py-0.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93"}, + {file = "rpds_py-0.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab"}, + {file = "rpds_py-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c"}, + {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338"}, + {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b"}, + {file = "rpds_py-0.18.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26"}, + {file = "rpds_py-0.18.1-cp39-none-win32.whl", hash = "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360"}, + {file = "rpds_py-0.18.1-cp39-none-win_amd64.whl", hash = "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c"}, + {file = "rpds_py-0.18.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909"}, + {file = "rpds_py-0.18.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49"}, + {file = "rpds_py-0.18.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e"}, + {file = "rpds_py-0.18.1.tar.gz", hash = "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f"}, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +description = "Send file to trash natively under Mac OS X, Windows and Linux" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, + {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, +] + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa", "pywin32"] +objc = ["pyobjc-framework-Cocoa"] +win32 = ["pywin32"] + [[package]] name = "setuptools" version = "70.1.1" @@ -1353,6 +2765,28 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -1364,6 +2798,36 @@ files = [ {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "tabulate" version = "0.9.0" @@ -1378,6 +2842,27 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "terminado" +version = "0.18.1" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, + {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, +] + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] +typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] + [[package]] name = "timeago" version = "1.0.16" @@ -1388,6 +2873,24 @@ files = [ {file = "timeago-1.0.16-py3-none-any.whl", hash = "sha256:9b8cb2e3102b329f35a04aa4531982d867b093b19481cfbb1dac7845fa2f79b0"}, ] +[[package]] +name = "tinycss2" +version = "1.3.0" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, + {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["pytest", "ruff"] + [[package]] name = "tomli" version = "2.0.1" @@ -1410,6 +2913,41 @@ files = [ {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, ] +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + [[package]] name = "types-protobuf" version = "5.26.0.20240422" @@ -1421,6 +2959,17 @@ files = [ {file = "types_protobuf-5.26.0.20240422-py3-none-any.whl", hash = "sha256:e4dc2554d342501d5aebc3c71203868b51118340e105fc190e3a64ca1be43831"}, ] +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20240316" +description = "Typing stubs for python-dateutil" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-python-dateutil-2.9.0.20240316.tar.gz", hash = "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202"}, + {file = "types_python_dateutil-2.9.0.20240316-py3-none-any.whl", hash = "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b"}, +] + [[package]] name = "types-pyyaml" version = "6.0.12.20240311" @@ -1479,6 +3028,20 @@ files = [ {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] +[[package]] +name = "uri-template" +version = "1.3.0" +description = "RFC 6570 URI Template Processor" +optional = false +python-versions = ">=3.7" +files = [ + {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, + {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, +] + +[package.extras] +dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-modern-annotations", "flake8-noqa", "flake8-pyproject", "flake8-requirements", "flake8-typechecking-import", "flake8-use-fstring", "mypy", "pep8-naming", "types-PyYAML"] + [[package]] name = "urllib3" version = "2.2.2" @@ -1496,6 +3059,32 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webcolors" +version = "24.6.0" +description = "A library for working with the color formats defined by HTML and CSS." +optional = false +python-versions = ">=3.8" +files = [ + {file = "webcolors-24.6.0-py3-none-any.whl", hash = "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1"}, + {file = "webcolors-24.6.0.tar.gz", hash = "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b"}, +] + +[package.extras] +docs = ["furo", "sphinx", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-notfound-page", "sphinxext-opengraph"] +tests = ["coverage[toml]"] + [[package]] name = "webencodings" version = "0.5.1" @@ -1507,6 +3096,22 @@ files = [ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] +[[package]] +name = "websocket-client" +version = "1.8.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + [[package]] name = "winrt-runtime" version = "2.0.0b1" @@ -1746,4 +3351,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "ad12848e1311886733ea3205795e49e405c2012e9dcff2df6e926ef1cd1dd4b0" +content-hash = "f5f6125129dc3a7a3b1eb805a0315d0ff6db80eca9fd96a77429b034a1f47bc7" diff --git a/pyproject.toml b/pyproject.toml index e0b99b4b..e26e9377 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,16 @@ 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" + [tool.poetry.extras] tunnel = ["pytap2"] @@ -58,6 +68,7 @@ mesh-tunnel = "meshtastic.__main__:tunnelMain [tunnel]" # 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"] From b063d33d775c45db1c657cad863032caa9f9650b Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 18:58:32 -0700 Subject: [PATCH 41/57] don't git jupyter temp directories --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d064e449..d6f5bdc1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ __pycache__ examples/__pycache__ meshtastic.spec .hypothesis/ -coverage.xml \ No newline at end of file +coverage.xml +.ipynb_checkpoints \ No newline at end of file From f2c427430c22bffacb8cc4ce381526d606b8872f Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 10:21:47 -0700 Subject: [PATCH 42/57] Update protobufs to master (required for powermon stuff) --- meshtastic/protobuf/admin_pb2.py | 20 +++--- meshtastic/protobuf/admin_pb2.pyi | 12 +++- meshtastic/protobuf/config_pb2.py | 60 ++++++++-------- meshtastic/protobuf/config_pb2.pyi | 98 ++++++++++++++++++++++++++- meshtastic/protobuf/mesh_pb2.py | 58 ++++++++-------- meshtastic/protobuf/mesh_pb2.pyi | 66 +++++++++++++++++- meshtastic/protobuf/powermon_pb2.py | 28 ++++++++ meshtastic/protobuf/powermon_pb2.pyi | 97 ++++++++++++++++++++++++++ meshtastic/protobuf/telemetry_pb2.py | 22 +++--- meshtastic/protobuf/telemetry_pb2.pyi | 44 +++++++++++- protobufs | 2 +- 11 files changed, 419 insertions(+), 88 deletions(-) create mode 100644 meshtastic/protobuf/powermon_pb2.py create mode 100644 meshtastic/protobuf/powermon_pb2.pyi diff --git a/meshtastic/protobuf/admin_pb2.py b/meshtastic/protobuf/admin_pb2.py index 1429f1e7..c002b8f1 100644 --- a/meshtastic/protobuf/admin_pb2.py +++ b/meshtastic/protobuf/admin_pb2.py @@ -18,7 +18,7 @@ from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/admin.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a+meshtastic/protobuf/connection_status.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xd5\x12\n\x0c\x41\x64minMessage\x12\x1d\n\x13get_channel_request\x18\x01 \x01(\rH\x00\x12<\n\x14get_channel_response\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x03 \x01(\x08H\x00\x12\x37\n\x12get_owner_response\x18\x04 \x01(\x0b\x32\x19.meshtastic.protobuf.UserH\x00\x12J\n\x12get_config_request\x18\x05 \x01(\x0e\x32,.meshtastic.protobuf.AdminMessage.ConfigTypeH\x00\x12:\n\x13get_config_response\x18\x06 \x01(\x0b\x32\x1b.meshtastic.protobuf.ConfigH\x00\x12W\n\x19get_module_config_request\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.AdminMessage.ModuleConfigTypeH\x00\x12G\n\x1aget_module_config_response\x18\x08 \x01(\x0b\x32!.meshtastic.protobuf.ModuleConfigH\x00\x12\x34\n*get_canned_message_module_messages_request\x18\n \x01(\x08H\x00\x12\x35\n+get_canned_message_module_messages_response\x18\x0b \x01(\tH\x00\x12%\n\x1bget_device_metadata_request\x18\x0c \x01(\x08H\x00\x12K\n\x1cget_device_metadata_response\x18\r \x01(\x0b\x32#.meshtastic.protobuf.DeviceMetadataH\x00\x12\x1e\n\x14get_ringtone_request\x18\x0e \x01(\x08H\x00\x12\x1f\n\x15get_ringtone_response\x18\x0f \x01(\tH\x00\x12.\n$get_device_connection_status_request\x18\x10 \x01(\x08H\x00\x12\\\n%get_device_connection_status_response\x18\x11 \x01(\x0b\x32+.meshtastic.protobuf.DeviceConnectionStatusH\x00\x12:\n\x0cset_ham_mode\x18\x12 \x01(\x0b\x32\".meshtastic.protobuf.HamParametersH\x00\x12/\n%get_node_remote_hardware_pins_request\x18\x13 \x01(\x08H\x00\x12\x65\n&get_node_remote_hardware_pins_response\x18\x14 \x01(\x0b\x32\x33.meshtastic.protobuf.NodeRemoteHardwarePinsResponseH\x00\x12 \n\x16\x65nter_dfu_mode_request\x18\x15 \x01(\x08H\x00\x12\x1d\n\x13\x64\x65lete_file_request\x18\x16 \x01(\tH\x00\x12.\n\tset_owner\x18 \x01(\x0b\x32\x19.meshtastic.protobuf.UserH\x00\x12\x33\n\x0bset_channel\x18! \x01(\x0b\x32\x1c.meshtastic.protobuf.ChannelH\x00\x12\x31\n\nset_config\x18\" \x01(\x0b\x32\x1b.meshtastic.protobuf.ConfigH\x00\x12>\n\x11set_module_config\x18# \x01(\x0b\x32!.meshtastic.protobuf.ModuleConfigH\x00\x12,\n\"set_canned_message_module_messages\x18$ \x01(\tH\x00\x12\x1e\n\x14set_ringtone_message\x18% \x01(\tH\x00\x12\x1b\n\x11remove_by_nodenum\x18& \x01(\rH\x00\x12\x1b\n\x11set_favorite_node\x18\' \x01(\rH\x00\x12\x1e\n\x14remove_favorite_node\x18( \x01(\rH\x00\x12;\n\x12set_fixed_position\x18) \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x00\x12\x1f\n\x15remove_fixed_position\x18* \x01(\x08H\x00\x12\x1d\n\x13\x62\x65gin_edit_settings\x18@ \x01(\x08H\x00\x12\x1e\n\x14\x63ommit_edit_settings\x18\x41 \x01(\x08H\x00\x12\x1c\n\x12reboot_ota_seconds\x18_ \x01(\x05H\x00\x12\x18\n\x0e\x65xit_simulator\x18` \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18\x61 \x01(\x05H\x00\x12\x1a\n\x10shutdown_seconds\x18\x62 \x01(\x05H\x00\x12\x17\n\rfactory_reset\x18\x63 \x01(\x05H\x00\x12\x16\n\x0cnodedb_reset\x18\x64 \x01(\x05H\x00\"\x95\x01\n\nConfigType\x12\x11\n\rDEVICE_CONFIG\x10\x00\x12\x13\n\x0fPOSITION_CONFIG\x10\x01\x12\x10\n\x0cPOWER_CONFIG\x10\x02\x12\x12\n\x0eNETWORK_CONFIG\x10\x03\x12\x12\n\x0e\x44ISPLAY_CONFIG\x10\x04\x12\x0f\n\x0bLORA_CONFIG\x10\x05\x12\x14\n\x10\x42LUETOOTH_CONFIG\x10\x06\"\xbb\x02\n\x10ModuleConfigType\x12\x0f\n\x0bMQTT_CONFIG\x10\x00\x12\x11\n\rSERIAL_CONFIG\x10\x01\x12\x13\n\x0f\x45XTNOTIF_CONFIG\x10\x02\x12\x17\n\x13STOREFORWARD_CONFIG\x10\x03\x12\x14\n\x10RANGETEST_CONFIG\x10\x04\x12\x14\n\x10TELEMETRY_CONFIG\x10\x05\x12\x14\n\x10\x43\x41NNEDMSG_CONFIG\x10\x06\x12\x10\n\x0c\x41UDIO_CONFIG\x10\x07\x12\x19\n\x15REMOTEHARDWARE_CONFIG\x10\x08\x12\x17\n\x13NEIGHBORINFO_CONFIG\x10\t\x12\x1a\n\x16\x41MBIENTLIGHTING_CONFIG\x10\n\x12\x1a\n\x16\x44\x45TECTIONSENSOR_CONFIG\x10\x0b\x12\x15\n\x11PAXCOUNTER_CONFIG\x10\x0c\x42\x11\n\x0fpayload_variant\"[\n\rHamParameters\x12\x11\n\tcall_sign\x18\x01 \x01(\t\x12\x10\n\x08tx_power\x18\x02 \x01(\x05\x12\x11\n\tfrequency\x18\x03 \x01(\x02\x12\x12\n\nshort_name\x18\x04 \x01(\t\"o\n\x1eNodeRemoteHardwarePinsResponse\x12M\n\x19node_remote_hardware_pins\x18\x01 \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePinB`\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/protobuf/admin.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a+meshtastic/protobuf/connection_status.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xea\x12\n\x0c\x41\x64minMessage\x12\x1d\n\x13get_channel_request\x18\x01 \x01(\rH\x00\x12<\n\x14get_channel_response\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x03 \x01(\x08H\x00\x12\x37\n\x12get_owner_response\x18\x04 \x01(\x0b\x32\x19.meshtastic.protobuf.UserH\x00\x12J\n\x12get_config_request\x18\x05 \x01(\x0e\x32,.meshtastic.protobuf.AdminMessage.ConfigTypeH\x00\x12:\n\x13get_config_response\x18\x06 \x01(\x0b\x32\x1b.meshtastic.protobuf.ConfigH\x00\x12W\n\x19get_module_config_request\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.AdminMessage.ModuleConfigTypeH\x00\x12G\n\x1aget_module_config_response\x18\x08 \x01(\x0b\x32!.meshtastic.protobuf.ModuleConfigH\x00\x12\x34\n*get_canned_message_module_messages_request\x18\n \x01(\x08H\x00\x12\x35\n+get_canned_message_module_messages_response\x18\x0b \x01(\tH\x00\x12%\n\x1bget_device_metadata_request\x18\x0c \x01(\x08H\x00\x12K\n\x1cget_device_metadata_response\x18\r \x01(\x0b\x32#.meshtastic.protobuf.DeviceMetadataH\x00\x12\x1e\n\x14get_ringtone_request\x18\x0e \x01(\x08H\x00\x12\x1f\n\x15get_ringtone_response\x18\x0f \x01(\tH\x00\x12.\n$get_device_connection_status_request\x18\x10 \x01(\x08H\x00\x12\\\n%get_device_connection_status_response\x18\x11 \x01(\x0b\x32+.meshtastic.protobuf.DeviceConnectionStatusH\x00\x12:\n\x0cset_ham_mode\x18\x12 \x01(\x0b\x32\".meshtastic.protobuf.HamParametersH\x00\x12/\n%get_node_remote_hardware_pins_request\x18\x13 \x01(\x08H\x00\x12\x65\n&get_node_remote_hardware_pins_response\x18\x14 \x01(\x0b\x32\x33.meshtastic.protobuf.NodeRemoteHardwarePinsResponseH\x00\x12 \n\x16\x65nter_dfu_mode_request\x18\x15 \x01(\x08H\x00\x12\x1d\n\x13\x64\x65lete_file_request\x18\x16 \x01(\tH\x00\x12\x13\n\tset_scale\x18\x17 \x01(\rH\x00\x12.\n\tset_owner\x18 \x01(\x0b\x32\x19.meshtastic.protobuf.UserH\x00\x12\x33\n\x0bset_channel\x18! \x01(\x0b\x32\x1c.meshtastic.protobuf.ChannelH\x00\x12\x31\n\nset_config\x18\" \x01(\x0b\x32\x1b.meshtastic.protobuf.ConfigH\x00\x12>\n\x11set_module_config\x18# \x01(\x0b\x32!.meshtastic.protobuf.ModuleConfigH\x00\x12,\n\"set_canned_message_module_messages\x18$ \x01(\tH\x00\x12\x1e\n\x14set_ringtone_message\x18% \x01(\tH\x00\x12\x1b\n\x11remove_by_nodenum\x18& \x01(\rH\x00\x12\x1b\n\x11set_favorite_node\x18\' \x01(\rH\x00\x12\x1e\n\x14remove_favorite_node\x18( \x01(\rH\x00\x12;\n\x12set_fixed_position\x18) \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x00\x12\x1f\n\x15remove_fixed_position\x18* \x01(\x08H\x00\x12\x1d\n\x13\x62\x65gin_edit_settings\x18@ \x01(\x08H\x00\x12\x1e\n\x14\x63ommit_edit_settings\x18\x41 \x01(\x08H\x00\x12\x1c\n\x12reboot_ota_seconds\x18_ \x01(\x05H\x00\x12\x18\n\x0e\x65xit_simulator\x18` \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18\x61 \x01(\x05H\x00\x12\x1a\n\x10shutdown_seconds\x18\x62 \x01(\x05H\x00\x12\x17\n\rfactory_reset\x18\x63 \x01(\x05H\x00\x12\x16\n\x0cnodedb_reset\x18\x64 \x01(\x05H\x00\"\x95\x01\n\nConfigType\x12\x11\n\rDEVICE_CONFIG\x10\x00\x12\x13\n\x0fPOSITION_CONFIG\x10\x01\x12\x10\n\x0cPOWER_CONFIG\x10\x02\x12\x12\n\x0eNETWORK_CONFIG\x10\x03\x12\x12\n\x0e\x44ISPLAY_CONFIG\x10\x04\x12\x0f\n\x0bLORA_CONFIG\x10\x05\x12\x14\n\x10\x42LUETOOTH_CONFIG\x10\x06\"\xbb\x02\n\x10ModuleConfigType\x12\x0f\n\x0bMQTT_CONFIG\x10\x00\x12\x11\n\rSERIAL_CONFIG\x10\x01\x12\x13\n\x0f\x45XTNOTIF_CONFIG\x10\x02\x12\x17\n\x13STOREFORWARD_CONFIG\x10\x03\x12\x14\n\x10RANGETEST_CONFIG\x10\x04\x12\x14\n\x10TELEMETRY_CONFIG\x10\x05\x12\x14\n\x10\x43\x41NNEDMSG_CONFIG\x10\x06\x12\x10\n\x0c\x41UDIO_CONFIG\x10\x07\x12\x19\n\x15REMOTEHARDWARE_CONFIG\x10\x08\x12\x17\n\x13NEIGHBORINFO_CONFIG\x10\t\x12\x1a\n\x16\x41MBIENTLIGHTING_CONFIG\x10\n\x12\x1a\n\x16\x44\x45TECTIONSENSOR_CONFIG\x10\x0b\x12\x15\n\x11PAXCOUNTER_CONFIG\x10\x0c\x42\x11\n\x0fpayload_variant\"[\n\rHamParameters\x12\x11\n\tcall_sign\x18\x01 \x01(\t\x12\x10\n\x08tx_power\x18\x02 \x01(\x05\x12\x11\n\tfrequency\x18\x03 \x01(\x02\x12\x12\n\nshort_name\x18\x04 \x01(\t\"o\n\x1eNodeRemoteHardwarePinsResponse\x12M\n\x19node_remote_hardware_pins\x18\x01 \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePinB`\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -27,13 +27,13 @@ DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\013AdminProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' _globals['_ADMINMESSAGE']._serialized_start=244 - _globals['_ADMINMESSAGE']._serialized_end=2633 - _globals['_ADMINMESSAGE_CONFIGTYPE']._serialized_start=2147 - _globals['_ADMINMESSAGE_CONFIGTYPE']._serialized_end=2296 - _globals['_ADMINMESSAGE_MODULECONFIGTYPE']._serialized_start=2299 - _globals['_ADMINMESSAGE_MODULECONFIGTYPE']._serialized_end=2614 - _globals['_HAMPARAMETERS']._serialized_start=2635 - _globals['_HAMPARAMETERS']._serialized_end=2726 - _globals['_NODEREMOTEHARDWAREPINSRESPONSE']._serialized_start=2728 - _globals['_NODEREMOTEHARDWAREPINSRESPONSE']._serialized_end=2839 + _globals['_ADMINMESSAGE']._serialized_end=2654 + _globals['_ADMINMESSAGE_CONFIGTYPE']._serialized_start=2168 + _globals['_ADMINMESSAGE_CONFIGTYPE']._serialized_end=2317 + _globals['_ADMINMESSAGE_MODULECONFIGTYPE']._serialized_start=2320 + _globals['_ADMINMESSAGE_MODULECONFIGTYPE']._serialized_end=2635 + _globals['_HAMPARAMETERS']._serialized_start=2656 + _globals['_HAMPARAMETERS']._serialized_end=2747 + _globals['_NODEREMOTEHARDWAREPINSRESPONSE']._serialized_start=2749 + _globals['_NODEREMOTEHARDWAREPINSRESPONSE']._serialized_end=2860 # @@protoc_insertion_point(module_scope) diff --git a/meshtastic/protobuf/admin_pb2.pyi b/meshtastic/protobuf/admin_pb2.pyi index cdfc6b70..08403854 100644 --- a/meshtastic/protobuf/admin_pb2.pyi +++ b/meshtastic/protobuf/admin_pb2.pyi @@ -241,6 +241,7 @@ class AdminMessage(google.protobuf.message.Message): GET_NODE_REMOTE_HARDWARE_PINS_RESPONSE_FIELD_NUMBER: builtins.int ENTER_DFU_MODE_REQUEST_FIELD_NUMBER: builtins.int DELETE_FILE_REQUEST_FIELD_NUMBER: builtins.int + SET_SCALE_FIELD_NUMBER: builtins.int SET_OWNER_FIELD_NUMBER: builtins.int SET_CHANNEL_FIELD_NUMBER: builtins.int SET_CONFIG_FIELD_NUMBER: builtins.int @@ -314,6 +315,10 @@ class AdminMessage(google.protobuf.message.Message): """ Delete the file by the specified path from the device """ + set_scale: builtins.int + """ + Set zero and offset for scale chips + """ set_canned_message_module_messages: builtins.str """ Set the Canned Message Module messages text. @@ -479,6 +484,7 @@ class AdminMessage(google.protobuf.message.Message): get_node_remote_hardware_pins_response: global___NodeRemoteHardwarePinsResponse | None = ..., enter_dfu_mode_request: builtins.bool = ..., delete_file_request: builtins.str = ..., + set_scale: builtins.int = ..., set_owner: meshtastic.protobuf.mesh_pb2.User | None = ..., set_channel: meshtastic.protobuf.channel_pb2.Channel | None = ..., set_config: meshtastic.protobuf.config_pb2.Config | None = ..., @@ -499,9 +505,9 @@ class AdminMessage(google.protobuf.message.Message): factory_reset: builtins.int = ..., nodedb_reset: builtins.int = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset", b"factory_reset", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "shutdown_seconds", b"shutdown_seconds"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset", b"factory_reset", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "shutdown_seconds", b"shutdown_seconds"]) -> None: ... - def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "begin_edit_settings", "commit_edit_settings", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset", "nodedb_reset"] | None: ... + def HasField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset", b"factory_reset", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "shutdown_seconds", b"shutdown_seconds"]) -> builtins.bool: ... + def ClearField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset", b"factory_reset", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "shutdown_seconds", b"shutdown_seconds"]) -> None: ... + def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "begin_edit_settings", "commit_edit_settings", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset", "nodedb_reset"] | None: ... global___AdminMessage = AdminMessage diff --git a/meshtastic/protobuf/config_pb2.py b/meshtastic/protobuf/config_pb2.py index c385527e..7aa80b94 100644 --- a/meshtastic/protobuf/config_pb2.py +++ b/meshtastic/protobuf/config_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/config.proto\x12\x13meshtastic.protobuf\"\xed \n\x06\x43onfig\x12:\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfigH\x00\x12>\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfigH\x00\x12\x38\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfigH\x00\x12<\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfigH\x00\x12<\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfigH\x00\x12\x36\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigH\x00\x12@\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfigH\x00\x1a\xa3\x05\n\x0c\x44\x65viceConfig\x12;\n\x04role\x18\x01 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x16\n\x0eserial_enabled\x18\x02 \x01(\x08\x12\x19\n\x11\x64\x65\x62ug_log_enabled\x18\x03 \x01(\x08\x12\x13\n\x0b\x62utton_gpio\x18\x04 \x01(\r\x12\x13\n\x0b\x62uzzer_gpio\x18\x05 \x01(\r\x12R\n\x10rebroadcast_mode\x18\x06 \x01(\x0e\x32\x38.meshtastic.protobuf.Config.DeviceConfig.RebroadcastMode\x12 \n\x18node_info_broadcast_secs\x18\x07 \x01(\r\x12\"\n\x1a\x64ouble_tap_as_button_press\x18\x08 \x01(\x08\x12\x12\n\nis_managed\x18\t \x01(\x08\x12\x1c\n\x14\x64isable_triple_click\x18\n \x01(\x08\x12\r\n\x05tzdef\x18\x0b \x01(\t\x12\x1e\n\x16led_heartbeat_disabled\x18\x0c \x01(\x08\"\xaa\x01\n\x04Role\x12\n\n\x06\x43LIENT\x10\x00\x12\x0f\n\x0b\x43LIENT_MUTE\x10\x01\x12\n\n\x06ROUTER\x10\x02\x12\x11\n\rROUTER_CLIENT\x10\x03\x12\x0c\n\x08REPEATER\x10\x04\x12\x0b\n\x07TRACKER\x10\x05\x12\n\n\x06SENSOR\x10\x06\x12\x07\n\x03TAK\x10\x07\x12\x11\n\rCLIENT_HIDDEN\x10\x08\x12\x12\n\x0eLOST_AND_FOUND\x10\t\x12\x0f\n\x0bTAK_TRACKER\x10\n\"Q\n\x0fRebroadcastMode\x12\x07\n\x03\x41LL\x10\x00\x12\x15\n\x11\x41LL_SKIP_DECODING\x10\x01\x12\x0e\n\nLOCAL_ONLY\x10\x02\x12\x0e\n\nKNOWN_ONLY\x10\x03\x1a\x9a\x05\n\x0ePositionConfig\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12(\n position_broadcast_smart_enabled\x18\x02 \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\x03 \x01(\x08\x12\x17\n\x0bgps_enabled\x18\x04 \x01(\x08\x42\x02\x18\x01\x12\x1b\n\x13gps_update_interval\x18\x05 \x01(\r\x12\x1c\n\x10gps_attempt_time\x18\x06 \x01(\rB\x02\x18\x01\x12\x16\n\x0eposition_flags\x18\x07 \x01(\r\x12\x0f\n\x07rx_gpio\x18\x08 \x01(\r\x12\x0f\n\x07tx_gpio\x18\t \x01(\r\x12(\n broadcast_smart_minimum_distance\x18\n \x01(\r\x12-\n%broadcast_smart_minimum_interval_secs\x18\x0b \x01(\r\x12\x13\n\x0bgps_en_gpio\x18\x0c \x01(\r\x12\x44\n\x08gps_mode\x18\r \x01(\x0e\x32\x32.meshtastic.protobuf.Config.PositionConfig.GpsMode\"\xab\x01\n\rPositionFlags\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x41LTITUDE\x10\x01\x12\x10\n\x0c\x41LTITUDE_MSL\x10\x02\x12\x16\n\x12GEOIDAL_SEPARATION\x10\x04\x12\x07\n\x03\x44OP\x10\x08\x12\t\n\x05HVDOP\x10\x10\x12\r\n\tSATINVIEW\x10 \x12\n\n\x06SEQ_NO\x10@\x12\x0e\n\tTIMESTAMP\x10\x80\x01\x12\x0c\n\x07HEADING\x10\x80\x02\x12\n\n\x05SPEED\x10\x80\x04\"5\n\x07GpsMode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\xea\x01\n\x0bPowerConfig\x12\x17\n\x0fis_power_saving\x18\x01 \x01(\x08\x12&\n\x1eon_battery_shutdown_after_secs\x18\x02 \x01(\r\x12\x1f\n\x17\x61\x64\x63_multiplier_override\x18\x03 \x01(\x02\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x10\n\x08sds_secs\x18\x06 \x01(\r\x12\x0f\n\x07ls_secs\x18\x07 \x01(\r\x12\x15\n\rmin_wake_secs\x18\x08 \x01(\r\x12\"\n\x1a\x64\x65vice_battery_ina_address\x18\t \x01(\r\x1a\x90\x03\n\rNetworkConfig\x12\x14\n\x0cwifi_enabled\x18\x01 \x01(\x08\x12\x11\n\twifi_ssid\x18\x03 \x01(\t\x12\x10\n\x08wifi_psk\x18\x04 \x01(\t\x12\x12\n\nntp_server\x18\x05 \x01(\t\x12\x13\n\x0b\x65th_enabled\x18\x06 \x01(\x08\x12K\n\x0c\x61\x64\x64ress_mode\x18\x07 \x01(\x0e\x32\x35.meshtastic.protobuf.Config.NetworkConfig.AddressMode\x12I\n\x0bipv4_config\x18\x08 \x01(\x0b\x32\x34.meshtastic.protobuf.Config.NetworkConfig.IpV4Config\x12\x16\n\x0ersyslog_server\x18\t \x01(\t\x1a\x46\n\nIpV4Config\x12\n\n\x02ip\x18\x01 \x01(\x07\x12\x0f\n\x07gateway\x18\x02 \x01(\x07\x12\x0e\n\x06subnet\x18\x03 \x01(\x07\x12\x0b\n\x03\x64ns\x18\x04 \x01(\x07\"#\n\x0b\x41\x64\x64ressMode\x12\x08\n\x04\x44HCP\x10\x00\x12\n\n\x06STATIC\x10\x01\x1a\xe2\x05\n\rDisplayConfig\x12\x16\n\x0escreen_on_secs\x18\x01 \x01(\r\x12Q\n\ngps_format\x18\x02 \x01(\x0e\x32=.meshtastic.protobuf.Config.DisplayConfig.GpsCoordinateFormat\x12!\n\x19\x61uto_screen_carousel_secs\x18\x03 \x01(\r\x12\x19\n\x11\x63ompass_north_top\x18\x04 \x01(\x08\x12\x13\n\x0b\x66lip_screen\x18\x05 \x01(\x08\x12\x45\n\x05units\x18\x06 \x01(\x0e\x32\x36.meshtastic.protobuf.Config.DisplayConfig.DisplayUnits\x12@\n\x04oled\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.DisplayConfig.OledType\x12J\n\x0b\x64isplaymode\x18\x08 \x01(\x0e\x32\x35.meshtastic.protobuf.Config.DisplayConfig.DisplayMode\x12\x14\n\x0cheading_bold\x18\t \x01(\x08\x12\x1d\n\x15wake_on_tap_or_motion\x18\n \x01(\x08\"M\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\"(\n\x0c\x44isplayUnits\x12\n\n\x06METRIC\x10\x00\x12\x0c\n\x08IMPERIAL\x10\x01\"M\n\x08OledType\x12\r\n\tOLED_AUTO\x10\x00\x12\x10\n\x0cOLED_SSD1306\x10\x01\x12\x0f\n\x0bOLED_SH1106\x10\x02\x12\x0f\n\x0bOLED_SH1107\x10\x03\"A\n\x0b\x44isplayMode\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08TWOCOLOR\x10\x01\x12\x0c\n\x08INVERTED\x10\x02\x12\t\n\x05\x43OLOR\x10\x03\x1a\xc2\x06\n\nLoRaConfig\x12\x12\n\nuse_preset\x18\x01 \x01(\x08\x12H\n\x0cmodem_preset\x18\x02 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x11\n\tbandwidth\x18\x03 \x01(\r\x12\x15\n\rspread_factor\x18\x04 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x05 \x01(\r\x12\x18\n\x10\x66requency_offset\x18\x06 \x01(\x02\x12\x41\n\x06region\x18\x07 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12\x11\n\thop_limit\x18\x08 \x01(\r\x12\x12\n\ntx_enabled\x18\t \x01(\x08\x12\x10\n\x08tx_power\x18\n \x01(\x05\x12\x13\n\x0b\x63hannel_num\x18\x0b \x01(\r\x12\x1b\n\x13override_duty_cycle\x18\x0c \x01(\x08\x12\x1e\n\x16sx126x_rx_boosted_gain\x18\r \x01(\x08\x12\x1a\n\x12override_frequency\x18\x0e \x01(\x02\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x13\n\x0bignore_mqtt\x18h \x01(\x08\"\xcd\x01\n\nRegionCode\x12\t\n\x05UNSET\x10\x00\x12\x06\n\x02US\x10\x01\x12\n\n\x06\x45U_433\x10\x02\x12\n\n\x06\x45U_868\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t\x12\x06\n\x02IN\x10\n\x12\n\n\x06NZ_865\x10\x0b\x12\x06\n\x02TH\x10\x0c\x12\x0b\n\x07LORA_24\x10\r\x12\n\n\x06UA_433\x10\x0e\x12\n\n\x06UA_868\x10\x0f\x12\n\n\x06MY_433\x10\x10\x12\n\n\x06MY_919\x10\x11\x12\n\n\x06SG_923\x10\x12\"\x94\x01\n\x0bModemPreset\x12\r\n\tLONG_FAST\x10\x00\x12\r\n\tLONG_SLOW\x10\x01\x12\x12\n\x0eVERY_LONG_SLOW\x10\x02\x12\x0f\n\x0bMEDIUM_SLOW\x10\x03\x12\x0f\n\x0bMEDIUM_FAST\x10\x04\x12\x0e\n\nSHORT_SLOW\x10\x05\x12\x0e\n\nSHORT_FAST\x10\x06\x12\x11\n\rLONG_MODERATE\x10\x07\x1a\xb6\x01\n\x0f\x42luetoothConfig\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x45\n\x04mode\x18\x02 \x01(\x0e\x32\x37.meshtastic.protobuf.Config.BluetoothConfig.PairingMode\x12\x11\n\tfixed_pin\x18\x03 \x01(\r\"8\n\x0bPairingMode\x12\x0e\n\nRANDOM_PIN\x10\x00\x12\r\n\tFIXED_PIN\x10\x01\x12\n\n\x06NO_PIN\x10\x02\x42\x11\n\x0fpayload_variantBa\n\x13\x63om.geeksville.meshB\x0c\x43onfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/protobuf/config.proto\x12\x13meshtastic.protobuf\"\xbf#\n\x06\x43onfig\x12:\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfigH\x00\x12>\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfigH\x00\x12\x38\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfigH\x00\x12<\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfigH\x00\x12<\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfigH\x00\x12\x36\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfigH\x00\x12@\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfigH\x00\x1a\xa3\x05\n\x0c\x44\x65viceConfig\x12;\n\x04role\x18\x01 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x16\n\x0eserial_enabled\x18\x02 \x01(\x08\x12\x19\n\x11\x64\x65\x62ug_log_enabled\x18\x03 \x01(\x08\x12\x13\n\x0b\x62utton_gpio\x18\x04 \x01(\r\x12\x13\n\x0b\x62uzzer_gpio\x18\x05 \x01(\r\x12R\n\x10rebroadcast_mode\x18\x06 \x01(\x0e\x32\x38.meshtastic.protobuf.Config.DeviceConfig.RebroadcastMode\x12 \n\x18node_info_broadcast_secs\x18\x07 \x01(\r\x12\"\n\x1a\x64ouble_tap_as_button_press\x18\x08 \x01(\x08\x12\x12\n\nis_managed\x18\t \x01(\x08\x12\x1c\n\x14\x64isable_triple_click\x18\n \x01(\x08\x12\r\n\x05tzdef\x18\x0b \x01(\t\x12\x1e\n\x16led_heartbeat_disabled\x18\x0c \x01(\x08\"\xaa\x01\n\x04Role\x12\n\n\x06\x43LIENT\x10\x00\x12\x0f\n\x0b\x43LIENT_MUTE\x10\x01\x12\n\n\x06ROUTER\x10\x02\x12\x11\n\rROUTER_CLIENT\x10\x03\x12\x0c\n\x08REPEATER\x10\x04\x12\x0b\n\x07TRACKER\x10\x05\x12\n\n\x06SENSOR\x10\x06\x12\x07\n\x03TAK\x10\x07\x12\x11\n\rCLIENT_HIDDEN\x10\x08\x12\x12\n\x0eLOST_AND_FOUND\x10\t\x12\x0f\n\x0bTAK_TRACKER\x10\n\"Q\n\x0fRebroadcastMode\x12\x07\n\x03\x41LL\x10\x00\x12\x15\n\x11\x41LL_SKIP_DECODING\x10\x01\x12\x0e\n\nLOCAL_ONLY\x10\x02\x12\x0e\n\nKNOWN_ONLY\x10\x03\x1a\x9a\x05\n\x0ePositionConfig\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12(\n position_broadcast_smart_enabled\x18\x02 \x01(\x08\x12\x16\n\x0e\x66ixed_position\x18\x03 \x01(\x08\x12\x17\n\x0bgps_enabled\x18\x04 \x01(\x08\x42\x02\x18\x01\x12\x1b\n\x13gps_update_interval\x18\x05 \x01(\r\x12\x1c\n\x10gps_attempt_time\x18\x06 \x01(\rB\x02\x18\x01\x12\x16\n\x0eposition_flags\x18\x07 \x01(\r\x12\x0f\n\x07rx_gpio\x18\x08 \x01(\r\x12\x0f\n\x07tx_gpio\x18\t \x01(\r\x12(\n broadcast_smart_minimum_distance\x18\n \x01(\r\x12-\n%broadcast_smart_minimum_interval_secs\x18\x0b \x01(\r\x12\x13\n\x0bgps_en_gpio\x18\x0c \x01(\r\x12\x44\n\x08gps_mode\x18\r \x01(\x0e\x32\x32.meshtastic.protobuf.Config.PositionConfig.GpsMode\"\xab\x01\n\rPositionFlags\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x41LTITUDE\x10\x01\x12\x10\n\x0c\x41LTITUDE_MSL\x10\x02\x12\x16\n\x12GEOIDAL_SEPARATION\x10\x04\x12\x07\n\x03\x44OP\x10\x08\x12\t\n\x05HVDOP\x10\x10\x12\r\n\tSATINVIEW\x10 \x12\n\n\x06SEQ_NO\x10@\x12\x0e\n\tTIMESTAMP\x10\x80\x01\x12\x0c\n\x07HEADING\x10\x80\x02\x12\n\n\x05SPEED\x10\x80\x04\"5\n\x07GpsMode\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07\x45NABLED\x10\x01\x12\x0f\n\x0bNOT_PRESENT\x10\x02\x1a\x84\x02\n\x0bPowerConfig\x12\x17\n\x0fis_power_saving\x18\x01 \x01(\x08\x12&\n\x1eon_battery_shutdown_after_secs\x18\x02 \x01(\r\x12\x1f\n\x17\x61\x64\x63_multiplier_override\x18\x03 \x01(\x02\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x10\n\x08sds_secs\x18\x06 \x01(\r\x12\x0f\n\x07ls_secs\x18\x07 \x01(\r\x12\x15\n\rmin_wake_secs\x18\x08 \x01(\r\x12\"\n\x1a\x64\x65vice_battery_ina_address\x18\t \x01(\r\x12\x18\n\x10powermon_enables\x18 \x01(\x04\x1a\x90\x03\n\rNetworkConfig\x12\x14\n\x0cwifi_enabled\x18\x01 \x01(\x08\x12\x11\n\twifi_ssid\x18\x03 \x01(\t\x12\x10\n\x08wifi_psk\x18\x04 \x01(\t\x12\x12\n\nntp_server\x18\x05 \x01(\t\x12\x13\n\x0b\x65th_enabled\x18\x06 \x01(\x08\x12K\n\x0c\x61\x64\x64ress_mode\x18\x07 \x01(\x0e\x32\x35.meshtastic.protobuf.Config.NetworkConfig.AddressMode\x12I\n\x0bipv4_config\x18\x08 \x01(\x0b\x32\x34.meshtastic.protobuf.Config.NetworkConfig.IpV4Config\x12\x16\n\x0ersyslog_server\x18\t \x01(\t\x1a\x46\n\nIpV4Config\x12\n\n\x02ip\x18\x01 \x01(\x07\x12\x0f\n\x07gateway\x18\x02 \x01(\x07\x12\x0e\n\x06subnet\x18\x03 \x01(\x07\x12\x0b\n\x03\x64ns\x18\x04 \x01(\x07\"#\n\x0b\x41\x64\x64ressMode\x12\x08\n\x04\x44HCP\x10\x00\x12\n\n\x06STATIC\x10\x01\x1a\xfa\x07\n\rDisplayConfig\x12\x16\n\x0escreen_on_secs\x18\x01 \x01(\r\x12Q\n\ngps_format\x18\x02 \x01(\x0e\x32=.meshtastic.protobuf.Config.DisplayConfig.GpsCoordinateFormat\x12!\n\x19\x61uto_screen_carousel_secs\x18\x03 \x01(\r\x12\x19\n\x11\x63ompass_north_top\x18\x04 \x01(\x08\x12\x13\n\x0b\x66lip_screen\x18\x05 \x01(\x08\x12\x45\n\x05units\x18\x06 \x01(\x0e\x32\x36.meshtastic.protobuf.Config.DisplayConfig.DisplayUnits\x12@\n\x04oled\x18\x07 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.DisplayConfig.OledType\x12J\n\x0b\x64isplaymode\x18\x08 \x01(\x0e\x32\x35.meshtastic.protobuf.Config.DisplayConfig.DisplayMode\x12\x14\n\x0cheading_bold\x18\t \x01(\x08\x12\x1d\n\x15wake_on_tap_or_motion\x18\n \x01(\x08\x12Y\n\x13\x63ompass_orientation\x18\x0b \x01(\x0e\x32<.meshtastic.protobuf.Config.DisplayConfig.CompassOrientation\"M\n\x13GpsCoordinateFormat\x12\x07\n\x03\x44\x45\x43\x10\x00\x12\x07\n\x03\x44MS\x10\x01\x12\x07\n\x03UTM\x10\x02\x12\x08\n\x04MGRS\x10\x03\x12\x07\n\x03OLC\x10\x04\x12\x08\n\x04OSGR\x10\x05\"(\n\x0c\x44isplayUnits\x12\n\n\x06METRIC\x10\x00\x12\x0c\n\x08IMPERIAL\x10\x01\"M\n\x08OledType\x12\r\n\tOLED_AUTO\x10\x00\x12\x10\n\x0cOLED_SSD1306\x10\x01\x12\x0f\n\x0bOLED_SH1106\x10\x02\x12\x0f\n\x0bOLED_SH1107\x10\x03\"A\n\x0b\x44isplayMode\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08TWOCOLOR\x10\x01\x12\x0c\n\x08INVERTED\x10\x02\x12\t\n\x05\x43OLOR\x10\x03\"\xba\x01\n\x12\x43ompassOrientation\x12\r\n\tDEGREES_0\x10\x00\x12\x0e\n\nDEGREES_90\x10\x01\x12\x0f\n\x0b\x44\x45GREES_180\x10\x02\x12\x0f\n\x0b\x44\x45GREES_270\x10\x03\x12\x16\n\x12\x44\x45GREES_0_INVERTED\x10\x04\x12\x17\n\x13\x44\x45GREES_90_INVERTED\x10\x05\x12\x18\n\x14\x44\x45GREES_180_INVERTED\x10\x06\x12\x18\n\x14\x44\x45GREES_270_INVERTED\x10\x07\x1a\xc2\x06\n\nLoRaConfig\x12\x12\n\nuse_preset\x18\x01 \x01(\x08\x12H\n\x0cmodem_preset\x18\x02 \x01(\x0e\x32\x32.meshtastic.protobuf.Config.LoRaConfig.ModemPreset\x12\x11\n\tbandwidth\x18\x03 \x01(\r\x12\x15\n\rspread_factor\x18\x04 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x05 \x01(\r\x12\x18\n\x10\x66requency_offset\x18\x06 \x01(\x02\x12\x41\n\x06region\x18\x07 \x01(\x0e\x32\x31.meshtastic.protobuf.Config.LoRaConfig.RegionCode\x12\x11\n\thop_limit\x18\x08 \x01(\r\x12\x12\n\ntx_enabled\x18\t \x01(\x08\x12\x10\n\x08tx_power\x18\n \x01(\x05\x12\x13\n\x0b\x63hannel_num\x18\x0b \x01(\r\x12\x1b\n\x13override_duty_cycle\x18\x0c \x01(\x08\x12\x1e\n\x16sx126x_rx_boosted_gain\x18\r \x01(\x08\x12\x1a\n\x12override_frequency\x18\x0e \x01(\x02\x12\x17\n\x0fignore_incoming\x18g \x03(\r\x12\x13\n\x0bignore_mqtt\x18h \x01(\x08\"\xcd\x01\n\nRegionCode\x12\t\n\x05UNSET\x10\x00\x12\x06\n\x02US\x10\x01\x12\n\n\x06\x45U_433\x10\x02\x12\n\n\x06\x45U_868\x10\x03\x12\x06\n\x02\x43N\x10\x04\x12\x06\n\x02JP\x10\x05\x12\x07\n\x03\x41NZ\x10\x06\x12\x06\n\x02KR\x10\x07\x12\x06\n\x02TW\x10\x08\x12\x06\n\x02RU\x10\t\x12\x06\n\x02IN\x10\n\x12\n\n\x06NZ_865\x10\x0b\x12\x06\n\x02TH\x10\x0c\x12\x0b\n\x07LORA_24\x10\r\x12\n\n\x06UA_433\x10\x0e\x12\n\n\x06UA_868\x10\x0f\x12\n\n\x06MY_433\x10\x10\x12\n\n\x06MY_919\x10\x11\x12\n\n\x06SG_923\x10\x12\"\x94\x01\n\x0bModemPreset\x12\r\n\tLONG_FAST\x10\x00\x12\r\n\tLONG_SLOW\x10\x01\x12\x12\n\x0eVERY_LONG_SLOW\x10\x02\x12\x0f\n\x0bMEDIUM_SLOW\x10\x03\x12\x0f\n\x0bMEDIUM_FAST\x10\x04\x12\x0e\n\nSHORT_SLOW\x10\x05\x12\x0e\n\nSHORT_FAST\x10\x06\x12\x11\n\rLONG_MODERATE\x10\x07\x1a\xd6\x01\n\x0f\x42luetoothConfig\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x45\n\x04mode\x18\x02 \x01(\x0e\x32\x37.meshtastic.protobuf.Config.BluetoothConfig.PairingMode\x12\x11\n\tfixed_pin\x18\x03 \x01(\r\x12\x1e\n\x16\x64\x65vice_logging_enabled\x18\x04 \x01(\x08\"8\n\x0bPairingMode\x12\x0e\n\nRANDOM_PIN\x10\x00\x12\r\n\tFIXED_PIN\x10\x01\x12\n\n\x06NO_PIN\x10\x02\x42\x11\n\x0fpayload_variantBa\n\x13\x63om.geeksville.meshB\x0c\x43onfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -26,7 +26,7 @@ _CONFIG_POSITIONCONFIG.fields_by_name['gps_attempt_time']._options = None _CONFIG_POSITIONCONFIG.fields_by_name['gps_attempt_time']._serialized_options = b'\030\001' _globals['_CONFIG']._serialized_start=58 - _globals['_CONFIG']._serialized_end=4263 + _globals['_CONFIG']._serialized_end=4601 _globals['_CONFIG_DEVICECONFIG']._serialized_start=497 _globals['_CONFIG_DEVICECONFIG']._serialized_end=1172 _globals['_CONFIG_DEVICECONFIG_ROLE']._serialized_start=919 @@ -40,31 +40,33 @@ _globals['_CONFIG_POSITIONCONFIG_GPSMODE']._serialized_start=1788 _globals['_CONFIG_POSITIONCONFIG_GPSMODE']._serialized_end=1841 _globals['_CONFIG_POWERCONFIG']._serialized_start=1844 - _globals['_CONFIG_POWERCONFIG']._serialized_end=2078 - _globals['_CONFIG_NETWORKCONFIG']._serialized_start=2081 - _globals['_CONFIG_NETWORKCONFIG']._serialized_end=2481 - _globals['_CONFIG_NETWORKCONFIG_IPV4CONFIG']._serialized_start=2374 - _globals['_CONFIG_NETWORKCONFIG_IPV4CONFIG']._serialized_end=2444 - _globals['_CONFIG_NETWORKCONFIG_ADDRESSMODE']._serialized_start=2446 - _globals['_CONFIG_NETWORKCONFIG_ADDRESSMODE']._serialized_end=2481 - _globals['_CONFIG_DISPLAYCONFIG']._serialized_start=2484 - _globals['_CONFIG_DISPLAYCONFIG']._serialized_end=3222 - _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_start=2957 - _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_end=3034 - _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_start=3036 - _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_end=3076 - _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_start=3078 - _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_end=3155 - _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_start=3157 - _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_end=3222 - _globals['_CONFIG_LORACONFIG']._serialized_start=3225 - _globals['_CONFIG_LORACONFIG']._serialized_end=4059 - _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_start=3703 - _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_end=3908 - _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_start=3911 - _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_end=4059 - _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_start=4062 - _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_end=4244 - _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_start=4188 - _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_end=4244 + _globals['_CONFIG_POWERCONFIG']._serialized_end=2104 + _globals['_CONFIG_NETWORKCONFIG']._serialized_start=2107 + _globals['_CONFIG_NETWORKCONFIG']._serialized_end=2507 + _globals['_CONFIG_NETWORKCONFIG_IPV4CONFIG']._serialized_start=2400 + _globals['_CONFIG_NETWORKCONFIG_IPV4CONFIG']._serialized_end=2470 + _globals['_CONFIG_NETWORKCONFIG_ADDRESSMODE']._serialized_start=2472 + _globals['_CONFIG_NETWORKCONFIG_ADDRESSMODE']._serialized_end=2507 + _globals['_CONFIG_DISPLAYCONFIG']._serialized_start=2510 + _globals['_CONFIG_DISPLAYCONFIG']._serialized_end=3528 + _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_start=3074 + _globals['_CONFIG_DISPLAYCONFIG_GPSCOORDINATEFORMAT']._serialized_end=3151 + _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_start=3153 + _globals['_CONFIG_DISPLAYCONFIG_DISPLAYUNITS']._serialized_end=3193 + _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_start=3195 + _globals['_CONFIG_DISPLAYCONFIG_OLEDTYPE']._serialized_end=3272 + _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_start=3274 + _globals['_CONFIG_DISPLAYCONFIG_DISPLAYMODE']._serialized_end=3339 + _globals['_CONFIG_DISPLAYCONFIG_COMPASSORIENTATION']._serialized_start=3342 + _globals['_CONFIG_DISPLAYCONFIG_COMPASSORIENTATION']._serialized_end=3528 + _globals['_CONFIG_LORACONFIG']._serialized_start=3531 + _globals['_CONFIG_LORACONFIG']._serialized_end=4365 + _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_start=4009 + _globals['_CONFIG_LORACONFIG_REGIONCODE']._serialized_end=4214 + _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_start=4217 + _globals['_CONFIG_LORACONFIG_MODEMPRESET']._serialized_end=4365 + _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_start=4368 + _globals['_CONFIG_BLUETOOTHCONFIG']._serialized_end=4582 + _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_start=4526 + _globals['_CONFIG_BLUETOOTHCONFIG_PAIRINGMODE']._serialized_end=4582 # @@protoc_insertion_point(module_scope) diff --git a/meshtastic/protobuf/config_pb2.pyi b/meshtastic/protobuf/config_pb2.pyi index 49d64deb..9da9dc40 100644 --- a/meshtastic/protobuf/config_pb2.pyi +++ b/meshtastic/protobuf/config_pb2.pyi @@ -580,6 +580,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 +624,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 +640,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): @@ -960,6 +967,79 @@ class Config(google.protobuf.message.Message): TFT Full Color Displays (not implemented yet) """ + class _CompassOrientation: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _CompassOrientationEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.DisplayConfig._CompassOrientation.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + DEGREES_0: Config.DisplayConfig._CompassOrientation.ValueType # 0 + """ + The compass and the display are in the same orientation. + """ + DEGREES_90: Config.DisplayConfig._CompassOrientation.ValueType # 1 + """ + Rotate the compass by 90 degrees. + """ + DEGREES_180: Config.DisplayConfig._CompassOrientation.ValueType # 2 + """ + Rotate the compass by 180 degrees. + """ + DEGREES_270: Config.DisplayConfig._CompassOrientation.ValueType # 3 + """ + Rotate the compass by 270 degrees. + """ + DEGREES_0_INVERTED: Config.DisplayConfig._CompassOrientation.ValueType # 4 + """ + Don't rotate the compass, but invert the result. + """ + DEGREES_90_INVERTED: Config.DisplayConfig._CompassOrientation.ValueType # 5 + """ + Rotate the compass by 90 degrees and invert. + """ + DEGREES_180_INVERTED: Config.DisplayConfig._CompassOrientation.ValueType # 6 + """ + Rotate the compass by 180 degrees and invert. + """ + DEGREES_270_INVERTED: Config.DisplayConfig._CompassOrientation.ValueType # 7 + """ + Rotate the compass by 270 degrees and invert. + """ + + class CompassOrientation(_CompassOrientation, metaclass=_CompassOrientationEnumTypeWrapper): ... + DEGREES_0: Config.DisplayConfig.CompassOrientation.ValueType # 0 + """ + The compass and the display are in the same orientation. + """ + DEGREES_90: Config.DisplayConfig.CompassOrientation.ValueType # 1 + """ + Rotate the compass by 90 degrees. + """ + DEGREES_180: Config.DisplayConfig.CompassOrientation.ValueType # 2 + """ + Rotate the compass by 180 degrees. + """ + DEGREES_270: Config.DisplayConfig.CompassOrientation.ValueType # 3 + """ + Rotate the compass by 270 degrees. + """ + DEGREES_0_INVERTED: Config.DisplayConfig.CompassOrientation.ValueType # 4 + """ + Don't rotate the compass, but invert the result. + """ + DEGREES_90_INVERTED: Config.DisplayConfig.CompassOrientation.ValueType # 5 + """ + Rotate the compass by 90 degrees and invert. + """ + DEGREES_180_INVERTED: Config.DisplayConfig.CompassOrientation.ValueType # 6 + """ + Rotate the compass by 180 degrees and invert. + """ + DEGREES_270_INVERTED: Config.DisplayConfig.CompassOrientation.ValueType # 7 + """ + Rotate the compass by 270 degrees and invert. + """ + SCREEN_ON_SECS_FIELD_NUMBER: builtins.int GPS_FORMAT_FIELD_NUMBER: builtins.int AUTO_SCREEN_CAROUSEL_SECS_FIELD_NUMBER: builtins.int @@ -970,6 +1050,7 @@ class Config(google.protobuf.message.Message): DISPLAYMODE_FIELD_NUMBER: builtins.int HEADING_BOLD_FIELD_NUMBER: builtins.int WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int + COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int screen_on_secs: builtins.int """ Number of seconds the screen stays on after pressing the user button or receiving a message @@ -1013,6 +1094,10 @@ class Config(google.protobuf.message.Message): """ Should we wake the screen up on accelerometer detected motion or tap """ + compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType + """ + Indicates how to rotate or invert the compass output to accurate display on the display. + """ def __init__( self, *, @@ -1026,8 +1111,9 @@ class Config(google.protobuf.message.Message): displaymode: global___Config.DisplayConfig.DisplayMode.ValueType = ..., heading_bold: builtins.bool = ..., wake_on_tap_or_motion: builtins.bool = ..., + compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ... + def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ... @typing.final class LoRaConfig(google.protobuf.message.Message): @@ -1451,6 +1537,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 @@ -1463,14 +1550,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 diff --git a/meshtastic/protobuf/mesh_pb2.py b/meshtastic/protobuf/mesh_pb2.py index 684b5130..720478e1 100644 --- a/meshtastic/protobuf/mesh_pb2.py +++ b/meshtastic/protobuf/mesh_pb2.py @@ -19,7 +19,7 @@ from meshtastic.protobuf import xmodem_pb2 as meshtastic_dot_protobuf_dot_xmodem__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mesh.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\x1a\"meshtastic/protobuf/portnums.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/xmodem.proto\"\xf7\x05\n\x08Position\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\x12@\n\x0f\x61ltitude_source\x18\x06 \x01(\x0e\x32\'.meshtastic.protobuf.Position.AltSource\x12\x11\n\ttimestamp\x18\x07 \x01(\x07\x12\x1f\n\x17timestamp_millis_adjust\x18\x08 \x01(\x05\x12\x14\n\x0c\x61ltitude_hae\x18\t \x01(\x11\x12#\n\x1b\x61ltitude_geoidal_separation\x18\n \x01(\x11\x12\x0c\n\x04PDOP\x18\x0b \x01(\r\x12\x0c\n\x04HDOP\x18\x0c \x01(\r\x12\x0c\n\x04VDOP\x18\r \x01(\r\x12\x14\n\x0cgps_accuracy\x18\x0e \x01(\r\x12\x14\n\x0cground_speed\x18\x0f \x01(\r\x12\x14\n\x0cground_track\x18\x10 \x01(\r\x12\x13\n\x0b\x66ix_quality\x18\x11 \x01(\r\x12\x10\n\x08\x66ix_type\x18\x12 \x01(\r\x12\x14\n\x0csats_in_view\x18\x13 \x01(\r\x12\x11\n\tsensor_id\x18\x14 \x01(\r\x12\x13\n\x0bnext_update\x18\x15 \x01(\r\x12\x12\n\nseq_number\x18\x16 \x01(\r\x12\x16\n\x0eprecision_bits\x18\x17 \x01(\r\"N\n\tLocSource\x12\r\n\tLOC_UNSET\x10\x00\x12\x0e\n\nLOC_MANUAL\x10\x01\x12\x10\n\x0cLOC_INTERNAL\x10\x02\x12\x10\n\x0cLOC_EXTERNAL\x10\x03\"b\n\tAltSource\x12\r\n\tALT_UNSET\x10\x00\x12\x0e\n\nALT_MANUAL\x10\x01\x12\x10\n\x0c\x41LT_INTERNAL\x10\x02\x12\x10\n\x0c\x41LT_EXTERNAL\x10\x03\x12\x12\n\x0e\x41LT_BAROMETRIC\x10\x04\"\xd6\x01\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x13\n\x07macaddr\x18\x04 \x01(\x0c\x42\x02\x18\x01\x12\x34\n\x08hw_model\x18\x05 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x06 \x01(\x08\x12;\n\x04role\x18\x07 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x01 \x03(\x07\"\x97\x03\n\x07Routing\x12<\n\rroute_request\x18\x01 \x01(\x0b\x32#.meshtastic.protobuf.RouteDiscoveryH\x00\x12:\n\x0broute_reply\x18\x02 \x01(\x0b\x32#.meshtastic.protobuf.RouteDiscoveryH\x00\x12:\n\x0c\x65rror_reason\x18\x03 \x01(\x0e\x32\".meshtastic.protobuf.Routing.ErrorH\x00\"\xca\x01\n\x05\x45rror\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08NO_ROUTE\x10\x01\x12\x0b\n\x07GOT_NAK\x10\x02\x12\x0b\n\x07TIMEOUT\x10\x03\x12\x10\n\x0cNO_INTERFACE\x10\x04\x12\x12\n\x0eMAX_RETRANSMIT\x10\x05\x12\x0e\n\nNO_CHANNEL\x10\x06\x12\r\n\tTOO_LARGE\x10\x07\x12\x0f\n\x0bNO_RESPONSE\x10\x08\x12\x14\n\x10\x44UTY_CYCLE_LIMIT\x10\t\x12\x0f\n\x0b\x42\x41\x44_REQUEST\x10 \x12\x12\n\x0eNOT_AUTHORIZED\x10!B\t\n\x07variant\"\xb0\x01\n\x04\x44\x61ta\x12-\n\x07portnum\x18\x01 \x01(\x0e\x32\x1c.meshtastic.protobuf.PortNum\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12\x15\n\rwant_response\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x65st\x18\x04 \x01(\x07\x12\x0e\n\x06source\x18\x05 \x01(\x07\x12\x12\n\nrequest_id\x18\x06 \x01(\x07\x12\x10\n\x08reply_id\x18\x07 \x01(\x07\x12\r\n\x05\x65moji\x18\x08 \x01(\x07\"\x93\x01\n\x08Waypoint\x12\n\n\x02id\x18\x01 \x01(\r\x12\x12\n\nlatitude_i\x18\x02 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x03 \x01(\x0f\x12\x0e\n\x06\x65xpire\x18\x04 \x01(\r\x12\x11\n\tlocked_to\x18\x05 \x01(\r\x12\x0c\n\x04name\x18\x06 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\x12\x0c\n\x04icon\x18\x08 \x01(\x07\"l\n\x16MqttClientProxyMessage\x12\r\n\x05topic\x18\x01 \x01(\t\x12\x0e\n\x04\x64\x61ta\x18\x02 \x01(\x0cH\x00\x12\x0e\n\x04text\x18\x03 \x01(\tH\x00\x12\x10\n\x08retained\x18\x04 \x01(\x08\x42\x11\n\x0fpayload_variant\"\xb0\x04\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x07\x12\n\n\x02to\x18\x02 \x01(\x07\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\x12,\n\x07\x64\x65\x63oded\x18\x04 \x01(\x0b\x32\x19.meshtastic.protobuf.DataH\x00\x12\x13\n\tencrypted\x18\x05 \x01(\x0cH\x00\x12\n\n\x02id\x18\x06 \x01(\x07\x12\x0f\n\x07rx_time\x18\x07 \x01(\x07\x12\x0e\n\x06rx_snr\x18\x08 \x01(\x02\x12\x11\n\thop_limit\x18\t \x01(\r\x12\x10\n\x08want_ack\x18\n \x01(\x08\x12:\n\x08priority\x18\x0b \x01(\x0e\x32(.meshtastic.protobuf.MeshPacket.Priority\x12\x0f\n\x07rx_rssi\x18\x0c \x01(\x05\x12<\n\x07\x64\x65layed\x18\r \x01(\x0e\x32\'.meshtastic.protobuf.MeshPacket.DelayedB\x02\x18\x01\x12\x10\n\x08via_mqtt\x18\x0e \x01(\x08\x12\x11\n\thop_start\x18\x0f \x01(\r\"[\n\x08Priority\x12\t\n\x05UNSET\x10\x00\x12\x07\n\x03MIN\x10\x01\x12\x0e\n\nBACKGROUND\x10\n\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10@\x12\x0c\n\x08RELIABLE\x10\x46\x12\x07\n\x03\x41\x43K\x10x\x12\x07\n\x03MAX\x10\x7f\"B\n\x07\x44\x65layed\x12\x0c\n\x08NO_DELAY\x10\x00\x12\x15\n\x11\x44\x45LAYED_BROADCAST\x10\x01\x12\x12\n\x0e\x44\x45LAYED_DIRECT\x10\x02\x42\x11\n\x0fpayload_variant\"\x99\x02\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\'\n\x04user\x18\x02 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12/\n\x08position\x18\x03 \x01(\x0b\x32\x1d.meshtastic.protobuf.Position\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\"P\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\r\x12\x14\n\x0creboot_count\x18\x08 \x01(\r\x12\x17\n\x0fmin_app_version\x18\x0b \x01(\r\"\xc9\x01\n\tLogRecord\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x07\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x33\n\x05level\x18\x04 \x01(\x0e\x32$.meshtastic.protobuf.LogRecord.Level\"X\n\x05Level\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x43RITICAL\x10\x32\x12\t\n\x05\x45RROR\x10(\x12\x0b\n\x07WARNING\x10\x1e\x12\x08\n\x04INFO\x10\x14\x12\t\n\x05\x44\x45\x42UG\x10\n\x12\t\n\x05TRACE\x10\x05\"P\n\x0bQueueStatus\x12\x0b\n\x03res\x18\x01 \x01(\x05\x12\x0c\n\x04\x66ree\x18\x02 \x01(\r\x12\x0e\n\x06maxlen\x18\x03 \x01(\r\x12\x16\n\x0emesh_packet_id\x18\x04 \x01(\r\"\xbe\x05\n\tFromRadio\x12\n\n\x02id\x18\x01 \x01(\r\x12\x31\n\x06packet\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacketH\x00\x12\x32\n\x07my_info\x18\x03 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfoH\x00\x12\x32\n\tnode_info\x18\x04 \x01(\x0b\x32\x1d.meshtastic.protobuf.NodeInfoH\x00\x12-\n\x06\x63onfig\x18\x05 \x01(\x0b\x32\x1b.meshtastic.protobuf.ConfigH\x00\x12\x34\n\nlog_record\x18\x06 \x01(\x0b\x32\x1e.meshtastic.protobuf.LogRecordH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x07 \x01(\rH\x00\x12\x12\n\x08rebooted\x18\x08 \x01(\x08H\x00\x12\x39\n\x0cmoduleConfig\x18\t \x01(\x0b\x32!.meshtastic.protobuf.ModuleConfigH\x00\x12/\n\x07\x63hannel\x18\n \x01(\x0b\x32\x1c.meshtastic.protobuf.ChannelH\x00\x12\x37\n\x0bqueueStatus\x18\x0b \x01(\x0b\x32 .meshtastic.protobuf.QueueStatusH\x00\x12\x33\n\x0cxmodemPacket\x18\x0c \x01(\x0b\x32\x1b.meshtastic.protobuf.XModemH\x00\x12\x37\n\x08metadata\x18\r \x01(\x0b\x32#.meshtastic.protobuf.DeviceMetadataH\x00\x12M\n\x16mqttClientProxyMessage\x18\x0e \x01(\x0b\x32+.meshtastic.protobuf.MqttClientProxyMessageH\x00\x42\x11\n\x0fpayload_variant\"\xb8\x02\n\x07ToRadio\x12\x31\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacketH\x00\x12\x18\n\x0ewant_config_id\x18\x03 \x01(\rH\x00\x12\x14\n\ndisconnect\x18\x04 \x01(\x08H\x00\x12\x33\n\x0cxmodemPacket\x18\x05 \x01(\x0b\x32\x1b.meshtastic.protobuf.XModemH\x00\x12M\n\x16mqttClientProxyMessage\x18\x06 \x01(\x0b\x32+.meshtastic.protobuf.MqttClientProxyMessageH\x00\x12\x33\n\theartbeat\x18\x07 \x01(\x0b\x32\x1e.meshtastic.protobuf.HeartbeatH\x00\x42\x11\n\x0fpayload_variant\"I\n\nCompressed\x12-\n\x07portnum\x18\x01 \x01(\x0e\x32\x1c.meshtastic.protobuf.PortNum\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x90\x01\n\x0cNeighborInfo\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x17\n\x0flast_sent_by_id\x18\x02 \x01(\r\x12$\n\x1cnode_broadcast_interval_secs\x18\x03 \x01(\r\x12\x30\n\tneighbors\x18\x04 \x03(\x0b\x32\x1d.meshtastic.protobuf.Neighbor\"d\n\x08Neighbor\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x0b\n\x03snr\x18\x02 \x01(\x02\x12\x14\n\x0clast_rx_time\x18\x03 \x01(\x07\x12$\n\x1cnode_broadcast_interval_secs\x18\x04 \x01(\r\"\xbf\x02\n\x0e\x44\x65viceMetadata\x12\x18\n\x10\x66irmware_version\x18\x01 \x01(\t\x12\x1c\n\x14\x64\x65vice_state_version\x18\x02 \x01(\r\x12\x13\n\x0b\x63\x61nShutdown\x18\x03 \x01(\x08\x12\x0f\n\x07hasWifi\x18\x04 \x01(\x08\x12\x14\n\x0chasBluetooth\x18\x05 \x01(\x08\x12\x13\n\x0bhasEthernet\x18\x06 \x01(\x08\x12;\n\x04role\x18\x07 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x16\n\x0eposition_flags\x18\x08 \x01(\r\x12\x34\n\x08hw_model\x18\t \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x19\n\x11hasRemoteHardware\x18\n \x01(\x08\"\x0b\n\tHeartbeat\"^\n\x15NodeRemoteHardwarePin\x12\x10\n\x08node_num\x18\x01 \x01(\r\x12\x33\n\x03pin\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.RemoteHardwarePin\"e\n\x0e\x43hunkedPayload\x12\x12\n\npayload_id\x18\x01 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x02 \x01(\r\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\r\x12\x15\n\rpayload_chunk\x18\x04 \x01(\x0c\"\x1f\n\rresend_chunks\x12\x0e\n\x06\x63hunks\x18\x01 \x03(\r\"\xb3\x01\n\x16\x43hunkedPayloadResponse\x12\x12\n\npayload_id\x18\x01 \x01(\r\x12\x1a\n\x10request_transfer\x18\x02 \x01(\x08H\x00\x12\x19\n\x0f\x61\x63\x63\x65pt_transfer\x18\x03 \x01(\x08H\x00\x12;\n\rresend_chunks\x18\x04 \x01(\x0b\x32\".meshtastic.protobuf.resend_chunksH\x00\x42\x11\n\x0fpayload_variant*\xeb\x08\n\rHardwareModel\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08TLORA_V2\x10\x01\x12\x0c\n\x08TLORA_V1\x10\x02\x12\x12\n\x0eTLORA_V2_1_1P6\x10\x03\x12\t\n\x05TBEAM\x10\x04\x12\x0f\n\x0bHELTEC_V2_0\x10\x05\x12\x0e\n\nTBEAM_V0P7\x10\x06\x12\n\n\x06T_ECHO\x10\x07\x12\x10\n\x0cTLORA_V1_1P3\x10\x08\x12\x0b\n\x07RAK4631\x10\t\x12\x0f\n\x0bHELTEC_V2_1\x10\n\x12\r\n\tHELTEC_V1\x10\x0b\x12\x18\n\x14LILYGO_TBEAM_S3_CORE\x10\x0c\x12\x0c\n\x08RAK11200\x10\r\x12\x0b\n\x07NANO_G1\x10\x0e\x12\x12\n\x0eTLORA_V2_1_1P8\x10\x0f\x12\x0f\n\x0bTLORA_T3_S3\x10\x10\x12\x14\n\x10NANO_G1_EXPLORER\x10\x11\x12\x11\n\rNANO_G2_ULTRA\x10\x12\x12\r\n\tLORA_TYPE\x10\x13\x12\x0b\n\x07WIPHONE\x10\x14\x12\x0e\n\nWIO_WM1110\x10\x15\x12\x0e\n\nSTATION_G1\x10\x19\x12\x0c\n\x08RAK11310\x10\x1a\x12\x14\n\x10SENSELORA_RP2040\x10\x1b\x12\x10\n\x0cSENSELORA_S3\x10\x1c\x12\r\n\tCANARYONE\x10\x1d\x12\x0f\n\x0bRP2040_LORA\x10\x1e\x12\x0e\n\nSTATION_G2\x10\x1f\x12\x11\n\rLORA_RELAY_V1\x10 \x12\x0e\n\nNRF52840DK\x10!\x12\x07\n\x03PPR\x10\"\x12\x0f\n\x0bGENIEBLOCKS\x10#\x12\x11\n\rNRF52_UNKNOWN\x10$\x12\r\n\tPORTDUINO\x10%\x12\x0f\n\x0b\x41NDROID_SIM\x10&\x12\n\n\x06\x44IY_V1\x10\'\x12\x15\n\x11NRF52840_PCA10059\x10(\x12\n\n\x06\x44R_DEV\x10)\x12\x0b\n\x07M5STACK\x10*\x12\r\n\tHELTEC_V3\x10+\x12\x11\n\rHELTEC_WSL_V3\x10,\x12\x13\n\x0f\x42\x45TAFPV_2400_TX\x10-\x12\x17\n\x13\x42\x45TAFPV_900_NANO_TX\x10.\x12\x0c\n\x08RPI_PICO\x10/\x12\x1b\n\x17HELTEC_WIRELESS_TRACKER\x10\x30\x12\x19\n\x15HELTEC_WIRELESS_PAPER\x10\x31\x12\n\n\x06T_DECK\x10\x32\x12\x0e\n\nT_WATCH_S3\x10\x33\x12\x11\n\rPICOMPUTER_S3\x10\x34\x12\x0f\n\x0bHELTEC_HT62\x10\x35\x12\x12\n\x0e\x45\x42YTE_ESP32_S3\x10\x36\x12\x11\n\rESP32_S3_PICO\x10\x37\x12\r\n\tCHATTER_2\x10\x38\x12\x1e\n\x1aHELTEC_WIRELESS_PAPER_V1_0\x10\x39\x12 \n\x1cHELTEC_WIRELESS_TRACKER_V1_0\x10:\x12\x0b\n\x07UNPHONE\x10;\x12\x0c\n\x08TD_LORAC\x10<\x12\x13\n\x0f\x43\x44\x45\x42YTE_EORA_S3\x10=\x12\x0f\n\x0bTWC_MESH_V4\x10>\x12\x16\n\x12NRF52_PROMICRO_DIY\x10?\x12\x1f\n\x1bRADIOMASTER_900_BANDIT_NANO\x10@\x12\x0f\n\nPRIVATE_HW\x10\xff\x01*,\n\tConstants\x12\x08\n\x04ZERO\x10\x00\x12\x15\n\x10\x44\x41TA_PAYLOAD_LEN\x10\xed\x01*\xee\x01\n\x11\x43riticalErrorCode\x12\x08\n\x04NONE\x10\x00\x12\x0f\n\x0bTX_WATCHDOG\x10\x01\x12\x14\n\x10SLEEP_ENTER_WAIT\x10\x02\x12\x0c\n\x08NO_RADIO\x10\x03\x12\x0f\n\x0bUNSPECIFIED\x10\x04\x12\x15\n\x11UBLOX_UNIT_FAILED\x10\x05\x12\r\n\tNO_AXP192\x10\x06\x12\x19\n\x15INVALID_RADIO_SETTING\x10\x07\x12\x13\n\x0fTRANSMIT_FAILED\x10\x08\x12\x0c\n\x08\x42ROWNOUT\x10\t\x12\x12\n\x0eSX1262_FAILURE\x10\n\x12\x11\n\rRADIO_SPI_BUG\x10\x0b\x42_\n\x13\x63om.geeksville.meshB\nMeshProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/mesh.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\x1a\"meshtastic/protobuf/portnums.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/xmodem.proto\"\xf7\x05\n\x08Position\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\x12@\n\x0f\x61ltitude_source\x18\x06 \x01(\x0e\x32\'.meshtastic.protobuf.Position.AltSource\x12\x11\n\ttimestamp\x18\x07 \x01(\x07\x12\x1f\n\x17timestamp_millis_adjust\x18\x08 \x01(\x05\x12\x14\n\x0c\x61ltitude_hae\x18\t \x01(\x11\x12#\n\x1b\x61ltitude_geoidal_separation\x18\n \x01(\x11\x12\x0c\n\x04PDOP\x18\x0b \x01(\r\x12\x0c\n\x04HDOP\x18\x0c \x01(\r\x12\x0c\n\x04VDOP\x18\r \x01(\r\x12\x14\n\x0cgps_accuracy\x18\x0e \x01(\r\x12\x14\n\x0cground_speed\x18\x0f \x01(\r\x12\x14\n\x0cground_track\x18\x10 \x01(\r\x12\x13\n\x0b\x66ix_quality\x18\x11 \x01(\r\x12\x10\n\x08\x66ix_type\x18\x12 \x01(\r\x12\x14\n\x0csats_in_view\x18\x13 \x01(\r\x12\x11\n\tsensor_id\x18\x14 \x01(\r\x12\x13\n\x0bnext_update\x18\x15 \x01(\r\x12\x12\n\nseq_number\x18\x16 \x01(\r\x12\x16\n\x0eprecision_bits\x18\x17 \x01(\r\"N\n\tLocSource\x12\r\n\tLOC_UNSET\x10\x00\x12\x0e\n\nLOC_MANUAL\x10\x01\x12\x10\n\x0cLOC_INTERNAL\x10\x02\x12\x10\n\x0cLOC_EXTERNAL\x10\x03\"b\n\tAltSource\x12\r\n\tALT_UNSET\x10\x00\x12\x0e\n\nALT_MANUAL\x10\x01\x12\x10\n\x0c\x41LT_INTERNAL\x10\x02\x12\x10\n\x0c\x41LT_EXTERNAL\x10\x03\x12\x12\n\x0e\x41LT_BAROMETRIC\x10\x04\"\xd6\x01\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x13\n\x07macaddr\x18\x04 \x01(\x0c\x42\x02\x18\x01\x12\x34\n\x08hw_model\x18\x05 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x06 \x01(\x08\x12;\n\x04role\x18\x07 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x01 \x03(\x07\"\x97\x03\n\x07Routing\x12<\n\rroute_request\x18\x01 \x01(\x0b\x32#.meshtastic.protobuf.RouteDiscoveryH\x00\x12:\n\x0broute_reply\x18\x02 \x01(\x0b\x32#.meshtastic.protobuf.RouteDiscoveryH\x00\x12:\n\x0c\x65rror_reason\x18\x03 \x01(\x0e\x32\".meshtastic.protobuf.Routing.ErrorH\x00\"\xca\x01\n\x05\x45rror\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08NO_ROUTE\x10\x01\x12\x0b\n\x07GOT_NAK\x10\x02\x12\x0b\n\x07TIMEOUT\x10\x03\x12\x10\n\x0cNO_INTERFACE\x10\x04\x12\x12\n\x0eMAX_RETRANSMIT\x10\x05\x12\x0e\n\nNO_CHANNEL\x10\x06\x12\r\n\tTOO_LARGE\x10\x07\x12\x0f\n\x0bNO_RESPONSE\x10\x08\x12\x14\n\x10\x44UTY_CYCLE_LIMIT\x10\t\x12\x0f\n\x0b\x42\x41\x44_REQUEST\x10 \x12\x12\n\x0eNOT_AUTHORIZED\x10!B\t\n\x07variant\"\xb0\x01\n\x04\x44\x61ta\x12-\n\x07portnum\x18\x01 \x01(\x0e\x32\x1c.meshtastic.protobuf.PortNum\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12\x15\n\rwant_response\x18\x03 \x01(\x08\x12\x0c\n\x04\x64\x65st\x18\x04 \x01(\x07\x12\x0e\n\x06source\x18\x05 \x01(\x07\x12\x12\n\nrequest_id\x18\x06 \x01(\x07\x12\x10\n\x08reply_id\x18\x07 \x01(\x07\x12\r\n\x05\x65moji\x18\x08 \x01(\x07\"\x93\x01\n\x08Waypoint\x12\n\n\x02id\x18\x01 \x01(\r\x12\x12\n\nlatitude_i\x18\x02 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x03 \x01(\x0f\x12\x0e\n\x06\x65xpire\x18\x04 \x01(\r\x12\x11\n\tlocked_to\x18\x05 \x01(\r\x12\x0c\n\x04name\x18\x06 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\x12\x0c\n\x04icon\x18\x08 \x01(\x07\"l\n\x16MqttClientProxyMessage\x12\r\n\x05topic\x18\x01 \x01(\t\x12\x0e\n\x04\x64\x61ta\x18\x02 \x01(\x0cH\x00\x12\x0e\n\x04text\x18\x03 \x01(\tH\x00\x12\x10\n\x08retained\x18\x04 \x01(\x08\x42\x11\n\x0fpayload_variant\"\xb0\x04\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x07\x12\n\n\x02to\x18\x02 \x01(\x07\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\x12,\n\x07\x64\x65\x63oded\x18\x04 \x01(\x0b\x32\x19.meshtastic.protobuf.DataH\x00\x12\x13\n\tencrypted\x18\x05 \x01(\x0cH\x00\x12\n\n\x02id\x18\x06 \x01(\x07\x12\x0f\n\x07rx_time\x18\x07 \x01(\x07\x12\x0e\n\x06rx_snr\x18\x08 \x01(\x02\x12\x11\n\thop_limit\x18\t \x01(\r\x12\x10\n\x08want_ack\x18\n \x01(\x08\x12:\n\x08priority\x18\x0b \x01(\x0e\x32(.meshtastic.protobuf.MeshPacket.Priority\x12\x0f\n\x07rx_rssi\x18\x0c \x01(\x05\x12<\n\x07\x64\x65layed\x18\r \x01(\x0e\x32\'.meshtastic.protobuf.MeshPacket.DelayedB\x02\x18\x01\x12\x10\n\x08via_mqtt\x18\x0e \x01(\x08\x12\x11\n\thop_start\x18\x0f \x01(\r\"[\n\x08Priority\x12\t\n\x05UNSET\x10\x00\x12\x07\n\x03MIN\x10\x01\x12\x0e\n\nBACKGROUND\x10\n\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10@\x12\x0c\n\x08RELIABLE\x10\x46\x12\x07\n\x03\x41\x43K\x10x\x12\x07\n\x03MAX\x10\x7f\"B\n\x07\x44\x65layed\x12\x0c\n\x08NO_DELAY\x10\x00\x12\x15\n\x11\x44\x45LAYED_BROADCAST\x10\x01\x12\x12\n\x0e\x44\x45LAYED_DIRECT\x10\x02\x42\x11\n\x0fpayload_variant\"\x99\x02\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\'\n\x04user\x18\x02 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12/\n\x08position\x18\x03 \x01(\x0b\x32\x1d.meshtastic.protobuf.Position\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\"P\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\r\x12\x14\n\x0creboot_count\x18\x08 \x01(\r\x12\x17\n\x0fmin_app_version\x18\x0b \x01(\r\"\xc9\x01\n\tLogRecord\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04time\x18\x02 \x01(\x07\x12\x0e\n\x06source\x18\x03 \x01(\t\x12\x33\n\x05level\x18\x04 \x01(\x0e\x32$.meshtastic.protobuf.LogRecord.Level\"X\n\x05Level\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08\x43RITICAL\x10\x32\x12\t\n\x05\x45RROR\x10(\x12\x0b\n\x07WARNING\x10\x1e\x12\x08\n\x04INFO\x10\x14\x12\t\n\x05\x44\x45\x42UG\x10\n\x12\t\n\x05TRACE\x10\x05\"P\n\x0bQueueStatus\x12\x0b\n\x03res\x18\x01 \x01(\x05\x12\x0c\n\x04\x66ree\x18\x02 \x01(\r\x12\x0e\n\x06maxlen\x18\x03 \x01(\r\x12\x16\n\x0emesh_packet_id\x18\x04 \x01(\r\"\xf1\x05\n\tFromRadio\x12\n\n\x02id\x18\x01 \x01(\r\x12\x31\n\x06packet\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacketH\x00\x12\x32\n\x07my_info\x18\x03 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfoH\x00\x12\x32\n\tnode_info\x18\x04 \x01(\x0b\x32\x1d.meshtastic.protobuf.NodeInfoH\x00\x12-\n\x06\x63onfig\x18\x05 \x01(\x0b\x32\x1b.meshtastic.protobuf.ConfigH\x00\x12\x34\n\nlog_record\x18\x06 \x01(\x0b\x32\x1e.meshtastic.protobuf.LogRecordH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x07 \x01(\rH\x00\x12\x12\n\x08rebooted\x18\x08 \x01(\x08H\x00\x12\x39\n\x0cmoduleConfig\x18\t \x01(\x0b\x32!.meshtastic.protobuf.ModuleConfigH\x00\x12/\n\x07\x63hannel\x18\n \x01(\x0b\x32\x1c.meshtastic.protobuf.ChannelH\x00\x12\x37\n\x0bqueueStatus\x18\x0b \x01(\x0b\x32 .meshtastic.protobuf.QueueStatusH\x00\x12\x33\n\x0cxmodemPacket\x18\x0c \x01(\x0b\x32\x1b.meshtastic.protobuf.XModemH\x00\x12\x37\n\x08metadata\x18\r \x01(\x0b\x32#.meshtastic.protobuf.DeviceMetadataH\x00\x12M\n\x16mqttClientProxyMessage\x18\x0e \x01(\x0b\x32+.meshtastic.protobuf.MqttClientProxyMessageH\x00\x12\x31\n\x08\x66ileInfo\x18\x0f \x01(\x0b\x32\x1d.meshtastic.protobuf.FileInfoH\x00\x42\x11\n\x0fpayload_variant\"1\n\x08\x46ileInfo\x12\x11\n\tfile_name\x18\x01 \x01(\t\x12\x12\n\nsize_bytes\x18\x02 \x01(\r\"\xb8\x02\n\x07ToRadio\x12\x31\n\x06packet\x18\x01 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacketH\x00\x12\x18\n\x0ewant_config_id\x18\x03 \x01(\rH\x00\x12\x14\n\ndisconnect\x18\x04 \x01(\x08H\x00\x12\x33\n\x0cxmodemPacket\x18\x05 \x01(\x0b\x32\x1b.meshtastic.protobuf.XModemH\x00\x12M\n\x16mqttClientProxyMessage\x18\x06 \x01(\x0b\x32+.meshtastic.protobuf.MqttClientProxyMessageH\x00\x12\x33\n\theartbeat\x18\x07 \x01(\x0b\x32\x1e.meshtastic.protobuf.HeartbeatH\x00\x42\x11\n\x0fpayload_variant\"I\n\nCompressed\x12-\n\x07portnum\x18\x01 \x01(\x0e\x32\x1c.meshtastic.protobuf.PortNum\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"\x90\x01\n\x0cNeighborInfo\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x17\n\x0flast_sent_by_id\x18\x02 \x01(\r\x12$\n\x1cnode_broadcast_interval_secs\x18\x03 \x01(\r\x12\x30\n\tneighbors\x18\x04 \x03(\x0b\x32\x1d.meshtastic.protobuf.Neighbor\"d\n\x08Neighbor\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x0b\n\x03snr\x18\x02 \x01(\x02\x12\x14\n\x0clast_rx_time\x18\x03 \x01(\x07\x12$\n\x1cnode_broadcast_interval_secs\x18\x04 \x01(\r\"\xbf\x02\n\x0e\x44\x65viceMetadata\x12\x18\n\x10\x66irmware_version\x18\x01 \x01(\t\x12\x1c\n\x14\x64\x65vice_state_version\x18\x02 \x01(\r\x12\x13\n\x0b\x63\x61nShutdown\x18\x03 \x01(\x08\x12\x0f\n\x07hasWifi\x18\x04 \x01(\x08\x12\x14\n\x0chasBluetooth\x18\x05 \x01(\x08\x12\x13\n\x0bhasEthernet\x18\x06 \x01(\x08\x12;\n\x04role\x18\x07 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x16\n\x0eposition_flags\x18\x08 \x01(\r\x12\x34\n\x08hw_model\x18\t \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x19\n\x11hasRemoteHardware\x18\n \x01(\x08\"\x0b\n\tHeartbeat\"^\n\x15NodeRemoteHardwarePin\x12\x10\n\x08node_num\x18\x01 \x01(\r\x12\x33\n\x03pin\x18\x02 \x01(\x0b\x32&.meshtastic.protobuf.RemoteHardwarePin\"e\n\x0e\x43hunkedPayload\x12\x12\n\npayload_id\x18\x01 \x01(\r\x12\x13\n\x0b\x63hunk_count\x18\x02 \x01(\r\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\r\x12\x15\n\rpayload_chunk\x18\x04 \x01(\x0c\"\x1f\n\rresend_chunks\x12\x0e\n\x06\x63hunks\x18\x01 \x03(\r\"\xb3\x01\n\x16\x43hunkedPayloadResponse\x12\x12\n\npayload_id\x18\x01 \x01(\r\x12\x1a\n\x10request_transfer\x18\x02 \x01(\x08H\x00\x12\x19\n\x0f\x61\x63\x63\x65pt_transfer\x18\x03 \x01(\x08H\x00\x12;\n\rresend_chunks\x18\x04 \x01(\x0b\x32\".meshtastic.protobuf.resend_chunksH\x00\x42\x11\n\x0fpayload_variant*\xab\t\n\rHardwareModel\x12\t\n\x05UNSET\x10\x00\x12\x0c\n\x08TLORA_V2\x10\x01\x12\x0c\n\x08TLORA_V1\x10\x02\x12\x12\n\x0eTLORA_V2_1_1P6\x10\x03\x12\t\n\x05TBEAM\x10\x04\x12\x0f\n\x0bHELTEC_V2_0\x10\x05\x12\x0e\n\nTBEAM_V0P7\x10\x06\x12\n\n\x06T_ECHO\x10\x07\x12\x10\n\x0cTLORA_V1_1P3\x10\x08\x12\x0b\n\x07RAK4631\x10\t\x12\x0f\n\x0bHELTEC_V2_1\x10\n\x12\r\n\tHELTEC_V1\x10\x0b\x12\x18\n\x14LILYGO_TBEAM_S3_CORE\x10\x0c\x12\x0c\n\x08RAK11200\x10\r\x12\x0b\n\x07NANO_G1\x10\x0e\x12\x12\n\x0eTLORA_V2_1_1P8\x10\x0f\x12\x0f\n\x0bTLORA_T3_S3\x10\x10\x12\x14\n\x10NANO_G1_EXPLORER\x10\x11\x12\x11\n\rNANO_G2_ULTRA\x10\x12\x12\r\n\tLORA_TYPE\x10\x13\x12\x0b\n\x07WIPHONE\x10\x14\x12\x0e\n\nWIO_WM1110\x10\x15\x12\x0b\n\x07RAK2560\x10\x16\x12\x13\n\x0fHELTEC_HRU_3601\x10\x17\x12\x0e\n\nSTATION_G1\x10\x19\x12\x0c\n\x08RAK11310\x10\x1a\x12\x14\n\x10SENSELORA_RP2040\x10\x1b\x12\x10\n\x0cSENSELORA_S3\x10\x1c\x12\r\n\tCANARYONE\x10\x1d\x12\x0f\n\x0bRP2040_LORA\x10\x1e\x12\x0e\n\nSTATION_G2\x10\x1f\x12\x11\n\rLORA_RELAY_V1\x10 \x12\x0e\n\nNRF52840DK\x10!\x12\x07\n\x03PPR\x10\"\x12\x0f\n\x0bGENIEBLOCKS\x10#\x12\x11\n\rNRF52_UNKNOWN\x10$\x12\r\n\tPORTDUINO\x10%\x12\x0f\n\x0b\x41NDROID_SIM\x10&\x12\n\n\x06\x44IY_V1\x10\'\x12\x15\n\x11NRF52840_PCA10059\x10(\x12\n\n\x06\x44R_DEV\x10)\x12\x0b\n\x07M5STACK\x10*\x12\r\n\tHELTEC_V3\x10+\x12\x11\n\rHELTEC_WSL_V3\x10,\x12\x13\n\x0f\x42\x45TAFPV_2400_TX\x10-\x12\x17\n\x13\x42\x45TAFPV_900_NANO_TX\x10.\x12\x0c\n\x08RPI_PICO\x10/\x12\x1b\n\x17HELTEC_WIRELESS_TRACKER\x10\x30\x12\x19\n\x15HELTEC_WIRELESS_PAPER\x10\x31\x12\n\n\x06T_DECK\x10\x32\x12\x0e\n\nT_WATCH_S3\x10\x33\x12\x11\n\rPICOMPUTER_S3\x10\x34\x12\x0f\n\x0bHELTEC_HT62\x10\x35\x12\x12\n\x0e\x45\x42YTE_ESP32_S3\x10\x36\x12\x11\n\rESP32_S3_PICO\x10\x37\x12\r\n\tCHATTER_2\x10\x38\x12\x1e\n\x1aHELTEC_WIRELESS_PAPER_V1_0\x10\x39\x12 \n\x1cHELTEC_WIRELESS_TRACKER_V1_0\x10:\x12\x0b\n\x07UNPHONE\x10;\x12\x0c\n\x08TD_LORAC\x10<\x12\x13\n\x0f\x43\x44\x45\x42YTE_EORA_S3\x10=\x12\x0f\n\x0bTWC_MESH_V4\x10>\x12\x16\n\x12NRF52_PROMICRO_DIY\x10?\x12\x1f\n\x1bRADIOMASTER_900_BANDIT_NANO\x10@\x12\x1c\n\x18HELTEC_CAPSULE_SENSOR_V3\x10\x41\x12\x0f\n\nPRIVATE_HW\x10\xff\x01*,\n\tConstants\x12\x08\n\x04ZERO\x10\x00\x12\x15\n\x10\x44\x41TA_PAYLOAD_LEN\x10\xed\x01*\xee\x01\n\x11\x43riticalErrorCode\x12\x08\n\x04NONE\x10\x00\x12\x0f\n\x0bTX_WATCHDOG\x10\x01\x12\x14\n\x10SLEEP_ENTER_WAIT\x10\x02\x12\x0c\n\x08NO_RADIO\x10\x03\x12\x0f\n\x0bUNSPECIFIED\x10\x04\x12\x15\n\x11UBLOX_UNIT_FAILED\x10\x05\x12\r\n\tNO_AXP192\x10\x06\x12\x19\n\x15INVALID_RADIO_SETTING\x10\x07\x12\x13\n\x0fTRANSMIT_FAILED\x10\x08\x12\x0c\n\x08\x42ROWNOUT\x10\t\x12\x12\n\x0eSX1262_FAILURE\x10\n\x12\x11\n\rRADIO_SPI_BUG\x10\x0b\x42_\n\x13\x63om.geeksville.meshB\nMeshProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -31,12 +31,12 @@ _USER.fields_by_name['macaddr']._serialized_options = b'\030\001' _MESHPACKET.fields_by_name['delayed']._options = None _MESHPACKET.fields_by_name['delayed']._serialized_options = b'\030\001' - _globals['_HARDWAREMODEL']._serialized_start=5442 - _globals['_HARDWAREMODEL']._serialized_end=6573 - _globals['_CONSTANTS']._serialized_start=6575 - _globals['_CONSTANTS']._serialized_end=6619 - _globals['_CRITICALERRORCODE']._serialized_start=6622 - _globals['_CRITICALERRORCODE']._serialized_end=6860 + _globals['_HARDWAREMODEL']._serialized_start=5544 + _globals['_HARDWAREMODEL']._serialized_end=6739 + _globals['_CONSTANTS']._serialized_start=6741 + _globals['_CONSTANTS']._serialized_end=6785 + _globals['_CRITICALERRORCODE']._serialized_start=6788 + _globals['_CRITICALERRORCODE']._serialized_end=7026 _globals['_POSITION']._serialized_start=273 _globals['_POSITION']._serialized_end=1032 _globals['_POSITION_LOCSOURCE']._serialized_start=854 @@ -74,25 +74,27 @@ _globals['_QUEUESTATUS']._serialized_start=3266 _globals['_QUEUESTATUS']._serialized_end=3346 _globals['_FROMRADIO']._serialized_start=3349 - _globals['_FROMRADIO']._serialized_end=4051 - _globals['_TORADIO']._serialized_start=4054 - _globals['_TORADIO']._serialized_end=4366 - _globals['_COMPRESSED']._serialized_start=4368 - _globals['_COMPRESSED']._serialized_end=4441 - _globals['_NEIGHBORINFO']._serialized_start=4444 - _globals['_NEIGHBORINFO']._serialized_end=4588 - _globals['_NEIGHBOR']._serialized_start=4590 - _globals['_NEIGHBOR']._serialized_end=4690 - _globals['_DEVICEMETADATA']._serialized_start=4693 - _globals['_DEVICEMETADATA']._serialized_end=5012 - _globals['_HEARTBEAT']._serialized_start=5014 - _globals['_HEARTBEAT']._serialized_end=5025 - _globals['_NODEREMOTEHARDWAREPIN']._serialized_start=5027 - _globals['_NODEREMOTEHARDWAREPIN']._serialized_end=5121 - _globals['_CHUNKEDPAYLOAD']._serialized_start=5123 - _globals['_CHUNKEDPAYLOAD']._serialized_end=5224 - _globals['_RESEND_CHUNKS']._serialized_start=5226 - _globals['_RESEND_CHUNKS']._serialized_end=5257 - _globals['_CHUNKEDPAYLOADRESPONSE']._serialized_start=5260 - _globals['_CHUNKEDPAYLOADRESPONSE']._serialized_end=5439 + _globals['_FROMRADIO']._serialized_end=4102 + _globals['_FILEINFO']._serialized_start=4104 + _globals['_FILEINFO']._serialized_end=4153 + _globals['_TORADIO']._serialized_start=4156 + _globals['_TORADIO']._serialized_end=4468 + _globals['_COMPRESSED']._serialized_start=4470 + _globals['_COMPRESSED']._serialized_end=4543 + _globals['_NEIGHBORINFO']._serialized_start=4546 + _globals['_NEIGHBORINFO']._serialized_end=4690 + _globals['_NEIGHBOR']._serialized_start=4692 + _globals['_NEIGHBOR']._serialized_end=4792 + _globals['_DEVICEMETADATA']._serialized_start=4795 + _globals['_DEVICEMETADATA']._serialized_end=5114 + _globals['_HEARTBEAT']._serialized_start=5116 + _globals['_HEARTBEAT']._serialized_end=5127 + _globals['_NODEREMOTEHARDWAREPIN']._serialized_start=5129 + _globals['_NODEREMOTEHARDWAREPIN']._serialized_end=5223 + _globals['_CHUNKEDPAYLOAD']._serialized_start=5225 + _globals['_CHUNKEDPAYLOAD']._serialized_end=5326 + _globals['_RESEND_CHUNKS']._serialized_start=5328 + _globals['_RESEND_CHUNKS']._serialized_end=5359 + _globals['_CHUNKEDPAYLOADRESPONSE']._serialized_start=5362 + _globals['_CHUNKEDPAYLOADRESPONSE']._serialized_end=5541 # @@protoc_insertion_point(module_scope) diff --git a/meshtastic/protobuf/mesh_pb2.pyi b/meshtastic/protobuf/mesh_pb2.pyi index 89b0a755..77880b41 100644 --- a/meshtastic/protobuf/mesh_pb2.pyi +++ b/meshtastic/protobuf/mesh_pb2.pyi @@ -121,6 +121,14 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._ """ WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk """ + 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 @@ -295,6 +303,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._ RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS """ + 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 + """ PRIVATE_HW: _HardwareModel.ValueType # 255 """ ------------------------------------------------------------------------------------------------------------------------------------------ @@ -400,6 +412,14 @@ WIO_WM1110: HardwareModel.ValueType # 21 """ WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk """ +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 @@ -574,6 +594,10 @@ RADIOMASTER_900_BANDIT_NANO: HardwareModel.ValueType # 64 RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS """ +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 +""" PRIVATE_HW: HardwareModel.ValueType # 255 """ ------------------------------------------------------------------------------------------------------------------------------------------ @@ -2034,6 +2058,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, @@ -2121,6 +2146,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, *, @@ -2138,13 +2169,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): """ diff --git a/meshtastic/protobuf/powermon_pb2.py b/meshtastic/protobuf/powermon_pb2.py new file mode 100644 index 00000000..4c344741 --- /dev/null +++ b/meshtastic/protobuf/powermon_pb2.py @@ -0,0 +1,28 @@ +# -*- 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\x42\x63\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 +# @@protoc_insertion_point(module_scope) diff --git a/meshtastic/protobuf/powermon_pb2.pyi b/meshtastic/protobuf/powermon_pb2.pyi new file mode 100644 index 00000000..5519b748 --- /dev/null +++ b/meshtastic/protobuf/powermon_pb2.pyi @@ -0,0 +1,97 @@ +""" +@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 diff --git a/meshtastic/protobuf/telemetry_pb2.py b/meshtastic/protobuf/telemetry_pb2.py index 9c2cf4ef..5a1188d2 100644 --- a/meshtastic/protobuf/telemetry_pb2.py +++ b/meshtastic/protobuf/telemetry_pb2.py @@ -13,7 +13,7 @@ -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\"\xa6\x02\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x12\x0b\n\x03iaq\x18\x07 \x01(\r\x12\x10\n\x08\x64istance\x18\x08 \x01(\x02\x12\x0b\n\x03lux\x18\t \x01(\x02\x12\x11\n\twhite_lux\x18\n \x01(\x02\x12\x0e\n\x06ir_lux\x18\x0b \x01(\x02\x12\x0e\n\x06uv_lux\x18\x0c \x01(\x02\x12\x16\n\x0ewind_direction\x18\r \x01(\r\x12\x12\n\nwind_speed\x18\x0e \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\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*\xdd\x02\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x12\x0c\n\x08RCWL9620\x10\x10\x12\t\n\x05SHT4X\x10\x11\x12\x0c\n\x08VEML7700\x10\x12\x12\x0c\n\x08MLX90632\x10\x13\x12\x0b\n\x07OPT3001\x10\x14\x12\x0c\n\x08LTR390UV\x10\x15\x12\x0e\n\nTSL25911FN\x10\x16\x12\t\n\x05\x41HT10\x10\x17\x12\x10\n\x0c\x44\x46ROBOT_LARK\x10\x18\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') +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') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -21,16 +21,18 @@ 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=1259 - _globals['_TELEMETRYSENSORTYPE']._serialized_end=1608 + _globals['_TELEMETRYSENSORTYPE']._serialized_start=1339 + _globals['_TELEMETRYSENSORTYPE']._serialized_end=1701 _globals['_DEVICEMETRICS']._serialized_start=61 _globals['_DEVICEMETRICS']._serialized_end=190 _globals['_ENVIRONMENTMETRICS']._serialized_start=193 - _globals['_ENVIRONMENTMETRICS']._serialized_end=487 - _globals['_POWERMETRICS']._serialized_start=490 - _globals['_POWERMETRICS']._serialized_end=630 - _globals['_AIRQUALITYMETRICS']._serialized_start=633 - _globals['_AIRQUALITYMETRICS']._serialized_end=952 - _globals['_TELEMETRY']._serialized_start=955 - _globals['_TELEMETRY']._serialized_end=1256 + _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 # @@protoc_insertion_point(module_scope) diff --git a/meshtastic/protobuf/telemetry_pb2.pyi b/meshtastic/protobuf/telemetry_pb2.pyi index aba51c8f..fb0f4ddc 100644 --- a/meshtastic/protobuf/telemetry_pb2.pyi +++ b/meshtastic/protobuf/telemetry_pb2.pyi @@ -123,6 +123,10 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra """ DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) """ + NAU7802: _TelemetrySensorType.ValueType # 25 + """ + NAU7802 Scale Chip or compatible + """ class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper): """ @@ -229,6 +233,10 @@ DFROBOT_LARK: TelemetrySensorType.ValueType # 24 """ DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) """ +NAU7802: TelemetrySensorType.ValueType # 25 +""" +NAU7802 Scale Chip or compatible +""" global___TelemetrySensorType = TelemetrySensorType @typing.final @@ -299,6 +307,7 @@ class EnvironmentMetrics(google.protobuf.message.Message): UV_LUX_FIELD_NUMBER: builtins.int WIND_DIRECTION_FIELD_NUMBER: builtins.int WIND_SPEED_FIELD_NUMBER: builtins.int + WEIGHT_FIELD_NUMBER: builtins.int temperature: builtins.float """ Temperature measured @@ -357,6 +366,10 @@ class EnvironmentMetrics(google.protobuf.message.Message): """ Wind speed in m/s """ + weight: builtins.float + """ + Weight in KG + """ def __init__( self, *, @@ -374,8 +387,9 @@ class EnvironmentMetrics(google.protobuf.message.Message): uv_lux: builtins.float = ..., wind_direction: builtins.int = ..., wind_speed: builtins.float = ..., + weight: 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", "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_speed", b"wind_speed"]) -> None: ... global___EnvironmentMetrics = EnvironmentMetrics @@ -574,3 +588,31 @@ class Telemetry(google.protobuf.message.Message): def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics"] | None: ... global___Telemetry = Telemetry + +@typing.final +class Nau7802Config(google.protobuf.message.Message): + """ + NAU7802 Telemetry configuration, for saving to flash + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ZEROOFFSET_FIELD_NUMBER: builtins.int + CALIBRATIONFACTOR_FIELD_NUMBER: builtins.int + zeroOffset: builtins.int + """ + The offset setting for the NAU7802 + """ + calibrationFactor: builtins.float + """ + The calibration factor for the NAU7802 + """ + def __init__( + self, + *, + zeroOffset: builtins.int = ..., + calibrationFactor: builtins.float = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["calibrationFactor", b"calibrationFactor", "zeroOffset", b"zeroOffset"]) -> None: ... + +global___Nau7802Config = Nau7802Config diff --git a/protobufs b/protobufs index 4da558d0..a3030d5f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4da558d0f73c46ef91b74431facee73c09affbfc +Subproject commit a3030d5ff187091c9fbbd08dd797cca5085736fe From 320bb30d29a87340efc84ebe2b7baa9082a986ba Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 11:12:02 -0700 Subject: [PATCH 43/57] Use .feather files as our long-term representation --- meshtastic/slog/arrow.py | 33 +++++++++++++++++++++++++++++++++ meshtastic/slog/slog.py | 8 ++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py index 225d3ce1..eae77c2c 100644 --- a/meshtastic/slog/arrow.py +++ b/meshtastic/slog/arrow.py @@ -1,6 +1,10 @@ """Utilities for Apache Arrow serialization.""" +import logging +import os + import pyarrow as pa +import pyarrow.feather as feather chunk_size = 1000 # disk writes are batched based on this number of rows @@ -42,3 +46,32 @@ def add_row(self, row_dict: dict): 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") diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 49ed62cf..82791302 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -17,7 +17,7 @@ from meshtastic.mesh_interface import MeshInterface from meshtastic.powermon import PowerMeter -from .arrow import ArrowWriter +from .arrow import FeatherWriter @dataclass(init=False) @@ -54,7 +54,7 @@ class PowerLogger: def __init__(self, pMeter: PowerMeter, file_path: str, interval=0.2) -> None: """Initialize the PowerLogger object.""" self.pMeter = pMeter - self.writer = ArrowWriter(file_path) + self.writer = FeatherWriter(file_path) self.interval = interval self.is_logging = True self.thread = threading.Thread( @@ -98,7 +98,7 @@ def __init__(self, client: MeshInterface, dir_path: str) -> None: client (MeshInterface): The MeshInterface object to monitor. """ self.client = client - self.writer = ArrowWriter(f"{dir_path}/slog.arrow") + self.writer = FeatherWriter(f"{dir_path}/slog") self.raw_file = open( # pylint: disable=consider-using-with f"{dir_path}/raw.txt", "w", encoding="utf8" ) @@ -175,7 +175,7 @@ def __init__( self.power_logger: Optional[PowerLogger] = ( None if not power_meter - else PowerLogger(power_meter, f"{self.dir_name}/power.arrow") + else PowerLogger(power_meter, f"{self.dir_name}/power") ) # Store a lambda so we can find it again to unregister From 047a56d5540f4c8d9a176e49d1b5f7956d18df70 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 12:59:28 -0700 Subject: [PATCH 44/57] speed up file writing --- meshtastic/slog/arrow.py | 4 +++- meshtastic/slog/slog.py | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py index eae77c2c..a2d32e6f 100644 --- a/meshtastic/slog/arrow.py +++ b/meshtastic/slog/arrow.py @@ -33,7 +33,8 @@ def _write(self): """Write the new rows to the file.""" if len(self.new_rows) > 0: if self.schema is None: - self.schema = pa.Table.from_pylist(self.new_rows).schema + # 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)) @@ -75,3 +76,4 @@ def close(self): # 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) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 82791302..7f99d38a 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -5,6 +5,7 @@ import os import re import threading +import io import time from dataclasses import dataclass from datetime import datetime @@ -99,7 +100,7 @@ def __init__(self, client: MeshInterface, dir_path: str) -> None: """ self.client = client self.writer = FeatherWriter(f"{dir_path}/slog") - self.raw_file = open( # pylint: disable=consider-using-with + self.raw_file: Optional[io.TextIOWrapper] = open( # pylint: disable=consider-using-with f"{dir_path}/raw.txt", "w", encoding="utf8" ) self.listener = pub.subscribe(self._onLogMessage, TOPIC_MESHTASTIC_LOG_LINE) @@ -108,7 +109,9 @@ def close(self) -> None: """Stop logging.""" pub.unsubscribe(self.listener, TOPIC_MESHTASTIC_LOG_LINE) self.writer.close() - self.raw_file.close() # Close the raw.txt file + f = self.raw_file + self.raw_file = None # mark that we are shutting down + f.close() # Close the raw.txt file def _onLogMessage( self, line: str, interface: MeshInterface # pylint: disable=unused-argument @@ -134,7 +137,8 @@ def _onLogMessage( logging.warning(f"Failed to parse slog {line} with {d.format}") else: logging.warning(f"Unknown Structured Log: {line}") - self.raw_file.write(line + "\n") # Write the raw log + if self.raw_file: + self.raw_file.write(line + "\n") # Write the raw log class LogSet: From 715a085183f8d5c69e13ea76a18d364c4f24c7be Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 12:59:52 -0700 Subject: [PATCH 45/57] add more dataviz tooling --- poetry.lock | 664 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 5 + 2 files changed, 668 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 8e0d8cd0..bf2c2865 100644 --- a/poetry.lock +++ b/poetry.lock @@ -311,6 +311,17 @@ files = [ {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, ] +[[package]] +name = "blinker" +version = "1.8.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, +] + [[package]] name = "certifi" version = "2024.6.2" @@ -527,6 +538,69 @@ traitlets = ">=4" [package.extras] test = ["pytest"] +[[package]] +name = "contourpy" +version = "1.2.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +files = [ + {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, + {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, + {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, + {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, + {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, + {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, + {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, + {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, + {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, + {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, + {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, + {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, + {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, + {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, + {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, + {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, + {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, + {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, + {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, + {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, + {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, + {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, + {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, +] + +[package.dependencies] +numpy = ">=1.20" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] + [[package]] name = "coverage" version = "7.5.4" @@ -594,6 +668,87 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "dash" +version = "2.17.1" +description = "A Python framework for building reactive web-apps. Developed by Plotly." +optional = false +python-versions = ">=3.8" +files = [ + {file = "dash-2.17.1-py3-none-any.whl", hash = "sha256:3eefc9ac67003f93a06bc3e500cae0a6787c48e6c81f6f61514239ae2da414e4"}, + {file = "dash-2.17.1.tar.gz", hash = "sha256:ee2d9c319de5dcc1314085710b72cd5fa63ff994d913bf72979b7130daeea28e"}, +] + +[package.dependencies] +dash-core-components = "2.0.0" +dash-html-components = "2.0.0" +dash-table = "5.0.0" +Flask = ">=1.0.4,<3.1" +importlib-metadata = "*" +nest-asyncio = "*" +plotly = ">=5.0.0" +requests = "*" +retrying = "*" +setuptools = "*" +typing-extensions = ">=4.1.1" +Werkzeug = "<3.1" + +[package.extras] +celery = ["celery[redis] (>=5.1.2)", "redis (>=3.5.3)"] +ci = ["black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==7.0.0)", "flaky (==3.8.1)", "flask-talisman (==1.0.0)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.26.3)", "openpyxl", "orjson (==3.10.3)", "pandas (>=1.4.0)", "pyarrow", "pylint (==3.0.3)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "pyzmq (==25.1.2)", "xlrd (>=2.0.1)"] +compress = ["flask-compress"] +dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"] +diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] +testing = ["beautifulsoup4 (>=4.8.2)", "cryptography", "dash-testing-stub (>=0.0.2)", "lxml (>=4.6.2)", "multiprocess (>=0.70.12)", "percy (>=2.0.2)", "psutil (>=5.8.0)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)"] + +[[package]] +name = "dash-core-components" +version = "2.0.0" +description = "Core component suite for Dash" +optional = false +python-versions = "*" +files = [ + {file = "dash_core_components-2.0.0-py3-none-any.whl", hash = "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346"}, + {file = "dash_core_components-2.0.0.tar.gz", hash = "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee"}, +] + +[[package]] +name = "dash-html-components" +version = "2.0.0" +description = "Vanilla HTML components for Dash" +optional = false +python-versions = "*" +files = [ + {file = "dash_html_components-2.0.0-py3-none-any.whl", hash = "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63"}, + {file = "dash_html_components-2.0.0.tar.gz", hash = "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50"}, +] + +[[package]] +name = "dash-table" +version = "5.0.0" +description = "Dash table" +optional = false +python-versions = "*" +files = [ + {file = "dash_table-5.0.0-py3-none-any.whl", hash = "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9"}, + {file = "dash_table-5.0.0.tar.gz", hash = "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308"}, +] + [[package]] name = "dbus-fast" version = "2.21.3" @@ -758,6 +913,94 @@ files = [ [package.extras] devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] +[[package]] +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "fonttools" +version = "4.53.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.53.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:52a6e0a7a0bf611c19bc8ec8f7592bdae79c8296c70eb05917fd831354699b20"}, + {file = "fonttools-4.53.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:099634631b9dd271d4a835d2b2a9e042ccc94ecdf7e2dd9f7f34f7daf333358d"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40013572bfb843d6794a3ce076c29ef4efd15937ab833f520117f8eccc84fd6"}, + {file = "fonttools-4.53.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715b41c3e231f7334cbe79dfc698213dcb7211520ec7a3bc2ba20c8515e8a3b5"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74ae2441731a05b44d5988d3ac2cf784d3ee0a535dbed257cbfff4be8bb49eb9"}, + {file = "fonttools-4.53.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:95db0c6581a54b47c30860d013977b8a14febc206c8b5ff562f9fe32738a8aca"}, + {file = "fonttools-4.53.0-cp310-cp310-win32.whl", hash = "sha256:9cd7a6beec6495d1dffb1033d50a3f82dfece23e9eb3c20cd3c2444d27514068"}, + {file = "fonttools-4.53.0-cp310-cp310-win_amd64.whl", hash = "sha256:daaef7390e632283051e3cf3e16aff2b68b247e99aea916f64e578c0449c9c68"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a209d2e624ba492df4f3bfad5996d1f76f03069c6133c60cd04f9a9e715595ec"}, + {file = "fonttools-4.53.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f520d9ac5b938e6494f58a25c77564beca7d0199ecf726e1bd3d56872c59749"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eceef49f457253000e6a2d0f7bd08ff4e9fe96ec4ffce2dbcb32e34d9c1b8161"}, + {file = "fonttools-4.53.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1f3e34373aa16045484b4d9d352d4c6b5f9f77ac77a178252ccbc851e8b2ee"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28d072169fe8275fb1a0d35e3233f6df36a7e8474e56cb790a7258ad822b6fd6"}, + {file = "fonttools-4.53.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a2a6ba400d386e904fd05db81f73bee0008af37799a7586deaa4aef8cd5971e"}, + {file = "fonttools-4.53.0-cp311-cp311-win32.whl", hash = "sha256:bb7273789f69b565d88e97e9e1da602b4ee7ba733caf35a6c2affd4334d4f005"}, + {file = "fonttools-4.53.0-cp311-cp311-win_amd64.whl", hash = "sha256:9fe9096a60113e1d755e9e6bda15ef7e03391ee0554d22829aa506cdf946f796"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d8f191a17369bd53a5557a5ee4bab91d5330ca3aefcdf17fab9a497b0e7cff7a"}, + {file = "fonttools-4.53.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93156dd7f90ae0a1b0e8871032a07ef3178f553f0c70c386025a808f3a63b1f4"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bff98816cb144fb7b85e4b5ba3888a33b56ecef075b0e95b95bcd0a5fbf20f06"}, + {file = "fonttools-4.53.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:973d030180eca8255b1bce6ffc09ef38a05dcec0e8320cc9b7bcaa65346f341d"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4ee5a24e281fbd8261c6ab29faa7fd9a87a12e8c0eed485b705236c65999109"}, + {file = "fonttools-4.53.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5bc124fae781a4422f61b98d1d7faa47985f663a64770b78f13d2c072410c2"}, + {file = "fonttools-4.53.0-cp312-cp312-win32.whl", hash = "sha256:a239afa1126b6a619130909c8404070e2b473dd2b7fc4aacacd2e763f8597fea"}, + {file = "fonttools-4.53.0-cp312-cp312-win_amd64.whl", hash = "sha256:45b4afb069039f0366a43a5d454bc54eea942bfb66b3fc3e9a2c07ef4d617380"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:93bc9e5aaa06ff928d751dc6be889ff3e7d2aa393ab873bc7f6396a99f6fbb12"}, + {file = "fonttools-4.53.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2367d47816cc9783a28645bc1dac07f8ffc93e0f015e8c9fc674a5b76a6da6e4"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:907fa0b662dd8fc1d7c661b90782ce81afb510fc4b7aa6ae7304d6c094b27bce"}, + {file = "fonttools-4.53.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e0ad3c6ea4bd6a289d958a1eb922767233f00982cf0fe42b177657c86c80a8f"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:73121a9b7ff93ada888aaee3985a88495489cc027894458cb1a736660bdfb206"}, + {file = "fonttools-4.53.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ee595d7ba9bba130b2bec555a40aafa60c26ce68ed0cf509983e0f12d88674fd"}, + {file = "fonttools-4.53.0-cp38-cp38-win32.whl", hash = "sha256:fca66d9ff2ac89b03f5aa17e0b21a97c21f3491c46b583bb131eb32c7bab33af"}, + {file = "fonttools-4.53.0-cp38-cp38-win_amd64.whl", hash = "sha256:31f0e3147375002aae30696dd1dc596636abbd22fca09d2e730ecde0baad1d6b"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d6166192dcd925c78a91d599b48960e0a46fe565391c79fe6de481ac44d20ac"}, + {file = "fonttools-4.53.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef50ec31649fbc3acf6afd261ed89d09eb909b97cc289d80476166df8438524d"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f193f060391a455920d61684a70017ef5284ccbe6023bb056e15e5ac3de11d1"}, + {file = "fonttools-4.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c555e039d268445172b909b1b6bdcba42ada1cf4a60e367d68702e3f87e5f64"}, + {file = "fonttools-4.53.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a4788036201c908079e89ae3f5399b33bf45b9ea4514913f4dbbe4fac08efe0"}, + {file = "fonttools-4.53.0-cp39-cp39-win32.whl", hash = "sha256:d1a24f51a3305362b94681120c508758a88f207fa0a681c16b5a4172e9e6c7a9"}, + {file = "fonttools-4.53.0-cp39-cp39-win_amd64.whl", hash = "sha256:1e677bfb2b4bd0e5e99e0f7283e65e47a9814b0486cb64a41adf9ef110e078f2"}, + {file = "fonttools-4.53.0-py3-none-any.whl", hash = "sha256:6b4f04b1fbc01a3569d63359f2227c89ab294550de277fd09d8fca6185669fa4"}, + {file = "fonttools-4.53.0.tar.gz", hash = "sha256:c93ed66d32de1559b6fc348838c7572d5c0ac1e4a258e76763a5caddd8944002"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + [[package]] name = "fqdn" version = "1.5.1" @@ -888,6 +1131,24 @@ doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linke perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +[[package]] +name = "importlib-resources" +version = "6.4.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"}, + {file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -932,6 +1193,29 @@ pyqt5 = ["pyqt5"] pyside6 = ["pyside6"] test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] +[[package]] +name = "ipympl" +version = "0.9.4" +description = "Matplotlib Jupyter Extension" +optional = false +python-versions = ">=3.9" +files = [ + {file = "ipympl-0.9.4-py3-none-any.whl", hash = "sha256:5b0c08c6f4f6ea655ba58239363457c10fb921557f5038c1a46db4457d6d6b0e"}, + {file = "ipympl-0.9.4.tar.gz", hash = "sha256:cfb53c5b4fcbcee6d18f095eecfc6c6c474303d5b744e72cc66e7a2804708907"}, +] + +[package.dependencies] +ipython = "<9" +ipython-genutils = "*" +ipywidgets = ">=7.6.0,<9" +matplotlib = ">=3.4.0,<4" +numpy = "*" +pillow = "*" +traitlets = "<6" + +[package.extras] +docs = ["myst-nb", "sphinx (>=1.5)", "sphinx-book-theme", "sphinx-copybutton", "sphinx-thebe", "sphinx-togglebutton"] + [[package]] name = "ipython" version = "8.18.1" @@ -969,6 +1253,38 @@ qtconsole = ["qtconsole"] test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +optional = false +python-versions = "*" +files = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] + +[[package]] +name = "ipywidgets" +version = "8.1.3" +description = "Jupyter interactive widgets" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"}, + {file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"}, +] + +[package.dependencies] +comm = ">=0.1.3" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0.11,<3.1.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0.11,<4.1.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + [[package]] name = "isoduration" version = "20.11.0" @@ -997,6 +1313,17 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + [[package]] name = "jedi" version = "0.19.1" @@ -1308,6 +1635,130 @@ docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pyd openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.8.0)", "pytest (>=7.0,<8)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] +[[package]] +name = "jupyterlab-widgets" +version = "3.0.11" +description = "Jupyter interactive widgets for JupyterLab" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"}, + {file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + [[package]] name = "macholib" version = "1.16.3" @@ -1428,6 +1879,59 @@ files = [ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] +[[package]] +name = "matplotlib" +version = "3.9.0" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +files = [ + {file = "matplotlib-3.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2bcee1dffaf60fe7656183ac2190bd630842ff87b3153afb3e384d966b57fe56"}, + {file = "matplotlib-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f988bafb0fa39d1074ddd5bacd958c853e11def40800c5824556eb630f94d3b"}, + {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe428e191ea016bb278758c8ee82a8129c51d81d8c4bc0846c09e7e8e9057241"}, + {file = "matplotlib-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf3978060a106fab40c328778b148f590e27f6fa3cd15a19d6892575bce387d"}, + {file = "matplotlib-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e7f03e5cbbfacdd48c8ea394d365d91ee8f3cae7e6ec611409927b5ed997ee4"}, + {file = "matplotlib-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:13beb4840317d45ffd4183a778685e215939be7b08616f431c7795276e067463"}, + {file = "matplotlib-3.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:063af8587fceeac13b0936c42a2b6c732c2ab1c98d38abc3337e430e1ff75e38"}, + {file = "matplotlib-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a2fa6d899e17ddca6d6526cf6e7ba677738bf2a6a9590d702c277204a7c6152"}, + {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550cdda3adbd596078cca7d13ed50b77879104e2e46392dcd7c75259d8f00e85"}, + {file = "matplotlib-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cce0f31b351e3551d1f3779420cf8f6ec0d4a8cf9c0237a3b549fd28eb4abb"}, + {file = "matplotlib-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c53aeb514ccbbcbab55a27f912d79ea30ab21ee0531ee2c09f13800efb272674"}, + {file = "matplotlib-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5be985db2596d761cdf0c2eaf52396f26e6a64ab46bd8cd810c48972349d1be"}, + {file = "matplotlib-3.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c79f3a585f1368da6049318bdf1f85568d8d04b2e89fc24b7e02cc9b62017382"}, + {file = "matplotlib-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bdd1ecbe268eb3e7653e04f451635f0fb0f77f07fd070242b44c076c9106da84"}, + {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e85a1a6d732f645f1403ce5e6727fd9418cd4574521d5803d3d94911038e5"}, + {file = "matplotlib-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a490715b3b9984fa609116481b22178348c1a220a4499cda79132000a79b4db"}, + {file = "matplotlib-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8146ce83cbc5dc71c223a74a1996d446cd35cfb6a04b683e1446b7e6c73603b7"}, + {file = "matplotlib-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:d91a4ffc587bacf5c4ce4ecfe4bcd23a4b675e76315f2866e588686cc97fccdf"}, + {file = "matplotlib-3.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:616fabf4981a3b3c5a15cd95eba359c8489c4e20e03717aea42866d8d0465956"}, + {file = "matplotlib-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cd53c79fd02f1c1808d2cfc87dd3cf4dbc63c5244a58ee7944497107469c8d8a"}, + {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06a478f0d67636554fa78558cfbcd7b9dba85b51f5c3b5a0c9be49010cf5f321"}, + {file = "matplotlib-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c40af649d19c85f8073e25e5806926986806fa6d54be506fbf02aef47d5a89"}, + {file = "matplotlib-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52146fc3bd7813cc784562cb93a15788be0b2875c4655e2cc6ea646bfa30344b"}, + {file = "matplotlib-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:0fc51eaa5262553868461c083d9adadb11a6017315f3a757fc45ec6ec5f02888"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bd4f2831168afac55b881db82a7730992aa41c4f007f1913465fb182d6fb20c0"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:290d304e59be2b33ef5c2d768d0237f5bd132986bdcc66f80bc9bcc300066a03"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff2e239c26be4f24bfa45860c20ffccd118d270c5b5d081fa4ea409b5469fcd"}, + {file = "matplotlib-3.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:af4001b7cae70f7eaacfb063db605280058246de590fa7874f00f62259f2df7e"}, + {file = "matplotlib-3.9.0.tar.gz", hash = "sha256:e6d29ea6c19e34b30fb7d88b7081f869a03014f66fe06d62cc77d5a6ea88ed7a"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] + [[package]] name = "matplotlib-inline" version = "0.1.7" @@ -1813,6 +2317,92 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" +[[package]] +name = "pillow" +version = "10.3.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, + {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, + {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, + {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, + {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, + {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, + {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, + {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, + {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, + {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, + {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, + {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, + {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, + {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, + {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, + {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, + {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, + {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, + {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, + {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, + {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, + {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, + {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, + {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, + {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, + {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, + {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, + {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, + {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, + {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, + {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, + {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, + {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" version = "4.2.2" @@ -1829,6 +2419,21 @@ docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx- test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] type = ["mypy (>=1.8)"] +[[package]] +name = "plotly" +version = "5.22.0" +description = "An open-source, interactive data visualization library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "plotly-5.22.0-py3-none-any.whl", hash = "sha256:68fc1901f098daeb233cc3dd44ec9dc31fb3ca4f4e53189344199c43496ed006"}, + {file = "plotly-5.22.0.tar.gz", hash = "sha256:859fdadbd86b5770ae2466e542b761b247d1c6b49daed765b95bb8c7063e7469"}, +] + +[package.dependencies] +packaging = "*" +tenacity = ">=6.2.0" + [[package]] name = "pluggy" version = "1.5.0" @@ -2581,6 +3186,20 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "retrying" +version = "1.3.4" +description = "Retrying" +optional = false +python-versions = "*" +files = [ + {file = "retrying-1.3.4-py3-none-any.whl", hash = "sha256:8cc4d43cb8e1125e0ff3344e9de678fefd85db3b750b81b2240dc0183af37b35"}, + {file = "retrying-1.3.4.tar.gz", hash = "sha256:345da8c5765bd982b1d1915deb9102fd3d1f7ad16bd84a9700b85f64d24e8f3e"}, +] + +[package.dependencies] +six = ">=1.7.0" + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -2842,6 +3461,21 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "tenacity" +version = "8.4.2" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tenacity-8.4.2-py3-none-any.whl", hash = "sha256:9e6f7cf7da729125c7437222f8a522279751cdfbe6b67bfe64f75d3a348661b2"}, + {file = "tenacity-8.4.2.tar.gz", hash = "sha256:cd80a53a79336edba8489e767f729e4f391c896956b57140b5d7511a64bbd3ef"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + [[package]] name = "terminado" version = "0.18.1" @@ -3102,6 +3736,34 @@ docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] optional = ["python-socks", "wsaccel"] test = ["websockets"] +[[package]] +name = "werkzeug" +version = "3.0.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "widgetsnbextension" +version = "4.0.11" +description = "Jupyter interactive widgets for Jupyter Notebook" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"}, + {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, +] + [[package]] name = "winrt-runtime" version = "2.0.0b1" @@ -3341,4 +4003,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "c06a63c0dc330add8ff30c22a1c351f31e90a3cdb71afce7ce4644feb48c1038" +content-hash = "895a19621735918ee08874535bea6aabd349900ecc71c88ed4f3474dc935932a" diff --git a/pyproject.toml b/pyproject.toml index 698ae717..609c1f68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,11 @@ 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"] From 67bb6665f2af3070e27fb276e358a1a24b45506d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 15:29:18 -0700 Subject: [PATCH 46/57] the stock PPK2 API is super inefficient, remove lots of buffering --- meshtastic/powermon/ppk2.py | 58 ++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 53285fa6..3831fafa 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -31,12 +31,22 @@ def __init__(self, portName: Optional[str] = None): else: portName = devs[0] - self.r = r = ppk2_api.PPK2_MP(portName) # serial port will be different for you + 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.r.start_measuring() # send command to ppk2 - self.current_measurements = [0.0] # reset current measurements to 0mA self.measuring = True + self.reset_measurements() self.measurement_thread = threading.Thread( target=self.measurement_loop, daemon=True, name="ppk2 measurement" @@ -50,32 +60,54 @@ def __init__(self, portName: Optional[str] = None): 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"": + if read_data != b'': samples, _ = self.r.get_samples(read_data) - self.current_measurements += samples - time.sleep(0.001) # FIXME figure out correct sleep duration + + # update invariants + 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 min(self.current_measurements) / 1000 + return self.current_min / 1000 def get_max_current_mA(self): """Returns max current in mA (since last call to this method).""" - return max(self.current_measurements) / 1000 + return self.current_max / 1000 def get_average_current_mA(self): """Returns average current in mA (since last call to this method).""" - average_current_mA = ( - sum(self.current_measurements) / len(self.current_measurements) - ) / 1000 # measurements are in microamperes, divide by 1000 - - return average_current_mA + 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_measurements = [ self.current_measurements[-1] ] + 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.""" From 229454656092437aee6200bdef25a0c6b5ca3201 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 16:43:14 -0700 Subject: [PATCH 47/57] fix bogus high current reading on first ppk2 read --- meshtastic/powermon/ppk2.py | 53 ++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 3831fafa..1e2504d2 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -31,6 +31,7 @@ def __init__(self, portName: Optional[str] = None): else: portName = devs[0] + self.measuring = False self.current_max = 0 self.current_min = 0 self.current_sum = 0 @@ -41,20 +42,15 @@ def __init__(self, portName: Optional[str] = None): 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 + self.r = r = ppk2_api.PPK2_API( + portName + ) # serial port will be different for you r.get_modifiers() - self.r.start_measuring() # send command to ppk2 - self.measuring = True - self.reset_measurements() - self.measurement_thread = threading.Thread( target=self.measurement_loop, daemon=True, name="ppk2 measurement" ) - self.measurement_thread.start() - 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): @@ -63,15 +59,20 @@ def measurement_loop(self): # 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'': + if read_data != b"": samples, _ = self.r.get_samples(read_data) # update invariants - 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)}") + 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) @@ -92,7 +93,9 @@ def get_average_current_mA(self): 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 + return ( + self.current_sum / self.current_num_samples / 1000 + ) # measurements are in microamperes, divide by 1000 def reset_measurements(self): """Reset current measurements.""" @@ -102,7 +105,7 @@ def reset_measurements(self): self.current_sum = 0 self.current_num_samples = 0 - #if self.num_data_reads: + # 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 @@ -124,13 +127,25 @@ def setIsSupply(self, s: bool): ) # set source voltage in mV BEFORE setting source mode # Note: source voltage must be set even if we are using the amp meter mode - if ( - not s - ): # min power outpuf of PPK2. If less than this assume we want just 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") From c9351236e62a6c61f888c442cbf9c4343492e65d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 26 Jun 2024 17:28:55 -0700 Subject: [PATCH 48/57] blacklist hantek oscilliscope --- meshtastic/util.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index ef12344a..c95aef0f 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -25,14 +25,16 @@ 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) + 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]) """Some devices are highly likely to be meshtastic. 0x239a RAK4631 0x303a Heltec tracker""" -whitelistVids = dict.fromkeys([0x239a, 0x303a]) +whitelistVids = dict.fromkeys([0x239a, 0x303a, 0x04b4]) def quoteBooleans(a_string): From 119be810009154929c3eed6c076d2f945939b3cd Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Thu, 27 Jun 2024 16:36:31 -0700 Subject: [PATCH 49/57] PowerStress WIP --- .vscode/launch.json | 8 ++ .vscode/settings.json | 2 + meshtastic/__init__.py | 4 + meshtastic/__main__.py | 26 +++--- meshtastic/powermon/__init__.py | 1 + meshtastic/powermon/stress.py | 78 +++++++++++++++++ meshtastic/protobuf/portnums_pb2.py | 4 +- meshtastic/protobuf/portnums_pb2.pyi | 8 ++ meshtastic/protobuf/powermon_pb2.py | 6 +- meshtastic/protobuf/powermon_pb2.pyi | 124 +++++++++++++++++++++++++++ protobufs | 2 +- 11 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 meshtastic/powermon/stress.py diff --git a/.vscode/launch.json b/.vscode/launch.json index cef11f42..6d4b9b54 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -182,6 +182,14 @@ "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", "stdout"] + }, { "name": "meshtastic test", "type": "debugpy", diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b0d16e0..8fbd0a0e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,9 @@ "boardid", "Meshtastic", "milliwatt", + "portnums", "powermon", + "POWERSTRESS", "pyarrow", "TORADIO", "Vids" diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index 75508d00..f4d91fda 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -97,6 +97,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect remote_hardware_pb2, storeforward_pb2, telemetry_pb2, + powermon_pb2 ) from . import ( util, @@ -229,6 +230,9 @@ def _receiveInfoUpdate(iface, asDict): 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), diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index aa8f90d9..f4790097 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -11,6 +11,7 @@ import platform import sys import time +from typing import Optional import pyqrcode # type: ignore[import-untyped] import yaml @@ -25,9 +26,11 @@ 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 +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""" args = mt_config.args @@ -849,6 +852,15 @@ def setSimpleConfig(modem_preset): qr = pyqrcode.create(url) print(qr.terminal()) + if args.slog_out: + # Setup loggers + global meter + LogSet(interface, args.slog_out if args.slog_out != 'default' else None, meter) + + if args.power_stress: + stress = PowerStress(interface) + stress.run() + if args.listen: closeNow = False @@ -989,6 +1001,7 @@ def export_config(interface): def create_power_meter(): """Setup the power meter.""" + global meter args = mt_config.args meter = None # assume no power meter if args.power_riden: @@ -1009,7 +1022,6 @@ def create_power_meter(): if args.power_wait: input("Powered on, press enter to continue...") - return meter def common(): """Shared code for all of our command line wrappers.""" @@ -1029,7 +1041,7 @@ def common(): meshtastic.util.support_info() meshtastic.util.our_exit("", 0) - meter = create_power_meter() + create_power_meter() if args.ch_index is not None: channelIndex = int(args.ch_index) @@ -1120,11 +1132,6 @@ def common(): # We assume client is fully connected now onConnected(client) - log_set = None - if args.slog_out: - # Setup loggers - log_set = LogSet(client, args.slog_out if args.slog_out != 'default' else None, meter) - have_tunnel = platform.system() == "Linux" if ( args.noproto or args.reply or (have_tunnel and args.tunnel) or args.listen @@ -1135,9 +1142,6 @@ def common(): except KeyboardInterrupt: logging.info("Exiting due to keyboard interrupt") - if log_set: - log_set.close() - # don't call exit, background threads might be running still # sys.exit(0) diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py index a8f578f4..aefc8ae3 100644 --- a/meshtastic/powermon/__init__.py +++ b/meshtastic/powermon/__init__.py @@ -4,3 +4,4 @@ from .ppk2 import PPK2PowerSupply from .riden import RidenPowerSupply from .sim import SimPowerSupply +from .stress import PowerStress \ No newline at end of file diff --git a/meshtastic/powermon/stress.py b/meshtastic/powermon/stress.py new file mode 100644 index 00000000..b847e122 --- /dev/null +++ b/meshtastic/powermon/stress.py @@ -0,0 +1,78 @@ +"""Power stress testing support. +""" +import logging +import time + +from pubsub import pub # type: ignore[import-untyped] + +from meshtastic.protobuf import portnums_pb2 +from meshtastic.protobuf.powermon_pb2 import PowerStressMessage + + +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: PowerStressMessage.Opcode.ValueType, num_seconds: float = 0.0, onResponse=None + ): + r = PowerStressMessage() + r.cmd = cmd + r.num_seconds = num_seconds + + return self.iface.sendData( + r, + self.node_id, + portnums_pb2.POWERSTRESS_APP, + wantAck=True, + wantResponse=False, + 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, interface): + nonlocal gotAck + gotAck = True + + logging.info("Starting power stress test, attempting to contact UUT...") + self.client.sendPowerStress(PowerStressMessage.PRINT_INFO, onResponse=onResponse) + + # Wait for the response + while not gotAck: + time.sleep(0.1) + + logging.info("Power stress test complete.") \ No newline at end of file diff --git a/meshtastic/protobuf/portnums_pb2.py b/meshtastic/protobuf/portnums_pb2.py index bb0cd07b..7f851eb4 100644 --- a/meshtastic/protobuf/portnums_pb2.py +++ b/meshtastic/protobuf/portnums_pb2.py @@ -13,7 +13,7 @@ -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 @@ 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) diff --git a/meshtastic/protobuf/portnums_pb2.pyi b/meshtastic/protobuf/portnums_pb2.pyi index fda2e890..3c7546c8 100644 --- a/meshtastic/protobuf/portnums_pb2.pyi +++ b/meshtastic/protobuf/portnums_pb2.pyi @@ -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. diff --git a/meshtastic/protobuf/powermon_pb2.py b/meshtastic/protobuf/powermon_pb2.py index 4c344741..326a66f1 100644 --- a/meshtastic/protobuf/powermon_pb2.py +++ b/meshtastic/protobuf/powermon_pb2.py @@ -13,7 +13,7 @@ -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 @@ _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) diff --git a/meshtastic/protobuf/powermon_pb2.pyi b/meshtastic/protobuf/powermon_pb2.pyi index 5519b748..6c51a281 100644 --- a/meshtastic/protobuf/powermon_pb2.pyi +++ b/meshtastic/protobuf/powermon_pb2.pyi @@ -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 diff --git a/protobufs b/protobufs index a3030d5f..c25e0c4e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a3030d5ff187091c9fbbd08dd797cca5085736fe +Subproject commit c25e0c4e0ba99a5e2e94a1ca7313f999bf794fbd From dabb4ea44cde32778946fdbe692bafde97de990e Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Jun 2024 09:40:33 -0700 Subject: [PATCH 50/57] PowerStress client approximately works --- meshtastic/__main__.py | 8 +++---- meshtastic/powermon/stress.py | 4 ++-- meshtastic/slog/slog.py | 40 +++++++++++++++++++++-------------- protobufs | 2 +- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index f4790097..bc93c276 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -852,14 +852,14 @@ def setSimpleConfig(modem_preset): qr = pyqrcode.create(url) print(qr.terminal()) - if args.slog_out: + if args.slog_out or args.power_stress: # Setup loggers global meter LogSet(interface, args.slog_out if args.slog_out != 'default' else None, meter) - if args.power_stress: - stress = PowerStress(interface) - stress.run() + if args.power_stress: + stress = PowerStress(interface) + stress.run() if args.listen: closeNow = False diff --git a/meshtastic/powermon/stress.py b/meshtastic/powermon/stress.py index b847e122..0ab8d7dd 100644 --- a/meshtastic/powermon/stress.py +++ b/meshtastic/powermon/stress.py @@ -47,7 +47,7 @@ def sendPowerStress( self.node_id, portnums_pb2.POWERSTRESS_APP, wantAck=True, - wantResponse=False, + wantResponse=True, onResponse=onResponse, onResponseAckPermitted=True ) @@ -64,7 +64,7 @@ def run(self): # Send the power stress command gotAck = False - def onResponse(packet, interface): + def onResponse(packet: dict): # pylint: disable=unused-argument nonlocal gotAck gotAck = True diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 7f99d38a..e7c8efcf 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -1,11 +1,11 @@ """code logging power consumption of meshtastic devices.""" import atexit +import io import logging import os import re import threading -import io import time from dataclasses import dataclass from datetime import datetime @@ -100,10 +100,17 @@ def __init__(self, client: MeshInterface, dir_path: str) -> None: """ self.client = client self.writer = FeatherWriter(f"{dir_path}/slog") - self.raw_file: Optional[io.TextIOWrapper] = open( # pylint: disable=consider-using-with + self.raw_file: Optional[ + io.TextIOWrapper + ] = open( # pylint: disable=consider-using-with f"{dir_path}/raw.txt", "w", encoding="utf8" ) - self.listener = pub.subscribe(self._onLogMessage, TOPIC_MESHTASTIC_LOG_LINE) + + # 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.""" @@ -113,9 +120,7 @@ def close(self) -> None: self.raw_file = None # mark that we are shutting down f.close() # Close the raw.txt file - def _onLogMessage( - self, line: str, interface: MeshInterface # pylint: disable=unused-argument - ) -> None: + def _onLogMessage(self, line: str) -> None: """Handle log messages. line (str): the line of log output @@ -126,17 +131,20 @@ def _onLogMessage( 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}") - 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}") + if(src != "PM"): + logging.warning(f"Not yet handling structured log {src} (FIXME)") else: - logging.warning(f"Unknown Structured Log: {line}") + 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 diff --git a/protobufs b/protobufs index c25e0c4e..9d747b6c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit c25e0c4e0ba99a5e2e94a1ca7313f999bf794fbd +Subproject commit 9d747b6cf08e2762fdf9530173a7ff825b9daa90 From 542f99b28f8a6406b44f348c67e5849f63b6a987 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 28 Jun 2024 09:41:17 -0700 Subject: [PATCH 51/57] handle the new LogRecord protobufs (backwards/forwards compatible) with old firmware --- meshtastic/mesh_interface.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index af767149..38eca07b 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -150,6 +150,11 @@ 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()})" @@ -927,7 +932,8 @@ def _handleFromRadio(self, fromRadioBytes): 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) From 821d3e95f15e378e95619f4f965cdb5886927b21 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 29 Jun 2024 08:40:16 -0700 Subject: [PATCH 52/57] remvoe unneeded paren --- meshtastic/slog/slog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index e7c8efcf..9c6ae6dd 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -131,7 +131,7 @@ def _onLogMessage(self, line: str) -> None: 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"): + if src != "PM": logging.warning(f"Not yet handling structured log {src} (FIXME)") else: d = log_defs.get(src) From 1da687cf2d8ff731551584bd12eef31df9001093 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sat, 29 Jun 2024 16:15:32 -0700 Subject: [PATCH 53/57] move @thebentern spiffy logging so it is shared with !ble log sources --- meshtastic/ble_interface.py | 12 +----------- meshtastic/mesh_interface.py | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/meshtastic/ble_interface.py b/meshtastic/ble_interface.py index 69cb9a3b..1ebc57b7 100644 --- a/meshtastic/ble_interface.py +++ b/meshtastic/ble_interface.py @@ -8,7 +8,6 @@ from threading import Thread from typing import Optional -import print_color # type: ignore[import-untyped] from bleak import BleakClient, BleakScanner, BLEDevice from bleak.exc import BleakDBusError, BleakError @@ -82,16 +81,7 @@ def from_num_handler(self, _, b): # pylint: disable=C0116 async def 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]: diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 38eca07b..2c0ceebb 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -16,6 +16,7 @@ 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 @@ -143,8 +144,21 @@ def __exit__(self, exc_type, exc_value, traceback): @staticmethod def _printLogLine(line, interface): - """Print a line of log output""" - interface.debugOut.write(line + "\n") + """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.""" From 13ca8fd68144342f83ba2a942cc7b0defcf77b6d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 30 Jun 2024 06:31:09 -0700 Subject: [PATCH 54/57] debug launch configs --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ae7616bc..7cbe3b00 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "module": "meshtastic", "justMyCode": false, - "args": ["--ble", "--ble-dest", "Meshtastic_9f6e"] + "args": ["--ble", "--info", "--seriallog", "stdout"] }, { "name": "meshtastic BLE scan", From ae2ef78560f21aa8cfe90dd58a9916e07498c614 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 3 Jul 2024 09:53:23 -0700 Subject: [PATCH 55/57] fix linter warnings (note: the linter test for min/max is buggy so disabled) --- .pylintrc | 2 +- meshtastic/__main__.py | 5 ++--- meshtastic/powermon/__init__.py | 2 +- meshtastic/powermon/ppk2.py | 5 +++-- meshtastic/powermon/stress.py | 29 ++++++++++++++++------------- meshtastic/slog/arrow.py | 2 +- meshtastic/slog/slog.py | 3 ++- 7 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.pylintrc b/.pylintrc index 3201e418..c94cddf2 100644 --- a/.pylintrc +++ b/.pylintrc @@ -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] diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 36913590..f0708499 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -854,7 +854,7 @@ def setSimpleConfig(modem_preset): if args.slog_out or args.power_stress: # Setup loggers - global meter + global meter # pylint: disable=global-variable-not-assigned LogSet(interface, args.slog_out if args.slog_out != 'default' else None, meter) if args.power_stress: @@ -1001,9 +1001,8 @@ def export_config(interface): def create_power_meter(): """Setup the power meter.""" - global meter + global meter # pylint: disable=global-statement args = mt_config.args - meter = None # assume no power meter if args.power_riden: meter = RidenPowerSupply(args.power_riden) elif args.power_ppk2_supply or args.power_ppk2_meter: diff --git a/meshtastic/powermon/__init__.py b/meshtastic/powermon/__init__.py index aefc8ae3..12826cc1 100644 --- a/meshtastic/powermon/__init__.py +++ b/meshtastic/powermon/__init__.py @@ -4,4 +4,4 @@ from .ppk2 import PPK2PowerSupply from .riden import RidenPowerSupply from .sim import SimPowerSupply -from .stress import PowerStress \ No newline at end of file +from .stress import PowerStress diff --git a/meshtastic/powermon/ppk2.py b/meshtastic/powermon/ppk2.py index 1e2504d2..de400b97 100644 --- a/meshtastic/powermon/ppk2.py +++ b/meshtastic/powermon/ppk2.py @@ -9,7 +9,6 @@ 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. @@ -130,7 +129,9 @@ def setIsSupply(self, s: bool): # 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. + 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 diff --git a/meshtastic/powermon/stress.py b/meshtastic/powermon/stress.py index 0ab8d7dd..665e58be 100644 --- a/meshtastic/powermon/stress.py +++ b/meshtastic/powermon/stress.py @@ -3,10 +3,7 @@ import logging import time -from pubsub import pub # type: ignore[import-untyped] - -from meshtastic.protobuf import portnums_pb2 -from meshtastic.protobuf.powermon_pb2 import PowerStressMessage +from ..protobuf import ( portnums_pb2, powermon_pb2 ) def onPowerStressResponse(packet, interface): @@ -20,7 +17,7 @@ class PowerStressClient: The client stub for talking to the firmware PowerStress module. """ - def __init__(self, iface, node_id = None): + def __init__(self, iface, node_id=None): """ Create a new PowerStressClient instance. @@ -31,14 +28,18 @@ def __init__(self, iface, node_id = None): if not node_id: node_id = iface.myInfo.my_node_num - self.node_id = node_id + self.node_id = node_id # No need to subscribe - because we # pub.subscribe(onGPIOreceive, "meshtastic.receive.powerstress") def sendPowerStress( - self, cmd: PowerStressMessage.Opcode.ValueType, num_seconds: float = 0.0, onResponse=None + self, + cmd: powermon_pb2.PowerStressMessage.Opcode.ValueType, + num_seconds: float = 0.0, + onResponse=None, ): - r = PowerStressMessage() + """Client goo for talking with the device side agent.""" + r = powermon_pb2.PowerStressMessage() r.cmd = cmd r.num_seconds = num_seconds @@ -49,16 +50,16 @@ def sendPowerStress( wantAck=True, wantResponse=True, onResponse=onResponse, - onResponseAckPermitted=True + 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 @@ -68,11 +69,13 @@ 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(PowerStressMessage.PRINT_INFO, onResponse=onResponse) + 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.") \ No newline at end of file + logging.info("Power stress test complete.") diff --git a/meshtastic/slog/arrow.py b/meshtastic/slog/arrow.py index a2d32e6f..08337044 100644 --- a/meshtastic/slog/arrow.py +++ b/meshtastic/slog/arrow.py @@ -4,7 +4,7 @@ import os import pyarrow as pa -import pyarrow.feather as feather +from pyarrow import feather chunk_size = 1000 # disk writes are batched based on this number of rows diff --git a/meshtastic/slog/slog.py b/meshtastic/slog/slog.py index 9c6ae6dd..d5c19980 100644 --- a/meshtastic/slog/slog.py +++ b/meshtastic/slog/slog.py @@ -118,7 +118,8 @@ def close(self) -> None: self.writer.close() f = self.raw_file self.raw_file = None # mark that we are shutting down - f.close() # Close the raw.txt file + if f: + f.close() # Close the raw.txt file def _onLogMessage(self, line: str) -> None: """Handle log messages. From 5695ec7102e1fb9e1ec182a77b4357c34a2b7f27 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 3 Jul 2024 11:00:42 -0700 Subject: [PATCH 56/57] change --slog to use nargs --- meshtastic/__main__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index f0708499..87d23cb5 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -852,10 +852,10 @@ def setSimpleConfig(modem_preset): qr = pyqrcode.create(url) print(qr.terminal()) - if args.slog_out or args.power_stress: + if args.slog or args.power_stress: # Setup loggers global meter # pylint: disable=global-variable-not-assigned - LogSet(interface, args.slog_out if args.slog_out != 'default' else None, meter) + LogSet(interface, args.slog if args.slog != 'default' else None, meter) if args.power_stress: stress = PowerStress(interface) @@ -1584,8 +1584,11 @@ def initParser(): ) power_group.add_argument( - "--slog-out", - help="A directory to store structured logging to, or 'default' for automatically selected.", + "--slog", + help="Store structured-logs (slogs) for this run, optionally you can specifiy a destination directory", + nargs="?", + default=None, + const="default" ) group.add_argument( From 63327986b4d4f9f93c0528d8a23db57200dbcf41 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Wed, 3 Jul 2024 20:11:59 -0700 Subject: [PATCH 57/57] fix incorrect Vid - thanks @ianmcorvidae for notixing. 0x04b4 is cypress semi but commonly used in Chinese oscopes (like mine). So it was supposed to be a blacklist not a whitelist! --- meshtastic/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index c95aef0f..8cb177cd 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -25,16 +25,18 @@ from meshtastic.version import get_active_version """Some devices such as a seger jlink or st-link we never want to accidentally open + 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, 0x0925]) +blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4]) """Some devices are highly likely to be meshtastic. 0x239a RAK4631 0x303a Heltec tracker""" -whitelistVids = dict.fromkeys([0x239a, 0x303a, 0x04b4]) +whitelistVids = dict.fromkeys([0x239a, 0x303a]) def quoteBooleans(a_string):