Compare commits

...

62 Commits
2.5.3 ... 2.5.7

Author SHA1 Message Date
github-actions
f56b9eefa6 bump version to 2.5.7 2024-12-22 03:46:10 +00:00
Ian McEwen
2de1f1921c Merge pull request #723 from ianmcorvidae/add-wcwidth-optional
Add 'wcwidth' as an optional cli dependency
2024-12-21 20:32:47 -07:00
Ian McEwen
227507780e Add 'wcwidth' as an optional cli dependency
Fixes #598 when installed as meshtastic[cli], as should now be
recommended by documentation.
2024-12-21 20:29:07 -07:00
Ian McEwen
9f286c9023 Merge pull request #721 from ianmcorvidae/shell-completion
Add very simple shell completion using argcomplete
2024-12-21 20:26:06 -07:00
Ian McEwen
0b1545393e Make mypy happy with the optional import of argcomplete 2024-12-21 20:23:35 -07:00
Ian McEwen
245a9e40b1 Add very simple shell completion using argcomplete 2024-12-21 20:12:26 -07:00
Ian McEwen
749c6a70bc Merge pull request #720 from ianmcorvidae/winserver-fix
Fix windows-11 detection for non-float platform.release() values
2024-12-21 14:22:55 -07:00
Ian McEwen
afd071c24e Fix windows-11 detection for non-float platform.release() values (fixes #639) 2024-12-21 14:17:41 -07:00
Ian McEwen
29f355bd61 Merge pull request #719 from ianmcorvidae/ignore-nodes
Support setting nodes ignored in nodedb (with 2.5.13+ firmware)
2024-12-21 13:50:44 -07:00
Ian McEwen
4b6d7a8587 Support setting nodes ignored in nodedb (with 2.5.13+ firmware) 2024-12-21 13:46:17 -07:00
Ian McEwen
a765bccf4d Merge pull request #718 from ianmcorvidae/show-unknown-more-nicely
Display unknown hop count as '?', and header as just 'Hops'
2024-12-20 23:08:19 -07:00
Ian McEwen
f950ecae2d Display unknown hop count as '?', and header as just 'Hops' 2024-12-20 23:04:36 -07:00
Ian McEwen
7c7170a5dd Merge pull request #706 from meshtastic/dependabot/pip/tornado-6.4.2
Bump tornado from 6.4.1 to 6.4.2
2024-12-20 22:50:01 -07:00
Ian McEwen
df191e149b Merge pull request #713 from esev/favorite
Support setting/removing nodes as favorites
2024-12-20 22:36:23 -07:00
Ian McEwen
843abe587f Back out release workflow changes from hotfix 2024-12-20 14:59:26 -07:00
Ian McEwen
ff7dcc3afb Merge branch '2.5.6-hotfix' 2024-12-20 14:47:52 -07:00
Ian McEwen
d66b8fa9dd fix import-related errors 2024-12-20 14:45:54 -07:00
Ian McEwen
f6f8ccfcbc Fix message_to_json_shows_all test for added MyNodeInfo fields 2024-12-20 13:51:27 -07:00
Ian McEwen
cace959ca4 Update protobufs (to 2.5.17) and main python version (to 2.5.7) 2024-12-20 13:45:58 -07:00
github-actions
01ffd83d64 bump version to 2.5.6 2024-12-20 20:42:21 +00:00
Eric Severance
18ac0d6d5c Support setting/removing nodes as favorites 2024-12-17 17:38:51 -08:00
Ian McEwen
7c89e231bd Merge pull request #711 from ianmcorvidae/optionalize-deps
Make several dependencies optional (dotmap, print_color, and pyqrcode)
2024-12-12 21:19:15 -07:00
Ian McEwen
4673824236 Add the newly optional dependencies to an extras group 2024-12-12 21:14:58 -07:00
Ian McEwen
d87eddfd33 Make pyqrcode optional 2024-12-12 21:01:38 -07:00
Ian McEwen
31f322f1c2 Make dotmap (via meshtastic.test) and print_color optional 2024-12-12 20:59:22 -07:00
Ian McEwen
89b41c1a19 Remove pexpect too 2024-12-12 20:40:59 -07:00
Ian McEwen
1a5ca789c2 Remove pyparsing and webencodings dependencies, unsure why they're here at all 2024-12-12 19:48:29 -07:00
Ian McEwen
03ac322583 reset mypy-protobuf to non-optional, hopefully without breaking stuff 2024-11-26 14:35:48 -07:00
Ian McEwen
c63814358a prep 2.5.6a0 & protobufs 2024-11-26 14:31:33 -07:00
github-actions
663fabce74 bump version to 2.5.5 2024-11-26 21:22:11 +00:00
Ian McEwen
6243965044 Merge pull request #707 from meshtastic/request-localstats
Allow request-telemetry to elicit LocalStats response
2024-11-24 11:31:01 -07:00
Ben Meadors
b180b6fb15 Allow request-telemetry to illicit LocalStats response 2024-11-24 08:19:40 -06:00
dependabot[bot]
7bb8e4e9dd Bump tornado from 6.4.1 to 6.4.2
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2.
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)

---
updated-dependencies:
- dependency-name: tornado
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-22 22:17:33 +00:00
Ian McEwen
4c7ac60be6 appease mypy too 2024-11-15 11:55:25 -07:00
Ian McEwen
0b086d10f8 appease pylint 2024-11-15 11:50:35 -07:00
Ian McEwen
426795fccd semi-experimentally, add fallback code for message_to_json, allowing older protobuf library versions 2024-11-15 11:47:17 -07:00
Ian McEwen
fb88ee114c bump protobufs to 2.5.5 2024-11-01 09:06:02 -07:00
github-actions
a4630b53eb bump version to 2.5.4 2024-11-01 16:03:00 +00:00
Ian McEwen
646aa981d5 update readme to link to docs again 2024-10-29 14:22:09 -07:00
Ian McEwen
9381acd6ac Merge pull request #681 from william-stearns/wls_add_types2
Wls add types2
2024-10-29 06:50:52 -07:00
Ian McEwen
384063db19 Fix some remaining mypy complaints 2024-10-29 06:47:16 -07:00
Ian McEwen
20d75d9023 Merge branch 'master' into wls_add_types2 2024-10-29 06:19:16 -07:00
Ian McEwen
0deb1d788f Merge pull request #702 from meshtastic/dependabot/pip/werkzeug-3.0.6
Bump werkzeug from 3.0.4 to 3.0.6
2024-10-28 20:36:24 -07:00
Ian McEwen
1070d9202b Merge pull request #698 from lachesis/lachesis/fix-remote-module-cfg-get
Add missing camel to snake conversion
2024-10-28 20:35:10 -07:00
Ian McEwen
b90de8b73b Merge pull request #701 from fmoessbauer/master
A lot of fixes around setting / retrieving base64 encoded values
2024-10-28 20:29:05 -07:00
dependabot[bot]
2e79ecf759 Bump werkzeug from 3.0.4 to 3.0.6
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.4 to 3.0.6.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/werkzeug/compare/3.0.4...3.0.6)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-26 00:29:31 +00:00
Felix Moessbauer
578d3e4b24 do not double-print value when setting a repeated value
When clearning or appending to a repeated value, both the "Clearing..."
/ "Adding..." line and the "Set..." line were shown. However, this is
misleading as the only performed operation is the clearing / appending.

We fix this by directly returning from the function in case of clearing
/ appending.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
4ca13bcede fix setting of list item on configure
When setting the whole configuration via --configure, list types like
adminKey need special handling. Previously this failed as a list cannot
be appended to a list. The new code adds dedicated logic to replace the
repeated value when passing a list. Also, all items of that list are
converted into the correct (typed) form before setting them.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
6ceae7c72f setPref: pass typed value instead of string
By passing a typed value we can conserve the type information to later
use that to convert it back into the correct protobuf type. The type
conversion is now done inside setPref.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
4c29d7dd0f refactor camel and snake naming in setPref
Same change as done in getPerf to have less branches (simplifies the
code).
2024-10-25 17:11:06 +02:00
Felix Moessbauer
839bbbcad2 config: correctly print byte and array types on get
When getting config values of type bytes or list (technically a protobuf
repeated container type), these were directly printed on the output.
However, the retrieved values could not be set by --set again, as the
format was different (e.g. python string representation of bytes vs.
base64 prefixed and encoded as expected by --set).

We fix this by adding a toStr utility function (similar to the fromStr)
function to convert byte types correctly to the base64 representation.
Further, we check if the type is repeated and apply this operation to
all values.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
1abb9fb213 refactor getPref to use uniform printing logic
We add a local helper function to print a single setting. This is a
preparation to correctly print non-trivial types. The existing code
in getPref is ported over to use that function. By that, the output
of "wholeField" is changed slightly to always print the full path for
each setting (e.g. "security.serialEnabled" instead of
"security:\nserialEnabled"). This improves support for grepping on the
output.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
7fcbbe9b80 refactor camel and snake naming in getPref
We have a lot of code duplication by checkin over and over again if
field should be named in camel or snake notation. We simplify this by
writing the choosen variant into a variable and just use that name
across the code.

No functional change.
2024-10-25 14:13:50 +02:00
Eric Swanson
073274cb00 Add missing camel to snake conversion 2024-10-21 22:06:43 -04:00
Ian McEwen
92a3986a8f Improve comments for pdoc 2024-10-21 16:39:47 -07:00
William Stearns
e335f12a3b attempts to fix mypy issues 2024-10-11 00:59:02 -04:00
William Stearns
0da405168f pylint cleanups 2024-10-10 23:49:20 -04:00
William Stearns
58d9039a04 another missing import 2024-10-10 16:49:06 -04:00
William Stearns
f77e788aa8 fix missing import 2024-10-10 16:33:07 -04:00
William Stearns
aba381fb54 Merge branch 'master' into wls_add_types 2024-10-08 23:28:25 -04:00
William Stearns
60de9dddb1 Remove references to BLEClient breaking CI checks 2024-07-09 19:54:01 -04:00
William Stearns
a29ee840f2 Adding mypy typing 2024-06-15 23:22:43 -04:00
37 changed files with 2459 additions and 1357 deletions

View File

@@ -16,7 +16,7 @@ Events are delivered using a publish-subscribe model, and you can subscribe to o
**[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)** **[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)**
(Documentation/API Reference is currently offline) **[API Documentation](https://python.meshtastic.org)**
## Call for Contributors ## Call for Contributors

View File

@@ -80,7 +80,6 @@ from typing import *
import google.protobuf.json_format import google.protobuf.json_format
import serial # type: ignore[import-untyped] import serial # type: ignore[import-untyped]
from dotmap import DotMap # type: ignore[import-untyped]
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from pubsub import pub # type: ignore[import-untyped] from pubsub import pub # type: ignore[import-untyped]
from tabulate import tabulate from tabulate import tabulate
@@ -111,13 +110,13 @@ from . import (
LOCAL_ADDR = "^local" LOCAL_ADDR = "^local"
"""A special ID that means the local node""" """A special ID that means the local node"""
BROADCAST_NUM = 0xFFFFFFFF BROADCAST_NUM: int = 0xFFFFFFFF
"""if using 8 bit nodenums this will be shortened on the target""" """if using 8 bit nodenums this will be shortened on the target"""
BROADCAST_ADDR = "^all" BROADCAST_ADDR = "^all"
"""A special ID that means broadcast""" """A special ID that means broadcast"""
OUR_APP_VERSION = 20300 OUR_APP_VERSION: int = 20300
"""The numeric buildnumber (shared with android apps) specifying the """The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand level of device code we are guaranteed to understand
@@ -134,7 +133,9 @@ class ResponseHandler(NamedTuple):
"""A pending response callback, waiting for a response to one of our messages""" """A pending response callback, waiting for a response to one of our messages"""
# requestId: int - used only as a key # requestId: int - used only as a key
#: a callable to call when a response is received
callback: Callable callback: Callable
#: Whether ACKs and NAKs should be passed to this handler
ackPermitted: bool = False ackPermitted: bool = False
# FIXME, add timestamp and age out old requests # FIXME, add timestamp and age out old requests
@@ -142,11 +143,11 @@ class ResponseHandler(NamedTuple):
class KnownProtocol(NamedTuple): class KnownProtocol(NamedTuple):
"""Used to automatically decode known protocol payloads""" """Used to automatically decode known protocol payloads"""
#: A descriptive name (e.g. "text", "user", "admin")
name: str name: str
# portnum: int, now a key #: If set, will be called to parse as a protocol buffer
# If set, will be called to prase as a protocol buffer
protobufFactory: Optional[Callable] = None protobufFactory: Optional[Callable] = None
# If set, invoked as onReceive(interface, packet) #: If set, invoked as onReceive(interface, packet)
onReceive: Optional[Callable] = None onReceive: Optional[Callable] = None

View File

@@ -5,21 +5,41 @@
# later we can have a separate changelist to refactor main.py into smaller files # later we can have a separate changelist to refactor main.py into smaller files
# pylint: disable=too-many-lines # pylint: disable=too-many-lines
from typing import List, Optional, Union
from types import ModuleType
import argparse import argparse
argcomplete: Union[None, ModuleType] = None
try:
import argcomplete
except ImportError as e:
pass # already set to None by default above
import logging import logging
import os import os
import platform import platform
import sys import sys
import time import time
from typing import Optional
import pyqrcode # type: ignore[import-untyped] try:
import pyqrcode # type: ignore[import-untyped]
except ImportError as e:
pyqrcode = None
import yaml import yaml
from google.protobuf.json_format import MessageToDict from google.protobuf.json_format import MessageToDict
from pubsub import pub # type: ignore[import-untyped] from pubsub import pub # type: ignore[import-untyped]
import meshtastic.test try:
import meshtastic.test
have_test = True
except ImportError as e:
have_test = False
import meshtastic.util import meshtastic.util
import meshtastic.serial_interface
import meshtastic.tcp_interface
from meshtastic import BROADCAST_ADDR, mt_config, remote_hardware from meshtastic import BROADCAST_ADDR, mt_config, remote_hardware
from meshtastic.ble_interface import BLEInterface from meshtastic.ble_interface import BLEInterface
from meshtastic.mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
@@ -42,7 +62,7 @@ except ImportError as e:
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2 from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2
from meshtastic.version import get_active_version from meshtastic.version import get_active_version
def onReceive(packet, interface): def onReceive(packet, interface) -> None:
"""Callback invoked when a packet arrives""" """Callback invoked when a packet arrives"""
args = mt_config.args args = mt_config.args
try: try:
@@ -73,7 +93,7 @@ def onReceive(packet, interface):
print(f"Warning: There is no field {ex} in the packet.") print(f"Warning: There is no field {ex} in the packet.")
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613 def onConnection(interface, topic=pub.AUTO_TOPIC) -> None: # pylint: disable=W0613
"""Callback invoked when we connect/disconnect from a radio""" """Callback invoked when we connect/disconnect from a radio"""
print(f"Connection changed: {topic.getName()}") print(f"Connection changed: {topic.getName()}")
@@ -85,8 +105,16 @@ def checkChannel(interface: MeshInterface, channelIndex: int) -> bool:
return ch and ch.role != channel_pb2.Channel.Role.DISABLED return ch and ch.role != channel_pb2.Channel.Role.DISABLED
def getPref(node, comp_name): def getPref(node, comp_name) -> bool:
"""Get a channel or preferences value""" """Get a channel or preferences value"""
def _printSetting(config_type, uni_name, pref_value, repeated):
"""Pretty print the setting"""
if repeated:
pref_value = [meshtastic.util.toStr(v) for v in pref_value]
else:
pref_value = meshtastic.util.toStr(pref_value)
print(f"{str(config_type.name)}.{uni_name}: {str(pref_value)}")
logging.debug(f"{str(config_type.name)}.{uni_name}: {str(pref_value)}")
name = splitCompoundName(comp_name) name = splitCompoundName(comp_name)
wholeField = name[0] == name[1] # We want the whole field wholeField = name[0] == name[1] # We want the whole field
@@ -94,17 +122,18 @@ def getPref(node, comp_name):
camel_name = meshtastic.util.snake_to_camel(name[1]) camel_name = meshtastic.util.snake_to_camel(name[1])
# Note: protobufs has the keys in snake_case, so snake internally # Note: protobufs has the keys in snake_case, so snake internally
snake_name = meshtastic.util.camel_to_snake(name[1]) snake_name = meshtastic.util.camel_to_snake(name[1])
uni_name = camel_name if mt_config.camel_case else snake_name
logging.debug(f"snake_name:{snake_name} camel_name:{camel_name}") logging.debug(f"snake_name:{snake_name} camel_name:{camel_name}")
logging.debug(f"use camel:{mt_config.camel_case}") logging.debug(f"use camel:{mt_config.camel_case}")
# First validate the input # First validate the input
localConfig = node.localConfig localConfig = node.localConfig
moduleConfig = node.moduleConfig moduleConfig = node.moduleConfig
found = False found: bool = False
for config in [localConfig, moduleConfig]: for config in [localConfig, moduleConfig]:
objDesc = config.DESCRIPTOR objDesc = config.DESCRIPTOR
config_type = objDesc.fields_by_name.get(name[0]) config_type = objDesc.fields_by_name.get(name[0])
pref = False pref = "" #FIXME - is this correct to leave as an empty string if not found?
if config_type: if config_type:
pref = config_type.message_type.fields_by_name.get(snake_name) pref = config_type.message_type.fields_by_name.get(snake_name)
if pref or wholeField: if pref or wholeField:
@@ -112,38 +141,26 @@ def getPref(node, comp_name):
break break
if not found: if not found:
if mt_config.camel_case: print(
print( f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have attribute {uni_name}."
f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have an attribute {snake_name}." )
)
else:
print(
f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have attribute {snake_name}."
)
print("Choices are...") print("Choices are...")
printConfig(localConfig) printConfig(localConfig)
printConfig(moduleConfig) printConfig(moduleConfig)
return False return False
# Check if we need to request the config # Check if we need to request the config
if len(config.ListFields()) != 0: if len(config.ListFields()) != 0 and not isinstance(pref, str): # if str, it's still the empty string, I think
# read the value # read the value
config_values = getattr(config, config_type.name) config_values = getattr(config, config_type.name)
if not wholeField: if not wholeField:
pref_value = getattr(config_values, pref.name) pref_value = getattr(config_values, pref.name)
if mt_config.camel_case: repeated = pref.label == pref.LABEL_REPEATED
print(f"{str(config_type.name)}.{camel_name}: {str(pref_value)}") _printSetting(config_type, uni_name, pref_value, repeated)
logging.debug(
f"{str(config_type.name)}.{camel_name}: {str(pref_value)}"
)
else:
print(f"{str(config_type.name)}.{snake_name}: {str(pref_value)}")
logging.debug(
f"{str(config_type.name)}.{snake_name}: {str(pref_value)}"
)
else: else:
print(f"{str(config_type.name)}:\n{str(config_values)}") for field in config_values.ListFields():
logging.debug(f"{str(config_type.name)}: {str(config_values)}") repeated = field[0].label == field[0].LABEL_REPEATED
_printSetting(config_type, field[0].name, field[1], repeated)
else: else:
# Always show whole field for remote node # Always show whole field for remote node
node.requestConfig(config_type) node.requestConfig(config_type)
@@ -151,16 +168,16 @@ def getPref(node, comp_name):
return True return True
def splitCompoundName(comp_name): def splitCompoundName(comp_name: str) -> List[str]:
"""Split compound (dot separated) preference name into parts""" """Split compound (dot separated) preference name into parts"""
name = comp_name.split(".") name: List[str] = comp_name.split(".")
if len(name) < 2: if len(name) < 2:
name[0] = comp_name name[0] = comp_name
name.append(comp_name) name.append(comp_name)
return name return name
def traverseConfig(config_root, config, interface_config): def traverseConfig(config_root, config, interface_config) -> bool:
"""Iterate through current config level preferences and either traverse deeper if preference is a dict or set preference""" """Iterate through current config level preferences and either traverse deeper if preference is a dict or set preference"""
snake_name = meshtastic.util.camel_to_snake(config_root) snake_name = meshtastic.util.camel_to_snake(config_root)
for pref in config: for pref in config:
@@ -168,18 +185,19 @@ def traverseConfig(config_root, config, interface_config):
if isinstance(config[pref], dict): if isinstance(config[pref], dict):
traverseConfig(pref_name, config[pref], interface_config) traverseConfig(pref_name, config[pref], interface_config)
else: else:
setPref(interface_config, pref_name, str(config[pref])) setPref(interface_config, pref_name, config[pref])
return True return True
def setPref(config, comp_name, valStr) -> bool: def setPref(config, comp_name, raw_val) -> bool:
"""Set a channel or preferences value""" """Set a channel or preferences value"""
name = splitCompoundName(comp_name) name = splitCompoundName(comp_name)
snake_name = meshtastic.util.camel_to_snake(name[-1]) snake_name = meshtastic.util.camel_to_snake(name[-1])
camel_name = meshtastic.util.snake_to_camel(name[-1]) camel_name = meshtastic.util.snake_to_camel(name[-1])
uni_name = camel_name if mt_config.camel_case else snake_name
logging.debug(f"snake_name:{snake_name}") logging.debug(f"snake_name:{snake_name}")
logging.debug(f"camel_name:{camel_name}") logging.debug(f"camel_name:{camel_name}")
@@ -201,10 +219,13 @@ def setPref(config, comp_name, valStr) -> bool:
if (not pref) or (not config_type): if (not pref) or (not config_type):
return False return False
val = meshtastic.util.fromStr(valStr) if isinstance(raw_val, str):
logging.debug(f"valStr:{valStr} val:{val}") val = meshtastic.util.fromStr(raw_val)
else:
val = raw_val
logging.debug(f"valStr:{raw_val} val:{val}")
if snake_name == "wifi_psk" and len(valStr) < 8: if snake_name == "wifi_psk" and len(str(raw_val)) < 8:
print(f"Warning: network.wifi_psk must be 8 or more characters.") print(f"Warning: network.wifi_psk must be 8 or more characters.")
return False return False
@@ -216,14 +237,9 @@ def setPref(config, comp_name, valStr) -> bool:
if e: if e:
val = e.number val = e.number
else: else:
if mt_config.camel_case: print(
print( f"{name[0]}.{uni_name} does not have an enum called {val}, so you can not set it."
f"{name[0]}.{camel_name} does not have an enum called {val}, so you can not set it." )
)
else:
print(
f"{name[0]}.{snake_name} does not have an enum called {val}, so you can not set it."
)
print(f"Choices in sorted order are:") print(f"Choices in sorted order are:")
names = [] names = []
for f in enumType.values: for f in enumType.values:
@@ -244,7 +260,11 @@ def setPref(config, comp_name, valStr) -> bool:
except TypeError: except TypeError:
# The setter didn't like our arg type guess try again as a string # The setter didn't like our arg type guess try again as a string
config_values = getattr(config_part, config_type.name) config_values = getattr(config_part, config_type.name)
setattr(config_values, pref.name, valStr) setattr(config_values, pref.name, str(val))
elif type(val) == list:
new_vals = [meshtastic.util.fromStr(x) for x in val]
config_values = getattr(config, config_type.name)
getattr(config_values, pref.name)[:] = new_vals
else: else:
config_values = getattr(config, config_type.name) config_values = getattr(config, config_type.name)
if val == 0: if val == 0:
@@ -252,16 +272,14 @@ def setPref(config, comp_name, valStr) -> bool:
print(f"Clearing {pref.name} list") print(f"Clearing {pref.name} list")
del getattr(config_values, pref.name)[:] del getattr(config_values, pref.name)[:]
else: else:
print(f"Adding '{val}' to the {pref.name} list") print(f"Adding '{raw_val}' to the {pref.name} list")
cur_vals = [x for x in getattr(config_values, pref.name) if x not in [0, "", b""]] cur_vals = [x for x in getattr(config_values, pref.name) if x not in [0, "", b""]]
cur_vals.append(val) cur_vals.append(val)
getattr(config_values, pref.name)[:] = cur_vals getattr(config_values, pref.name)[:] = cur_vals
return True
prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else "" prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else ""
if mt_config.camel_case: print(f"Set {prefix}{uni_name} to {raw_val}")
print(f"Set {prefix}{camel_name} to {valStr}")
else:
print(f"Set {prefix}{snake_name} to {valStr}")
return True return True
@@ -434,6 +452,26 @@ def onConnected(interface):
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).removeNode(args.remove_node) interface.getNode(args.dest, False, **getNode_kwargs).removeNode(args.remove_node)
if args.set_favorite_node:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).setFavorite(args.set_favorite_node)
if args.remove_favorite_node:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).removeFavorite(args.remove_favorite_node)
if args.set_ignored_node:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).setIgnored(args.set_ignored_node)
if args.remove_ignored_node:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).removeIgnored(args.remove_ignored_node)
if args.reset_nodedb: if args.reset_nodedb:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
@@ -481,6 +519,8 @@ def onConnected(interface):
"air_quality": "air_quality_metrics", "air_quality": "air_quality_metrics",
"airquality": "air_quality_metrics", "airquality": "air_quality_metrics",
"power": "power_metrics", "power": "power_metrics",
"localstats": "local_stats",
"local_stats": "local_stats",
} }
telemType = telemMap.get(args.request_telemetry, "device_metrics") telemType = telemMap.get(args.request_telemetry, "device_metrics")
print( print(
@@ -891,8 +931,11 @@ def onConnected(interface):
else: else:
urldesc = "Primary channel URL" urldesc = "Primary channel URL"
print(f"{urldesc}: {url}") print(f"{urldesc}: {url}")
qr = pyqrcode.create(url) if pyqrcode is not None:
print(qr.terminal()) qr = pyqrcode.create(url)
print(qr.terminal())
else:
print("Install pyqrcode to view a QR code printed to terminal.")
log_set: Optional = None # type: ignore[annotation-unchecked] log_set: Optional = None # type: ignore[annotation-unchecked]
# we need to keep a reference to the logset so it doesn't get GCed early # we need to keep a reference to the logset so it doesn't get GCed early
@@ -960,7 +1003,7 @@ def onConnected(interface):
sys.exit(1) sys.exit(1)
def printConfig(config): def printConfig(config) -> None:
"""print configuration""" """print configuration"""
objDesc = config.DESCRIPTOR objDesc = config.DESCRIPTOR
for config_section in objDesc.fields: for config_section in objDesc.fields:
@@ -977,12 +1020,12 @@ def printConfig(config):
print(f" {temp_name}") print(f" {temp_name}")
def onNode(node): def onNode(node) -> None:
"""Callback invoked when the node DB changes""" """Callback invoked when the node DB changes"""
print(f"Node changed: {node}") print(f"Node changed: {node}")
def subscribe(): def subscribe() -> None:
"""Subscribe to the topics the user probably wants to see, prints output to stdout""" """Subscribe to the topics the user probably wants to see, prints output to stdout"""
pub.subscribe(onReceive, "meshtastic.receive") pub.subscribe(onReceive, "meshtastic.receive")
# pub.subscribe(onConnection, "meshtastic.connection") # pub.subscribe(onConnection, "meshtastic.connection")
@@ -993,7 +1036,7 @@ def subscribe():
# pub.subscribe(onNode, "meshtastic.node") # pub.subscribe(onNode, "meshtastic.node")
def export_config(interface): def export_config(interface) -> str:
"""used in --export-config""" """used in --export-config"""
configObj = {} configObj = {}
@@ -1025,7 +1068,7 @@ def export_config(interface):
if alt: if alt:
configObj["location"]["alt"] = alt configObj["location"]["alt"] = alt
config = MessageToDict(interface.localNode.localConfig) config = MessageToDict(interface.localNode.localConfig) #checkme - Used as a dictionary here and a string below
if config: if config:
# Convert inner keys to correct snake/camelCase # Convert inner keys to correct snake/camelCase
prefs = {} prefs = {}
@@ -1044,7 +1087,7 @@ def export_config(interface):
for i in range(len(prefs[pref]['adminKey'])): for i in range(len(prefs[pref]['adminKey'])):
prefs[pref]['adminKey'][i] = 'base64:' + prefs[pref]['adminKey'][i] prefs[pref]['adminKey'][i] = 'base64:' + prefs[pref]['adminKey'][i]
if mt_config.camel_case: if mt_config.camel_case:
configObj["config"] = config configObj["config"] = config #Identical command here and 2 lines below?
else: else:
configObj["config"] = config configObj["config"] = config
@@ -1060,10 +1103,11 @@ def export_config(interface):
else: else:
configObj["module_config"] = prefs configObj["module_config"] = prefs
config = "# start of Meshtastic configure yaml\n" config_txt = "# start of Meshtastic configure yaml\n" #checkme - "config" (now changed to config_out)
config += yaml.dump(configObj) #was used as a string here and a Dictionary above
print(config) config_txt += yaml.dump(configObj)
return config print(config_txt)
return config_txt
def create_power_meter(): def create_power_meter():
@@ -1142,11 +1186,14 @@ def common():
parser.print_help(sys.stderr) parser.print_help(sys.stderr)
meshtastic.util.our_exit("", 1) meshtastic.util.our_exit("", 1)
elif args.test: elif args.test:
result = meshtastic.test.testAll() if not have_test:
if not result: meshtastic.util.our_exit("Test module could not be important. Ensure you have the 'dotmap' module installed.")
meshtastic.util.our_exit("Warning: Test was not successful.")
else: else:
meshtastic.util.our_exit("Test was a success.", 0) result = meshtastic.test.testAll()
if not result:
meshtastic.util.our_exit("Warning: Test was not successful.")
else:
meshtastic.util.our_exit("Test was a success.", 0)
else: else:
if args.seriallog == "stdout": if args.seriallog == "stdout":
logfile = sys.stdout logfile = sys.stdout
@@ -1683,6 +1730,26 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
help="Tell the destination node to remove a specific node from its DB, by node number or ID", help="Tell the destination node to remove a specific node from its DB, by node number or ID",
metavar="!xxxxxxxx" metavar="!xxxxxxxx"
) )
group.add_argument(
"--set-favorite-node",
help="Tell the destination node to set the specified node to be favorited on the NodeDB on the devicein its DB, by number or ID",
metavar="!xxxxxxxx"
)
group.add_argument(
"--remove-favorite-node",
help="Tell the destination node to set the specified node to be un-favorited on the NodeDB on the device, by number or ID",
metavar="!xxxxxxxx"
)
group.add_argument(
"--set-ignored-node",
help="Tell the destination node to set the specified node to be ignored on the NodeDB on the devicein its DB, by number or ID",
metavar="!xxxxxxxx"
)
group.add_argument(
"--remove-ignored-node",
help="Tell the destination node to set the specified node to be un-ignored on the NodeDB on the device, by number or ID",
metavar="!xxxxxxxx"
)
group.add_argument( group.add_argument(
"--reset-nodedb", "--reset-nodedb",
help="Tell the destination node to clear its list of nodes", help="Tell the destination node to clear its list of nodes",
@@ -1902,6 +1969,8 @@ def initParser():
parser.set_defaults(deprecated=None) parser.set_defaults(deprecated=None)
if argcomplete is not None:
argcomplete.autocomplete(parser)
args = parser.parse_args() args = parser.parse_args()
mt_config.args = args mt_config.args = args
mt_config.parser = parser mt_config.parser = parser

View File

@@ -5,6 +5,7 @@ import atexit
import logging import logging
import struct import struct
import time import time
import io
from threading import Thread from threading import Thread
from typing import List, Optional from typing import List, Optional
@@ -34,9 +35,9 @@ class BLEInterface(MeshInterface):
self, self,
address: Optional[str], address: Optional[str],
noProto: bool = False, noProto: bool = False,
debugOut=None, debugOut: Optional[io.TextIOWrapper]=None,
noNodes: bool = False, noNodes: bool = False,
): ) -> None:
MeshInterface.__init__( MeshInterface.__init__(
self, debugOut=debugOut, noProto=noProto, noNodes=noNodes self, debugOut=debugOut, noProto=noProto, noNodes=noNodes
) )
@@ -82,7 +83,7 @@ class BLEInterface(MeshInterface):
# Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit # Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
self._exit_handler = atexit.register(self.client.disconnect) self._exit_handler = atexit.register(self.client.disconnect)
def from_num_handler(self, _, b): # pylint: disable=C0116 def from_num_handler(self, _, b: bytes) -> None: # pylint: disable=C0116
"""Handle callbacks for fromnum notify. """Handle callbacks for fromnum notify.
Note: this method does not need to be async because it is just setting a bool. Note: this method does not need to be async because it is just setting a bool.
""" """
@@ -150,9 +151,12 @@ class BLEInterface(MeshInterface):
) )
return addressed_devices[0] return addressed_devices[0]
def _sanitize_address(address): # pylint: disable=E0213 def _sanitize_address(self, address: Optional[str]) -> Optional[str]: # pylint: disable=E0213
"Standardize BLE address by removing extraneous characters and lowercasing." "Standardize BLE address by removing extraneous characters and lowercasing."
return address.replace("-", "").replace("_", "").replace(":", "").lower() if address is None:
return None
else:
return address.replace("-", "").replace("_", "").replace(":", "").lower()
def connect(self, address: Optional[str] = None) -> "BLEClient": def connect(self, address: Optional[str] = None) -> "BLEClient":
"Connect to a device by address." "Connect to a device by address."
@@ -164,12 +168,16 @@ class BLEInterface(MeshInterface):
client.discover() client.discover()
return client return client
def _receiveFromRadioImpl(self): def _receiveFromRadioImpl(self) -> None:
while self._want_receive: while self._want_receive:
if self.should_read: if self.should_read:
self.should_read = False self.should_read = False
retries = 0 retries: int = 0
while self._want_receive: while self._want_receive:
if self.client is None:
logging.debug(f"BLE client is None, shutting down")
self._want_receive = False
continue
try: try:
b = bytes(self.client.read_gatt_char(FROMRADIO_UUID)) b = bytes(self.client.read_gatt_char(FROMRADIO_UUID))
except BleakDBusError as e: except BleakDBusError as e:
@@ -194,8 +202,8 @@ class BLEInterface(MeshInterface):
else: else:
time.sleep(0.01) time.sleep(0.01)
def _sendToRadioImpl(self, toRadio): def _sendToRadioImpl(self, toRadio) -> None:
b = toRadio.SerializeToString() b: bytes = toRadio.SerializeToString()
if b and self.client: # we silently ignore writes while we are shutting down if b and self.client: # we silently ignore writes while we are shutting down
logging.debug(f"TORADIO write: {b.hex()}") logging.debug(f"TORADIO write: {b.hex()}")
try: try:
@@ -211,7 +219,7 @@ class BLEInterface(MeshInterface):
time.sleep(0.01) time.sleep(0.01)
self.should_read = True self.should_read = True
def close(self): def close(self) -> None:
try: try:
MeshInterface.close(self) MeshInterface.close(self)
except Exception as e: except Exception as e:
@@ -236,7 +244,7 @@ class BLEInterface(MeshInterface):
class BLEClient: class BLEClient:
"""Client for managing connection to a BLE device""" """Client for managing connection to a BLE device"""
def __init__(self, address=None, **kwargs): def __init__(self, address=None, **kwargs) -> None:
self._eventLoop = asyncio.new_event_loop() self._eventLoop = asyncio.new_event_loop()
self._eventThread = Thread( self._eventThread = Thread(
target=self._run_event_loop, name="BLEClient", daemon=True target=self._run_event_loop, name="BLEClient", daemon=True

View File

@@ -15,7 +15,11 @@ from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Union from typing import Any, Callable, Dict, List, Optional, Union
import google.protobuf.json_format import google.protobuf.json_format
import print_color # type: ignore[import-untyped] try:
import print_color # type: ignore[import-untyped]
except ImportError as e:
print_color = None
from pubsub import pub # type: ignore[import-untyped] from pubsub import pub # type: ignore[import-untyped]
from tabulate import tabulate from tabulate import tabulate
@@ -153,7 +157,7 @@ class MeshInterface: # pylint: disable=R0902
@staticmethod @staticmethod
def _printLogLine(line, interface): def _printLogLine(line, interface):
"""Print a line of log output.""" """Print a line of log output."""
if interface.debugOut == sys.stdout: if print_color is not None and interface.debugOut == sys.stdout:
# this isn't quite correct (could cause false positives), but currently our formatting differs between different log representations # this isn't quite correct (could cause false positives), but currently our formatting differs between different log representations
if "DEBUG" in line: if "DEBUG" in line:
print_color.print(line, color="cyan", end=None) print_color.print(line, color="cyan", end=None)
@@ -299,7 +303,7 @@ class MeshInterface: # pylint: disable=R0902
row.update( row.update(
{ {
"SNR": formatFloat(node.get("snr"), 2, " dB"), "SNR": formatFloat(node.get("snr"), 2, " dB"),
"Hops Away": node.get("hopsAway", "0/unknown"), "Hops": node.get("hopsAway", "?"),
"Channel": node.get("channel", 0), "Channel": node.get("channel", 0),
"LastHeard": getLH(node.get("lastHeard")), "LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")), "Since": getTimeAgo(node.get("lastHeard")),
@@ -617,6 +621,8 @@ class MeshInterface: # pylint: disable=R0902
r.air_quality_metrics.CopyFrom(telemetry_pb2.AirQualityMetrics()) r.air_quality_metrics.CopyFrom(telemetry_pb2.AirQualityMetrics())
elif telemetryType == "power_metrics": elif telemetryType == "power_metrics":
r.power_metrics.CopyFrom(telemetry_pb2.PowerMetrics()) r.power_metrics.CopyFrom(telemetry_pb2.PowerMetrics())
elif telemetryType == "local_stats":
r.local_stats.CopyFrom(telemetry_pb2.LocalStats())
else: # fall through to device metrics else: # fall through to device metrics
if self.nodesByNum is not None: if self.nodesByNum is not None:
node = self.nodesByNum.get(self.localNode.nodeNum) node = self.nodesByNum.get(self.localNode.nodeNum)

View File

@@ -13,6 +13,8 @@ with rather more easily once the code is simplified by this change.
""" """
from typing import Any, Optional
def reset(): def reset():
""" """
Restore the namespace to pristine condition. Restore the namespace to pristine condition.
@@ -33,5 +35,5 @@ args = None
parser = None parser = None
channel_index = None channel_index = None
logfile = None logfile = None
tunnelInstance = None tunnelInstance: Optional[Any] = None
camel_case = False camel_case = False

View File

@@ -121,7 +121,7 @@ class Node:
) )
return return
if config_values is not None: if config_values is not None:
raw_config = getattr(getattr(adminMessage['raw'], oneof), field) raw_config = getattr(getattr(adminMessage['raw'], oneof), camel_to_snake(field))
config_values.CopyFrom(raw_config) config_values.CopyFrom(raw_config)
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}") print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
@@ -668,6 +668,78 @@ class Node:
onResponse = self.onAckNak onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse) return self._sendAdmin(p, onResponse=onResponse)
def setFavorite(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be favorited on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.set_favorite_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def removeFavorite(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be un-favorited on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.remove_favorite_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def setIgnored(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be ignored on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.set_ignored_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def removeIgnored(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be un-ignored on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.remove_ignored_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def resetNodeDb(self): def resetNodeDb(self):
"""Tell the node to reset its list of nodes.""" """Tell the node to reset its list of nodes."""
self.ensureSessionKey() self.ensureSessionKey()

View File

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,7 @@ import google.protobuf.message
import meshtastic.protobuf.channel_pb2 import meshtastic.protobuf.channel_pb2
import meshtastic.protobuf.config_pb2 import meshtastic.protobuf.config_pb2
import meshtastic.protobuf.connection_status_pb2 import meshtastic.protobuf.connection_status_pb2
import meshtastic.protobuf.device_ui_pb2
import meshtastic.protobuf.mesh_pb2 import meshtastic.protobuf.mesh_pb2
import meshtastic.protobuf.module_config_pb2 import meshtastic.protobuf.module_config_pb2
import sys import sys
@@ -74,6 +75,10 @@ class AdminMessage(google.protobuf.message.Message):
""" """
SESSIONKEY_CONFIG: AdminMessage._ConfigType.ValueType # 8 SESSIONKEY_CONFIG: AdminMessage._ConfigType.ValueType # 8
"""""" """"""
DEVICEUI_CONFIG: AdminMessage._ConfigType.ValueType # 9
"""
device-ui config
"""
class ConfigType(_ConfigType, metaclass=_ConfigTypeEnumTypeWrapper): class ConfigType(_ConfigType, metaclass=_ConfigTypeEnumTypeWrapper):
""" """
@@ -114,6 +119,10 @@ class AdminMessage(google.protobuf.message.Message):
""" """
SESSIONKEY_CONFIG: AdminMessage.ConfigType.ValueType # 8 SESSIONKEY_CONFIG: AdminMessage.ConfigType.ValueType # 8
"""""" """"""
DEVICEUI_CONFIG: AdminMessage.ConfigType.ValueType # 9
"""
device-ui config
"""
class _ModuleConfigType: class _ModuleConfigType:
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)
@@ -267,6 +276,11 @@ class AdminMessage(google.protobuf.message.Message):
SET_FIXED_POSITION_FIELD_NUMBER: builtins.int SET_FIXED_POSITION_FIELD_NUMBER: builtins.int
REMOVE_FIXED_POSITION_FIELD_NUMBER: builtins.int REMOVE_FIXED_POSITION_FIELD_NUMBER: builtins.int
SET_TIME_ONLY_FIELD_NUMBER: builtins.int SET_TIME_ONLY_FIELD_NUMBER: builtins.int
GET_UI_CONFIG_REQUEST_FIELD_NUMBER: builtins.int
GET_UI_CONFIG_RESPONSE_FIELD_NUMBER: builtins.int
STORE_UI_CONFIG_FIELD_NUMBER: builtins.int
SET_IGNORED_NODE_FIELD_NUMBER: builtins.int
REMOVE_IGNORED_NODE_FIELD_NUMBER: builtins.int
BEGIN_EDIT_SETTINGS_FIELD_NUMBER: builtins.int BEGIN_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
COMMIT_EDIT_SETTINGS_FIELD_NUMBER: builtins.int COMMIT_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
FACTORY_RESET_DEVICE_FIELD_NUMBER: builtins.int FACTORY_RESET_DEVICE_FIELD_NUMBER: builtins.int
@@ -369,6 +383,18 @@ class AdminMessage(google.protobuf.message.Message):
Set time only on the node Set time only on the node
Convenience method to set the time on the node (as Net quality) without any other position data Convenience method to set the time on the node (as Net quality) without any other position data
""" """
get_ui_config_request: builtins.bool
"""
Tell the node to send the stored ui data.
"""
set_ignored_node: builtins.int
"""
Set specified node-num to be ignored on the NodeDB on the device
"""
remove_ignored_node: builtins.int
"""
Set specified node-num to be un-ignored on the NodeDB on the device
"""
begin_edit_settings: builtins.bool begin_edit_settings: builtins.bool
""" """
Begins an edit transaction for config, module config, owner, and channel settings changes Begins an edit transaction for config, module config, owner, and channel settings changes
@@ -490,6 +516,18 @@ class AdminMessage(google.protobuf.message.Message):
Set fixed position data on the node and then set the position.fixed_position = true Set fixed position data on the node and then set the position.fixed_position = true
""" """
@property
def get_ui_config_response(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
"""
Reply stored device ui data.
"""
@property
def store_ui_config(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
"""
Tell the node to store UI data persistently.
"""
def __init__( def __init__(
self, self,
*, *,
@@ -528,6 +566,11 @@ class AdminMessage(google.protobuf.message.Message):
set_fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ..., set_fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
remove_fixed_position: builtins.bool = ..., remove_fixed_position: builtins.bool = ...,
set_time_only: builtins.int = ..., set_time_only: builtins.int = ...,
get_ui_config_request: builtins.bool = ...,
get_ui_config_response: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
store_ui_config: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
set_ignored_node: builtins.int = ...,
remove_ignored_node: builtins.int = ...,
begin_edit_settings: builtins.bool = ..., begin_edit_settings: builtins.bool = ...,
commit_edit_settings: builtins.bool = ..., commit_edit_settings: builtins.bool = ...,
factory_reset_device: builtins.int = ..., factory_reset_device: builtins.int = ...,
@@ -538,9 +581,9 @@ class AdminMessage(google.protobuf.message.Message):
factory_reset_config: builtins.int = ..., factory_reset_config: builtins.int = ...,
nodedb_reset: builtins.int = ..., nodedb_reset: builtins.int = ...,
) -> None: ... ) -> 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_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "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", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds"]) -> builtins.bool: ... 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_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "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", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_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", "remove_ignored_node", b"remove_ignored_node", "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_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> 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_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "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", "session_passkey", b"session_passkey", "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", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds"]) -> None: ... 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_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "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", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_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", "remove_ignored_node", b"remove_ignored_node", "session_passkey", b"session_passkey", "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_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> 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", "set_time_only", "begin_edit_settings", "commit_edit_settings", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset"] | 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", "set_time_only", "get_ui_config_request", "get_ui_config_response", "store_ui_config", "set_ignored_node", "remove_ignored_node", "begin_edit_settings", "commit_edit_settings", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset"] | None: ...
global___AdminMessage = AdminMessage global___AdminMessage = AdminMessage

View File

@@ -256,7 +256,7 @@ class TAKPacket(google.protobuf.message.Message):
detail: builtins.bytes detail: builtins.bytes
""" """
Generic CoT detail XML Generic CoT detail XML
May be compressed / truncated by the sender May be compressed / truncated by the sender (EUD)
""" """
@property @property
def contact(self) -> global___Contact: def contact(self) -> global___Contact:

View File

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ import google.protobuf.descriptor
import google.protobuf.internal.containers import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message import google.protobuf.message
import meshtastic.protobuf.device_ui_pb2
import sys import sys
import typing import typing
@@ -210,6 +211,15 @@ class Config(google.protobuf.message.Message):
Ignores observed messages from foreign meshes like LOCAL_ONLY, Ignores observed messages from foreign meshes like LOCAL_ONLY,
but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB) but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
""" """
NONE: Config.DeviceConfig._RebroadcastMode.ValueType # 4
"""
Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.
"""
CORE_PORTNUMS_ONLY: Config.DeviceConfig._RebroadcastMode.ValueType # 5
"""
Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc.
Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing.
"""
class RebroadcastMode(_RebroadcastMode, metaclass=_RebroadcastModeEnumTypeWrapper): class RebroadcastMode(_RebroadcastMode, metaclass=_RebroadcastModeEnumTypeWrapper):
""" """
@@ -236,6 +246,15 @@ class Config(google.protobuf.message.Message):
Ignores observed messages from foreign meshes like LOCAL_ONLY, Ignores observed messages from foreign meshes like LOCAL_ONLY,
but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB) but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
""" """
NONE: Config.DeviceConfig.RebroadcastMode.ValueType # 4
"""
Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.
"""
CORE_PORTNUMS_ONLY: Config.DeviceConfig.RebroadcastMode.ValueType # 5
"""
Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc.
Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing.
"""
ROLE_FIELD_NUMBER: builtins.int ROLE_FIELD_NUMBER: builtins.int
SERIAL_ENABLED_FIELD_NUMBER: builtins.int SERIAL_ENABLED_FIELD_NUMBER: builtins.int
@@ -1202,6 +1221,18 @@ class Config(google.protobuf.message.Message):
""" """
Singapore 923mhz Singapore 923mhz
""" """
PH_433: Config.LoRaConfig._RegionCode.ValueType # 19
"""
Philippines 433mhz
"""
PH_868: Config.LoRaConfig._RegionCode.ValueType # 20
"""
Philippines 868mhz
"""
PH_915: Config.LoRaConfig._RegionCode.ValueType # 21
"""
Philippines 915mhz
"""
class RegionCode(_RegionCode, metaclass=_RegionCodeEnumTypeWrapper): ... class RegionCode(_RegionCode, metaclass=_RegionCodeEnumTypeWrapper): ...
UNSET: Config.LoRaConfig.RegionCode.ValueType # 0 UNSET: Config.LoRaConfig.RegionCode.ValueType # 0
@@ -1280,6 +1311,18 @@ class Config(google.protobuf.message.Message):
""" """
Singapore 923mhz Singapore 923mhz
""" """
PH_433: Config.LoRaConfig.RegionCode.ValueType # 19
"""
Philippines 433mhz
"""
PH_868: Config.LoRaConfig.RegionCode.ValueType # 20
"""
Philippines 868mhz
"""
PH_915: Config.LoRaConfig.RegionCode.ValueType # 21
"""
Philippines 915mhz
"""
class _ModemPreset: class _ModemPreset:
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)
@@ -1660,6 +1703,7 @@ class Config(google.protobuf.message.Message):
BLUETOOTH_FIELD_NUMBER: builtins.int BLUETOOTH_FIELD_NUMBER: builtins.int
SECURITY_FIELD_NUMBER: builtins.int SECURITY_FIELD_NUMBER: builtins.int
SESSIONKEY_FIELD_NUMBER: builtins.int SESSIONKEY_FIELD_NUMBER: builtins.int
DEVICE_UI_FIELD_NUMBER: builtins.int
@property @property
def device(self) -> global___Config.DeviceConfig: ... def device(self) -> global___Config.DeviceConfig: ...
@property @property
@@ -1678,6 +1722,8 @@ class Config(google.protobuf.message.Message):
def security(self) -> global___Config.SecurityConfig: ... def security(self) -> global___Config.SecurityConfig: ...
@property @property
def sessionkey(self) -> global___Config.SessionkeyConfig: ... def sessionkey(self) -> global___Config.SessionkeyConfig: ...
@property
def device_ui(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig: ...
def __init__( def __init__(
self, self,
*, *,
@@ -1690,9 +1736,10 @@ class Config(google.protobuf.message.Message):
bluetooth: global___Config.BluetoothConfig | None = ..., bluetooth: global___Config.BluetoothConfig | None = ...,
security: global___Config.SecurityConfig | None = ..., security: global___Config.SecurityConfig | None = ...,
sessionkey: global___Config.SessionkeyConfig | None = ..., sessionkey: global___Config.SessionkeyConfig | None = ...,
device_ui: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "device_ui", b"device_ui", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> None: ... def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "device_ui", b"device_ui", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["device", "position", "power", "network", "display", "lora", "bluetooth", "security", "sessionkey"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["device", "position", "power", "network", "display", "lora", "bluetooth", "security", "sessionkey", "device_ui"] | None: ...
global___Config = Config global___Config = Config

34
meshtastic/protobuf/device_ui_pb2.py generated Normal file
View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/protobuf/device_ui.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/device_ui.proto\x12\x13meshtastic.protobuf\"\xbf\x03\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\"\x96\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xfc\x01\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x63\n\x13\x63om.geeksville.meshB\x0e\x44\x65viceUIProtosZ\"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.device_ui_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_THEME']._serialized_start=791
_globals['_THEME']._serialized_end=828
_globals['_LANGUAGE']._serialized_start=831
_globals['_LANGUAGE']._serialized_end=1083
_globals['_DEVICEUICONFIG']._serialized_start=61
_globals['_DEVICEUICONFIG']._serialized_end=508
_globals['_NODEFILTER']._serialized_start=511
_globals['_NODEFILTER']._serialized_end=661
_globals['_NODEHIGHLIGHT']._serialized_start=663
_globals['_NODEHIGHLIGHT']._serialized_end=789
# @@protoc_insertion_point(module_scope)

386
meshtastic/protobuf/device_ui_pb2.pyi generated Normal file
View File

@@ -0,0 +1,386 @@
"""
@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 _Theme:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _ThemeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Theme.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
DARK: _Theme.ValueType # 0
"""
Dark
"""
LIGHT: _Theme.ValueType # 1
"""
Light
"""
RED: _Theme.ValueType # 2
"""
Red
"""
class Theme(_Theme, metaclass=_ThemeEnumTypeWrapper): ...
DARK: Theme.ValueType # 0
"""
Dark
"""
LIGHT: Theme.ValueType # 1
"""
Light
"""
RED: Theme.ValueType # 2
"""
Red
"""
global___Theme = Theme
class _Language:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Language.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
ENGLISH: _Language.ValueType # 0
"""
English
"""
FRENCH: _Language.ValueType # 1
"""
French
"""
GERMAN: _Language.ValueType # 2
"""
German
"""
ITALIAN: _Language.ValueType # 3
"""
Italian
"""
PORTUGUESE: _Language.ValueType # 4
"""
Portuguese
"""
SPANISH: _Language.ValueType # 5
"""
Spanish
"""
SWEDISH: _Language.ValueType # 6
"""
Swedish
"""
FINNISH: _Language.ValueType # 7
"""
Finnish
"""
POLISH: _Language.ValueType # 8
"""
Polish
"""
TURKISH: _Language.ValueType # 9
"""
Turkish
"""
SERBIAN: _Language.ValueType # 10
"""
Serbian
"""
RUSSIAN: _Language.ValueType # 11
"""
Russian
"""
DUTCH: _Language.ValueType # 12
"""
Dutch
"""
GREEK: _Language.ValueType # 13
"""
Greek
"""
NORWEGIAN: _Language.ValueType # 14
"""
Norwegian
"""
SIMPLIFIED_CHINESE: _Language.ValueType # 30
"""
Simplified Chinese (experimental)
"""
TRADITIONAL_CHINESE: _Language.ValueType # 31
"""
Traditional Chinese (experimental)
"""
class Language(_Language, metaclass=_LanguageEnumTypeWrapper):
"""
Localization
"""
ENGLISH: Language.ValueType # 0
"""
English
"""
FRENCH: Language.ValueType # 1
"""
French
"""
GERMAN: Language.ValueType # 2
"""
German
"""
ITALIAN: Language.ValueType # 3
"""
Italian
"""
PORTUGUESE: Language.ValueType # 4
"""
Portuguese
"""
SPANISH: Language.ValueType # 5
"""
Spanish
"""
SWEDISH: Language.ValueType # 6
"""
Swedish
"""
FINNISH: Language.ValueType # 7
"""
Finnish
"""
POLISH: Language.ValueType # 8
"""
Polish
"""
TURKISH: Language.ValueType # 9
"""
Turkish
"""
SERBIAN: Language.ValueType # 10
"""
Serbian
"""
RUSSIAN: Language.ValueType # 11
"""
Russian
"""
DUTCH: Language.ValueType # 12
"""
Dutch
"""
GREEK: Language.ValueType # 13
"""
Greek
"""
NORWEGIAN: Language.ValueType # 14
"""
Norwegian
"""
SIMPLIFIED_CHINESE: Language.ValueType # 30
"""
Simplified Chinese (experimental)
"""
TRADITIONAL_CHINESE: Language.ValueType # 31
"""
Traditional Chinese (experimental)
"""
global___Language = Language
@typing.final
class DeviceUIConfig(google.protobuf.message.Message):
"""
Protobuf structures for device-ui persistency
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
VERSION_FIELD_NUMBER: builtins.int
SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int
SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int
SCREEN_LOCK_FIELD_NUMBER: builtins.int
SETTINGS_LOCK_FIELD_NUMBER: builtins.int
PIN_CODE_FIELD_NUMBER: builtins.int
THEME_FIELD_NUMBER: builtins.int
ALERT_ENABLED_FIELD_NUMBER: builtins.int
BANNER_ENABLED_FIELD_NUMBER: builtins.int
RING_TONE_ID_FIELD_NUMBER: builtins.int
LANGUAGE_FIELD_NUMBER: builtins.int
NODE_FILTER_FIELD_NUMBER: builtins.int
NODE_HIGHLIGHT_FIELD_NUMBER: builtins.int
CALIBRATION_DATA_FIELD_NUMBER: builtins.int
version: builtins.int
"""
A version integer used to invalidate saved files when we make incompatible changes.
"""
screen_brightness: builtins.int
"""
TFT display brightness 1..255
"""
screen_timeout: builtins.int
"""
Screen timeout 0..900
"""
screen_lock: builtins.bool
"""
Screen/Settings lock enabled
"""
settings_lock: builtins.bool
pin_code: builtins.int
theme: global___Theme.ValueType
"""
Color theme
"""
alert_enabled: builtins.bool
"""
Audible message, banner and ring tone
"""
banner_enabled: builtins.bool
ring_tone_id: builtins.int
language: global___Language.ValueType
"""
Localization
"""
calibration_data: builtins.bytes
"""
8 integers for screen calibration data
"""
@property
def node_filter(self) -> global___NodeFilter:
"""
Node list filter
"""
@property
def node_highlight(self) -> global___NodeHighlight:
"""
Node list highlightening
"""
def __init__(
self,
*,
version: builtins.int = ...,
screen_brightness: builtins.int = ...,
screen_timeout: builtins.int = ...,
screen_lock: builtins.bool = ...,
settings_lock: builtins.bool = ...,
pin_code: builtins.int = ...,
theme: global___Theme.ValueType = ...,
alert_enabled: builtins.bool = ...,
banner_enabled: builtins.bool = ...,
ring_tone_id: builtins.int = ...,
language: global___Language.ValueType = ...,
node_filter: global___NodeFilter | None = ...,
node_highlight: global___NodeHighlight | None = ...,
calibration_data: builtins.bytes = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "language", b"language", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
global___DeviceUIConfig = DeviceUIConfig
@typing.final
class NodeFilter(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
UNKNOWN_SWITCH_FIELD_NUMBER: builtins.int
OFFLINE_SWITCH_FIELD_NUMBER: builtins.int
PUBLIC_KEY_SWITCH_FIELD_NUMBER: builtins.int
HOPS_AWAY_FIELD_NUMBER: builtins.int
POSITION_SWITCH_FIELD_NUMBER: builtins.int
NODE_NAME_FIELD_NUMBER: builtins.int
unknown_switch: builtins.bool
"""
Filter unknown nodes
"""
offline_switch: builtins.bool
"""
Filter offline nodes
"""
public_key_switch: builtins.bool
"""
Filter nodes w/o public key
"""
hops_away: builtins.int
"""
Filter based on hops away
"""
position_switch: builtins.bool
"""
Filter nodes w/o position
"""
node_name: builtins.str
"""
Filter nodes by matching name string
"""
def __init__(
self,
*,
unknown_switch: builtins.bool = ...,
offline_switch: builtins.bool = ...,
public_key_switch: builtins.bool = ...,
hops_away: builtins.int = ...,
position_switch: builtins.bool = ...,
node_name: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["hops_away", b"hops_away", "node_name", b"node_name", "offline_switch", b"offline_switch", "position_switch", b"position_switch", "public_key_switch", b"public_key_switch", "unknown_switch", b"unknown_switch"]) -> None: ...
global___NodeFilter = NodeFilter
@typing.final
class NodeHighlight(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
CHAT_SWITCH_FIELD_NUMBER: builtins.int
POSITION_SWITCH_FIELD_NUMBER: builtins.int
TELEMETRY_SWITCH_FIELD_NUMBER: builtins.int
IAQ_SWITCH_FIELD_NUMBER: builtins.int
NODE_NAME_FIELD_NUMBER: builtins.int
chat_switch: builtins.bool
"""
Hightlight nodes w/ active chat
"""
position_switch: builtins.bool
"""
Highlight nodes w/ position
"""
telemetry_switch: builtins.bool
"""
Highlight nodes w/ telemetry data
"""
iaq_switch: builtins.bool
"""
Highlight nodes w/ iaq data
"""
node_name: builtins.str
"""
Highlight nodes by matching name string
"""
def __init__(
self,
*,
chat_switch: builtins.bool = ...,
position_switch: builtins.bool = ...,
telemetry_switch: builtins.bool = ...,
iaq_switch: builtins.bool = ...,
node_name: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["chat_switch", b"chat_switch", "iaq_switch", b"iaq_switch", "node_name", b"node_name", "position_switch", b"position_switch", "telemetry_switch", b"telemetry_switch"]) -> None: ...
global___NodeHighlight = NodeHighlight

View File

@@ -12,14 +12,13 @@ _sym_db = _symbol_database.Default()
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__pb2 from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__pb2
from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__pb2
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2 from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2 from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2 from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__pb2
import nanopb_pb2 as nanopb__pb2 import nanopb_pb2 as nanopb__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/config.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xe2\x01\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\"\xb8\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x42\x0c\n\n_hops_away\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xb2\x02\n\x08OEMStore\x12\x16\n\x0eoem_icon_width\x18\x01 \x01(\r\x12\x17\n\x0foem_icon_height\x18\x02 \x01(\r\x12\x15\n\roem_icon_bits\x18\x03 \x01(\x0c\x12\x32\n\x08oem_font\x18\x04 \x01(\x0e\x32 .meshtastic.protobuf.ScreenFonts\x12\x10\n\x08oem_text\x18\x05 \x01(\t\x12\x13\n\x0boem_aes_key\x18\x06 \x01(\x0c\x12:\n\x10oem_local_config\x18\x07 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12G\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42m\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/config.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xe2\x01\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\"\xde\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x10\n\x08next_hop\x18\x0c \x01(\rB\x0c\n\n_hops_away\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\rBm\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -33,18 +32,14 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_DEVICESTATE.fields_by_name['no_save']._serialized_options = b'\030\001' _DEVICESTATE.fields_by_name['no_save']._serialized_options = b'\030\001'
_DEVICESTATE.fields_by_name['node_db_lite']._options = None _DEVICESTATE.fields_by_name['node_db_lite']._options = None
_DEVICESTATE.fields_by_name['node_db_lite']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>' _DEVICESTATE.fields_by_name['node_db_lite']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>'
_globals['_SCREENFONTS']._serialized_start=1856 _globals['_POSITIONLITE']._serialized_start=214
_globals['_SCREENFONTS']._serialized_end=1918 _globals['_POSITIONLITE']._serialized_end=367
_globals['_POSITIONLITE']._serialized_start=251 _globals['_USERLITE']._serialized_start=370
_globals['_POSITIONLITE']._serialized_end=404 _globals['_USERLITE']._serialized_end=596
_globals['_USERLITE']._serialized_start=407 _globals['_NODEINFOLITE']._serialized_start=599
_globals['_USERLITE']._serialized_end=633 _globals['_NODEINFOLITE']._serialized_end=949
_globals['_NODEINFOLITE']._serialized_start=636 _globals['_DEVICESTATE']._serialized_start=952
_globals['_NODEINFOLITE']._serialized_end=948 _globals['_DEVICESTATE']._serialized_end=1466
_globals['_DEVICESTATE']._serialized_start=951 _globals['_CHANNELFILE']._serialized_start=1468
_globals['_DEVICESTATE']._serialized_end=1465 _globals['_CHANNELFILE']._serialized_end=1546
_globals['_CHANNELFILE']._serialized_start=1467
_globals['_CHANNELFILE']._serialized_end=1545
_globals['_OEMSTORE']._serialized_start=1548
_globals['_OEMSTORE']._serialized_end=1854
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -7,61 +7,15 @@ import builtins
import collections.abc import collections.abc
import google.protobuf.descriptor import google.protobuf.descriptor
import google.protobuf.internal.containers import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message import google.protobuf.message
import meshtastic.protobuf.channel_pb2 import meshtastic.protobuf.channel_pb2
import meshtastic.protobuf.config_pb2 import meshtastic.protobuf.config_pb2
import meshtastic.protobuf.localonly_pb2
import meshtastic.protobuf.mesh_pb2 import meshtastic.protobuf.mesh_pb2
import meshtastic.protobuf.telemetry_pb2 import meshtastic.protobuf.telemetry_pb2
import sys
import typing import typing
if sys.version_info >= (3, 10):
import typing as typing_extensions
else:
import typing_extensions
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
class _ScreenFonts:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _ScreenFontsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ScreenFonts.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
FONT_SMALL: _ScreenFonts.ValueType # 0
"""
TODO: REPLACE
"""
FONT_MEDIUM: _ScreenFonts.ValueType # 1
"""
TODO: REPLACE
"""
FONT_LARGE: _ScreenFonts.ValueType # 2
"""
TODO: REPLACE
"""
class ScreenFonts(_ScreenFonts, metaclass=_ScreenFontsEnumTypeWrapper):
"""
Font sizes for the device screen
"""
FONT_SMALL: ScreenFonts.ValueType # 0
"""
TODO: REPLACE
"""
FONT_MEDIUM: ScreenFonts.ValueType # 1
"""
TODO: REPLACE
"""
FONT_LARGE: ScreenFonts.ValueType # 2
"""
TODO: REPLACE
"""
global___ScreenFonts = ScreenFonts
@typing.final @typing.final
class PositionLite(google.protobuf.message.Message): class PositionLite(google.protobuf.message.Message):
""" """
@@ -188,6 +142,8 @@ class NodeInfoLite(google.protobuf.message.Message):
VIA_MQTT_FIELD_NUMBER: builtins.int VIA_MQTT_FIELD_NUMBER: builtins.int
HOPS_AWAY_FIELD_NUMBER: builtins.int HOPS_AWAY_FIELD_NUMBER: builtins.int
IS_FAVORITE_FIELD_NUMBER: builtins.int IS_FAVORITE_FIELD_NUMBER: builtins.int
IS_IGNORED_FIELD_NUMBER: builtins.int
NEXT_HOP_FIELD_NUMBER: builtins.int
num: builtins.int num: builtins.int
""" """
The node number The node number
@@ -211,13 +167,22 @@ class NodeInfoLite(google.protobuf.message.Message):
""" """
hops_away: builtins.int hops_away: builtins.int
""" """
Number of hops away from us this node is (0 if adjacent) Number of hops away from us this node is (0 if direct neighbor)
""" """
is_favorite: builtins.bool is_favorite: builtins.bool
""" """
True if node is in our favorites list True if node is in our favorites list
Persists between NodeDB internal clean ups Persists between NodeDB internal clean ups
""" """
is_ignored: builtins.bool
"""
True if node is in our ignored list
Persists between NodeDB internal clean ups
"""
next_hop: builtins.int
"""
Last byte of the node number of the node that should be used as the next hop to reach this node.
"""
@property @property
def user(self) -> global___UserLite: def user(self) -> global___UserLite:
""" """
@@ -250,9 +215,11 @@ class NodeInfoLite(google.protobuf.message.Message):
via_mqtt: builtins.bool = ..., via_mqtt: builtins.bool = ...,
hops_away: builtins.int | None = ..., hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ..., is_favorite: builtins.bool = ...,
is_ignored: builtins.bool = ...,
next_hop: builtins.int = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ... def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "last_heard", b"last_heard", "next_hop", b"next_hop", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
global___NodeInfoLite = NodeInfoLite global___NodeInfoLite = NodeInfoLite
@@ -391,73 +358,3 @@ class ChannelFile(google.protobuf.message.Message):
def ClearField(self, field_name: typing.Literal["channels", b"channels", "version", b"version"]) -> None: ... def ClearField(self, field_name: typing.Literal["channels", b"channels", "version", b"version"]) -> None: ...
global___ChannelFile = ChannelFile global___ChannelFile = ChannelFile
@typing.final
class OEMStore(google.protobuf.message.Message):
"""
This can be used for customizing the firmware distribution. If populated,
show a secondary bootup screen with custom logo and text for 2.5 seconds.
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
OEM_ICON_WIDTH_FIELD_NUMBER: builtins.int
OEM_ICON_HEIGHT_FIELD_NUMBER: builtins.int
OEM_ICON_BITS_FIELD_NUMBER: builtins.int
OEM_FONT_FIELD_NUMBER: builtins.int
OEM_TEXT_FIELD_NUMBER: builtins.int
OEM_AES_KEY_FIELD_NUMBER: builtins.int
OEM_LOCAL_CONFIG_FIELD_NUMBER: builtins.int
OEM_LOCAL_MODULE_CONFIG_FIELD_NUMBER: builtins.int
oem_icon_width: builtins.int
"""
The Logo width in Px
"""
oem_icon_height: builtins.int
"""
The Logo height in Px
"""
oem_icon_bits: builtins.bytes
"""
The Logo in XBM bytechar format
"""
oem_font: global___ScreenFonts.ValueType
"""
Use this font for the OEM text.
"""
oem_text: builtins.str
"""
Use this font for the OEM text.
"""
oem_aes_key: builtins.bytes
"""
The default device encryption key, 16 or 32 byte
"""
@property
def oem_local_config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
"""
A Preset LocalConfig to apply during factory reset
"""
@property
def oem_local_module_config(self) -> meshtastic.protobuf.localonly_pb2.LocalModuleConfig:
"""
A Preset LocalModuleConfig to apply during factory reset
"""
def __init__(
self,
*,
oem_icon_width: builtins.int = ...,
oem_icon_height: builtins.int = ...,
oem_icon_bits: builtins.bytes = ...,
oem_font: global___ScreenFonts.ValueType = ...,
oem_text: builtins.str = ...,
oem_aes_key: builtins.bytes = ...,
oem_local_config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
oem_local_module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["oem_local_config", b"oem_local_config", "oem_local_module_config", b"oem_local_module_config"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["oem_aes_key", b"oem_aes_key", "oem_font", b"oem_font", "oem_icon_bits", b"oem_icon_bits", "oem_icon_height", b"oem_icon_height", "oem_icon_width", b"oem_icon_width", "oem_local_config", b"oem_local_config", "oem_local_module_config", b"oem_local_module_config", "oem_text", b"oem_text"]) -> None: ...
global___OEMStore = OEMStore

View File

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@ import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message import google.protobuf.message
import meshtastic.protobuf.channel_pb2 import meshtastic.protobuf.channel_pb2
import meshtastic.protobuf.config_pb2 import meshtastic.protobuf.config_pb2
import meshtastic.protobuf.device_ui_pb2
import meshtastic.protobuf.module_config_pb2 import meshtastic.protobuf.module_config_pb2
import meshtastic.protobuf.portnums_pb2 import meshtastic.protobuf.portnums_pb2
import meshtastic.protobuf.telemetry_pb2 import meshtastic.protobuf.telemetry_pb2
@@ -379,6 +380,23 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
Lilygo TLora-C6 with the new ESP32-C6 MCU Lilygo TLora-C6 with the new ESP32-C6 MCU
""" """
WISMESH_TAP: _HardwareModel.ValueType # 84
"""
WisMesh Tap
RAK-4631 w/ TFT in injection modled case
"""
ROUTASTIC: _HardwareModel.ValueType # 85
"""
Similar to PORTDUINO but used by Routastic devices, this is not any
particular device and does not run Meshtastic's code but supports
the same frame format.
Runs on linux, see https://github.com/Jorropo/routastic
"""
MESH_TAB: _HardwareModel.ValueType # 86
"""
Mesh-Tab, esp32 based
https://github.com/valzzu/Mesh-Tab
"""
PRIVATE_HW: _HardwareModel.ValueType # 255 PRIVATE_HW: _HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -742,6 +760,23 @@ TLORA_C6: HardwareModel.ValueType # 83
""" """
Lilygo TLora-C6 with the new ESP32-C6 MCU Lilygo TLora-C6 with the new ESP32-C6 MCU
""" """
WISMESH_TAP: HardwareModel.ValueType # 84
"""
WisMesh Tap
RAK-4631 w/ TFT in injection modled case
"""
ROUTASTIC: HardwareModel.ValueType # 85
"""
Similar to PORTDUINO but used by Routastic devices, this is not any
particular device and does not run Meshtastic's code but supports
the same frame format.
Runs on linux, see https://github.com/Jorropo/routastic
"""
MESH_TAB: HardwareModel.ValueType # 86
"""
Mesh-Tab, esp32 based
https://github.com/valzzu/Mesh-Tab
"""
PRIVATE_HW: HardwareModel.ValueType # 255 PRIVATE_HW: HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -761,7 +796,7 @@ class _ConstantsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._Enum
First enum must be zero, and we are just using this enum to First enum must be zero, and we are just using this enum to
pass int constants between two very different environments pass int constants between two very different environments
""" """
DATA_PAYLOAD_LEN: _Constants.ValueType # 237 DATA_PAYLOAD_LEN: _Constants.ValueType # 233
""" """
From mesh.options From mesh.options
note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is
@@ -778,7 +813,7 @@ ZERO: Constants.ValueType # 0
First enum must be zero, and we are just using this enum to First enum must be zero, and we are just using this enum to
pass int constants between two very different environments pass int constants between two very different environments
""" """
DATA_PAYLOAD_LEN: Constants.ValueType # 237 DATA_PAYLOAD_LEN: Constants.ValueType # 233
""" """
From mesh.options From mesh.options
note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is
@@ -923,10 +958,138 @@ If you see this failure in the field please post in the forum because we are int
""" """
global___CriticalErrorCode = CriticalErrorCode global___CriticalErrorCode = CriticalErrorCode
class _ExcludedModules:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _ExcludedModulesEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ExcludedModules.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
EXCLUDED_NONE: _ExcludedModules.ValueType # 0
"""
Default value of 0 indicates no modules are excluded.
"""
MQTT_CONFIG: _ExcludedModules.ValueType # 1
"""
MQTT module
"""
SERIAL_CONFIG: _ExcludedModules.ValueType # 2
"""
Serial module
"""
EXTNOTIF_CONFIG: _ExcludedModules.ValueType # 4
"""
External Notification module
"""
STOREFORWARD_CONFIG: _ExcludedModules.ValueType # 8
"""
Store and Forward module
"""
RANGETEST_CONFIG: _ExcludedModules.ValueType # 16
"""
Range Test module
"""
TELEMETRY_CONFIG: _ExcludedModules.ValueType # 32
"""
Telemetry module
"""
CANNEDMSG_CONFIG: _ExcludedModules.ValueType # 64
"""
Canned Message module
"""
AUDIO_CONFIG: _ExcludedModules.ValueType # 128
"""
Audio module
"""
REMOTEHARDWARE_CONFIG: _ExcludedModules.ValueType # 256
"""
Remote Hardware module
"""
NEIGHBORINFO_CONFIG: _ExcludedModules.ValueType # 512
"""
Neighbor Info module
"""
AMBIENTLIGHTING_CONFIG: _ExcludedModules.ValueType # 1024
"""
Ambient Lighting module
"""
DETECTIONSENSOR_CONFIG: _ExcludedModules.ValueType # 2048
"""
Detection Sensor module
"""
PAXCOUNTER_CONFIG: _ExcludedModules.ValueType # 4096
"""
Paxcounter module
"""
class ExcludedModules(_ExcludedModules, metaclass=_ExcludedModulesEnumTypeWrapper):
"""
Enum for modules excluded from a device's configuration.
Each value represents a ModuleConfigType that can be toggled as excluded
by setting its corresponding bit in the `excluded_modules` bitmask field.
"""
EXCLUDED_NONE: ExcludedModules.ValueType # 0
"""
Default value of 0 indicates no modules are excluded.
"""
MQTT_CONFIG: ExcludedModules.ValueType # 1
"""
MQTT module
"""
SERIAL_CONFIG: ExcludedModules.ValueType # 2
"""
Serial module
"""
EXTNOTIF_CONFIG: ExcludedModules.ValueType # 4
"""
External Notification module
"""
STOREFORWARD_CONFIG: ExcludedModules.ValueType # 8
"""
Store and Forward module
"""
RANGETEST_CONFIG: ExcludedModules.ValueType # 16
"""
Range Test module
"""
TELEMETRY_CONFIG: ExcludedModules.ValueType # 32
"""
Telemetry module
"""
CANNEDMSG_CONFIG: ExcludedModules.ValueType # 64
"""
Canned Message module
"""
AUDIO_CONFIG: ExcludedModules.ValueType # 128
"""
Audio module
"""
REMOTEHARDWARE_CONFIG: ExcludedModules.ValueType # 256
"""
Remote Hardware module
"""
NEIGHBORINFO_CONFIG: ExcludedModules.ValueType # 512
"""
Neighbor Info module
"""
AMBIENTLIGHTING_CONFIG: ExcludedModules.ValueType # 1024
"""
Ambient Lighting module
"""
DETECTIONSENSOR_CONFIG: ExcludedModules.ValueType # 2048
"""
Detection Sensor module
"""
PAXCOUNTER_CONFIG: ExcludedModules.ValueType # 4096
"""
Paxcounter module
"""
global___ExcludedModules = ExcludedModules
@typing.final @typing.final
class Position(google.protobuf.message.Message): class Position(google.protobuf.message.Message):
""" """
a gps position A GPS Position
""" """
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
@@ -1783,6 +1946,10 @@ class MeshPacket(google.protobuf.message.Message):
""" """
Higher priority for specific message types (portnums) to distinguish between other reliable packets. Higher priority for specific message types (portnums) to distinguish between other reliable packets.
""" """
ALERT: MeshPacket._Priority.ValueType # 110
"""
Higher priority alert message used for critical alerts which take priority over other reliable packets.
"""
ACK: MeshPacket._Priority.ValueType # 120 ACK: MeshPacket._Priority.ValueType # 120
""" """
Ack/naks are sent with very high priority to ensure that retransmission Ack/naks are sent with very high priority to ensure that retransmission
@@ -1846,6 +2013,10 @@ class MeshPacket(google.protobuf.message.Message):
""" """
Higher priority for specific message types (portnums) to distinguish between other reliable packets. Higher priority for specific message types (portnums) to distinguish between other reliable packets.
""" """
ALERT: MeshPacket.Priority.ValueType # 110
"""
Higher priority alert message used for critical alerts which take priority over other reliable packets.
"""
ACK: MeshPacket.Priority.ValueType # 120 ACK: MeshPacket.Priority.ValueType # 120
""" """
Ack/naks are sent with very high priority to ensure that retransmission Ack/naks are sent with very high priority to ensure that retransmission
@@ -1910,6 +2081,8 @@ class MeshPacket(google.protobuf.message.Message):
HOP_START_FIELD_NUMBER: builtins.int HOP_START_FIELD_NUMBER: builtins.int
PUBLIC_KEY_FIELD_NUMBER: builtins.int PUBLIC_KEY_FIELD_NUMBER: builtins.int
PKI_ENCRYPTED_FIELD_NUMBER: builtins.int PKI_ENCRYPTED_FIELD_NUMBER: builtins.int
NEXT_HOP_FIELD_NUMBER: builtins.int
RELAY_NODE_FIELD_NUMBER: builtins.int
to: builtins.int to: builtins.int
""" """
The (immediate) destination for this packet The (immediate) destination for this packet
@@ -1954,7 +2127,7 @@ class MeshPacket(google.protobuf.message.Message):
""" """
hop_limit: builtins.int hop_limit: builtins.int
""" """
If unset treated as zero (no forwarding, send to adjacent nodes only) If unset treated as zero (no forwarding, send to direct neighbor nodes only)
if 1, allow hopping through one node, etc... if 1, allow hopping through one node, etc...
For our usecase real world topologies probably have a max of about 3. For our usecase real world topologies probably have a max of about 3.
This field is normally placed into a few of bits in the header. This field is normally placed into a few of bits in the header.
@@ -2001,6 +2174,16 @@ class MeshPacket(google.protobuf.message.Message):
""" """
Indicates whether the packet was en/decrypted using PKI Indicates whether the packet was en/decrypted using PKI
""" """
next_hop: builtins.int
"""
Last byte of the node number of the node that should be used as the next hop in routing.
Set by the firmware internally, clients are not supposed to set this.
"""
relay_node: builtins.int
"""
Last byte of the node number of the node that will relay/relayed this packet.
Set by the firmware internally, clients are not supposed to set this.
"""
@property @property
def decoded(self) -> global___Data: def decoded(self) -> global___Data:
""" """
@@ -2026,9 +2209,11 @@ class MeshPacket(google.protobuf.message.Message):
hop_start: builtins.int = ..., hop_start: builtins.int = ...,
public_key: builtins.bytes = ..., public_key: builtins.bytes = ...,
pki_encrypted: builtins.bool = ..., pki_encrypted: builtins.bool = ...,
next_hop: builtins.int = ...,
relay_node: builtins.int = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ... def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "next_hop", b"next_hop", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "relay_node", b"relay_node", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["decoded", "encrypted"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["decoded", "encrypted"] | None: ...
global___MeshPacket = MeshPacket global___MeshPacket = MeshPacket
@@ -2066,6 +2251,7 @@ class NodeInfo(google.protobuf.message.Message):
VIA_MQTT_FIELD_NUMBER: builtins.int VIA_MQTT_FIELD_NUMBER: builtins.int
HOPS_AWAY_FIELD_NUMBER: builtins.int HOPS_AWAY_FIELD_NUMBER: builtins.int
IS_FAVORITE_FIELD_NUMBER: builtins.int IS_FAVORITE_FIELD_NUMBER: builtins.int
IS_IGNORED_FIELD_NUMBER: builtins.int
num: builtins.int num: builtins.int
""" """
The node number The node number
@@ -2079,7 +2265,7 @@ class NodeInfo(google.protobuf.message.Message):
""" """
TODO: REMOVE/INTEGRATE TODO: REMOVE/INTEGRATE
Not currently used (till full DSR deployment?) Our current preferred node node for routing - might be the same as num if Not currently used (till full DSR deployment?) Our current preferred node node for routing - might be the same as num if
we are adjacent Or zero if we don't yet know a route to this node. we are direct neighbor or zero if we don't yet know a route to this node.
fixed32 next_hop = 5; fixed32 next_hop = 5;
@@ -2095,13 +2281,18 @@ class NodeInfo(google.protobuf.message.Message):
""" """
hops_away: builtins.int hops_away: builtins.int
""" """
Number of hops away from us this node is (0 if adjacent) Number of hops away from us this node is (0 if direct neighbor)
""" """
is_favorite: builtins.bool is_favorite: builtins.bool
""" """
True if node is in our favorites list True if node is in our favorites list
Persists between NodeDB internal clean ups Persists between NodeDB internal clean ups
""" """
is_ignored: builtins.bool
"""
True if node is in our ignored list
Persists between NodeDB internal clean ups
"""
@property @property
def user(self) -> global___User: def user(self) -> global___User:
""" """
@@ -2134,9 +2325,10 @@ class NodeInfo(google.protobuf.message.Message):
via_mqtt: builtins.bool = ..., via_mqtt: builtins.bool = ...,
hops_away: builtins.int | None = ..., hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ..., is_favorite: builtins.bool = ...,
is_ignored: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ... def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
global___NodeInfo = NodeInfo global___NodeInfo = NodeInfo
@@ -2154,6 +2346,8 @@ class MyNodeInfo(google.protobuf.message.Message):
MY_NODE_NUM_FIELD_NUMBER: builtins.int MY_NODE_NUM_FIELD_NUMBER: builtins.int
REBOOT_COUNT_FIELD_NUMBER: builtins.int REBOOT_COUNT_FIELD_NUMBER: builtins.int
MIN_APP_VERSION_FIELD_NUMBER: builtins.int MIN_APP_VERSION_FIELD_NUMBER: builtins.int
DEVICE_ID_FIELD_NUMBER: builtins.int
PIO_ENV_FIELD_NUMBER: builtins.int
my_node_num: builtins.int my_node_num: builtins.int
""" """
Tells the phone what our node number is, default starting value is Tells the phone what our node number is, default starting value is
@@ -2169,14 +2363,24 @@ class MyNodeInfo(google.protobuf.message.Message):
The minimum app version that can talk to this device. The minimum app version that can talk to this device.
Phone/PC apps should compare this to their build number and if too low tell the user they must update their app Phone/PC apps should compare this to their build number and if too low tell the user they must update their app
""" """
device_id: builtins.bytes
"""
Unique hardware identifier for this device
"""
pio_env: builtins.str
"""
The PlatformIO environment used to build this firmware
"""
def __init__( def __init__(
self, self,
*, *,
my_node_num: builtins.int = ..., my_node_num: builtins.int = ...,
reboot_count: builtins.int = ..., reboot_count: builtins.int = ...,
min_app_version: builtins.int = ..., min_app_version: builtins.int = ...,
device_id: builtins.bytes = ...,
pio_env: builtins.str = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["min_app_version", b"min_app_version", "my_node_num", b"my_node_num", "reboot_count", b"reboot_count"]) -> None: ... def ClearField(self, field_name: typing.Literal["device_id", b"device_id", "min_app_version", b"min_app_version", "my_node_num", b"my_node_num", "pio_env", b"pio_env", "reboot_count", b"reboot_count"]) -> None: ...
global___MyNodeInfo = MyNodeInfo global___MyNodeInfo = MyNodeInfo
@@ -2348,6 +2552,7 @@ class FromRadio(google.protobuf.message.Message):
MQTTCLIENTPROXYMESSAGE_FIELD_NUMBER: builtins.int MQTTCLIENTPROXYMESSAGE_FIELD_NUMBER: builtins.int
FILEINFO_FIELD_NUMBER: builtins.int FILEINFO_FIELD_NUMBER: builtins.int
CLIENTNOTIFICATION_FIELD_NUMBER: builtins.int CLIENTNOTIFICATION_FIELD_NUMBER: builtins.int
DEVICEUICONFIG_FIELD_NUMBER: builtins.int
id: builtins.int id: builtins.int
""" """
The packet id, used to allow the phone to request missing read packets from the FIFO, The packet id, used to allow the phone to request missing read packets from the FIFO,
@@ -2447,6 +2652,12 @@ class FromRadio(google.protobuf.message.Message):
Notification message to the client Notification message to the client
""" """
@property
def deviceuiConfig(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
"""
Persistent data for device-ui
"""
def __init__( def __init__(
self, self,
*, *,
@@ -2466,10 +2677,11 @@ class FromRadio(google.protobuf.message.Message):
mqttClientProxyMessage: global___MqttClientProxyMessage | None = ..., mqttClientProxyMessage: global___MqttClientProxyMessage | None = ...,
fileInfo: global___FileInfo | None = ..., fileInfo: global___FileInfo | None = ...,
clientNotification: global___ClientNotification | None = ..., clientNotification: global___ClientNotification | None = ...,
deviceuiConfig: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "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 HasField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "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", "clientNotification", b"clientNotification", "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 ClearField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "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", "clientNotification"] | 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", "clientNotification", "deviceuiConfig"] | None: ...
global___FromRadio = FromRadio global___FromRadio = FromRadio
@@ -2749,6 +2961,7 @@ class DeviceMetadata(google.protobuf.message.Message):
HW_MODEL_FIELD_NUMBER: builtins.int HW_MODEL_FIELD_NUMBER: builtins.int
HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int
HASPKC_FIELD_NUMBER: builtins.int HASPKC_FIELD_NUMBER: builtins.int
EXCLUDED_MODULES_FIELD_NUMBER: builtins.int
firmware_version: builtins.str firmware_version: builtins.str
""" """
Device firmware version string Device firmware version string
@@ -2793,6 +3006,11 @@ class DeviceMetadata(google.protobuf.message.Message):
""" """
Has PKC capabilities Has PKC capabilities
""" """
excluded_modules: builtins.int
"""
Bit field of boolean for excluded modules
(bitwise OR of ExcludedModules)
"""
def __init__( def __init__(
self, self,
*, *,
@@ -2807,8 +3025,9 @@ class DeviceMetadata(google.protobuf.message.Message):
hw_model: global___HardwareModel.ValueType = ..., hw_model: global___HardwareModel.ValueType = ...,
hasRemoteHardware: builtins.bool = ..., hasRemoteHardware: builtins.bool = ...,
hasPKC: builtins.bool = ..., hasPKC: builtins.bool = ...,
excluded_modules: builtins.int = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasPKC", b"hasPKC", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ... def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "excluded_modules", b"excluded_modules", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasPKC", b"hasPKC", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ...
global___DeviceMetadata = DeviceMetadata global___DeviceMetadata = DeviceMetadata

View File

File diff suppressed because one or more lines are too long

View File

@@ -225,6 +225,7 @@ class ModuleConfig(google.protobuf.message.Message):
ENABLED_FIELD_NUMBER: builtins.int ENABLED_FIELD_NUMBER: builtins.int
UPDATE_INTERVAL_FIELD_NUMBER: builtins.int UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
TRANSMIT_OVER_LORA_FIELD_NUMBER: builtins.int
enabled: builtins.bool enabled: builtins.bool
""" """
Whether the Module is enabled Whether the Module is enabled
@@ -232,15 +233,21 @@ class ModuleConfig(google.protobuf.message.Message):
update_interval: builtins.int update_interval: builtins.int
""" """
Interval in seconds of how often we should try to send our Interval in seconds of how often we should try to send our
Neighbor Info to the mesh Neighbor Info (minimum is 14400, i.e., 4 hours)
"""
transmit_over_lora: builtins.bool
"""
Whether in addition to sending it to MQTT and the PhoneAPI, our NeighborInfo should be transmitted over LoRa.
Note that this is not available on a channel with default key and name.
""" """
def __init__( def __init__(
self, self,
*, *,
enabled: builtins.bool = ..., enabled: builtins.bool = ...,
update_interval: builtins.int = ..., update_interval: builtins.int = ...,
transmit_over_lora: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "update_interval", b"update_interval"]) -> None: ... def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "transmit_over_lora", b"transmit_over_lora", "update_interval", b"update_interval"]) -> None: ...
@typing.final @typing.final
class DetectionSensorConfig(google.protobuf.message.Message): class DetectionSensorConfig(google.protobuf.message.Message):
@@ -835,6 +842,9 @@ class ModuleConfig(google.protobuf.message.Message):
POWER_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int POWER_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
POWER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int POWER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
POWER_SCREEN_ENABLED_FIELD_NUMBER: builtins.int POWER_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
device_update_interval: builtins.int device_update_interval: builtins.int
""" """
Interval in seconds of how often we should try to send our Interval in seconds of how often we should try to send our
@@ -870,18 +880,30 @@ class ModuleConfig(google.protobuf.message.Message):
""" """
power_measurement_enabled: builtins.bool power_measurement_enabled: builtins.bool
""" """
Interval in seconds of how often we should try to send our Enable/disable Power metrics
air quality metrics to the mesh
""" """
power_update_interval: builtins.int power_update_interval: builtins.int
""" """
Interval in seconds of how often we should try to send our Interval in seconds of how often we should try to send our
air quality metrics to the mesh power metrics to the mesh
""" """
power_screen_enabled: builtins.bool power_screen_enabled: builtins.bool
""" """
Enable/Disable the power measurement module on-device display
"""
health_measurement_enabled: builtins.bool
"""
Preferences for the (Health) Telemetry Module
Enable/Disable the telemetry measurement module measurement collection
"""
health_update_interval: builtins.int
"""
Interval in seconds of how often we should try to send our Interval in seconds of how often we should try to send our
air quality metrics to the mesh health metrics to the mesh
"""
health_screen_enabled: builtins.bool
"""
Enable/Disable the health telemetry module on-device display
""" """
def __init__( def __init__(
self, self,
@@ -896,8 +918,11 @@ class ModuleConfig(google.protobuf.message.Message):
power_measurement_enabled: builtins.bool = ..., power_measurement_enabled: builtins.bool = ...,
power_update_interval: builtins.int = ..., power_update_interval: builtins.int = ...,
power_screen_enabled: builtins.bool = ..., power_screen_enabled: builtins.bool = ...,
health_measurement_enabled: builtins.bool = ...,
health_update_interval: builtins.int = ...,
health_screen_enabled: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ... def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
@typing.final @typing.final
class CannedMessageConfig(google.protobuf.message.Message): class CannedMessageConfig(google.protobuf.message.Message):

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
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') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xb1\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\tALERT_APP\x10\x0b\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() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -22,5 +22,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' 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_start=60
_globals['_PORTNUM']._serialized_end=606 _globals['_PORTNUM']._serialized_end=621
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -93,6 +93,10 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
Same as Text Message but originating from Detection Sensor Module. Same as Text Message but originating from Detection Sensor Module.
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9 NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
""" """
ALERT_APP: _PortNum.ValueType # 11
"""
Same as Text Message but used for critical alerts.
"""
REPLY_APP: _PortNum.ValueType # 32 REPLY_APP: _PortNum.ValueType # 32
""" """
Provides a 'ping' service that replies to any packet it receives. Provides a 'ping' service that replies to any packet it receives.
@@ -278,6 +282,10 @@ DETECTION_SENSOR_APP: PortNum.ValueType # 10
Same as Text Message but originating from Detection Sensor Module. Same as Text Message but originating from Detection Sensor Module.
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9 NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
""" """
ALERT_APP: PortNum.ValueType # 11
"""
Same as Text Message but used for critical alerts.
"""
REPLY_APP: PortNum.ValueType # 32 REPLY_APP: PortNum.ValueType # 32
""" """
Provides a 'ping' service that replies to any packet it receives. Provides a 'ping' service that replies to any packet it receives.

View File

File diff suppressed because one or more lines are too long

View File

@@ -143,6 +143,26 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
""" """
Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
""" """
MAX30102: _TelemetrySensorType.ValueType # 30
"""
MAX30102 Pulse Oximeter and Heart-Rate Sensor
"""
MLX90614: _TelemetrySensorType.ValueType # 31
"""
MLX90614 non-contact IR temperature sensor
"""
SCD4X: _TelemetrySensorType.ValueType # 32
"""
SCD40/SCD41 CO2, humidity, temperature sensor
"""
RADSENS: _TelemetrySensorType.ValueType # 33
"""
ClimateGuard RadSens, radiation, Geiger-Muller Tube
"""
INA226: _TelemetrySensorType.ValueType # 34
"""
High accuracy current and voltage
"""
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper): class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
""" """
@@ -269,6 +289,26 @@ CUSTOM_SENSOR: TelemetrySensorType.ValueType # 29
""" """
Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
""" """
MAX30102: TelemetrySensorType.ValueType # 30
"""
MAX30102 Pulse Oximeter and Heart-Rate Sensor
"""
MLX90614: TelemetrySensorType.ValueType # 31
"""
MLX90614 non-contact IR temperature sensor
"""
SCD4X: TelemetrySensorType.ValueType # 32
"""
SCD40/SCD41 CO2, humidity, temperature sensor
"""
RADSENS: TelemetrySensorType.ValueType # 33
"""
ClimateGuard RadSens, radiation, Geiger-Muller Tube
"""
INA226: TelemetrySensorType.ValueType # 34
"""
High accuracy current and voltage
"""
global___TelemetrySensorType = TelemetrySensorType global___TelemetrySensorType = TelemetrySensorType
@typing.final @typing.final
@@ -353,6 +393,7 @@ class EnvironmentMetrics(google.protobuf.message.Message):
WEIGHT_FIELD_NUMBER: builtins.int WEIGHT_FIELD_NUMBER: builtins.int
WIND_GUST_FIELD_NUMBER: builtins.int WIND_GUST_FIELD_NUMBER: builtins.int
WIND_LULL_FIELD_NUMBER: builtins.int WIND_LULL_FIELD_NUMBER: builtins.int
RADIATION_FIELD_NUMBER: builtins.int
temperature: builtins.float temperature: builtins.float
""" """
Temperature measured Temperature measured
@@ -423,6 +464,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
""" """
Wind lull in m/s Wind lull in m/s
""" """
radiation: builtins.float
"""
Radiation in µR/h
"""
def __init__( def __init__(
self, self,
*, *,
@@ -443,9 +488,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
weight: builtins.float | None = ..., weight: builtins.float | None = ...,
wind_gust: builtins.float | None = ..., wind_gust: builtins.float | None = ...,
wind_lull: builtins.float | None = ..., wind_lull: builtins.float | None = ...,
radiation: builtins.float | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ... def HasField(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", "_radiation", b"_radiation", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "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", "radiation", b"radiation", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ... 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", "_radiation", b"_radiation", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "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", "radiation", b"radiation", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_barometric_pressure", b"_barometric_pressure"]) -> typing.Literal["barometric_pressure"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_barometric_pressure", b"_barometric_pressure"]) -> typing.Literal["barometric_pressure"] | None: ...
@typing.overload @typing.overload
@@ -461,6 +507,8 @@ class EnvironmentMetrics(google.protobuf.message.Message):
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_lux", b"_lux"]) -> typing.Literal["lux"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_lux", b"_lux"]) -> typing.Literal["lux"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_radiation", b"_radiation"]) -> typing.Literal["radiation"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_relative_humidity", b"_relative_humidity"]) -> typing.Literal["relative_humidity"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_relative_humidity", b"_relative_humidity"]) -> typing.Literal["relative_humidity"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_temperature", b"_temperature"]) -> typing.Literal["temperature"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_temperature", b"_temperature"]) -> typing.Literal["temperature"] | None: ...
@@ -568,6 +616,7 @@ class AirQualityMetrics(google.protobuf.message.Message):
PARTICLES_25UM_FIELD_NUMBER: builtins.int PARTICLES_25UM_FIELD_NUMBER: builtins.int
PARTICLES_50UM_FIELD_NUMBER: builtins.int PARTICLES_50UM_FIELD_NUMBER: builtins.int
PARTICLES_100UM_FIELD_NUMBER: builtins.int PARTICLES_100UM_FIELD_NUMBER: builtins.int
CO2_FIELD_NUMBER: builtins.int
pm10_standard: builtins.int pm10_standard: builtins.int
""" """
Concentration Units Standard PM1.0 Concentration Units Standard PM1.0
@@ -616,6 +665,10 @@ class AirQualityMetrics(google.protobuf.message.Message):
""" """
10.0um Particle Count 10.0um Particle Count
""" """
co2: builtins.int
"""
10.0um Particle Count
"""
def __init__( def __init__(
self, self,
*, *,
@@ -631,9 +684,12 @@ class AirQualityMetrics(google.protobuf.message.Message):
particles_25um: builtins.int | None = ..., particles_25um: builtins.int | None = ...,
particles_50um: builtins.int | None = ..., particles_50um: builtins.int | None = ...,
particles_100um: builtins.int | None = ..., particles_100um: builtins.int | None = ...,
co2: builtins.int | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_50um", b"_particles_50um", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_co2", b"_co2", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_50um", b"_particles_50um", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "co2", b"co2", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_50um", b"_particles_50um", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> None: ... def ClearField(self, field_name: typing.Literal["_co2", b"_co2", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_50um", b"_particles_50um", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "co2", b"co2", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_co2", b"_co2"]) -> typing.Literal["co2"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_03um", b"_particles_03um"]) -> typing.Literal["particles_03um"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_particles_03um", b"_particles_03um"]) -> typing.Literal["particles_03um"] | None: ...
@typing.overload @typing.overload
@@ -677,6 +733,9 @@ class LocalStats(google.protobuf.message.Message):
NUM_PACKETS_RX_BAD_FIELD_NUMBER: builtins.int NUM_PACKETS_RX_BAD_FIELD_NUMBER: builtins.int
NUM_ONLINE_NODES_FIELD_NUMBER: builtins.int NUM_ONLINE_NODES_FIELD_NUMBER: builtins.int
NUM_TOTAL_NODES_FIELD_NUMBER: builtins.int NUM_TOTAL_NODES_FIELD_NUMBER: builtins.int
NUM_RX_DUPE_FIELD_NUMBER: builtins.int
NUM_TX_RELAY_FIELD_NUMBER: builtins.int
NUM_TX_RELAY_CANCELED_FIELD_NUMBER: builtins.int
uptime_seconds: builtins.int uptime_seconds: builtins.int
""" """
How long the device has been running since the last reboot (in seconds) How long the device has been running since the last reboot (in seconds)
@@ -695,7 +754,7 @@ class LocalStats(google.protobuf.message.Message):
""" """
num_packets_rx: builtins.int num_packets_rx: builtins.int
""" """
Number of packets received good Number of packets received (both good and bad)
""" """
num_packets_rx_bad: builtins.int num_packets_rx_bad: builtins.int
""" """
@@ -709,6 +768,20 @@ class LocalStats(google.protobuf.message.Message):
""" """
Number of nodes total Number of nodes total
""" """
num_rx_dupe: builtins.int
"""
Number of received packets that were duplicates (due to multiple nodes relaying).
If this number is high, there are nodes in the mesh relaying packets when it's unnecessary, for example due to the ROUTER/REPEATER role.
"""
num_tx_relay: builtins.int
"""
Number of packets we transmitted that were a relay for others (not originating from ourselves).
"""
num_tx_relay_canceled: builtins.int
"""
Number of times we canceled a packet to be relayed, because someone else did it before us.
This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you.
"""
def __init__( def __init__(
self, self,
*, *,
@@ -720,11 +793,55 @@ class LocalStats(google.protobuf.message.Message):
num_packets_rx_bad: builtins.int = ..., num_packets_rx_bad: builtins.int = ...,
num_online_nodes: builtins.int = ..., num_online_nodes: builtins.int = ...,
num_total_nodes: builtins.int = ..., num_total_nodes: builtins.int = ...,
num_rx_dupe: builtins.int = ...,
num_tx_relay: builtins.int = ...,
num_tx_relay_canceled: builtins.int = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_total_nodes", b"num_total_nodes", "uptime_seconds", b"uptime_seconds"]) -> None: ... def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
global___LocalStats = LocalStats global___LocalStats = LocalStats
@typing.final
class HealthMetrics(google.protobuf.message.Message):
"""
Health telemetry metrics
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
HEART_BPM_FIELD_NUMBER: builtins.int
SPO2_FIELD_NUMBER: builtins.int
TEMPERATURE_FIELD_NUMBER: builtins.int
heart_bpm: builtins.int
"""
Heart rate (beats per minute)
"""
spO2: builtins.int
"""
SpO2 (blood oxygen saturation) level
"""
temperature: builtins.float
"""
Body temperature in degrees Celsius
"""
def __init__(
self,
*,
heart_bpm: builtins.int | None = ...,
spO2: builtins.int | None = ...,
temperature: builtins.float | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["_heart_bpm", b"_heart_bpm", "_spO2", b"_spO2", "_temperature", b"_temperature", "heart_bpm", b"heart_bpm", "spO2", b"spO2", "temperature", b"temperature"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_heart_bpm", b"_heart_bpm", "_spO2", b"_spO2", "_temperature", b"_temperature", "heart_bpm", b"heart_bpm", "spO2", b"spO2", "temperature", b"temperature"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_heart_bpm", b"_heart_bpm"]) -> typing.Literal["heart_bpm"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_spO2", b"_spO2"]) -> typing.Literal["spO2"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_temperature", b"_temperature"]) -> typing.Literal["temperature"] | None: ...
global___HealthMetrics = HealthMetrics
@typing.final @typing.final
class Telemetry(google.protobuf.message.Message): class Telemetry(google.protobuf.message.Message):
""" """
@@ -739,6 +856,7 @@ class Telemetry(google.protobuf.message.Message):
AIR_QUALITY_METRICS_FIELD_NUMBER: builtins.int AIR_QUALITY_METRICS_FIELD_NUMBER: builtins.int
POWER_METRICS_FIELD_NUMBER: builtins.int POWER_METRICS_FIELD_NUMBER: builtins.int
LOCAL_STATS_FIELD_NUMBER: builtins.int LOCAL_STATS_FIELD_NUMBER: builtins.int
HEALTH_METRICS_FIELD_NUMBER: builtins.int
time: builtins.int time: builtins.int
""" """
Seconds since 1970 - or 0 for unknown/unset Seconds since 1970 - or 0 for unknown/unset
@@ -773,6 +891,12 @@ class Telemetry(google.protobuf.message.Message):
Local device mesh statistics Local device mesh statistics
""" """
@property
def health_metrics(self) -> global___HealthMetrics:
"""
Health telemetry metrics
"""
def __init__( def __init__(
self, self,
*, *,
@@ -782,10 +906,11 @@ class Telemetry(google.protobuf.message.Message):
air_quality_metrics: global___AirQualityMetrics | None = ..., air_quality_metrics: global___AirQualityMetrics | None = ...,
power_metrics: global___PowerMetrics | None = ..., power_metrics: global___PowerMetrics | None = ...,
local_stats: global___LocalStats | None = ..., local_stats: global___LocalStats | None = ...,
health_metrics: global___HealthMetrics | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "variant", b"variant"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "variant", b"variant"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "time", b"time", "variant", b"variant"]) -> None: ... def ClearField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "time", b"time", "variant", b"variant"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics", "local_stats"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics", "local_stats", "health_metrics"] | None: ...
global___Telemetry = Telemetry global___Telemetry = Telemetry

View File

@@ -8,7 +8,7 @@ from meshtastic.protobuf import portnums_pb2, remote_hardware_pb2
from meshtastic.util import our_exit from meshtastic.util import our_exit
def onGPIOreceive(packet, interface): def onGPIOreceive(packet, interface) -> None:
"""Callback for received GPIO responses""" """Callback for received GPIO responses"""
logging.debug(f"packet:{packet} interface:{interface}") logging.debug(f"packet:{packet} interface:{interface}")
gpioValue = 0 gpioValue = 0
@@ -37,7 +37,7 @@ class RemoteHardwareClient:
code for how you can connect to your own custom meshtastic services code for how you can connect to your own custom meshtastic services
""" """
def __init__(self, iface): def __init__(self, iface) -> None:
""" """
Constructor Constructor

View File

@@ -5,7 +5,7 @@ import logging
import platform import platform
import time import time
from typing import Optional from typing import List, Optional
import serial # type: ignore[import-untyped] import serial # type: ignore[import-untyped]
@@ -19,7 +19,7 @@ if platform.system() != "Windows":
class SerialInterface(StreamInterface): class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link""" """Interface class for meshtastic devices over a serial link"""
def __init__(self, devPath: Optional[str]=None, debugOut=None, noProto=False, connectNow=True, noNodes: bool=False): def __init__(self, devPath: Optional[str]=None, debugOut=None, noProto: bool=False, connectNow: bool=True, noNodes: bool=False) -> None:
"""Constructor, opens a connection to a specified serial port, or if unspecified try to """Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing find one Meshtastic device by probing
@@ -32,13 +32,13 @@ class SerialInterface(StreamInterface):
self.devPath: Optional[str] = devPath self.devPath: Optional[str] = devPath
if self.devPath is None: if self.devPath is None:
ports = meshtastic.util.findPorts(True) ports: List[str] = meshtastic.util.findPorts(True)
logging.debug(f"ports:{ports}") logging.debug(f"ports:{ports}")
if len(ports) == 0: if len(ports) == 0:
print("No Serial Meshtastic device detected, attempting TCP connection on localhost.") print("No Serial Meshtastic device detected, attempting TCP connection on localhost.")
return return
elif len(ports) > 1: elif len(ports) > 1:
message = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n" message: str = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
message += f" Ports detected:{ports}" message += f" Ports detected:{ports}"
meshtastic.util.our_exit(message) meshtastic.util.our_exit(message)
else: else:
@@ -59,14 +59,14 @@ class SerialInterface(StreamInterface):
self.stream = serial.Serial( self.stream = serial.Serial(
self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0 self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0
) )
self.stream.flush() self.stream.flush() # type: ignore[attr-defined]
time.sleep(0.1) time.sleep(0.1)
StreamInterface.__init__( StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
) )
def close(self): def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
if self.stream: # Stream can be null if we were already closed if self.stream: # Stream can be null if we were already closed
self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary

View File

@@ -1,10 +1,13 @@
"""Stream Interface base class """Stream Interface base class
""" """
import io
import logging import logging
import threading import threading
import time import time
import traceback import traceback
from typing import Optional, cast
import serial # type: ignore[import-untyped] import serial # type: ignore[import-untyped]
from meshtastic.mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
@@ -19,7 +22,7 @@ MAX_TO_FROM_RADIO_SIZE = 512
class StreamInterface(MeshInterface): class StreamInterface(MeshInterface):
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)""" """Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
def __init__(self, debugOut=None, noProto=False, connectNow=True, noNodes=False): def __init__(self, debugOut: Optional[io.TextIOWrapper]=None, noProto: bool=False, connectNow: bool=True, noNodes: bool=False) -> None:
"""Constructor, opens a connection to self.stream """Constructor, opens a connection to self.stream
Keyword Arguments: Keyword Arguments:
@@ -35,6 +38,7 @@ class StreamInterface(MeshInterface):
raise Exception( # pylint: disable=W0719 raise Exception( # pylint: disable=W0719
"StreamInterface is now abstract (to update existing code create SerialInterface instead)" "StreamInterface is now abstract (to update existing code create SerialInterface instead)"
) )
self.stream: Optional[serial.Serial] # only serial uses this, TCPInterface overrides the relevant methods instead
self._rxBuf = bytes() # empty self._rxBuf = bytes() # empty
self._wantExit = False self._wantExit = False
@@ -52,7 +56,7 @@ class StreamInterface(MeshInterface):
if not noProto: if not noProto:
self.waitForConfig() self.waitForConfig()
def connect(self): def connect(self) -> None:
"""Connect to our radio """Connect to our radio
Normally this is called automatically by the constructor, but if you Normally this is called automatically by the constructor, but if you
@@ -63,7 +67,7 @@ class StreamInterface(MeshInterface):
# if the reading statemachine was parsing a bad packet make sure # if the reading statemachine was parsing a bad packet make sure
# we write enough start bytes to force it to resync (we don't use START1 # we write enough start bytes to force it to resync (we don't use START1
# because we want to ensure it is looking for START1) # because we want to ensure it is looking for START1)
p = bytearray([START2] * 32) p: bytes = bytearray([START2] * 32)
self._writeBytes(p) self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running time.sleep(0.1) # wait 100ms to give device time to start running
@@ -74,7 +78,7 @@ class StreamInterface(MeshInterface):
if not self.noProto: # Wait for the db download if using the protocol if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected() self._waitConnected()
def _disconnected(self): def _disconnected(self) -> None:
"""We override the superclass implementation to close our port""" """We override the superclass implementation to close our port"""
MeshInterface._disconnected(self) MeshInterface._disconnected(self)
@@ -86,7 +90,7 @@ class StreamInterface(MeshInterface):
# pylint: disable=W0201 # pylint: disable=W0201
self.stream = None self.stream = None
def _writeBytes(self, b): def _writeBytes(self, b: bytes) -> None:
"""Write an array of bytes to our stream and flush""" """Write an array of bytes to our stream and flush"""
if self.stream: # ignore writes when stream is closed if self.stream: # ignore writes when stream is closed
self.stream.write(b) self.stream.write(b)
@@ -98,24 +102,24 @@ class StreamInterface(MeshInterface):
# we sleep here to give the TBeam a chance to work # we sleep here to give the TBeam a chance to work
time.sleep(0.1) time.sleep(0.1)
def _readBytes(self, length): def _readBytes(self, length) -> Optional[bytes]:
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""
if self.stream: if self.stream:
return self.stream.read(length) return self.stream.read(length)
else: else:
return None return None
def _sendToRadioImpl(self, toRadio): def _sendToRadioImpl(self, toRadio) -> None:
"""Send a ToRadio protobuf to the device""" """Send a ToRadio protobuf to the device"""
logging.debug(f"Sending: {stripnl(toRadio)}") logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString() b: bytes = toRadio.SerializeToString()
bufLen = len(b) bufLen: int = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays # We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF]) header: bytes = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
logging.debug(f"sending header:{header} b:{b}") logging.debug(f"sending header:{header!r} b:{b!r}")
self._writeBytes(header + b) self._writeBytes(header + b)
def close(self): def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
logging.debug("Closing stream") logging.debug("Closing stream")
MeshInterface.close(self) MeshInterface.close(self)
@@ -142,7 +146,7 @@ class StreamInterface(MeshInterface):
else: else:
self.cur_log_line += utf self.cur_log_line += utf
def __reader(self): def __reader(self) -> None:
"""The reader thread that reads bytes from our stream""" """The reader thread that reads bytes from our stream"""
logging.debug("in __reader()") logging.debug("in __reader()")
empty = bytes() empty = bytes()
@@ -150,13 +154,13 @@ class StreamInterface(MeshInterface):
try: try:
while not self._wantExit: while not self._wantExit:
# logging.debug("reading character") # logging.debug("reading character")
b = self._readBytes(1) b: Optional[bytes] = self._readBytes(1)
# logging.debug("In reader loop") # logging.debug("In reader loop")
# logging.debug(f"read returned {b}") # logging.debug(f"read returned {b}")
if len(b) > 0: if b is not None and len(cast(bytes, b)) > 0:
c = b[0] c: int = b[0]
# logging.debug(f'c:{c}') # logging.debug(f'c:{c}')
ptr = len(self._rxBuf) ptr: int = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead # Assume we want to append this byte, fixme use bytearray instead
self._rxBuf = self._rxBuf + b self._rxBuf = self._rxBuf + b

View File

@@ -3,7 +3,7 @@
# pylint: disable=R0917 # pylint: disable=R0917
import logging import logging
import socket import socket
from typing import Optional from typing import Optional, cast
from meshtastic.stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
@@ -16,9 +16,9 @@ class TCPInterface(StreamInterface):
self, self,
hostname: str, hostname: str,
debugOut=None, debugOut=None,
noProto=False, noProto: bool=False,
connectNow=True, connectNow: bool=True,
portNumber=DEFAULT_TCP_PORT, portNumber: int=DEFAULT_TCP_PORT,
noNodes:bool=False, noNodes:bool=False,
): ):
"""Constructor, opens a connection to a specified IP address/hostname """Constructor, opens a connection to a specified IP address/hostname
@@ -29,14 +29,16 @@ class TCPInterface(StreamInterface):
self.stream = None self.stream = None
self.hostname = hostname self.hostname: str = hostname
self.portNumber = portNumber self.portNumber: int = portNumber
self.socket: Optional[socket.socket] = None
if connectNow: if connectNow:
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe] logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
server_address = (hostname, portNumber) server_address: tuple[str, int] = (hostname, portNumber)
sock = socket.create_connection(server_address) sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket: Optional[socket.socket] = sock self.socket = sock
else: else:
self.socket = None self.socket = None
@@ -44,25 +46,26 @@ class TCPInterface(StreamInterface):
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
) )
def _socket_shutdown(self): def _socket_shutdown(self) -> None:
"""Shutdown the socket. """Shutdown the socket.
Note: Broke out this line so the exception could be unit tested. Note: Broke out this line so the exception could be unit tested.
""" """
self.socket.shutdown(socket.SHUT_RDWR) if self.socket: #mian: please check that this should be "if self.socket:"
cast(socket.socket, self.socket).shutdown(socket.SHUT_RDWR)
def myConnect(self): def myConnect(self) -> None:
"""Connect to socket""" """Connect to socket"""
server_address = (self.hostname, self.portNumber) server_address: tuple[str, int] = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address) sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket = sock self.socket = sock
def close(self): def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
logging.debug("Closing TCP stream") logging.debug("Closing TCP stream")
StreamInterface.close(self) StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread. # Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here # Therefore we force the shutdown by closing the socket here
self._wantExit = True self._wantExit: bool = True
if not self.socket is None: if not self.socket is None:
try: try:
self._socket_shutdown() self._socket_shutdown()
@@ -70,10 +73,14 @@ class TCPInterface(StreamInterface):
pass # Ignore errors in shutdown, because we might have a race with the server pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close() self.socket.close()
def _writeBytes(self, b): def _writeBytes(self, b: bytes) -> None:
"""Write an array of bytes to our stream and flush""" """Write an array of bytes to our stream and flush"""
self.socket.send(b) if self.socket:
self.socket.send(b)
def _readBytes(self, length): def _readBytes(self, length) -> Optional[bytes]:
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""
return self.socket.recv(length) if self.socket:
return self.socket.recv(length)
else:
return None

View File

@@ -5,6 +5,9 @@ import logging
import sys import sys
import time import time
import traceback import traceback
import io
from typing import List, Optional
from dotmap import DotMap # type: ignore[import-untyped] from dotmap import DotMap # type: ignore[import-untyped]
from pubsub import pub # type: ignore[import-untyped] from pubsub import pub # type: ignore[import-untyped]
@@ -15,19 +18,19 @@ from meshtastic.serial_interface import SerialInterface
from meshtastic.tcp_interface import TCPInterface from meshtastic.tcp_interface import TCPInterface
"""The interfaces we are using for our tests""" """The interfaces we are using for our tests"""
interfaces = None interfaces: List = []
"""A list of all packets we received while the current test was running""" """A list of all packets we received while the current test was running"""
receivedPackets = None receivedPackets: Optional[List] = None
testsRunning = False testsRunning: bool = False
testNumber = 0 testNumber: int = 0
sendingInterface = None sendingInterface = None
def onReceive(packet, interface): def onReceive(packet, interface) -> None:
"""Callback invoked when a packet arrives""" """Callback invoked when a packet arrives"""
if sendingInterface == interface: if sendingInterface == interface:
pass pass
@@ -42,20 +45,20 @@ def onReceive(packet, interface):
receivedPackets.append(p) receivedPackets.append(p)
def onNode(node): def onNode(node) -> None:
"""Callback invoked when the node DB changes""" """Callback invoked when the node DB changes"""
print(f"Node changed: {node}") print(f"Node changed: {node}")
def subscribe(): def subscribe() -> None:
"""Subscribe to the topics the user probably wants to see, prints output to stdout""" """Subscribe to the topics the user probably wants to see, prints output to stdout"""
pub.subscribe(onNode, "meshtastic.node") pub.subscribe(onNode, "meshtastic.node")
def testSend( def testSend(
fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False fromInterface, toInterface, isBroadcast: bool=False, asBinary: bool=False, wantAck: bool=False
): ) -> bool:
""" """
Sends one test packet between two nodes and then returns success or failure Sends one test packet between two nodes and then returns success or failure
@@ -93,16 +96,16 @@ def testSend(
return False # Failed to send return False # Failed to send
def runTests(numTests=50, wantAck=False, maxFailures=0): def runTests(numTests: int=50, wantAck: bool=False, maxFailures: int=0) -> bool:
"""Run the tests.""" """Run the tests."""
logging.info(f"Running {numTests} tests with wantAck={wantAck}") logging.info(f"Running {numTests} tests with wantAck={wantAck}")
numFail = 0 numFail: int = 0
numSuccess = 0 numSuccess: int = 0
for _ in range(numTests): for _ in range(numTests):
# pylint: disable=W0603 # pylint: disable=W0603
global testNumber global testNumber
testNumber = testNumber + 1 testNumber = testNumber + 1
isBroadcast = True isBroadcast:bool = True
# asBinary=(i % 2 == 0) # asBinary=(i % 2 == 0)
success = testSend( success = testSend(
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck
@@ -126,10 +129,10 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
return True return True
def testThread(numTests=50): def testThread(numTests=50) -> bool:
"""Test thread""" """Test thread"""
logging.info("Found devices, starting tests...") logging.info("Found devices, starting tests...")
result = runTests(numTests, wantAck=True) result: bool = runTests(numTests, wantAck=True)
if result: if result:
# Run another test # Run another test
# Allow a few dropped packets # Allow a few dropped packets
@@ -137,25 +140,25 @@ def testThread(numTests=50):
return result return result
def onConnection(topic=pub.AUTO_TOPIC): def onConnection(topic=pub.AUTO_TOPIC) -> None:
"""Callback invoked when we connect/disconnect from a radio""" """Callback invoked when we connect/disconnect from a radio"""
print(f"Connection changed: {topic.getName()}") print(f"Connection changed: {topic.getName()}")
def openDebugLog(portName): def openDebugLog(portName) -> io.TextIOWrapper:
"""Open the debug log file""" """Open the debug log file"""
debugname = "log" + portName.replace("/", "_") debugname = "log" + portName.replace("/", "_")
logging.info(f"Writing serial debugging to {debugname}") logging.info(f"Writing serial debugging to {debugname}")
return open(debugname, "w+", buffering=1, encoding="utf8") return open(debugname, "w+", buffering=1, encoding="utf8")
def testAll(numTests=5): def testAll(numTests: int=5) -> bool:
""" """
Run a series of tests using devices we can find. Run a series of tests using devices we can find.
This is called from the cli with the "--test" option. This is called from the cli with the "--test" option.
""" """
ports = meshtastic.util.findPorts(True) ports: List[str] = meshtastic.util.findPorts(True)
if len(ports) < 2: if len(ports) < 2:
meshtastic.util.our_exit( meshtastic.util.our_exit(
"Warning: Must have at least two devices connected to USB." "Warning: Must have at least two devices connected to USB."
@@ -175,7 +178,7 @@ def testAll(numTests=5):
) )
logging.info("Ports opened, starting test") logging.info("Ports opened, starting test")
result = testThread(numTests) result: bool = testThread(numTests)
for i in interfaces: for i in interfaces:
i.close() i.close()
@@ -183,7 +186,7 @@ def testAll(numTests=5):
return result return result
def testSimulator(): def testSimulator() -> None:
""" """
Assume that someone has launched meshtastic-native as a simulated node. Assume that someone has launched meshtastic-native as a simulated node.
Talk to that node over TCP, do some operations and if they are successful Talk to that node over TCP, do some operations and if they are successful
@@ -195,7 +198,7 @@ def testSimulator():
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.info("Connecting to simulator on localhost!") logging.info("Connecting to simulator on localhost!")
try: try:
iface = TCPInterface("localhost") iface: meshtastic.tcp_interface.TCPInterface = TCPInterface("localhost")
iface.showInfo() iface.showInfo()
iface.localNode.showInfo() iface.localNode.showInfo()
iface.localNode.exitSimulator() iface.localNode.exitSimulator()

View File

@@ -2652,3 +2652,64 @@ def test_tunnel_tunnel_arg(
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Connected to radio", out, re.MULTILINE)
assert err == "" assert err == ""
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_set_favorite_node():
"""Test --set-favorite-node node"""
sys.argv = ["", "--set-favorite-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.setFavorite.assert_called_once_with("!12345678")
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_remove_favorite_node():
"""Test --remove-favorite-node node"""
sys.argv = ["", "--remove-favorite-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
mocked_node.iface = iface
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.removeFavorite.assert_called_once_with("!12345678")
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_set_ignored_node():
"""Test --set-ignored-node node"""
sys.argv = ["", "--set-ignored-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.setIgnored.assert_called_once_with("!12345678")
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_remove_ignored_node():
"""Test --remove-ignored-node node"""
sys.argv = ["", "--remove-ignored-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
mocked_node.iface = iface
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.removeIgnored.assert_called_once_with("!12345678")

View File

@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from ..protobuf import localonly_pb2, config_pb2 from ..protobuf import admin_pb2, localonly_pb2, config_pb2
from ..protobuf.channel_pb2 import Channel # pylint: disable=E0611 from ..protobuf.channel_pb2 import Channel # pylint: disable=E0611
from ..node import Node from ..node import Node
from ..serial_interface import SerialInterface from ..serial_interface import SerialInterface
@@ -1426,6 +1426,60 @@ def test_requestChannels_non_localNode_starting_index(caplog):
# assert err == '' # assert err == ''
@pytest.mark.unit
@pytest.mark.parametrize("favorite", ["!1dec0ded", 502009325])
def test_set_favorite(favorite):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.setFavorite(favorite)
assert amesg.set_favorite_node == 502009325
iface.sendData.assert_called_once()
@pytest.mark.unit
@pytest.mark.parametrize("favorite", ["!1dec0ded", 502009325])
def test_remove_favorite(favorite):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.removeFavorite(favorite)
assert amesg.remove_favorite_node == 502009325
iface.sendData.assert_called_once()
@pytest.mark.unit
@pytest.mark.parametrize("ignored", ["!1dec0ded", 502009325])
def test_set_ignored(ignored):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.setIgnored(ignored)
assert amesg.set_ignored_node == 502009325
iface.sendData.assert_called_once()
@pytest.mark.unit
@pytest.mark.parametrize("ignored", ["!1dec0ded", 502009325])
def test_remove_ignored(ignored):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.removeIgnored(ignored)
assert amesg.remove_ignored_node == 502009325
iface.sendData.assert_called_once()
# TODO # TODO
# @pytest.mark.unitslow # @pytest.mark.unitslow
# def test_waitForConfig(): # def test_waitForConfig():

View File

@@ -442,6 +442,13 @@ def test_is_windows11_false_win8_1(patched_platform, patched_release):
patched_platform.assert_called() patched_platform.assert_called()
patched_release.assert_called() patched_release.assert_called()
@patch("platform.release", return_value="2022Server")
@patch("platform.system", return_value="Windows")
def test_is_windows11_false_winserver(patched_platform, patched_release):
"""Test is_windows11()"""
assert is_windows11() is False
patched_platform.assert_called()
patched_release.assert_called()
@pytest.mark.unit @pytest.mark.unit
@patch("platform.system", return_value="Linux") @patch("platform.system", return_value="Linux")
@@ -556,7 +563,7 @@ def test_active_ports_on_supported_devices_mac_duplicates_check(mock_platform, m
def test_message_to_json_shows_all(): def test_message_to_json_shows_all():
"""Test that message_to_json prints fields that aren't included in data passed in""" """Test that message_to_json prints fields that aren't included in data passed in"""
actual = json.loads(message_to_json(mesh_pb2.MyNodeInfo())) actual = json.loads(message_to_json(mesh_pb2.MyNodeInfo()))
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0 } expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0, "deviceId": "", "pioEnv": "" }
assert actual == expected assert actual == expected
@pytest.mark.unit @pytest.mark.unit

View File

@@ -43,7 +43,7 @@ class Tunnel:
self.message = message self.message = message
super().__init__(self.message) super().__init__(self.message)
def __init__(self, iface, subnet="10.115", netmask="255.255.0.0"): def __init__(self, iface, subnet: str="10.115", netmask: str="255.255.0.0") -> None:
""" """
Constructor Constructor

View File

@@ -11,7 +11,7 @@ import threading
import time import time
import traceback import traceback
from queue import Queue from queue import Queue
from typing import List, NoReturn, Union from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from google.protobuf.message import Message from google.protobuf.message import Message
@@ -31,7 +31,7 @@ from meshtastic.version import get_active_version
0925 Lakeview Research Saleae Logic (logic analyzer) 0925 Lakeview Research Saleae Logic (logic analyzer)
04b4:602a Cypress Semiconductor Corp. Hantek DSO-6022BL (oscilloscope) 04b4:602a Cypress Semiconductor Corp. Hantek DSO-6022BL (oscilloscope)
""" """
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4]) blacklistVids: Dict = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
"""Some devices are highly likely to be meshtastic. """Some devices are highly likely to be meshtastic.
0x239a RAK4631 0x239a RAK4631
@@ -39,21 +39,21 @@ blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
whitelistVids = dict.fromkeys([0x239a, 0x303a]) whitelistVids = dict.fromkeys([0x239a, 0x303a])
def quoteBooleans(a_string): def quoteBooleans(a_string: str) -> str:
"""Quote booleans """Quote booleans
given a string that contains ": true", replace with ": 'true'" (or false) given a string that contains ": true", replace with ": 'true'" (or false)
""" """
tmp = a_string.replace(": true", ": 'true'") tmp: str = a_string.replace(": true", ": 'true'")
tmp = tmp.replace(": false", ": 'false'") tmp = tmp.replace(": false", ": 'false'")
return tmp return tmp
def genPSK256(): def genPSK256() -> bytes:
"""Generate a random preshared key""" """Generate a random preshared key"""
return os.urandom(32) return os.urandom(32)
def fromPSK(valstr): def fromPSK(valstr: str) -> Any:
"""A special version of fromStr that assumes the user is trying to set a PSK. """A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
""" """
@@ -70,7 +70,7 @@ def fromPSK(valstr):
return fromStr(valstr) return fromStr(valstr)
def fromStr(valstr): def fromStr(valstr: str) -> Any:
"""Try to parse as int, float or bool (and fallback to a string as last resort) """Try to parse as int, float or bool (and fallback to a string as last resort)
Returns: an int, bool, float, str or byte array (for strings of hex digits) Returns: an int, bool, float, str or byte array (for strings of hex digits)
@@ -78,6 +78,7 @@ def fromStr(valstr):
Args: Args:
valstr (string): A user provided string valstr (string): A user provided string
""" """
val: Any
if len(valstr) == 0: # Treat an emptystring as an empty bytes if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes() val = bytes()
elif valstr.startswith("0x"): elif valstr.startswith("0x"):
@@ -100,7 +101,15 @@ def fromStr(valstr):
return val return val
def pskToString(psk: bytes):
def toStr(raw_value):
"""Convert a value to a string that can be used in a config file"""
if isinstance(raw_value, bytes):
return "base64:" + base64.b64encode(raw_value).decode("utf-8")
return str(raw_value)
def pskToString(psk: bytes) -> str:
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string""" """Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
if len(psk) == 0: if len(psk) == 0:
return "unencrypted" return "unencrypted"
@@ -122,12 +131,12 @@ def stripnl(s) -> str:
return " ".join(s.split()) return " ".join(s.split())
def fixme(message): def fixme(message: str) -> None:
"""Raise an exception for things that needs to be fixed""" """Raise an exception for things that needs to be fixed"""
raise Exception(f"FIXME: {message}") # pylint: disable=W0719 raise Exception(f"FIXME: {message}") # pylint: disable=W0719
def catchAndIgnore(reason, closure): def catchAndIgnore(reason: str, closure) -> None:
"""Call a closure but if it throws an exception print it and continue""" """Call a closure but if it throws an exception print it and continue"""
try: try:
closure() closure()
@@ -145,7 +154,7 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]:
all_ports = serial.tools.list_ports.comports() all_ports = serial.tools.list_ports.comports()
# look for 'likely' meshtastic devices # look for 'likely' meshtastic devices
ports = list( ports: List = list(
map( map(
lambda port: port.device, lambda port: port.device,
filter( filter(
@@ -184,12 +193,12 @@ class dotdict(dict):
class Timeout: class Timeout:
"""Timeout class""" """Timeout class"""
def __init__(self, maxSecs: int=20): def __init__(self, maxSecs: int=20) -> None:
self.expireTime: Union[int, float] = 0 self.expireTime: Union[int, float] = 0
self.sleepInterval: float = 0.1 self.sleepInterval: float = 0.1
self.expireTimeout: int = maxSecs self.expireTimeout: int = maxSecs
def reset(self): def reset(self) -> None:
"""Restart the waitForSet timer""" """Restart the waitForSet timer"""
self.expireTime = time.time() + self.expireTimeout self.expireTime = time.time() + self.expireTimeout
@@ -248,7 +257,7 @@ class Timeout:
class Acknowledgment: class Acknowledgment:
"A class that records which type of acknowledgment was just received, if any." "A class that records which type of acknowledgment was just received, if any."
def __init__(self): def __init__(self) -> None:
"""initialize""" """initialize"""
self.receivedAck = False self.receivedAck = False
self.receivedNak = False self.receivedNak = False
@@ -257,7 +266,7 @@ class Acknowledgment:
self.receivedTelemetry = False self.receivedTelemetry = False
self.receivedPosition = False self.receivedPosition = False
def reset(self): def reset(self) -> None:
"""reset""" """reset"""
self.receivedAck = False self.receivedAck = False
self.receivedNak = False self.receivedNak = False
@@ -270,18 +279,18 @@ class Acknowledgment:
class DeferredExecution: class DeferredExecution:
"""A thread that accepts closures to run, and runs them as they are received""" """A thread that accepts closures to run, and runs them as they are received"""
def __init__(self, name): def __init__(self, name) -> None:
self.queue = Queue() self.queue: Queue = Queue()
# this thread must be marked as daemon, otherwise it will prevent clients from exiting # 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 = threading.Thread(target=self._run, args=(), name=name, daemon=True)
self.thread.daemon = True self.thread.daemon = True
self.thread.start() self.thread.start()
def queueWork(self, runnable): def queueWork(self, runnable) -> None:
"""Queue up the work""" """Queue up the work"""
self.queue.put(runnable) self.queue.put(runnable)
def _run(self): def _run(self) -> None:
while True: while True:
try: try:
o = self.queue.get() o = self.queue.get()
@@ -301,7 +310,7 @@ def our_exit(message, return_value=1) -> NoReturn:
sys.exit(return_value) sys.exit(return_value)
def support_info(): def support_info() -> None:
"""Print out info that helps troubleshooting of the cli.""" """Print out info that helps troubleshooting of the cli."""
print("") print("")
print("If having issues with meshtastic cli or python library") print("If having issues with meshtastic cli or python library")
@@ -330,7 +339,7 @@ def support_info():
print("Please add the output from the command: meshtastic --info") print("Please add the output from the command: meshtastic --info")
def remove_keys_from_dict(keys, adict): def remove_keys_from_dict(keys: Union[Tuple, List, Set], adict: Dict) -> Dict:
"""Return a dictionary without some keys in it. """Return a dictionary without some keys in it.
Will removed nested keys. Will removed nested keys.
""" """
@@ -345,33 +354,33 @@ def remove_keys_from_dict(keys, adict):
return adict return adict
def hexstr(barray): def hexstr(barray: bytes) -> str:
"""Print a string of hex digits""" """Print a string of hex digits"""
return ":".join(f"{x:02x}" for x in barray) return ":".join(f"{x:02x}" for x in barray)
def ipstr(barray): def ipstr(barray: bytes) -> str:
"""Print a string of ip digits""" """Print a string of ip digits"""
return ".".join(f"{x}" for x in barray) return ".".join(f"{x}" for x in barray)
def readnet_u16(p, offset): def readnet_u16(p, offset: int) -> int:
"""Read big endian u16 (network byte order)""" """Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1] return p[offset] * 256 + p[offset + 1]
def convert_mac_addr(val): def convert_mac_addr(val: str) -> Union[str, bytes]:
"""Convert the base 64 encoded value to a mac address """Convert the base 64 encoded value to a mac address
val - base64 encoded value (ex: '/c0gFyhb')) val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b') returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
""" """
if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val): if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val):
val_as_bytes = base64.b64decode(val) val_as_bytes: bytes = base64.b64decode(val)
return hexstr(val_as_bytes) return hexstr(val_as_bytes)
return val return val
def snake_to_camel(a_string): def snake_to_camel(a_string: str) -> str:
"""convert snake_case to camelCase""" """convert snake_case to camelCase"""
# split underscore using split # split underscore using split
temp = a_string.split("_") temp = a_string.split("_")
@@ -380,16 +389,16 @@ def snake_to_camel(a_string):
return result return result
def camel_to_snake(a_string): def camel_to_snake(a_string: str) -> str:
"""convert camelCase to snake_case""" """convert camelCase to snake_case"""
return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip( return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip(
"_" "_"
) )
def detect_supported_devices(): def detect_supported_devices() -> Set:
"""detect supported devices based on vendor id""" """detect supported devices based on vendor id"""
system = platform.system() system: str = platform.system()
# print(f'system:{system}') # print(f'system:{system}')
possible_devices = set() possible_devices = set()
@@ -447,9 +456,9 @@ def detect_supported_devices():
return possible_devices return possible_devices
def detect_windows_needs_driver(sd, print_reason=False): def detect_windows_needs_driver(sd, print_reason=False) -> bool:
"""detect if Windows user needs to install driver for a supported device""" """detect if Windows user needs to install driver for a supported device"""
need_to_install_driver = False need_to_install_driver: bool = False
if sd: if sd:
system = platform.system() system = platform.system()
@@ -475,7 +484,7 @@ def detect_windows_needs_driver(sd, print_reason=False):
return need_to_install_driver return need_to_install_driver
def eliminate_duplicate_port(ports): def eliminate_duplicate_port(ports: List) -> List:
"""Sometimes we detect 2 serial ports, but we really only need to use one of the ports. """Sometimes we detect 2 serial ports, but we really only need to use one of the ports.
ports is a list of ports ports is a list of ports
@@ -508,23 +517,23 @@ def eliminate_duplicate_port(ports):
return new_ports return new_ports
def is_windows11(): def is_windows11() -> bool:
"""Detect if Windows 11""" """Detect if Windows 11"""
is_win11 = False is_win11: bool = False
if platform.system() == "Windows": if platform.system() == "Windows":
if float(platform.release()) >= 10.0: try:
patch = platform.version().split(".")[2] if float(platform.release()) >= 10.0:
# in case they add some number suffix later, just get first 5 chars of patch patch = platform.version().split(".")[2]
patch = patch[:5] # in case they add some number suffix later, just get first 5 chars of patch
try: patch = patch[:5]
if int(patch) >= 22000: if int(patch) >= 22000:
is_win11 = True is_win11 = True
except Exception as e: except Exception as e:
print(f"problem detecting win11 e:{e}") print(f"problem detecting win11 e:{e}")
return is_win11 return is_win11
def get_unique_vendor_ids(): def get_unique_vendor_ids() -> Set[str]:
"""Return a set of unique vendor ids""" """Return a set of unique vendor ids"""
vids = set() vids = set()
for d in supported_devices: for d in supported_devices:
@@ -533,7 +542,7 @@ def get_unique_vendor_ids():
return vids return vids
def get_devices_with_vendor_id(vid): def get_devices_with_vendor_id(vid: str) -> Set: #Set[SupportedDevice]
"""Return a set of unique devices with the vendor id""" """Return a set of unique devices with the vendor id"""
sd = set() sd = set()
for d in supported_devices: for d in supported_devices:
@@ -542,11 +551,11 @@ def get_devices_with_vendor_id(vid):
return sd return sd
def active_ports_on_supported_devices(sds, eliminate_duplicates=False): def active_ports_on_supported_devices(sds, eliminate_duplicates=False) -> Set[str]:
"""Return a set of active ports based on the supplied supported devices""" """Return a set of active ports based on the supplied supported devices"""
ports = set() ports: Set = set()
baseports = set() baseports: Set = set()
system = platform.system() system: str = platform.system()
# figure out what possible base ports there are # figure out what possible base ports there are
for d in sds: for d in sds:
@@ -604,13 +613,13 @@ def active_ports_on_supported_devices(sds, eliminate_duplicates=False):
for com_port in com_ports: for com_port in com_ports:
ports.add(com_port) ports.add(com_port)
if eliminate_duplicates: if eliminate_duplicates:
ports = eliminate_duplicate_port(list(ports)) portlist: List = eliminate_duplicate_port(list(ports))
ports.sort() portlist.sort()
ports = set(ports) ports = set(portlist)
return ports return ports
def detect_windows_port(sd): def detect_windows_port(sd) -> Set[str]: #"sd" is a SupportedDevice from meshtastic.supported_device
"""detect if Windows port""" """detect if Windows port"""
ports = set() ports = set()
@@ -635,20 +644,26 @@ def detect_windows_port(sd):
return ports return ports
def check_if_newer_version(): def check_if_newer_version() -> Optional[str]:
"""Check pip to see if we are running the latest version.""" """Check pip to see if we are running the latest version."""
pypi_version = None pypi_version: Optional[str] = None
try: try:
url = "https://pypi.org/pypi/meshtastic/json" url: str = "https://pypi.org/pypi/meshtastic/json"
data = requests.get(url, timeout=5).json() data = requests.get(url, timeout=5).json()
pypi_version = data["info"]["version"] pypi_version = data["info"]["version"]
except Exception: except Exception:
pass pass
act_version = get_active_version() act_version = get_active_version()
if pypi_version is None:
return None
try: try:
parsed_act_version = pkg_version.parse(act_version) parsed_act_version = pkg_version.parse(act_version)
parsed_pypi_version = pkg_version.parse(pypi_version) parsed_pypi_version = pkg_version.parse(pypi_version)
#Note: if handed "None" when we can't download the pypi_version,
#this gets a TypeError:
#"TypeError: expected string or bytes-like object, got 'NoneType'"
#Handle that below?
except pkg_version.InvalidVersion: except pkg_version.InvalidVersion:
return pypi_version return pypi_version
@@ -660,5 +675,8 @@ def check_if_newer_version():
def message_to_json(message: Message, multiline: bool=False) -> str: def message_to_json(message: Message, multiline: bool=False) -> str:
"""Return protobuf message as JSON. Always print all fields, even when not present in data.""" """Return protobuf message as JSON. Always print all fields, even when not present in data."""
json = MessageToJson(message, always_print_fields_with_no_presence=True) try:
json = MessageToJson(message, always_print_fields_with_no_presence=True)
except TypeError:
json = MessageToJson(message, including_default_value_fields=True) # type: ignore[call-arg] # pylint: disable=E1123
return stripnl(json) if not multiline else json return stripnl(json) if not multiline else json

1562
poetry.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "meshtastic" name = "meshtastic"
version = "2.5.3" version = "2.5.7"
description = "Python API & client shell for talking to Meshtastic devices" description = "Python API & client shell for talking to Meshtastic devices"
authors = ["Meshtastic Developers <contact@meshtastic.org>"] authors = ["Meshtastic Developers <contact@meshtastic.org>"]
license = "GPL-3.0-only" license = "GPL-3.0-only"
@@ -9,24 +9,23 @@ readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9,<3.14" # 3.9 is needed for pandas, bleak requires <3.14 python = "^3.9,<3.14" # 3.9 is needed for pandas, bleak requires <3.14
pyserial = "^3.5" pyserial = "^3.5"
protobuf = ">=5.26.0" protobuf = ">=4.21.12"
dotmap = "^1.3.30"
pexpect = "^4.9.0"
pyqrcode = "^1.2.1"
tabulate = "^0.9.0" tabulate = "^0.9.0"
webencodings = "^0.5.1"
requests = "^2.31.0" requests = "^2.31.0"
pyparsing = "^3.1.2"
pyyaml = "^6.0.1" pyyaml = "^6.0.1"
pypubsub = "^4.0.3" pypubsub = "^4.0.3"
bleak = "^0.22.3" bleak = "^0.22.3"
packaging = "^24.0" packaging = "^24.0"
print-color = "^0.4.6" argcomplete = { version = "^3.5.2", optional = true }
pyqrcode = { version = "^1.2.1", optional = true }
dotmap = { version = "^1.3.30", optional = true }
print-color = { version = "^0.4.6", optional = true }
dash = { version = "^2.17.1", optional = true } dash = { version = "^2.17.1", optional = true }
pytap2 = { version = "^2.3.0", optional = true } pytap2 = { version = "^2.3.0", optional = true }
dash-bootstrap-components = { version = "^1.6.0", optional = true } dash-bootstrap-components = { version = "^1.6.0", optional = true }
pandas = { version = "^2.2.2", optional = true } pandas = { version = "^2.2.2", optional = true }
pandas-stubs = { version = "^2.2.2.240603", optional = true } pandas-stubs = { version = "^2.2.2.240603", optional = true }
wcwidth = {version = "^0.2.13", optional = true}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
hypothesis = "^6.103.2" hypothesis = "^6.103.2"
@@ -37,7 +36,7 @@ autopep8 = "^2.1.0"
pylint = "^3.2.3" pylint = "^3.2.3"
pyinstaller = "^6.8.0" pyinstaller = "^6.8.0"
mypy = "^1.10.0" mypy = "^1.10.0"
mypy-protobuf = "^3.6.0" mypy-protobuf = "^3.3.0"
types-protobuf = "^5.26.0.20240422" types-protobuf = "^5.26.0.20240422"
types-tabulate = "^0.9.0.20240106" types-tabulate = "^0.9.0.20240106"
types-requests = "^2.31.0.20240406" types-requests = "^2.31.0.20240406"
@@ -67,6 +66,7 @@ ipywidgets = "^8.1.3"
jupyterlab-widgets = "^3.0.11" jupyterlab-widgets = "^3.0.11"
[tool.poetry.extras] [tool.poetry.extras]
cli = ["pyqrcode", "print-color", "dotmap", "argcomplete", "wcwidth"]
tunnel = ["pytap2"] tunnel = ["pytap2"]
analysis = ["dash", "dash-bootstrap-components", "pandas", "pandas-stubs"] analysis = ["dash", "dash-bootstrap-components", "pandas", "pandas-stubs"]