mirror of
https://github.com/meshtastic/python.git
synced 2025-12-26 09:27:52 -05:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3615135e97 | ||
|
|
cfb23788e1 | ||
|
|
14ff3eb9c4 | ||
|
|
47e5b04d3b | ||
|
|
e5159f1156 | ||
|
|
593b05dbcd | ||
|
|
f519d1f2d2 | ||
|
|
8b36561406 | ||
|
|
e2b4948d45 | ||
|
|
7e3d347b63 | ||
|
|
c6efccdbd2 | ||
|
|
fe093ac34b | ||
|
|
2b10459db0 | ||
|
|
dd238dcbe3 | ||
|
|
e330afc899 | ||
|
|
a63f3f6e94 | ||
|
|
52eb112b95 | ||
|
|
d53ced216c | ||
|
|
f5ecd28705 | ||
|
|
82ad9b2f51 | ||
|
|
cbf7b9befe | ||
|
|
f3ba660cf4 | ||
|
|
4b143030d3 | ||
|
|
03aaa4c98e | ||
|
|
a79e17a575 | ||
|
|
38b163fa89 | ||
|
|
af4947d020 | ||
|
|
db1891b651 | ||
|
|
d7d9c7219a | ||
|
|
c60b5d4b05 | ||
|
|
83d82c518a | ||
|
|
8a95ce4636 | ||
|
|
0261313fc5 | ||
|
|
c1a6234694 | ||
|
|
66e32f812a | ||
|
|
eb85439000 | ||
|
|
885eb4898d | ||
|
|
172c123990 | ||
|
|
fcdd83838b | ||
|
|
58967e1d91 | ||
|
|
17f7e8e20e | ||
|
|
9b5a889676 | ||
|
|
ce7c61861f | ||
|
|
4adcbb6787 | ||
|
|
125f63419e | ||
|
|
cad5d18aff | ||
|
|
706d0649c1 | ||
|
|
167044907d | ||
|
|
ab1669994f | ||
|
|
6f67f33378 | ||
|
|
e60c8ea105 | ||
|
|
d633f8c895 | ||
|
|
ca82e1ce2b | ||
|
|
0ae23eec7e | ||
|
|
2fa85bac1f | ||
|
|
58fc614fb7 | ||
|
|
795b652069 | ||
|
|
213faa0cae | ||
|
|
68a2009e0e | ||
|
|
c76e4dac87 | ||
|
|
428be9fbce | ||
|
|
d83f7b2307 | ||
|
|
eb453a2e8a | ||
|
|
308ac93399 | ||
|
|
84417f0bb1 | ||
|
|
0bb3389b3b | ||
|
|
22b3062151 | ||
|
|
373b8a3139 | ||
|
|
51b543ff40 | ||
|
|
8752a0de6e | ||
|
|
7160e79fbf | ||
|
|
b73fcbff88 | ||
|
|
1b5b07e752 | ||
|
|
f5fa30cb22 | ||
|
|
46a8db286c |
22
.github/CONTRIBUTING.md
vendored
Normal file
22
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Contributing to Meshtastic Python
|
||||
|
||||
## Development resources
|
||||
- [API Documentation](https://python.meshtastic.org/)
|
||||
- [Meshtastic Python Development](https://meshtastic.org/docs/development/python/)
|
||||
- [Building Meshtastic Python](https://meshtastic.org/docs/development/python/building/)
|
||||
- [Using the Meshtastic Python Library](https://meshtastic.org/docs/development/python/library/)
|
||||
|
||||
## How to check your code (pytest/pylint) before a PR
|
||||
- [Pre-requisites](https://meshtastic.org/docs/development/python/building/#pre-requisites)
|
||||
- also execute `poetry install --all-extras --with dev,powermon` for all optional dependencies
|
||||
- check your code with github ci actions locally
|
||||
- You need to have act installed. You can get it at https://nektosact.com/
|
||||
- on linux: `act -P ubuntu-latest=-self-hosted --matrix "python-version:3.12"`
|
||||
- on windows:
|
||||
- linux checks (linux docker): `act --matrix "python-version:3.12"`
|
||||
- windows checks (windows host): `act -P ubuntu-latest=-self-hosted --matrix "python-version:3.12"`
|
||||
- or run all locally:
|
||||
- run `poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"`
|
||||
- run `poetry run mypy meshtastic/`
|
||||
- run `poetry run pytest`
|
||||
- more commands see [CI workflow](https://github.com/meshtastic/python/blob/master/.github/workflows/ci.yml)
|
||||
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@@ -4,6 +4,7 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "meshtastic BLE",
|
||||
"type": "debugpy",
|
||||
@@ -261,7 +262,14 @@
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--nodes", "--show-fields", "AKA,Pubkey,Role,Role,Role,Latitude,Latitude,deviceMetrics.voltage"]
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"name": "meshtastic --export-config",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--export-config", "config.json"]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ set -e
|
||||
#gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/*
|
||||
#gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/*
|
||||
|
||||
POETRYDIR=$(poetry env info --path)
|
||||
|
||||
if [[ -z "${POETRYDIR}" ]]; then
|
||||
poetry install
|
||||
fi
|
||||
|
||||
# protoc looks for mypy plugin in the python path
|
||||
source $(poetry env info --path)/bin/activate
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ owner_short: BOB
|
||||
|
||||
channel_url: https://www.meshtastic.org/e/#CgMSAQESCDgBQANIAVAe
|
||||
|
||||
canned_messages: Hi|Bye|Yes|No|Ok
|
||||
ringtone: 24:d=32,o=5,b=565:f6,p,f6,4p,p,f6,p,f6,2p,p,b6,p,b6,p,b6,p,b6,p,b,p,b,p,b,p,b,p,b,p,b,p,b,p,b,1p.,2p.,p
|
||||
|
||||
location:
|
||||
lat: 35.88888
|
||||
lon: -93.88888
|
||||
|
||||
@@ -35,6 +35,7 @@ type of packet, you should subscribe to the full topic name. If you want to see
|
||||
- `meshtastic.receive.data.portnum(packet)` (where portnum is an integer or well known PortNum enum)
|
||||
- `meshtastic.node.updated(node = NodeInfo)` - published when a node in the DB changes (appears, location changed, username changed, etc...)
|
||||
- `meshtastic.log.line(line)` - a raw unparsed log line from the radio
|
||||
- `meshtastic.clientNotification(notification, interface) - a ClientNotification sent from the radio
|
||||
|
||||
We receive position, user, or data packets from the mesh. You probably only care about `meshtastic.receive.data`. The first argument for
|
||||
that publish will be the packet. Text or binary data packets (from `sendData` or `sendText`) will both arrive this way. If you print packet
|
||||
@@ -128,6 +129,7 @@ NODELESS_WANT_CONFIG_ID = 69420
|
||||
|
||||
publishingThread = DeferredExecution("publishing")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ResponseHandler(NamedTuple):
|
||||
"""A pending response callback, waiting for a response to one of our messages"""
|
||||
@@ -159,31 +161,31 @@ def _onTextReceive(iface, asDict):
|
||||
#
|
||||
# Usually btw this problem is caused by apps sending binary data but setting the payload type to
|
||||
# text.
|
||||
logging.debug(f"in _onTextReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onTextReceive() asDict:{asDict}")
|
||||
try:
|
||||
asBytes = asDict["decoded"]["payload"]
|
||||
asDict["decoded"]["text"] = asBytes.decode("utf-8")
|
||||
except Exception as ex:
|
||||
logging.error(f"Malformatted utf8 in text message: {ex}")
|
||||
logger.error(f"Malformatted utf8 in text message: {ex}")
|
||||
_receiveInfoUpdate(iface, asDict)
|
||||
|
||||
|
||||
def _onPositionReceive(iface, asDict):
|
||||
"""Special auto parsing for received messages"""
|
||||
logging.debug(f"in _onPositionReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onPositionReceive() asDict:{asDict}")
|
||||
if "decoded" in asDict:
|
||||
if "position" in asDict["decoded"] and "from" in asDict:
|
||||
p = asDict["decoded"]["position"]
|
||||
logging.debug(f"p:{p}")
|
||||
logger.debug(f"p:{p}")
|
||||
p = iface._fixupPosition(p)
|
||||
logging.debug(f"after fixup p:{p}")
|
||||
logger.debug(f"after fixup p:{p}")
|
||||
# update node DB as needed
|
||||
iface._getOrCreateByNum(asDict["from"])["position"] = p
|
||||
|
||||
|
||||
def _onNodeInfoReceive(iface, asDict):
|
||||
"""Special auto parsing for received messages"""
|
||||
logging.debug(f"in _onNodeInfoReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onNodeInfoReceive() asDict:{asDict}")
|
||||
if "decoded" in asDict:
|
||||
if "user" in asDict["decoded"] and "from" in asDict:
|
||||
p = asDict["decoded"]["user"]
|
||||
@@ -197,7 +199,7 @@ def _onNodeInfoReceive(iface, asDict):
|
||||
|
||||
def _onTelemetryReceive(iface, asDict):
|
||||
"""Automatically update device metrics on received packets"""
|
||||
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onTelemetryReceive() asDict:{asDict}")
|
||||
if "from" not in asDict:
|
||||
return
|
||||
|
||||
@@ -221,7 +223,7 @@ def _onTelemetryReceive(iface, asDict):
|
||||
updateObj = telemetry.get(toUpdate)
|
||||
newMetrics = node.get(toUpdate, {})
|
||||
newMetrics.update(updateObj)
|
||||
logging.debug(f"updating {toUpdate} metrics for {asDict['from']} to {newMetrics}")
|
||||
logger.debug(f"updating {toUpdate} metrics for {asDict['from']} to {newMetrics}")
|
||||
node[toUpdate] = newMetrics
|
||||
|
||||
def _receiveInfoUpdate(iface, asDict):
|
||||
@@ -233,7 +235,7 @@ def _receiveInfoUpdate(iface, asDict):
|
||||
|
||||
def _onAdminReceive(iface, asDict):
|
||||
"""Special auto parsing for received messages"""
|
||||
logging.debug(f"in _onAdminReceive() asDict:{asDict}")
|
||||
logger.debug(f"in _onAdminReceive() asDict:{asDict}")
|
||||
if "decoded" in asDict and "from" in asDict and "admin" in asDict["decoded"]:
|
||||
adminMessage = asDict["decoded"]["admin"]["raw"]
|
||||
iface._getOrCreateByNum(asDict["from"])["adminSessionPassKey"] = adminMessage.session_passkey
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
|
||||
# We just hit the 1600 line limit for main.py, but I currently have a huge set of powermon/structured logging changes
|
||||
# later we can have a separate changelist to refactor main.py into smaller files
|
||||
# pylint: disable=too-many-lines
|
||||
# pylint: disable=R0917,C0302
|
||||
|
||||
from typing import List, Optional, Union
|
||||
from types import ModuleType
|
||||
|
||||
import argparse
|
||||
|
||||
argcomplete: Union[None, ModuleType] = None
|
||||
try:
|
||||
import argcomplete # type: ignore
|
||||
@@ -59,15 +60,17 @@ except ImportError as e:
|
||||
have_powermon = False
|
||||
powermon_exception = e
|
||||
meter = None
|
||||
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2
|
||||
from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2, mesh_pb2
|
||||
from meshtastic.version import get_active_version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def onReceive(packet, interface) -> None:
|
||||
"""Callback invoked when a packet arrives"""
|
||||
args = mt_config.args
|
||||
try:
|
||||
d = packet.get("decoded")
|
||||
logging.debug(f"in onReceive() d:{d}")
|
||||
logger.debug(f"in onReceive() d:{d}")
|
||||
|
||||
# Exit once we receive a reply
|
||||
if (
|
||||
@@ -101,7 +104,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC) -> None: # pylint: disable=W0
|
||||
def checkChannel(interface: MeshInterface, channelIndex: int) -> bool:
|
||||
"""Given an interface and channel index, return True if that channel is non-disabled on the local node"""
|
||||
ch = interface.localNode.getChannelByChannelIndex(channelIndex)
|
||||
logging.debug(f"ch:{ch}")
|
||||
logger.debug(f"ch:{ch}")
|
||||
return ch and ch.role != channel_pb2.Channel.Role.DISABLED
|
||||
|
||||
|
||||
@@ -114,7 +117,7 @@ def getPref(node, comp_name) -> bool:
|
||||
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)}")
|
||||
logger.debug(f"{str(config_type.name)}.{uni_name}: {str(pref_value)}")
|
||||
|
||||
name = splitCompoundName(comp_name)
|
||||
wholeField = name[0] == name[1] # We want the whole field
|
||||
@@ -123,8 +126,8 @@ def getPref(node, comp_name) -> bool:
|
||||
# Note: protobufs has the keys in snake_case, so snake internally
|
||||
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"use camel:{mt_config.camel_case}")
|
||||
logger.debug(f"snake_name:{snake_name} camel_name:{camel_name}")
|
||||
logger.debug(f"use camel:{mt_config.camel_case}")
|
||||
|
||||
# First validate the input
|
||||
localConfig = node.localConfig
|
||||
@@ -198,8 +201,8 @@ def setPref(config, comp_name, raw_val) -> bool:
|
||||
snake_name = meshtastic.util.camel_to_snake(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"camel_name:{camel_name}")
|
||||
logger.debug(f"snake_name:{snake_name}")
|
||||
logger.debug(f"camel_name:{camel_name}")
|
||||
|
||||
objDesc = config.DESCRIPTOR
|
||||
config_part = config
|
||||
@@ -223,7 +226,7 @@ def setPref(config, comp_name, raw_val) -> bool:
|
||||
val = meshtastic.util.fromStr(raw_val)
|
||||
else:
|
||||
val = raw_val
|
||||
logging.debug(f"valStr:{raw_val} val:{val}")
|
||||
logger.debug(f"valStr:{raw_val} val:{val}")
|
||||
|
||||
if snake_name == "wifi_psk" and len(str(raw_val)) < 8:
|
||||
print("Warning: network.wifi_psk must be 8 or more characters.")
|
||||
@@ -339,44 +342,60 @@ def onConnected(interface):
|
||||
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setFixedPosition(lat, lon, alt)
|
||||
|
||||
if args.set_owner or args.set_owner_short:
|
||||
if args.set_owner or args.set_owner_short or args.set_is_unmessageable:
|
||||
closeNow = True
|
||||
waitForAckNak = True
|
||||
|
||||
# Validate owner names before connecting to device
|
||||
if args.set_owner is not None:
|
||||
stripped_long_name = args.set_owner.strip()
|
||||
if not stripped_long_name:
|
||||
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
|
||||
long_name = args.set_owner.strip() if args.set_owner else None
|
||||
short_name = args.set_owner_short.strip() if args.set_owner_short else None
|
||||
|
||||
if hasattr(args, 'set_owner_short') and args.set_owner_short is not None:
|
||||
stripped_short_name = args.set_owner_short.strip()
|
||||
if not stripped_short_name:
|
||||
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
|
||||
if long_name is not None and not long_name:
|
||||
meshtastic.util.our_exit("ERROR: Long Name cannot be empty or contain only whitespace characters")
|
||||
|
||||
if args.set_owner and args.set_owner_short:
|
||||
print(f"Setting device owner to {args.set_owner} and short name to {args.set_owner_short}")
|
||||
elif args.set_owner:
|
||||
print(f"Setting device owner to {args.set_owner}")
|
||||
else: # short name only
|
||||
print(f"Setting device owner short to {args.set_owner_short}")
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(long_name=args.set_owner, short_name=args.set_owner_short)
|
||||
if short_name is not None and not short_name:
|
||||
meshtastic.util.our_exit("ERROR: Short Name cannot be empty or contain only whitespace characters")
|
||||
|
||||
if long_name and short_name:
|
||||
print(f"Setting device owner to {long_name} and short name to {short_name}")
|
||||
elif long_name:
|
||||
print(f"Setting device owner to {long_name}")
|
||||
elif short_name:
|
||||
print(f"Setting device owner short to {short_name}")
|
||||
|
||||
unmessagable = None
|
||||
if args.set_is_unmessageable is not None:
|
||||
unmessagable = (
|
||||
meshtastic.util.fromStr(args.set_is_unmessageable)
|
||||
if isinstance(args.set_is_unmessageable, str)
|
||||
else args.set_is_unmessageable
|
||||
)
|
||||
print(f"Setting device owner is_unmessageable to {unmessagable}")
|
||||
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
|
||||
long_name=long_name,
|
||||
short_name=short_name,
|
||||
is_unmessagable=unmessagable
|
||||
)
|
||||
|
||||
# TODO: add to export-config and configure
|
||||
if args.set_canned_message:
|
||||
closeNow = True
|
||||
waitForAckNak = True
|
||||
print(f"Setting canned plugin message to {args.set_canned_message}")
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).set_canned_message(
|
||||
args.set_canned_message
|
||||
)
|
||||
node = interface.getNode(args.dest, False, **getNode_kwargs)
|
||||
if node.module_available(mesh_pb2.CANNEDMSG_CONFIG):
|
||||
print(f"Setting canned plugin message to {args.set_canned_message}")
|
||||
node.set_canned_message(args.set_canned_message)
|
||||
else:
|
||||
print("Canned Message module is excluded by firmware; skipping set.")
|
||||
|
||||
# TODO: add to export-config and configure
|
||||
if args.set_ringtone:
|
||||
closeNow = True
|
||||
waitForAckNak = True
|
||||
print(f"Setting ringtone to {args.set_ringtone}")
|
||||
interface.getNode(args.dest, False, **getNode_kwargs).set_ringtone(args.set_ringtone)
|
||||
node = interface.getNode(args.dest, False, **getNode_kwargs)
|
||||
if node.module_available(mesh_pb2.EXTNOTIF_CONFIG):
|
||||
print(f"Setting ringtone to {args.set_ringtone}")
|
||||
node.set_ringtone(args.set_ringtone)
|
||||
else:
|
||||
print("External Notification is excluded by firmware; skipping ringtone set.")
|
||||
|
||||
if args.pos_fields:
|
||||
# If --pos-fields invoked with args, set position fields
|
||||
@@ -592,7 +611,7 @@ def onConnected(interface):
|
||||
time.sleep(1)
|
||||
if interface.gotResponse:
|
||||
break
|
||||
logging.debug(f"end of gpio_rd")
|
||||
logger.debug(f"end of gpio_rd")
|
||||
|
||||
if args.gpio_watch:
|
||||
bitmask = int(args.gpio_watch, 16)
|
||||
@@ -705,6 +724,16 @@ def onConnected(interface):
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channelUrl"])
|
||||
time.sleep(0.5)
|
||||
|
||||
if "canned_messages" in configuration:
|
||||
print("Setting canned message messages to", configuration["canned_messages"])
|
||||
interface.getNode(args.dest, **getNode_kwargs).set_canned_message(configuration["canned_messages"])
|
||||
time.sleep(0.5)
|
||||
|
||||
if "ringtone" in configuration:
|
||||
print("Setting ringtone to", configuration["ringtone"])
|
||||
interface.getNode(args.dest, **getNode_kwargs).set_ringtone(configuration["ringtone"])
|
||||
time.sleep(0.5)
|
||||
|
||||
if "location" in configuration:
|
||||
alt = 0
|
||||
lat = 0.0
|
||||
@@ -755,9 +784,20 @@ def onConnected(interface):
|
||||
if args.dest != BROADCAST_ADDR:
|
||||
print("Exporting configuration of remote nodes is not supported.")
|
||||
return
|
||||
# export the configuration (the opposite of '--configure')
|
||||
|
||||
closeNow = True
|
||||
export_config(interface)
|
||||
config_txt = export_config(interface)
|
||||
|
||||
if args.export_config == "-":
|
||||
# Output to stdout (preserves legacy use of `> file.yaml`)
|
||||
print(config_txt)
|
||||
else:
|
||||
try:
|
||||
with open(args.export_config, "w", encoding="utf-8") as f:
|
||||
f.write(config_txt)
|
||||
print(f"Exported configuration to {args.export_config}")
|
||||
except Exception as e:
|
||||
meshtastic.util.our_exit(f"ERROR: Failed to write config file: {e}")
|
||||
|
||||
if args.ch_set_url:
|
||||
closeNow = True
|
||||
@@ -928,12 +968,14 @@ def onConnected(interface):
|
||||
if args.get_canned_message:
|
||||
closeNow = True
|
||||
print("")
|
||||
interface.getNode(args.dest, **getNode_kwargs).get_canned_message()
|
||||
messages = interface.getNode(args.dest, **getNode_kwargs).get_canned_message()
|
||||
print(f"canned_plugin_message:{messages}")
|
||||
|
||||
if args.get_ringtone:
|
||||
closeNow = True
|
||||
print("")
|
||||
interface.getNode(args.dest, **getNode_kwargs).get_ringtone()
|
||||
ringtone = interface.getNode(args.dest, **getNode_kwargs).get_ringtone()
|
||||
print(f"ringtone:{ringtone}")
|
||||
|
||||
if args.info:
|
||||
print("")
|
||||
@@ -1025,7 +1067,7 @@ def onConnected(interface):
|
||||
# Even if others said we could close, stay open if the user asked for a tunnel
|
||||
closeNow = False
|
||||
if interface.noProto:
|
||||
logging.warning(f"Not starting Tunnel - disabled by noProto")
|
||||
logger.warning(f"Not starting Tunnel - disabled by noProto")
|
||||
else:
|
||||
if args.tunnel_net:
|
||||
tunnel.Tunnel(interface, subnet=args.tunnel_net)
|
||||
@@ -1088,15 +1130,38 @@ def subscribe() -> None:
|
||||
|
||||
# pub.subscribe(onNode, "meshtastic.node")
|
||||
|
||||
def set_missing_flags_false(config_dict: dict, true_defaults: set[tuple[str, str]]) -> None:
|
||||
"""Ensure that missing default=True keys are present in the config_dict and set to False."""
|
||||
for path in true_defaults:
|
||||
d = config_dict
|
||||
for key in path[:-1]:
|
||||
if key not in d or not isinstance(d[key], dict):
|
||||
d[key] = {}
|
||||
d = d[key]
|
||||
if path[-1] not in d:
|
||||
d[path[-1]] = False
|
||||
|
||||
def export_config(interface) -> str:
|
||||
"""used in --export-config"""
|
||||
configObj = {}
|
||||
|
||||
# A list of configuration keys that should be set to False if they are missing
|
||||
true_defaults = {
|
||||
("bluetooth", "enabled"),
|
||||
("lora", "sx126xRxBoostedGain"),
|
||||
("lora", "txEnabled"),
|
||||
("lora", "usePreset"),
|
||||
("position", "positionBroadcastSmartEnabled"),
|
||||
("security", "serialEnabled"),
|
||||
("mqtt", "encryptionEnabled"),
|
||||
}
|
||||
|
||||
owner = interface.getLongName()
|
||||
owner_short = interface.getShortName()
|
||||
channel_url = interface.localNode.getURL()
|
||||
myinfo = interface.getMyNodeInfo()
|
||||
canned_messages = interface.getCannedMessage()
|
||||
ringtone = interface.getRingtone()
|
||||
pos = myinfo.get("position")
|
||||
lat = None
|
||||
lon = None
|
||||
@@ -1115,6 +1180,10 @@ def export_config(interface) -> str:
|
||||
configObj["channelUrl"] = channel_url
|
||||
else:
|
||||
configObj["channel_url"] = channel_url
|
||||
if canned_messages:
|
||||
configObj["canned_messages"] = canned_messages
|
||||
if ringtone:
|
||||
configObj["ringtone"] = ringtone
|
||||
# lat and lon don't make much sense without the other (so fill with 0s), and alt isn't meaningful without both
|
||||
if lat or lon:
|
||||
configObj["location"] = {"lat": lat or float(0), "lon": lon or float(0)}
|
||||
@@ -1145,6 +1214,8 @@ def export_config(interface) -> str:
|
||||
else:
|
||||
configObj["config"] = config
|
||||
|
||||
set_missing_flags_false(configObj["config"], true_defaults)
|
||||
|
||||
module_config = MessageToDict(interface.localNode.moduleConfig)
|
||||
if module_config:
|
||||
# Convert inner keys to correct snake/camelCase
|
||||
@@ -1160,7 +1231,6 @@ def export_config(interface) -> str:
|
||||
config_txt = "# start of Meshtastic configure yaml\n" #checkme - "config" (now changed to config_out)
|
||||
#was used as a string here and a Dictionary above
|
||||
config_txt += yaml.dump(configObj)
|
||||
print(config_txt)
|
||||
return config_txt
|
||||
|
||||
|
||||
@@ -1188,14 +1258,14 @@ def create_power_meter():
|
||||
meter = SimPowerSupply()
|
||||
|
||||
if meter and v:
|
||||
logging.info(f"Setting power supply to {v} volts")
|
||||
logger.info(f"Setting power supply to {v} volts")
|
||||
meter.v = v
|
||||
meter.powerOn()
|
||||
|
||||
if args.power_wait:
|
||||
input("Powered on, press enter to continue...")
|
||||
else:
|
||||
logging.info("Powered-on, waiting for device to boot")
|
||||
logger.info("Powered-on, waiting for device to boot")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
@@ -1209,6 +1279,10 @@ def common():
|
||||
format="%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s",
|
||||
)
|
||||
|
||||
# set all meshtastic loggers to DEBUG
|
||||
if not (args.debug or args.listen) and args.debuglib:
|
||||
logging.getLogger('meshtastic').setLevel(logging.DEBUG)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help(sys.stderr)
|
||||
meshtastic.util.our_exit("", 1)
|
||||
@@ -1250,7 +1324,7 @@ def common():
|
||||
args.seriallog = "none" # assume no debug output in this case
|
||||
|
||||
if args.deprecated is not None:
|
||||
logging.error(
|
||||
logger.error(
|
||||
"This option has been deprecated, see help below for the correct replacement..."
|
||||
)
|
||||
parser.print_help(sys.stderr)
|
||||
@@ -1269,10 +1343,10 @@ def common():
|
||||
logfile = sys.stdout
|
||||
elif args.seriallog == "none":
|
||||
args.seriallog = None
|
||||
logging.debug("Not logging serial output")
|
||||
logger.debug("Not logging serial output")
|
||||
logfile = None
|
||||
else:
|
||||
logging.info(f"Logging serial output to {args.seriallog}")
|
||||
logger.info(f"Logging serial output to {args.seriallog}")
|
||||
# Note: using "line buffering"
|
||||
# pylint: disable=R1732
|
||||
logfile = open(args.seriallog, "w+", buffering=1, encoding="utf8")
|
||||
@@ -1280,7 +1354,7 @@ def common():
|
||||
|
||||
subscribe()
|
||||
if args.ble_scan:
|
||||
logging.debug("BLE scan starting")
|
||||
logger.debug("BLE scan starting")
|
||||
for x in BLEInterface.scan():
|
||||
print(f"Found: name='{x.name}' address='{x.address}'")
|
||||
meshtastic.util.our_exit("BLE scan finished", 0)
|
||||
@@ -1371,7 +1445,7 @@ def common():
|
||||
while True:
|
||||
time.sleep(1000)
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Exiting due to keyboard interrupt")
|
||||
logger.info("Exiting due to keyboard interrupt")
|
||||
|
||||
# don't call exit, background threads might be running still
|
||||
# sys.exit(0)
|
||||
@@ -1460,8 +1534,10 @@ def addImportExportArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar
|
||||
)
|
||||
group.add_argument(
|
||||
"--export-config",
|
||||
help="Export the configuration in yaml(.yml) format.",
|
||||
action="store_true",
|
||||
nargs="?",
|
||||
const="-", # default to "-" if no value provided
|
||||
metavar="FILE",
|
||||
help="Export device config as YAML (to stdout if no file given)"
|
||||
)
|
||||
return parser
|
||||
|
||||
@@ -1583,6 +1659,11 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"--set-ham", help="Set licensed Ham ID and turn off encryption", action="store"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--set-is-unmessageable", "--set-is-unmessagable",
|
||||
help="Set if a node is messageable or not", action="store"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-set-url", "--seturl",
|
||||
help="Set all channels and set LoRa config from a supplied URL",
|
||||
@@ -1971,6 +2052,10 @@ def initParser():
|
||||
"--debug", help="Show API library debug log messages", action="store_true"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--debuglib", help="Show only API library debug log messages", action="store_true"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--test",
|
||||
help="Run stress test against all connected Meshtastic devices",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from typing import cast, List
|
||||
|
||||
import dash_bootstrap_components as dbc # type: ignore[import-untyped]
|
||||
@@ -143,8 +144,8 @@ def create_dash(slog_path: str) -> Dash:
|
||||
"""
|
||||
app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||
|
||||
dpwr = read_pandas(f"{slog_path}/power.feather")
|
||||
dslog = read_pandas(f"{slog_path}/slog.feather")
|
||||
dpwr = read_pandas(os.path.join(slog_path, "power.feather"))
|
||||
dslog = read_pandas(os.path.join(slog_path, "slog.feather"))
|
||||
|
||||
pmon_raises = get_pmon_raises(dslog)
|
||||
|
||||
@@ -190,7 +191,7 @@ def main():
|
||||
parser = create_argparser()
|
||||
args = parser.parse_args()
|
||||
if not args.slog:
|
||||
args.slog = f"{root_dir()}/latest"
|
||||
args.slog = os.path.join(root_dir(), "latest")
|
||||
|
||||
app = create_dash(slog_path=args.slog)
|
||||
port = 8051
|
||||
|
||||
@@ -23,6 +23,7 @@ FROMRADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
|
||||
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
|
||||
LEGACY_LOGRADIO_UUID = "6c6fd238-78fa-436b-aacf-15c5be1ef2e2"
|
||||
LOGRADIO_UUID = "5a3d6e49-06e6-4423-9944-e9de8cdf9547"
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BLEInterface(MeshInterface):
|
||||
@@ -44,19 +45,19 @@ class BLEInterface(MeshInterface):
|
||||
|
||||
self.should_read = False
|
||||
|
||||
logging.debug("Threads starting")
|
||||
logger.debug("Threads starting")
|
||||
self._want_receive = True
|
||||
self._receiveThread: Optional[Thread] = Thread(
|
||||
target=self._receiveFromRadioImpl, name="BLEReceive", daemon=True
|
||||
)
|
||||
self._receiveThread.start()
|
||||
logging.debug("Threads running")
|
||||
logger.debug("Threads running")
|
||||
|
||||
self.client: Optional[BLEClient] = None
|
||||
try:
|
||||
logging.debug(f"BLE connecting to: {address if address else 'any'}")
|
||||
logger.debug(f"BLE connecting to: {address if address else 'any'}")
|
||||
self.client = self.connect(address)
|
||||
logging.debug("BLE connected")
|
||||
logger.debug("BLE connected")
|
||||
except BLEInterface.BLEError as e:
|
||||
self.close()
|
||||
raise e
|
||||
@@ -69,13 +70,13 @@ class BLEInterface(MeshInterface):
|
||||
if self.client.has_characteristic(LOGRADIO_UUID):
|
||||
self.client.start_notify(LOGRADIO_UUID, self.log_radio_handler)
|
||||
|
||||
logging.debug("Mesh configure starting")
|
||||
logger.debug("Mesh configure starting")
|
||||
self._startConfig()
|
||||
if not self.noProto:
|
||||
self._waitConnected(timeout=60.0)
|
||||
self.waitForConfig()
|
||||
|
||||
logging.debug("Register FROMNUM notify callback")
|
||||
logger.debug("Register FROMNUM notify callback")
|
||||
self.client.start_notify(FROMNUM_UUID, self.from_num_handler)
|
||||
|
||||
# We MUST run atexit (if we can) because otherwise (at least on linux) the BLE device is not disconnected
|
||||
@@ -99,7 +100,7 @@ class BLEInterface(MeshInterface):
|
||||
Note: this method does not need to be async because it is just setting a bool.
|
||||
"""
|
||||
from_num = struct.unpack("<I", bytes(b))[0]
|
||||
logging.debug(f"FROMNUM notify: {from_num}")
|
||||
logger.debug(f"FROMNUM notify: {from_num}")
|
||||
self.should_read = True
|
||||
|
||||
async def log_radio_handler(self, _, b): # pylint: disable=C0116
|
||||
@@ -114,7 +115,7 @@ class BLEInterface(MeshInterface):
|
||||
)
|
||||
self._handleLogLine(message)
|
||||
except google.protobuf.message.DecodeError:
|
||||
logging.warning("Malformed LogRecord received. Skipping.")
|
||||
logger.warning("Malformed LogRecord received. Skipping.")
|
||||
|
||||
async def legacy_log_radio_handler(self, _, b): # pylint: disable=C0116
|
||||
log_radio = b.decode("utf-8").replace("\n", "")
|
||||
@@ -124,7 +125,7 @@ class BLEInterface(MeshInterface):
|
||||
def scan() -> List[BLEDevice]:
|
||||
"""Scan for available BLE devices."""
|
||||
with BLEClient() as client:
|
||||
logging.info("Scanning for BLE devices (takes 10 seconds)...")
|
||||
logger.info("Scanning for BLE devices (takes 10 seconds)...")
|
||||
response = client.discover(
|
||||
timeout=10, return_adv=True, service_uuids=[SERVICE_UUID]
|
||||
)
|
||||
@@ -186,19 +187,19 @@ class BLEInterface(MeshInterface):
|
||||
retries: int = 0
|
||||
while self._want_receive:
|
||||
if self.client is None:
|
||||
logging.debug(f"BLE client is None, shutting down")
|
||||
logger.debug(f"BLE client is None, shutting down")
|
||||
self._want_receive = False
|
||||
continue
|
||||
try:
|
||||
b = bytes(self.client.read_gatt_char(FROMRADIO_UUID))
|
||||
except BleakDBusError as e:
|
||||
# Device disconnected probably, so end our read loop immediately
|
||||
logging.debug(f"Device disconnected, shutting down {e}")
|
||||
logger.debug(f"Device disconnected, shutting down {e}")
|
||||
self._want_receive = False
|
||||
except BleakError as e:
|
||||
# We were definitely disconnected
|
||||
if "Not connected" in str(e):
|
||||
logging.debug(f"Device disconnected, shutting down {e}")
|
||||
logger.debug(f"Device disconnected, shutting down {e}")
|
||||
self._want_receive = False
|
||||
else:
|
||||
raise BLEInterface.BLEError("Error reading BLE") from e
|
||||
@@ -208,7 +209,7 @@ class BLEInterface(MeshInterface):
|
||||
retries += 1
|
||||
continue
|
||||
break
|
||||
logging.debug(f"FROMRADIO read: {b.hex()}")
|
||||
logger.debug(f"FROMRADIO read: {b.hex()}")
|
||||
self._handleFromRadio(b)
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
@@ -216,7 +217,7 @@ class BLEInterface(MeshInterface):
|
||||
def _sendToRadioImpl(self, toRadio) -> None:
|
||||
b: bytes = toRadio.SerializeToString()
|
||||
if b and self.client: # we silently ignore writes while we are shutting down
|
||||
logging.debug(f"TORADIO write: {b.hex()}")
|
||||
logger.debug(f"TORADIO write: {b.hex()}")
|
||||
try:
|
||||
self.client.write_gatt_char(
|
||||
TORADIO_UUID, b, response=True
|
||||
@@ -234,7 +235,7 @@ class BLEInterface(MeshInterface):
|
||||
try:
|
||||
MeshInterface.close(self)
|
||||
except Exception as e:
|
||||
logging.error(f"Error closing mesh interface: {e}")
|
||||
logger.error(f"Error closing mesh interface: {e}")
|
||||
|
||||
if self._want_receive:
|
||||
self._want_receive = False # Tell the thread we want it to stop
|
||||
@@ -263,7 +264,7 @@ class BLEClient:
|
||||
self._eventThread.start()
|
||||
|
||||
if not address:
|
||||
logging.debug("No address provided - only discover method will work.")
|
||||
logger.debug("No address provided - only discover method will work.")
|
||||
return
|
||||
|
||||
self.bleak_client = BleakClient(address, **kwargs)
|
||||
|
||||
@@ -17,6 +17,7 @@ from decimal import Decimal
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
import google.protobuf.json_format
|
||||
|
||||
try:
|
||||
import print_color # type: ignore[import-untyped]
|
||||
except ImportError as e:
|
||||
@@ -46,6 +47,7 @@ from meshtastic.util import (
|
||||
stripnl,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _timeago(delta_secs: int) -> str:
|
||||
"""Convert a number of seconds in the past into a short, friendly string
|
||||
@@ -149,11 +151,11 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
def __exit__(self, exc_type, exc_value, trace):
|
||||
if exc_type is not None and exc_value is not None:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"An exception of type {exc_type} with value {exc_value} has occurred"
|
||||
)
|
||||
if trace is not None:
|
||||
logging.error(f"Traceback: {trace}")
|
||||
logger.error(f"Traceback:\n{''.join(traceback.format_tb(trace))}")
|
||||
self.close()
|
||||
|
||||
@staticmethod
|
||||
@@ -281,7 +283,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
def getNestedValue(node_dict: Dict[str, Any], key_path: str) -> Any:
|
||||
if key_path.index(".") < 0:
|
||||
logging.debug("getNestedValue was called without a nested path.")
|
||||
logger.debug("getNestedValue was called without a nested path.")
|
||||
return None
|
||||
keys = key_path.split(".")
|
||||
value: Optional[Union[str, dict]] = node_dict
|
||||
@@ -304,7 +306,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
rows: List[Dict[str, Any]] = []
|
||||
if self.nodesByNum:
|
||||
logging.debug(f"self.nodes:{self.nodes}")
|
||||
logger.debug(f"self.nodes:{self.nodes}")
|
||||
for node in self.nodesByNum.values():
|
||||
if not includeSelf and node["num"] == self.localNode.nodeNum:
|
||||
continue
|
||||
@@ -383,7 +385,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
n = meshtastic.node.Node(self, nodeId, timeout=timeout)
|
||||
# Only request device settings and channel info when necessary
|
||||
if requestChannels:
|
||||
logging.debug("About to requestChannels")
|
||||
logger.debug("About to requestChannels")
|
||||
n.requestChannels()
|
||||
retries_left = requestChannelAttempts
|
||||
last_index: int = 0
|
||||
@@ -411,7 +413,8 @@ class MeshInterface: # pylint: disable=R0902
|
||||
wantResponse: bool = False,
|
||||
onResponse: Optional[Callable[[dict], Any]] = None,
|
||||
channelIndex: int = 0,
|
||||
portNum: portnums_pb2.PortNum.ValueType = portnums_pb2.PortNum.TEXT_MESSAGE_APP
|
||||
portNum: portnums_pb2.PortNum.ValueType = portnums_pb2.PortNum.TEXT_MESSAGE_APP,
|
||||
replyId: Optional[int]=None,
|
||||
):
|
||||
"""Send a utf8 string to some other node, if the node has a display it
|
||||
will also be shown on the device.
|
||||
@@ -428,6 +431,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
send an application layer response
|
||||
portNum -- the application portnum (similar to IP port numbers)
|
||||
of the destination, see portnums.proto for a list
|
||||
replyId -- the ID of the message that this packet is a response to
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet
|
||||
and can be used to track future message acks/naks.
|
||||
@@ -441,6 +445,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
wantResponse=wantResponse,
|
||||
onResponse=onResponse,
|
||||
channelIndex=channelIndex,
|
||||
replyId=replyId
|
||||
)
|
||||
|
||||
|
||||
@@ -451,7 +456,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
onResponse: Optional[Callable[[dict], Any]] = None,
|
||||
channelIndex: int = 0,
|
||||
):
|
||||
"""Send an alert text to some other node. This is similar to a text message,
|
||||
"""Send an alert text to some other node. This is similar to a text message,
|
||||
but carries a higher priority and is capable of generating special notifications
|
||||
on certain clients.
|
||||
|
||||
@@ -503,6 +508,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
pkiEncrypted: Optional[bool]=False,
|
||||
publicKey: Optional[bytes]=None,
|
||||
priority: mesh_pb2.MeshPacket.Priority.ValueType=mesh_pb2.MeshPacket.Priority.RELIABLE,
|
||||
replyId: Optional[int]=None,
|
||||
): # pylint: disable=R0913
|
||||
"""Send a data packet to some other node
|
||||
|
||||
@@ -527,17 +533,18 @@ class MeshInterface: # pylint: disable=R0902
|
||||
will implicitly be true.
|
||||
channelIndex -- channel number to use
|
||||
hopLimit -- hop limit to use
|
||||
replyId -- the ID of the message that this packet is a response to
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet
|
||||
and can be used to track future message acks/naks.
|
||||
"""
|
||||
|
||||
if getattr(data, "SerializeToString", None):
|
||||
logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
|
||||
logger.debug(f"Serializing protobuf as data: {stripnl(data)}")
|
||||
data = data.SerializeToString()
|
||||
|
||||
logging.debug(f"len(data): {len(data)}")
|
||||
logging.debug(
|
||||
logger.debug(f"len(data): {len(data)}")
|
||||
logger.debug(
|
||||
f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}"
|
||||
)
|
||||
if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
|
||||
@@ -554,11 +561,13 @@ class MeshInterface: # pylint: disable=R0902
|
||||
meshPacket.decoded.portnum = portNum
|
||||
meshPacket.decoded.want_response = wantResponse
|
||||
meshPacket.id = self._generatePacketId()
|
||||
if replyId is not None:
|
||||
meshPacket.decoded.reply_id = replyId
|
||||
if priority is not None:
|
||||
meshPacket.priority = priority
|
||||
|
||||
if onResponse is not None:
|
||||
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
||||
logger.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
||||
self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted)
|
||||
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit, pkiEncrypted=pkiEncrypted, publicKey=publicKey)
|
||||
return p
|
||||
@@ -585,15 +594,15 @@ class MeshInterface: # pylint: disable=R0902
|
||||
p = mesh_pb2.Position()
|
||||
if latitude != 0.0:
|
||||
p.latitude_i = int(latitude / 1e-7)
|
||||
logging.debug(f"p.latitude_i:{p.latitude_i}")
|
||||
logger.debug(f"p.latitude_i:{p.latitude_i}")
|
||||
|
||||
if longitude != 0.0:
|
||||
p.longitude_i = int(longitude / 1e-7)
|
||||
logging.debug(f"p.longitude_i:{p.longitude_i}")
|
||||
logger.debug(f"p.longitude_i:{p.longitude_i}")
|
||||
|
||||
if altitude != 0:
|
||||
p.altitude = int(altitude)
|
||||
logging.debug(f"p.altitude:{p.altitude}")
|
||||
logger.debug(f"p.altitude:{p.altitude}")
|
||||
|
||||
if wantResponse:
|
||||
onResponse = self.onResponsePosition
|
||||
@@ -844,15 +853,15 @@ class MeshInterface: # pylint: disable=R0902
|
||||
# same algorithm as https://github.com/meshtastic/js/blob/715e35d2374276a43ffa93c628e3710875d43907/src/meshDevice.ts#L791
|
||||
seed = secrets.randbits(32)
|
||||
w.id = math.floor(seed * math.pow(2, -32) * 1e9)
|
||||
logging.debug(f"w.id:{w.id}")
|
||||
logger.debug(f"w.id:{w.id}")
|
||||
else:
|
||||
w.id = waypoint_id
|
||||
if latitude != 0.0:
|
||||
w.latitude_i = int(latitude * 1e7)
|
||||
logging.debug(f"w.latitude_i:{w.latitude_i}")
|
||||
logger.debug(f"w.latitude_i:{w.latitude_i}")
|
||||
if longitude != 0.0:
|
||||
w.longitude_i = int(longitude * 1e7)
|
||||
logging.debug(f"w.longitude_i:{w.longitude_i}")
|
||||
logger.debug(f"w.longitude_i:{w.longitude_i}")
|
||||
|
||||
if wantResponse:
|
||||
onResponse = self.onResponseWaypoint
|
||||
@@ -884,7 +893,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
Send a waypoint deletion packet to some other node (normally a broadcast)
|
||||
|
||||
NB: The id must be the waypoint's id and not the id of the packet creation.
|
||||
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet and
|
||||
can be used to track future message acks/naks.
|
||||
"""
|
||||
@@ -967,7 +976,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
else:
|
||||
nodeNum = node["num"]
|
||||
else:
|
||||
logging.warning("Warning: There were no self.nodes.")
|
||||
logger.warning("Warning: There were no self.nodes.")
|
||||
|
||||
meshPacket.to = nodeNum
|
||||
meshPacket.want_ack = wantAck
|
||||
@@ -991,11 +1000,11 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
toRadio.packet.CopyFrom(meshPacket)
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
"Not sending packet because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
logging.debug(f"Sending packet: {stripnl(meshPacket)}")
|
||||
logger.debug(f"Sending packet: {stripnl(meshPacket)}")
|
||||
self._sendToRadio(toRadio)
|
||||
return meshPacket
|
||||
|
||||
@@ -1046,7 +1055,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
"""Get info about my node."""
|
||||
if self.myInfo is None or self.nodesByNum is None:
|
||||
return None
|
||||
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
|
||||
logger.debug(f"self.nodesByNum:{self.nodesByNum}")
|
||||
return self.nodesByNum.get(self.myInfo.my_node_num)
|
||||
|
||||
def getMyUser(self):
|
||||
@@ -1077,6 +1086,20 @@ class MeshInterface: # pylint: disable=R0902
|
||||
return user.get("publicKey", None)
|
||||
return None
|
||||
|
||||
def getCannedMessage(self):
|
||||
"""Get canned message"""
|
||||
node = self.localNode
|
||||
if node is not None:
|
||||
return node.get_canned_message()
|
||||
return None
|
||||
|
||||
def getRingtone(self):
|
||||
"""Get ringtone"""
|
||||
node = self.localNode
|
||||
if node is not None:
|
||||
return node.get_ringtone()
|
||||
return None
|
||||
|
||||
def _waitConnected(self, timeout=30.0):
|
||||
"""Block until the initial node db download is complete, or timeout
|
||||
and raise an exception"""
|
||||
@@ -1122,7 +1145,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
def callback():
|
||||
self.heartbeatTimer = None
|
||||
interval = 300
|
||||
logging.debug(f"Sending heartbeat, interval {interval} seconds")
|
||||
logger.debug(f"Sending heartbeat, interval {interval} seconds")
|
||||
self.heartbeatTimer = threading.Timer(interval, callback)
|
||||
self.heartbeatTimer.start()
|
||||
self.sendHeartbeat()
|
||||
@@ -1180,11 +1203,11 @@ class MeshInterface: # pylint: disable=R0902
|
||||
def _sendToRadio(self, toRadio: mesh_pb2.ToRadio) -> None:
|
||||
"""Send a ToRadio protobuf to the device"""
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
"Not sending packet because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
# logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
|
||||
# logger.debug(f"Sending toRadio: {stripnl(toRadio)}")
|
||||
|
||||
if not toRadio.HasField("packet"):
|
||||
# not a meshpacket -- send immediately, give queue a chance,
|
||||
@@ -1197,38 +1220,38 @@ class MeshInterface: # pylint: disable=R0902
|
||||
resentQueue = collections.OrderedDict()
|
||||
|
||||
while self.queue:
|
||||
# logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
# logger.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
while not self._queueHasFreeSpace():
|
||||
logging.debug("Waiting for free space in TX Queue")
|
||||
logger.debug("Waiting for free space in TX Queue")
|
||||
time.sleep(0.5)
|
||||
try:
|
||||
toResend = self.queue.popitem(last=False)
|
||||
except KeyError:
|
||||
break
|
||||
packetId, packet = toResend
|
||||
# logging.warn(f"packet: {packetId:08x} {packet}")
|
||||
# logger.warn(f"packet: {packetId:08x} {packet}")
|
||||
resentQueue[packetId] = packet
|
||||
if packet is False:
|
||||
continue
|
||||
self._queueClaim()
|
||||
if packet != toRadio:
|
||||
logging.debug(f"Resending packet ID {packetId:08x} {packet}")
|
||||
logger.debug(f"Resending packet ID {packetId:08x} {packet}")
|
||||
self._sendToRadioImpl(packet)
|
||||
|
||||
# logging.warn("resentQueue: " + " ".join(f'{k:08x}' for k in resentQueue))
|
||||
# logger.warn("resentQueue: " + " ".join(f'{k:08x}' for k in resentQueue))
|
||||
for packetId, packet in resentQueue.items():
|
||||
if (
|
||||
self.queue.pop(packetId, False) is False
|
||||
): # Packet got acked under us
|
||||
logging.debug(f"packet {packetId:08x} got acked under us")
|
||||
logger.debug(f"packet {packetId:08x} got acked under us")
|
||||
continue
|
||||
if packet:
|
||||
self.queue[packetId] = packet
|
||||
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
# logger.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
|
||||
def _sendToRadioImpl(self, toRadio: mesh_pb2.ToRadio) -> None:
|
||||
"""Send a ToRadio protobuf to the device"""
|
||||
logging.error(f"Subclass must provide toradio: {toRadio}")
|
||||
logger.error(f"Subclass must provide toradio: {toRadio}")
|
||||
|
||||
def _handleConfigComplete(self) -> None:
|
||||
"""
|
||||
@@ -1244,22 +1267,22 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
def _handleQueueStatusFromRadio(self, queueStatus) -> None:
|
||||
self.queueStatus = queueStatus
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
|
||||
)
|
||||
|
||||
if queueStatus.res:
|
||||
return
|
||||
|
||||
# logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
# logger.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
justQueued = self.queue.pop(queueStatus.mesh_packet_id, None)
|
||||
|
||||
if justQueued is None and queueStatus.mesh_packet_id != 0:
|
||||
self.queue[queueStatus.mesh_packet_id] = False
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"Reply for unexpected packet ID {queueStatus.mesh_packet_id:08x}"
|
||||
)
|
||||
# logging.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
# logger.warn("queue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||
|
||||
def _handleFromRadio(self, fromRadioBytes):
|
||||
"""
|
||||
@@ -1267,30 +1290,30 @@ class MeshInterface: # pylint: disable=R0902
|
||||
|
||||
Called by subclasses."""
|
||||
fromRadio = mesh_pb2.FromRadio()
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}"
|
||||
)
|
||||
try:
|
||||
fromRadio.ParseFromString(fromRadioBytes)
|
||||
except Exception as ex:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Error while parsing FromRadio bytes:{fromRadioBytes} {ex}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
raise ex
|
||||
asDict = google.protobuf.json_format.MessageToDict(fromRadio)
|
||||
logging.debug(f"Received from radio: {fromRadio}")
|
||||
logger.debug(f"Received from radio: {fromRadio}")
|
||||
if fromRadio.HasField("my_info"):
|
||||
self.myInfo = fromRadio.my_info
|
||||
self.localNode.nodeNum = self.myInfo.my_node_num
|
||||
logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
|
||||
logger.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
|
||||
|
||||
elif fromRadio.HasField("metadata"):
|
||||
self.metadata = fromRadio.metadata
|
||||
logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
|
||||
logger.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
|
||||
|
||||
elif fromRadio.HasField("node_info"):
|
||||
logging.debug(f"Received nodeinfo: {asDict['nodeInfo']}")
|
||||
logger.debug(f"Received nodeinfo: {asDict['nodeInfo']}")
|
||||
|
||||
node = self._getOrCreateByNum(asDict["nodeInfo"]["num"])
|
||||
node.update(asDict["nodeInfo"])
|
||||
@@ -1298,7 +1321,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
newpos = self._fixupPosition(node["position"])
|
||||
node["position"] = newpos
|
||||
except:
|
||||
logging.debug("Node without position")
|
||||
logger.debug("Node without position")
|
||||
|
||||
# no longer necessary since we're mutating directly in nodesByNum via _getOrCreateByNum
|
||||
# self.nodesByNum[node["num"]] = node
|
||||
@@ -1313,7 +1336,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
elif fromRadio.config_complete_id == self.configId:
|
||||
# we ignore the config_complete_id, it is unneeded for our
|
||||
# stream API fromRadio.config_complete_id
|
||||
logging.debug(f"Config complete ID {self.configId}")
|
||||
logger.debug(f"Config complete ID {self.configId}")
|
||||
self._handleConfigComplete()
|
||||
elif fromRadio.HasField("channel"):
|
||||
self._handleChannel(fromRadio.channel)
|
||||
@@ -1323,6 +1346,14 @@ class MeshInterface: # pylint: disable=R0902
|
||||
self._handleLogRecord(fromRadio.log_record)
|
||||
elif fromRadio.HasField("queueStatus"):
|
||||
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
|
||||
elif fromRadio.HasField("clientNotification"):
|
||||
publishingThread.queueWork(
|
||||
lambda: pub.sendMessage(
|
||||
"meshtastic.clientNotification",
|
||||
notification=fromRadio.clientNotification,
|
||||
interface=self,
|
||||
)
|
||||
)
|
||||
|
||||
elif fromRadio.HasField("mqttClientProxyMessage"):
|
||||
publishingThread.queueWork(
|
||||
@@ -1420,7 +1451,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
)
|
||||
|
||||
else:
|
||||
logging.debug("Unexpected FromRadio payload")
|
||||
logger.debug("Unexpected FromRadio payload")
|
||||
|
||||
def _fixupPosition(self, position: Dict) -> Dict:
|
||||
"""Convert integer lat/lon into floats
|
||||
@@ -1454,7 +1485,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
try:
|
||||
return self.nodesByNum[num]["user"]["id"] # type: ignore[index]
|
||||
except:
|
||||
logging.debug(f"Node {num} not found for fromId")
|
||||
logger.debug(f"Node {num} not found for fromId")
|
||||
return None
|
||||
|
||||
def _getOrCreateByNum(self, nodeNum):
|
||||
@@ -1510,7 +1541,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
# from might be missing if the nodenum was zero.
|
||||
if not hack and "from" not in asDict:
|
||||
asDict["from"] = 0
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Device returned a packet we sent, ignoring: {stripnl(asDict)}"
|
||||
)
|
||||
print(
|
||||
@@ -1524,11 +1555,11 @@ class MeshInterface: # pylint: disable=R0902
|
||||
try:
|
||||
asDict["fromId"] = self._nodeNumToId(asDict["from"], False)
|
||||
except Exception as ex:
|
||||
logging.warning(f"Not populating fromId {ex}")
|
||||
logger.warning(f"Not populating fromId {ex}")
|
||||
try:
|
||||
asDict["toId"] = self._nodeNumToId(asDict["to"])
|
||||
except Exception as ex:
|
||||
logging.warning(f"Not populating toId {ex}")
|
||||
logger.warning(f"Not populating toId {ex}")
|
||||
|
||||
# We could provide our objects as DotMaps - which work with . notation or as dictionaries
|
||||
# asObj = DotMap(asDict)
|
||||
@@ -1548,7 +1579,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
# it to prevent confusion
|
||||
if "portnum" not in decoded:
|
||||
decoded["portnum"] = portnum
|
||||
logging.warning(f"portnum was not in decoded. Setting to:{portnum}")
|
||||
logger.warning(f"portnum was not in decoded. Setting to:{portnum}")
|
||||
else:
|
||||
portnum = decoded["portnum"]
|
||||
|
||||
@@ -1580,7 +1611,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
# Is this message in response to a request, if so, look for a handler
|
||||
requestId = decoded.get("requestId")
|
||||
if requestId is not None:
|
||||
logging.debug(f"Got a response for requestId {requestId}")
|
||||
logger.debug(f"Got a response for requestId {requestId}")
|
||||
# We ignore ACK packets unless the callback is named `onAckNak`
|
||||
# or the handler is set as ackPermitted, but send NAKs and
|
||||
# other, data-containing responses to the handlers
|
||||
@@ -1597,12 +1628,12 @@ class MeshInterface: # pylint: disable=R0902
|
||||
or handler.ackPermitted
|
||||
):
|
||||
handler = self.responseHandlers.pop(requestId, None)
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"Calling response handler for requestId {requestId}"
|
||||
)
|
||||
handler.callback(asDict)
|
||||
|
||||
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
||||
logger.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
||||
publishingThread.queueWork(
|
||||
lambda: pub.sendMessage(topic, packet=asDict, interface=self)
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ from meshtastic.util import (
|
||||
message_to_json,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Node:
|
||||
"""A model of a (local or remote) node in the mesh
|
||||
@@ -51,11 +52,21 @@ class Node:
|
||||
r += ")"
|
||||
return r
|
||||
|
||||
def module_available(self, excluded_bit: int) -> bool:
|
||||
"""Check DeviceMetadata.excluded_modules to see if a module is available."""
|
||||
meta = getattr(self.iface, "metadata", None)
|
||||
if meta is None:
|
||||
return True
|
||||
try:
|
||||
return (meta.excluded_modules & excluded_bit) == 0
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
def showChannels(self):
|
||||
"""Show human readable description of our channels."""
|
||||
print("Channels:")
|
||||
if self.channels:
|
||||
logging.debug(f"self.channels:{self.channels}")
|
||||
logger.debug(f"self.channels:{self.channels}")
|
||||
for c in self.channels:
|
||||
cStr = message_to_json(c.settings)
|
||||
# don't show disabled channels
|
||||
@@ -88,7 +99,7 @@ class Node:
|
||||
|
||||
def requestChannels(self, startingIndex: int = 0):
|
||||
"""Send regular MeshPackets to ask channels."""
|
||||
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
|
||||
logger.debug(f"requestChannels for nodeNum:{self.nodeNum}")
|
||||
# only initialize if we're starting out fresh
|
||||
if startingIndex == 0:
|
||||
self.channels = None
|
||||
@@ -97,7 +108,7 @@ class Node:
|
||||
|
||||
def onResponseRequestSettings(self, p):
|
||||
"""Handle the response packets for requesting settings _requestSettings()"""
|
||||
logging.debug(f"onResponseRequestSetting() p:{p}")
|
||||
logger.debug(f"onResponseRequestSetting() p:{p}")
|
||||
config_values = None
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -224,7 +235,7 @@ class Node:
|
||||
else:
|
||||
our_exit(f"Error: No valid config with name {config_name}")
|
||||
|
||||
logging.debug(f"Wrote: {config_name}")
|
||||
logger.debug(f"Wrote: {config_name}")
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
else:
|
||||
@@ -237,7 +248,7 @@ class Node:
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_channel.CopyFrom(self.channels[channelIndex])
|
||||
self._sendAdmin(p, adminIndex=adminIndex)
|
||||
logging.debug(f"Wrote channel {channelIndex}")
|
||||
logger.debug(f"Wrote channel {channelIndex}")
|
||||
|
||||
def getChannelByChannelIndex(self, channelIndex):
|
||||
"""Get channel by channelIndex
|
||||
@@ -298,9 +309,9 @@ class Node:
|
||||
return c.index
|
||||
return 0
|
||||
|
||||
def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False):
|
||||
def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False, is_unmessagable: Optional[bool]=None):
|
||||
"""Set device owner name"""
|
||||
logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
|
||||
logger.debug(f"in setOwner nodeNum:{self.nodeNum}")
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
|
||||
@@ -321,11 +332,14 @@ class Node:
|
||||
short_name = short_name[:nChars]
|
||||
print(f"Maximum is 4 characters, truncated to {short_name}")
|
||||
p.set_owner.short_name = short_name
|
||||
if is_unmessagable is not None:
|
||||
p.set_owner.is_unmessagable = is_unmessagable
|
||||
|
||||
# Note: These debug lines are used in unit tests
|
||||
logging.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:")
|
||||
logging.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:")
|
||||
logging.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}")
|
||||
logger.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:")
|
||||
logger.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:")
|
||||
logger.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}")
|
||||
logger.debug(f"p.set_owner.is_unmessagable:{p.set_owner.is_unmessagable}:")
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -408,7 +422,7 @@ class Node:
|
||||
ch.index = i
|
||||
ch.settings.CopyFrom(chs)
|
||||
self.channels[ch.index] = ch
|
||||
logging.debug(f"Channel i:{i} ch:{ch}")
|
||||
logger.debug(f"Channel i:{i} ch:{ch}")
|
||||
self.writeChannel(ch.index)
|
||||
i = i + 1
|
||||
|
||||
@@ -419,7 +433,7 @@ class Node:
|
||||
|
||||
def onResponseRequestRingtone(self, p):
|
||||
"""Handle the response packet for requesting ringtone part 1"""
|
||||
logging.debug(f"onResponseRequestRingtone() p:{p}")
|
||||
logger.debug(f"onResponseRequestRingtone() p:{p}")
|
||||
errorFound = False
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -432,12 +446,16 @@ class Node:
|
||||
self.ringtonePart = p["decoded"]["admin"][
|
||||
"raw"
|
||||
].get_ringtone_response
|
||||
logging.debug(f"self.ringtonePart:{self.ringtonePart}")
|
||||
logger.debug(f"self.ringtonePart:{self.ringtonePart}")
|
||||
self.gotResponse = True
|
||||
|
||||
def get_ringtone(self):
|
||||
"""Get the ringtone. Concatenate all pieces together and return a single string."""
|
||||
logging.debug(f"in get_ringtone()")
|
||||
logger.debug(f"in get_ringtone()")
|
||||
if not self.module_available(mesh_pb2.EXTNOTIF_CONFIG):
|
||||
logging.warning("External Notification module not present (excluded by firmware)")
|
||||
return None
|
||||
|
||||
if not self.ringtone:
|
||||
p1 = admin_pb2.AdminMessage()
|
||||
p1.get_ringtone_request = True
|
||||
@@ -448,18 +466,20 @@ class Node:
|
||||
while self.gotResponse is False:
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.debug(f"self.ringtone:{self.ringtone}")
|
||||
logger.debug(f"self.ringtone:{self.ringtone}")
|
||||
|
||||
self.ringtone = ""
|
||||
if self.ringtonePart:
|
||||
self.ringtone += self.ringtonePart
|
||||
|
||||
print(f"ringtone:{self.ringtone}")
|
||||
logging.debug(f"ringtone:{self.ringtone}")
|
||||
logger.debug(f"ringtone:{self.ringtone}")
|
||||
return self.ringtone
|
||||
|
||||
def set_ringtone(self, ringtone):
|
||||
"""Set the ringtone. The ringtone length must be less than 230 character."""
|
||||
if not self.module_available(mesh_pb2.EXTNOTIF_CONFIG):
|
||||
logging.warning("External Notification module not present (excluded by firmware)")
|
||||
return None
|
||||
|
||||
if len(ringtone) > 230:
|
||||
our_exit("Warning: The ringtone must be less than 230 characters.")
|
||||
@@ -479,7 +499,7 @@ class Node:
|
||||
if i == 0:
|
||||
p.set_ringtone_message = chunk
|
||||
|
||||
logging.debug(f"Setting ringtone '{chunk}' part {i+1}")
|
||||
logger.debug(f"Setting ringtone '{chunk}' part {i+1}")
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -489,7 +509,7 @@ class Node:
|
||||
|
||||
def onResponseRequestCannedMessagePluginMessageMessages(self, p):
|
||||
"""Handle the response packet for requesting canned message plugin message part 1"""
|
||||
logging.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}")
|
||||
logger.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}")
|
||||
errorFound = False
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -502,14 +522,17 @@ class Node:
|
||||
self.cannedPluginMessageMessages = p["decoded"]["admin"][
|
||||
"raw"
|
||||
].get_canned_message_module_messages_response
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
|
||||
)
|
||||
self.gotResponse = True
|
||||
|
||||
def get_canned_message(self):
|
||||
"""Get the canned message string. Concatenate all pieces together and return a single string."""
|
||||
logging.debug(f"in get_canned_message()")
|
||||
logger.debug(f"in get_canned_message()")
|
||||
if not self.module_available(mesh_pb2.CANNEDMSG_CONFIG):
|
||||
logging.warning("Canned Message module not present (excluded by firmware)")
|
||||
return None
|
||||
if not self.cannedPluginMessage:
|
||||
p1 = admin_pb2.AdminMessage()
|
||||
p1.get_canned_message_module_messages_request = True
|
||||
@@ -522,7 +545,7 @@ class Node:
|
||||
while self.gotResponse is False:
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
|
||||
)
|
||||
|
||||
@@ -530,12 +553,14 @@ class Node:
|
||||
if self.cannedPluginMessageMessages:
|
||||
self.cannedPluginMessage += self.cannedPluginMessageMessages
|
||||
|
||||
print(f"canned_plugin_message:{self.cannedPluginMessage}")
|
||||
logging.debug(f"canned_plugin_message:{self.cannedPluginMessage}")
|
||||
logger.debug(f"canned_plugin_message:{self.cannedPluginMessage}")
|
||||
return self.cannedPluginMessage
|
||||
|
||||
def set_canned_message(self, message):
|
||||
"""Set the canned message. The canned messages length must be less than 200 character."""
|
||||
if not self.module_available(mesh_pb2.CANNEDMSG_CONFIG):
|
||||
logging.warning("Canned Message module not present (excluded by firmware)")
|
||||
return None
|
||||
|
||||
if len(message) > 200:
|
||||
our_exit("Warning: The canned message must be less than 200 characters.")
|
||||
@@ -555,7 +580,7 @@ class Node:
|
||||
if i == 0:
|
||||
p.set_canned_message_module_messages = chunk
|
||||
|
||||
logging.debug(f"Setting canned message '{chunk}' part {i+1}")
|
||||
logger.debug(f"Setting canned message '{chunk}' part {i+1}")
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -569,7 +594,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.exit_simulator = True
|
||||
logging.debug("in exitSimulator()")
|
||||
logger.debug("in exitSimulator()")
|
||||
|
||||
return self._sendAdmin(p)
|
||||
|
||||
@@ -578,7 +603,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_seconds = secs
|
||||
logging.info(f"Telling node to reboot in {secs} seconds")
|
||||
logger.info(f"Telling node to reboot in {secs} seconds")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -592,7 +617,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.begin_edit_settings = True
|
||||
logging.info(f"Telling open a transaction to edit settings")
|
||||
logger.info(f"Telling open a transaction to edit settings")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -606,7 +631,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.commit_edit_settings = True
|
||||
logging.info(f"Telling node to commit open transaction for editing settings")
|
||||
logger.info(f"Telling node to commit open transaction for editing settings")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -620,7 +645,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.reboot_ota_seconds = secs
|
||||
logging.info(f"Telling node to reboot to OTA in {secs} seconds")
|
||||
logger.info(f"Telling node to reboot to OTA in {secs} seconds")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -634,7 +659,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.enter_dfu_mode_request = True
|
||||
logging.info(f"Telling node to enable DFU mode")
|
||||
logger.info(f"Telling node to enable DFU mode")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -648,7 +673,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.shutdown_seconds = secs
|
||||
logging.info(f"Telling node to shutdown in {secs} seconds")
|
||||
logger.info(f"Telling node to shutdown in {secs} seconds")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -661,7 +686,7 @@ class Node:
|
||||
"""Get the node's metadata."""
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.get_device_metadata_request = True
|
||||
logging.info(f"Requesting device metadata")
|
||||
logger.info(f"Requesting device metadata")
|
||||
|
||||
self._sendAdmin(
|
||||
p, wantResponse=True, onResponse=self.onRequestGetMetadata
|
||||
@@ -674,10 +699,10 @@ class Node:
|
||||
p = admin_pb2.AdminMessage()
|
||||
if full:
|
||||
p.factory_reset_device = True
|
||||
logging.info(f"Telling node to factory reset (full device reset)")
|
||||
logger.info(f"Telling node to factory reset (full device reset)")
|
||||
else:
|
||||
p.factory_reset_config = True
|
||||
logging.info(f"Telling node to factory reset (config reset)")
|
||||
logger.info(f"Telling node to factory reset (config reset)")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -781,7 +806,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.nodedb_reset = True
|
||||
logging.info(f"Telling node to reset the NodeDB")
|
||||
logger.info(f"Telling node to reset the NodeDB")
|
||||
|
||||
# If sending to a remote node, wait for ACK/NAK
|
||||
if self == self.iface.localNode:
|
||||
@@ -822,7 +847,7 @@ class Node:
|
||||
self.ensureSessionKey()
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.remove_fixed_position = True
|
||||
logging.info(f"Telling node to remove fixed position")
|
||||
logger.info(f"Telling node to remove fixed position")
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -837,7 +862,7 @@ class Node:
|
||||
timeSec = int(time.time())
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_time_only = timeSec
|
||||
logging.info(f"Setting node time to {timeSec}")
|
||||
logger.info(f"Setting node time to {timeSec}")
|
||||
|
||||
if self == self.iface.localNode:
|
||||
onResponse = None
|
||||
@@ -869,7 +894,7 @@ class Node:
|
||||
|
||||
def onRequestGetMetadata(self, p):
|
||||
"""Handle the response packet for requesting device metadata getMetadata()"""
|
||||
logging.debug(f"onRequestGetMetadata() p:{p}")
|
||||
logger.debug(f"onRequestGetMetadata() p:{p}")
|
||||
|
||||
if "routing" in p["decoded"]:
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
@@ -881,30 +906,30 @@ class Node:
|
||||
portnums_pb2.PortNum.ROUTING_APP
|
||||
):
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
|
||||
)
|
||||
self._timeout.expireTime = time.time() # Do not wait any longer
|
||||
return # Don't try to parse this routing message
|
||||
logging.debug(f"Retrying metadata request.")
|
||||
logger.debug(f"Retrying metadata request.")
|
||||
self.getMetadata()
|
||||
return
|
||||
|
||||
c = p["decoded"]["admin"]["raw"].get_device_metadata_response
|
||||
self._timeout.reset() # We made forward progress
|
||||
logging.debug(f"Received metadata {stripnl(c)}")
|
||||
logger.debug(f"Received metadata {stripnl(c)}")
|
||||
print(f"\nfirmware_version: {c.firmware_version}")
|
||||
print(f"device_state_version: {c.device_state_version}")
|
||||
|
||||
def onResponseRequestChannel(self, p):
|
||||
"""Handle the response packet for requesting a channel _requestChannel()"""
|
||||
logging.debug(f"onResponseRequestChannel() p:{p}")
|
||||
logger.debug(f"onResponseRequestChannel() p:{p}")
|
||||
|
||||
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
|
||||
portnums_pb2.PortNum.ROUTING_APP
|
||||
):
|
||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f'Channel request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
|
||||
)
|
||||
self._timeout.expireTime = time.time() # Do not wait any longer
|
||||
@@ -912,18 +937,18 @@ class Node:
|
||||
lastTried = 0
|
||||
if len(self.partialChannels) > 0:
|
||||
lastTried = self.partialChannels[-1].index
|
||||
logging.debug(f"Retrying previous channel request.")
|
||||
logger.debug(f"Retrying previous channel request.")
|
||||
self._requestChannel(lastTried)
|
||||
return
|
||||
|
||||
c = p["decoded"]["admin"]["raw"].get_channel_response
|
||||
self.partialChannels.append(c)
|
||||
self._timeout.reset() # We made forward progress
|
||||
logging.debug(f"Received channel {stripnl(c)}")
|
||||
logger.debug(f"Received channel {stripnl(c)}")
|
||||
index = c.index
|
||||
|
||||
if index >= 8 - 1:
|
||||
logging.debug("Finished downloading channels")
|
||||
logger.debug("Finished downloading channels")
|
||||
|
||||
self.channels = self.partialChannels
|
||||
self._fixupChannels()
|
||||
@@ -958,11 +983,11 @@ class Node:
|
||||
print(
|
||||
f"Requesting channel {channelNum} info from remote node (this could take a while)"
|
||||
)
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"Requesting channel {channelNum} info from remote node (this could take a while)"
|
||||
)
|
||||
else:
|
||||
logging.debug(f"Requesting channel {channelNum}")
|
||||
logger.debug(f"Requesting channel {channelNum}")
|
||||
|
||||
return self._sendAdmin(
|
||||
p, wantResponse=True, onResponse=self.onResponseRequestChannel
|
||||
@@ -979,7 +1004,7 @@ class Node:
|
||||
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
||||
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Not sending packet because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
@@ -987,7 +1012,7 @@ class Node:
|
||||
adminIndex == 0
|
||||
): # unless a special channel index was used, we want to use the admin index
|
||||
adminIndex = self.iface.localNode._getAdminChannelIndex()
|
||||
logging.debug(f"adminIndex:{adminIndex}")
|
||||
logger.debug(f"adminIndex:{adminIndex}")
|
||||
if isinstance(self.nodeNum, int):
|
||||
nodeid = self.nodeNum
|
||||
else: # assume string starting with !
|
||||
@@ -1008,7 +1033,7 @@ class Node:
|
||||
def ensureSessionKey(self):
|
||||
"""If our entry in iface.nodesByNum doesn't already have an adminSessionPassKey, make a request to get one"""
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Not ensuring session key, because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
|
||||
4
meshtastic/protobuf/admin_pb2.py
generated
4
meshtastic/protobuf/admin_pb2.py
generated
File diff suppressed because one or more lines are too long
104
meshtastic/protobuf/config_pb2.py
generated
104
meshtastic/protobuf/config_pb2.py
generated
File diff suppressed because one or more lines are too long
83
meshtastic/protobuf/config_pb2.pyi
generated
83
meshtastic/protobuf/config_pb2.pyi
generated
@@ -116,6 +116,13 @@ class Config(google.protobuf.message.Message):
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops.
|
||||
"""
|
||||
CLIENT_BASE: Config.DeviceConfig._Role.ValueType # 12
|
||||
"""
|
||||
Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
|
||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
||||
"""
|
||||
|
||||
class Role(_Role, metaclass=_RoleEnumTypeWrapper):
|
||||
"""
|
||||
@@ -200,6 +207,13 @@ class Config(google.protobuf.message.Message):
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops.
|
||||
"""
|
||||
CLIENT_BASE: Config.DeviceConfig.Role.ValueType # 12
|
||||
"""
|
||||
Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
|
||||
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
|
||||
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
|
||||
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node.
|
||||
"""
|
||||
|
||||
class _RebroadcastMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -299,6 +313,12 @@ class Config(google.protobuf.message.Message):
|
||||
Non-notification system buzzer tones only.
|
||||
Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts.
|
||||
"""
|
||||
DIRECT_MSG_ONLY: Config.DeviceConfig._BuzzerMode.ValueType # 4
|
||||
"""
|
||||
Direct Message notifications only.
|
||||
Buzzer is enabled only for direct messages and alerts, but not for button presses.
|
||||
External notification config determines the specifics of the notification behavior.
|
||||
"""
|
||||
|
||||
class BuzzerMode(_BuzzerMode, metaclass=_BuzzerModeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -326,6 +346,12 @@ class Config(google.protobuf.message.Message):
|
||||
Non-notification system buzzer tones only.
|
||||
Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts.
|
||||
"""
|
||||
DIRECT_MSG_ONLY: Config.DeviceConfig.BuzzerMode.ValueType # 4
|
||||
"""
|
||||
Direct Message notifications only.
|
||||
Buzzer is enabled only for direct messages and alerts, but not for button presses.
|
||||
External notification config determines the specifics of the notification behavior.
|
||||
"""
|
||||
|
||||
ROLE_FIELD_NUMBER: builtins.int
|
||||
SERIAL_ENABLED_FIELD_NUMBER: builtins.int
|
||||
@@ -1036,12 +1062,12 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
OLED_SH1107: Config.DisplayConfig._OledType.ValueType # 3
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x128 screens
|
||||
"""
|
||||
OLED_SH1107_128_64: Config.DisplayConfig._OledType.ValueType # 4
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x64 screens
|
||||
"""
|
||||
OLED_SH1107_128_128: Config.DisplayConfig._OledType.ValueType # 4
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x128 screens
|
||||
"""
|
||||
|
||||
class OledType(_OledType, metaclass=_OledTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -1062,12 +1088,12 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
OLED_SH1107: Config.DisplayConfig.OledType.ValueType # 3
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x128 screens
|
||||
"""
|
||||
OLED_SH1107_128_64: Config.DisplayConfig.OledType.ValueType # 4
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x64 screens
|
||||
"""
|
||||
OLED_SH1107_128_128: Config.DisplayConfig.OledType.ValueType # 4
|
||||
"""
|
||||
Can not be auto detected but set by proto. Used for 128x128 screens
|
||||
"""
|
||||
|
||||
class _DisplayMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -1202,6 +1228,7 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
gps_format: global___Config.DisplayConfig.GpsCoordinateFormat.ValueType
|
||||
"""
|
||||
Deprecated in 2.7.4: Unused
|
||||
How the GPS coordinates are formatted on the OLED screen.
|
||||
"""
|
||||
auto_screen_carousel_secs: builtins.int
|
||||
@@ -1367,6 +1394,26 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Philippines 915mhz
|
||||
"""
|
||||
ANZ_433: Config.LoRaConfig._RegionCode.ValueType # 22
|
||||
"""
|
||||
Australia / New Zealand 433MHz
|
||||
"""
|
||||
KZ_433: Config.LoRaConfig._RegionCode.ValueType # 23
|
||||
"""
|
||||
Kazakhstan 433MHz
|
||||
"""
|
||||
KZ_863: Config.LoRaConfig._RegionCode.ValueType # 24
|
||||
"""
|
||||
Kazakhstan 863MHz
|
||||
"""
|
||||
NP_865: Config.LoRaConfig._RegionCode.ValueType # 25
|
||||
"""
|
||||
Nepal 865MHz
|
||||
"""
|
||||
BR_902: Config.LoRaConfig._RegionCode.ValueType # 26
|
||||
"""
|
||||
Brazil 902MHz
|
||||
"""
|
||||
|
||||
class RegionCode(_RegionCode, metaclass=_RegionCodeEnumTypeWrapper): ...
|
||||
UNSET: Config.LoRaConfig.RegionCode.ValueType # 0
|
||||
@@ -1457,6 +1504,26 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Philippines 915mhz
|
||||
"""
|
||||
ANZ_433: Config.LoRaConfig.RegionCode.ValueType # 22
|
||||
"""
|
||||
Australia / New Zealand 433MHz
|
||||
"""
|
||||
KZ_433: Config.LoRaConfig.RegionCode.ValueType # 23
|
||||
"""
|
||||
Kazakhstan 433MHz
|
||||
"""
|
||||
KZ_863: Config.LoRaConfig.RegionCode.ValueType # 24
|
||||
"""
|
||||
Kazakhstan 863MHz
|
||||
"""
|
||||
NP_865: Config.LoRaConfig.RegionCode.ValueType # 25
|
||||
"""
|
||||
Nepal 865MHz
|
||||
"""
|
||||
BR_902: Config.LoRaConfig.RegionCode.ValueType # 26
|
||||
"""
|
||||
Brazil 902MHz
|
||||
"""
|
||||
|
||||
class _ModemPreset:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
|
||||
30
meshtastic/protobuf/device_ui_pb2.py
generated
30
meshtastic/protobuf/device_ui_pb2.py
generated
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xeb\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\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\"\xa7\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\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\"~\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\x08GeoPoint\x12\x0c\n\x04zoom\x18\x01 \x01(\x05\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"U\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\r\n\x05style\x18\x02 \x01(\t\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xa9\x02\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\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\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')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xda\x04\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\x12*\n\x08map_data\x18\x0f \x01(\x0b\x32\x18.meshtastic.protobuf.Map\x12\x36\n\x0c\x63ompass_mode\x18\x10 \x01(\x0e\x32 .meshtastic.protobuf.CompassMode\x12\x18\n\x10screen_rgb_color\x18\x11 \x01(\r\x12\x1b\n\x13is_clockface_analog\x18\x12 \x01(\x08\"\xa7\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\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\x05\"~\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\x08GeoPoint\x12\x0c\n\x04zoom\x18\x01 \x01(\x05\x12\x10\n\x08latitude\x18\x02 \x01(\x05\x12\x11\n\tlongitude\x18\x03 \x01(\x05\"U\n\x03Map\x12+\n\x04home\x18\x01 \x01(\x0b\x32\x1d.meshtastic.protobuf.GeoPoint\x12\r\n\x05style\x18\x02 \x01(\t\x12\x12\n\nfollow_gps\x18\x03 \x01(\x08*>\n\x0b\x43ompassMode\x12\x0b\n\x07\x44YNAMIC\x10\x00\x12\x0e\n\nFIXED_RING\x10\x01\x12\x12\n\x0e\x46REEZE_HEADING\x10\x02*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xb4\x02\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\r\n\tSLOVENIAN\x10\x0f\x12\r\n\tUKRAINIAN\x10\x10\x12\r\n\tBULGARIAN\x10\x11\x12\t\n\x05\x43ZECH\x10\x12\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)
|
||||
@@ -21,18 +21,20 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_
|
||||
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=1002
|
||||
_globals['_THEME']._serialized_end=1039
|
||||
_globals['_LANGUAGE']._serialized_start=1042
|
||||
_globals['_LANGUAGE']._serialized_end=1339
|
||||
_globals['_COMPASSMODE']._serialized_start=1113
|
||||
_globals['_COMPASSMODE']._serialized_end=1175
|
||||
_globals['_THEME']._serialized_start=1177
|
||||
_globals['_THEME']._serialized_end=1214
|
||||
_globals['_LANGUAGE']._serialized_start=1217
|
||||
_globals['_LANGUAGE']._serialized_end=1525
|
||||
_globals['_DEVICEUICONFIG']._serialized_start=61
|
||||
_globals['_DEVICEUICONFIG']._serialized_end=552
|
||||
_globals['_NODEFILTER']._serialized_start=555
|
||||
_globals['_NODEFILTER']._serialized_end=722
|
||||
_globals['_NODEHIGHLIGHT']._serialized_start=724
|
||||
_globals['_NODEHIGHLIGHT']._serialized_end=850
|
||||
_globals['_GEOPOINT']._serialized_start=852
|
||||
_globals['_GEOPOINT']._serialized_end=913
|
||||
_globals['_MAP']._serialized_start=915
|
||||
_globals['_MAP']._serialized_end=1000
|
||||
_globals['_DEVICEUICONFIG']._serialized_end=663
|
||||
_globals['_NODEFILTER']._serialized_start=666
|
||||
_globals['_NODEFILTER']._serialized_end=833
|
||||
_globals['_NODEHIGHLIGHT']._serialized_start=835
|
||||
_globals['_NODEHIGHLIGHT']._serialized_end=961
|
||||
_globals['_GEOPOINT']._serialized_start=963
|
||||
_globals['_GEOPOINT']._serialized_end=1024
|
||||
_globals['_MAP']._serialized_start=1026
|
||||
_globals['_MAP']._serialized_end=1111
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
65
meshtastic/protobuf/device_ui_pb2.pyi
generated
65
meshtastic/protobuf/device_ui_pb2.pyi
generated
@@ -17,6 +17,41 @@ else:
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
||||
|
||||
class _CompassMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _CompassModeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_CompassMode.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
DYNAMIC: _CompassMode.ValueType # 0
|
||||
"""
|
||||
Compass with dynamic ring and heading
|
||||
"""
|
||||
FIXED_RING: _CompassMode.ValueType # 1
|
||||
"""
|
||||
Compass with fixed ring and heading
|
||||
"""
|
||||
FREEZE_HEADING: _CompassMode.ValueType # 2
|
||||
"""
|
||||
Compass with heading and freeze option
|
||||
"""
|
||||
|
||||
class CompassMode(_CompassMode, metaclass=_CompassModeEnumTypeWrapper): ...
|
||||
|
||||
DYNAMIC: CompassMode.ValueType # 0
|
||||
"""
|
||||
Compass with dynamic ring and heading
|
||||
"""
|
||||
FIXED_RING: CompassMode.ValueType # 1
|
||||
"""
|
||||
Compass with fixed ring and heading
|
||||
"""
|
||||
FREEZE_HEADING: CompassMode.ValueType # 2
|
||||
"""
|
||||
Compass with heading and freeze option
|
||||
"""
|
||||
global___CompassMode = CompassMode
|
||||
|
||||
class _Theme:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
@@ -130,6 +165,10 @@ class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumT
|
||||
"""
|
||||
Bulgarian
|
||||
"""
|
||||
CZECH: _Language.ValueType # 18
|
||||
"""
|
||||
Czech
|
||||
"""
|
||||
SIMPLIFIED_CHINESE: _Language.ValueType # 30
|
||||
"""
|
||||
Simplified Chinese (experimental)
|
||||
@@ -216,6 +255,10 @@ BULGARIAN: Language.ValueType # 17
|
||||
"""
|
||||
Bulgarian
|
||||
"""
|
||||
CZECH: Language.ValueType # 18
|
||||
"""
|
||||
Czech
|
||||
"""
|
||||
SIMPLIFIED_CHINESE: Language.ValueType # 30
|
||||
"""
|
||||
Simplified Chinese (experimental)
|
||||
@@ -249,6 +292,9 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
||||
NODE_HIGHLIGHT_FIELD_NUMBER: builtins.int
|
||||
CALIBRATION_DATA_FIELD_NUMBER: builtins.int
|
||||
MAP_DATA_FIELD_NUMBER: builtins.int
|
||||
COMPASS_MODE_FIELD_NUMBER: builtins.int
|
||||
SCREEN_RGB_COLOR_FIELD_NUMBER: builtins.int
|
||||
IS_CLOCKFACE_ANALOG_FIELD_NUMBER: builtins.int
|
||||
version: builtins.int
|
||||
"""
|
||||
A version integer used to invalidate saved files when we make incompatible changes.
|
||||
@@ -285,6 +331,20 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
8 integers for screen calibration data
|
||||
"""
|
||||
compass_mode: global___CompassMode.ValueType
|
||||
"""
|
||||
Compass mode
|
||||
"""
|
||||
screen_rgb_color: builtins.int
|
||||
"""
|
||||
RGB color for BaseUI
|
||||
0xRRGGBB format, e.g. 0xFF0000 for red
|
||||
"""
|
||||
is_clockface_analog: builtins.bool
|
||||
"""
|
||||
Clockface analog style
|
||||
true for analog clockface, false for digital clockface
|
||||
"""
|
||||
@property
|
||||
def node_filter(self) -> global___NodeFilter:
|
||||
"""
|
||||
@@ -321,9 +381,12 @@ class DeviceUIConfig(google.protobuf.message.Message):
|
||||
node_highlight: global___NodeHighlight | None = ...,
|
||||
calibration_data: builtins.bytes = ...,
|
||||
map_data: global___Map | None = ...,
|
||||
compass_mode: global___CompassMode.ValueType = ...,
|
||||
screen_rgb_color: builtins.int = ...,
|
||||
is_clockface_analog: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["map_data", b"map_data", "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", "map_data", b"map_data", "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: ...
|
||||
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "compass_mode", b"compass_mode", "is_clockface_analog", b"is_clockface_analog", "language", b"language", "map_data", b"map_data", "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_rgb_color", b"screen_rgb_color", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
|
||||
|
||||
global___DeviceUIConfig = DeviceUIConfig
|
||||
|
||||
|
||||
6
meshtastic/protobuf/deviceonly_pb2.py
generated
6
meshtastic/protobuf/deviceonly_pb2.py
generated
@@ -12,14 +12,14 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__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 config_pb2 as meshtastic_dot_protobuf_dot_config__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 telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2
|
||||
from meshtastic.protobuf import nanopb_pb2 as meshtastic_dot_protobuf_dot_nanopb__pb2
|
||||
|
||||
|
||||
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#meshtastic/protobuf/localonly.proto\x1a meshtastic/protobuf/nanopb.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\"\x94\x02\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\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\xf0\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(\r\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xa1\x03\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\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\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\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \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\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBm\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a meshtastic/protobuf/config.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/nanopb.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\"\x94\x02\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\x12\x1c\n\x0fis_unmessagable\x18\t \x01(\x08H\x00\x88\x01\x01\x42\x12\n\x10_is_unmessagable\"\xf0\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(\r\x12\x10\n\x08\x62itfield\x18\r \x01(\rB\x0c\n\n_hops_away\"\xa1\x03\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\x19\n\rdid_gps_reset\x18\x0b \x01(\x08\x42\x02\x18\x01\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\"}\n\x0cNodeDatabase\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\\\n\x05nodes\x18\x02 \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\"\x86\x02\n\x11\x42\x61\x63kupPreferences\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x11\n\ttimestamp\x18\x02 \x01(\x07\x12\x30\n\x06\x63onfig\x18\x03 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12=\n\rmodule_config\x18\x04 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig\x12\x32\n\x08\x63hannels\x18\x05 \x01(\x0b\x32 .meshtastic.protobuf.ChannelFile\x12(\n\x05owner\x18\x06 \x01(\x0b\x32\x19.meshtastic.protobuf.UserBm\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
|
||||
148
meshtastic/protobuf/mesh_pb2.py
generated
148
meshtastic/protobuf/mesh_pb2.py
generated
File diff suppressed because one or more lines are too long
263
meshtastic/protobuf/mesh_pb2.pyi
generated
263
meshtastic/protobuf/mesh_pb2.pyi
generated
@@ -442,15 +442,15 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin
|
||||
"""
|
||||
LINK_32: _HardwareModel.ValueType # 98
|
||||
"""*
|
||||
"""
|
||||
Lilygo LINK32 board with sensors
|
||||
"""
|
||||
SEEED_WIO_TRACKER_L1: _HardwareModel.ValueType # 99
|
||||
"""*
|
||||
"""
|
||||
Seeed Tracker L1
|
||||
"""
|
||||
SEEED_WIO_TRACKER_L1_EINK: _HardwareModel.ValueType # 100
|
||||
"""*
|
||||
"""
|
||||
Seeed Tracker L1 EINK driver
|
||||
"""
|
||||
QWANTZ_TINY_ARMS: _HardwareModel.ValueType # 101
|
||||
@@ -458,17 +458,42 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
Reserved ID for future and past use
|
||||
"""
|
||||
T_DECK_PRO: _HardwareModel.ValueType # 102
|
||||
"""*
|
||||
"""
|
||||
Lilygo T-Deck Pro
|
||||
"""
|
||||
T_LORA_PAGER: _HardwareModel.ValueType # 103
|
||||
"""*
|
||||
"""
|
||||
Lilygo TLora Pager
|
||||
"""
|
||||
GAT562_MESH_TRIAL_TRACKER: _HardwareModel.ValueType # 104
|
||||
"""*
|
||||
"""
|
||||
GAT562 Mesh Trial Tracker
|
||||
"""
|
||||
WISMESH_TAG: _HardwareModel.ValueType # 105
|
||||
"""
|
||||
RAKwireless WisMesh Tag
|
||||
"""
|
||||
RAK3312: _HardwareModel.ValueType # 106
|
||||
"""
|
||||
RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/
|
||||
"""
|
||||
THINKNODE_M5: _HardwareModel.ValueType # 107
|
||||
"""
|
||||
Elecrow ThinkNode M5 https://www.elecrow.com/wiki/ThinkNode_M5_Meshtastic_LoRa_Signal_Transceiver_ESP32-S3.html
|
||||
"""
|
||||
HELTEC_MESH_SOLAR: _HardwareModel.ValueType # 108
|
||||
"""
|
||||
MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices.
|
||||
https://heltec.org/project/meshsolar/
|
||||
"""
|
||||
T_ECHO_LITE: _HardwareModel.ValueType # 109
|
||||
"""
|
||||
Lilygo T-Echo Lite
|
||||
"""
|
||||
HELTEC_V4: _HardwareModel.ValueType # 110
|
||||
"""
|
||||
New Heltec LoRA32 with ESP32-S3 CPU
|
||||
"""
|
||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -894,15 +919,15 @@ CROWPANEL: HardwareModel.ValueType # 97
|
||||
Elecrow CrowPanel Advance models, ESP32-S3 and TFT with SX1262 radio plugin
|
||||
"""
|
||||
LINK_32: HardwareModel.ValueType # 98
|
||||
"""*
|
||||
"""
|
||||
Lilygo LINK32 board with sensors
|
||||
"""
|
||||
SEEED_WIO_TRACKER_L1: HardwareModel.ValueType # 99
|
||||
"""*
|
||||
"""
|
||||
Seeed Tracker L1
|
||||
"""
|
||||
SEEED_WIO_TRACKER_L1_EINK: HardwareModel.ValueType # 100
|
||||
"""*
|
||||
"""
|
||||
Seeed Tracker L1 EINK driver
|
||||
"""
|
||||
QWANTZ_TINY_ARMS: HardwareModel.ValueType # 101
|
||||
@@ -910,17 +935,42 @@ QWANTZ_TINY_ARMS: HardwareModel.ValueType # 101
|
||||
Reserved ID for future and past use
|
||||
"""
|
||||
T_DECK_PRO: HardwareModel.ValueType # 102
|
||||
"""*
|
||||
"""
|
||||
Lilygo T-Deck Pro
|
||||
"""
|
||||
T_LORA_PAGER: HardwareModel.ValueType # 103
|
||||
"""*
|
||||
"""
|
||||
Lilygo TLora Pager
|
||||
"""
|
||||
GAT562_MESH_TRIAL_TRACKER: HardwareModel.ValueType # 104
|
||||
"""*
|
||||
"""
|
||||
GAT562 Mesh Trial Tracker
|
||||
"""
|
||||
WISMESH_TAG: HardwareModel.ValueType # 105
|
||||
"""
|
||||
RAKwireless WisMesh Tag
|
||||
"""
|
||||
RAK3312: HardwareModel.ValueType # 106
|
||||
"""
|
||||
RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/
|
||||
"""
|
||||
THINKNODE_M5: HardwareModel.ValueType # 107
|
||||
"""
|
||||
Elecrow ThinkNode M5 https://www.elecrow.com/wiki/ThinkNode_M5_Meshtastic_LoRa_Signal_Transceiver_ESP32-S3.html
|
||||
"""
|
||||
HELTEC_MESH_SOLAR: HardwareModel.ValueType # 108
|
||||
"""
|
||||
MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices.
|
||||
https://heltec.org/project/meshsolar/
|
||||
"""
|
||||
T_ECHO_LITE: HardwareModel.ValueType # 109
|
||||
"""
|
||||
Lilygo T-Echo Lite
|
||||
"""
|
||||
HELTEC_V4: HardwareModel.ValueType # 110
|
||||
"""
|
||||
New Heltec LoRA32 with ESP32-S3 CPU
|
||||
"""
|
||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1102,6 +1152,77 @@ If you see this failure in the field please post in the forum because we are int
|
||||
"""
|
||||
global___CriticalErrorCode = CriticalErrorCode
|
||||
|
||||
class _FirmwareEdition:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _FirmwareEditionEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_FirmwareEdition.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
VANILLA: _FirmwareEdition.ValueType # 0
|
||||
"""
|
||||
Vanilla firmware
|
||||
"""
|
||||
SMART_CITIZEN: _FirmwareEdition.ValueType # 1
|
||||
"""
|
||||
Firmware for use in the Smart Citizen environmental monitoring network
|
||||
"""
|
||||
OPEN_SAUCE: _FirmwareEdition.ValueType # 16
|
||||
"""
|
||||
Open Sauce, the maker conference held yearly in CA
|
||||
"""
|
||||
DEFCON: _FirmwareEdition.ValueType # 17
|
||||
"""
|
||||
DEFCON, the yearly hacker conference
|
||||
"""
|
||||
BURNING_MAN: _FirmwareEdition.ValueType # 18
|
||||
"""
|
||||
Burning Man, the yearly hippie gathering in the desert
|
||||
"""
|
||||
HAMVENTION: _FirmwareEdition.ValueType # 19
|
||||
"""
|
||||
Hamvention, the Dayton amateur radio convention
|
||||
"""
|
||||
DIY_EDITION: _FirmwareEdition.ValueType # 127
|
||||
"""
|
||||
Placeholder for DIY and unofficial events
|
||||
"""
|
||||
|
||||
class FirmwareEdition(_FirmwareEdition, metaclass=_FirmwareEditionEnumTypeWrapper):
|
||||
"""
|
||||
Enum to indicate to clients whether this firmware is a special firmware build, like an event.
|
||||
The first 16 values are reserved for non-event special firmwares, like the Smart Citizen use case.
|
||||
"""
|
||||
|
||||
VANILLA: FirmwareEdition.ValueType # 0
|
||||
"""
|
||||
Vanilla firmware
|
||||
"""
|
||||
SMART_CITIZEN: FirmwareEdition.ValueType # 1
|
||||
"""
|
||||
Firmware for use in the Smart Citizen environmental monitoring network
|
||||
"""
|
||||
OPEN_SAUCE: FirmwareEdition.ValueType # 16
|
||||
"""
|
||||
Open Sauce, the maker conference held yearly in CA
|
||||
"""
|
||||
DEFCON: FirmwareEdition.ValueType # 17
|
||||
"""
|
||||
DEFCON, the yearly hacker conference
|
||||
"""
|
||||
BURNING_MAN: FirmwareEdition.ValueType # 18
|
||||
"""
|
||||
Burning Man, the yearly hippie gathering in the desert
|
||||
"""
|
||||
HAMVENTION: FirmwareEdition.ValueType # 19
|
||||
"""
|
||||
Hamvention, the Dayton amateur radio convention
|
||||
"""
|
||||
DIY_EDITION: FirmwareEdition.ValueType # 127
|
||||
"""
|
||||
Placeholder for DIY and unofficial events
|
||||
"""
|
||||
global___FirmwareEdition = FirmwareEdition
|
||||
|
||||
class _ExcludedModules:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
@@ -1764,6 +1885,11 @@ class Routing(google.protobuf.message.Message):
|
||||
"""
|
||||
Admin packet sent using PKC, but not from a public key on the admin key list
|
||||
"""
|
||||
RATE_LIMIT_EXCEEDED: Routing._Error.ValueType # 38
|
||||
"""
|
||||
Airtime fairness rate limit exceeded for a packet
|
||||
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
||||
"""
|
||||
|
||||
class Error(_Error, metaclass=_ErrorEnumTypeWrapper):
|
||||
"""
|
||||
@@ -1837,6 +1963,11 @@ class Routing(google.protobuf.message.Message):
|
||||
"""
|
||||
Admin packet sent using PKC, but not from a public key on the admin key list
|
||||
"""
|
||||
RATE_LIMIT_EXCEEDED: Routing.Error.ValueType # 38
|
||||
"""
|
||||
Airtime fairness rate limit exceeded for a packet
|
||||
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime
|
||||
"""
|
||||
|
||||
ROUTE_REQUEST_FIELD_NUMBER: builtins.int
|
||||
ROUTE_REPLY_FIELD_NUMBER: builtins.int
|
||||
@@ -2267,6 +2398,83 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
The message is delayed and was originally a direct message
|
||||
"""
|
||||
|
||||
class _TransportMechanism:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _TransportMechanismEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MeshPacket._TransportMechanism.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
TRANSPORT_INTERNAL: MeshPacket._TransportMechanism.ValueType # 0
|
||||
"""
|
||||
The default case is that the node generated a packet itself
|
||||
"""
|
||||
TRANSPORT_LORA: MeshPacket._TransportMechanism.ValueType # 1
|
||||
"""
|
||||
Arrived via the primary LoRa radio
|
||||
"""
|
||||
TRANSPORT_LORA_ALT1: MeshPacket._TransportMechanism.ValueType # 2
|
||||
"""
|
||||
Arrived via a secondary LoRa radio
|
||||
"""
|
||||
TRANSPORT_LORA_ALT2: MeshPacket._TransportMechanism.ValueType # 3
|
||||
"""
|
||||
Arrived via a tertiary LoRa radio
|
||||
"""
|
||||
TRANSPORT_LORA_ALT3: MeshPacket._TransportMechanism.ValueType # 4
|
||||
"""
|
||||
Arrived via a quaternary LoRa radio
|
||||
"""
|
||||
TRANSPORT_MQTT: MeshPacket._TransportMechanism.ValueType # 5
|
||||
"""
|
||||
Arrived via an MQTT connection
|
||||
"""
|
||||
TRANSPORT_MULTICAST_UDP: MeshPacket._TransportMechanism.ValueType # 6
|
||||
"""
|
||||
Arrived via Multicast UDP
|
||||
"""
|
||||
TRANSPORT_API: MeshPacket._TransportMechanism.ValueType # 7
|
||||
"""
|
||||
Arrived via API connection
|
||||
"""
|
||||
|
||||
class TransportMechanism(_TransportMechanism, metaclass=_TransportMechanismEnumTypeWrapper):
|
||||
"""
|
||||
Enum to identify which transport mechanism this packet arrived over
|
||||
"""
|
||||
|
||||
TRANSPORT_INTERNAL: MeshPacket.TransportMechanism.ValueType # 0
|
||||
"""
|
||||
The default case is that the node generated a packet itself
|
||||
"""
|
||||
TRANSPORT_LORA: MeshPacket.TransportMechanism.ValueType # 1
|
||||
"""
|
||||
Arrived via the primary LoRa radio
|
||||
"""
|
||||
TRANSPORT_LORA_ALT1: MeshPacket.TransportMechanism.ValueType # 2
|
||||
"""
|
||||
Arrived via a secondary LoRa radio
|
||||
"""
|
||||
TRANSPORT_LORA_ALT2: MeshPacket.TransportMechanism.ValueType # 3
|
||||
"""
|
||||
Arrived via a tertiary LoRa radio
|
||||
"""
|
||||
TRANSPORT_LORA_ALT3: MeshPacket.TransportMechanism.ValueType # 4
|
||||
"""
|
||||
Arrived via a quaternary LoRa radio
|
||||
"""
|
||||
TRANSPORT_MQTT: MeshPacket.TransportMechanism.ValueType # 5
|
||||
"""
|
||||
Arrived via an MQTT connection
|
||||
"""
|
||||
TRANSPORT_MULTICAST_UDP: MeshPacket.TransportMechanism.ValueType # 6
|
||||
"""
|
||||
Arrived via Multicast UDP
|
||||
"""
|
||||
TRANSPORT_API: MeshPacket.TransportMechanism.ValueType # 7
|
||||
"""
|
||||
Arrived via API connection
|
||||
"""
|
||||
|
||||
FROM_FIELD_NUMBER: builtins.int
|
||||
TO_FIELD_NUMBER: builtins.int
|
||||
CHANNEL_FIELD_NUMBER: builtins.int
|
||||
@@ -2287,6 +2495,7 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
NEXT_HOP_FIELD_NUMBER: builtins.int
|
||||
RELAY_NODE_FIELD_NUMBER: builtins.int
|
||||
TX_AFTER_FIELD_NUMBER: builtins.int
|
||||
TRANSPORT_MECHANISM_FIELD_NUMBER: builtins.int
|
||||
to: builtins.int
|
||||
"""
|
||||
The (immediate) destination for this packet
|
||||
@@ -2394,6 +2603,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
Timestamp after which this packet may be sent.
|
||||
Set by the firmware internally, clients are not supposed to set this.
|
||||
"""
|
||||
transport_mechanism: global___MeshPacket.TransportMechanism.ValueType
|
||||
"""
|
||||
Indicates which transport mechanism this packet arrived over
|
||||
"""
|
||||
@property
|
||||
def decoded(self) -> global___Data:
|
||||
"""
|
||||
@@ -2422,9 +2635,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
next_hop: builtins.int = ...,
|
||||
relay_node: builtins.int = ...,
|
||||
tx_after: builtins.int = ...,
|
||||
transport_mechanism: global___MeshPacket.TransportMechanism.ValueType = ...,
|
||||
) -> None: ...
|
||||
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", "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", "tx_after", b"tx_after", "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", "transport_mechanism", b"transport_mechanism", "tx_after", b"tx_after", "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: ...
|
||||
|
||||
global___MeshPacket = MeshPacket
|
||||
@@ -2567,6 +2781,8 @@ class MyNodeInfo(google.protobuf.message.Message):
|
||||
MIN_APP_VERSION_FIELD_NUMBER: builtins.int
|
||||
DEVICE_ID_FIELD_NUMBER: builtins.int
|
||||
PIO_ENV_FIELD_NUMBER: builtins.int
|
||||
FIRMWARE_EDITION_FIELD_NUMBER: builtins.int
|
||||
NODEDB_COUNT_FIELD_NUMBER: builtins.int
|
||||
my_node_num: builtins.int
|
||||
"""
|
||||
Tells the phone what our node number is, default starting value is
|
||||
@@ -2590,6 +2806,15 @@ class MyNodeInfo(google.protobuf.message.Message):
|
||||
"""
|
||||
The PlatformIO environment used to build this firmware
|
||||
"""
|
||||
firmware_edition: global___FirmwareEdition.ValueType
|
||||
"""
|
||||
The indicator for whether this device is running event firmware and which
|
||||
"""
|
||||
nodedb_count: builtins.int
|
||||
"""
|
||||
The number of nodes in the nodedb.
|
||||
This is used by the phone to know how many NodeInfo packets to expect on want_config
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -2598,8 +2823,10 @@ class MyNodeInfo(google.protobuf.message.Message):
|
||||
min_app_version: builtins.int = ...,
|
||||
device_id: builtins.bytes = ...,
|
||||
pio_env: builtins.str = ...,
|
||||
firmware_edition: global___FirmwareEdition.ValueType = ...,
|
||||
nodedb_count: builtins.int = ...,
|
||||
) -> 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: ...
|
||||
def ClearField(self, field_name: typing.Literal["device_id", b"device_id", "firmware_edition", b"firmware_edition", "min_app_version", b"min_app_version", "my_node_num", b"my_node_num", "nodedb_count", b"nodedb_count", "pio_env", b"pio_env", "reboot_count", b"reboot_count"]) -> None: ...
|
||||
|
||||
global___MyNodeInfo = MyNodeInfo
|
||||
|
||||
@@ -3365,9 +3592,17 @@ class Heartbeat(google.protobuf.message.Message):
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
NONCE_FIELD_NUMBER: builtins.int
|
||||
nonce: builtins.int
|
||||
"""
|
||||
The nonce of the heartbeat message
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
nonce: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["nonce", b"nonce"]) -> None: ...
|
||||
|
||||
global___Heartbeat = Heartbeat
|
||||
|
||||
|
||||
48
meshtastic/protobuf/module_config_pb2.py
generated
48
meshtastic/protobuf/module_config_pb2.py
generated
File diff suppressed because one or more lines are too long
17
meshtastic/protobuf/module_config_pb2.pyi
generated
17
meshtastic/protobuf/module_config_pb2.pyi
generated
@@ -564,6 +564,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""VE.Direct is a serial protocol used by Victron Energy products
|
||||
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||
"""
|
||||
MS_CONFIG: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 8
|
||||
"""Used to configure and view some parameters of MeshSolar.
|
||||
https://heltec.org/project/meshsolar/
|
||||
"""
|
||||
|
||||
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -583,6 +587,10 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
"""VE.Direct is a serial protocol used by Victron Energy products
|
||||
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||
"""
|
||||
MS_CONFIG: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 8
|
||||
"""Used to configure and view some parameters of MeshSolar.
|
||||
https://heltec.org/project/meshsolar/
|
||||
"""
|
||||
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
ECHO_FIELD_NUMBER: builtins.int
|
||||
@@ -816,6 +824,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
ENABLED_FIELD_NUMBER: builtins.int
|
||||
SENDER_FIELD_NUMBER: builtins.int
|
||||
SAVE_FIELD_NUMBER: builtins.int
|
||||
CLEAR_ON_REBOOT_FIELD_NUMBER: builtins.int
|
||||
enabled: builtins.bool
|
||||
"""
|
||||
Enable the Range Test Module
|
||||
@@ -829,14 +838,20 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
Bool value indicating that this node should save a RangeTest.csv file.
|
||||
ESP32 Only
|
||||
"""
|
||||
clear_on_reboot: builtins.bool
|
||||
"""
|
||||
Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
|
||||
ESP32 Only
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
enabled: builtins.bool = ...,
|
||||
sender: builtins.int = ...,
|
||||
save: builtins.bool = ...,
|
||||
clear_on_reboot: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "save", b"save", "sender", b"sender"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["clear_on_reboot", b"clear_on_reboot", "enabled", b"enabled", "save", b"save", "sender", b"sender"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class TelemetryConfig(google.protobuf.message.Message):
|
||||
|
||||
4
meshtastic/protobuf/portnums_pb2.py
generated
4
meshtastic/protobuf/portnums_pb2.py
generated
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xe5\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\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\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\x18\n\x14RETICULUM_TUNNEL_APP\x10L\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*\xf6\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\x18\n\x14KEY_VERIFICATION_APP\x10\x0c\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\x18\n\x14RETICULUM_TUNNEL_APP\x10L\x12\x0f\n\x0b\x43\x41YENNE_APP\x10M\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
@@ -22,5 +22,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||
_globals['_PORTNUM']._serialized_start=60
|
||||
_globals['_PORTNUM']._serialized_end=673
|
||||
_globals['_PORTNUM']._serialized_end=690
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
12
meshtastic/protobuf/portnums_pb2.pyi
generated
12
meshtastic/protobuf/portnums_pb2.pyi
generated
@@ -188,6 +188,12 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
|
||||
Reticulum Network Stack Tunnel App
|
||||
ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface
|
||||
"""
|
||||
CAYENNE_APP: _PortNum.ValueType # 77
|
||||
"""
|
||||
App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes. Offers ability to send
|
||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
ENCODING: CayenneLLP
|
||||
"""
|
||||
PRIVATE_APP: _PortNum.ValueType # 256
|
||||
"""
|
||||
Private applications should use portnums >= 256.
|
||||
@@ -386,6 +392,12 @@ RETICULUM_TUNNEL_APP: PortNum.ValueType # 76
|
||||
Reticulum Network Stack Tunnel App
|
||||
ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface
|
||||
"""
|
||||
CAYENNE_APP: PortNum.ValueType # 77
|
||||
"""
|
||||
App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes. Offers ability to send
|
||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
ENCODING: CayenneLLP
|
||||
"""
|
||||
PRIVATE_APP: PortNum.ValueType # 256
|
||||
"""
|
||||
Private applications should use portnums >= 256.
|
||||
|
||||
32
meshtastic/protobuf/telemetry_pb2.py
generated
32
meshtastic/protobuf/telemetry_pb2.py
generated
File diff suppressed because one or more lines are too long
248
meshtastic/protobuf/telemetry_pb2.pyi
generated
248
meshtastic/protobuf/telemetry_pb2.pyi
generated
@@ -183,6 +183,26 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
||||
"""
|
||||
PCT2075 Temperature Sensor
|
||||
"""
|
||||
ADS1X15: _TelemetrySensorType.ValueType # 40
|
||||
"""
|
||||
ADS1X15 ADC
|
||||
"""
|
||||
ADS1X15_ALT: _TelemetrySensorType.ValueType # 41
|
||||
"""
|
||||
ADS1X15 ADC_ALT
|
||||
"""
|
||||
SFA30: _TelemetrySensorType.ValueType # 42
|
||||
"""
|
||||
Sensirion SFA30 Formaldehyde sensor
|
||||
"""
|
||||
SEN5X: _TelemetrySensorType.ValueType # 43
|
||||
"""
|
||||
SEN5X PM SENSORS
|
||||
"""
|
||||
TSL2561: _TelemetrySensorType.ValueType # 44
|
||||
"""
|
||||
TSL2561 light sensor
|
||||
"""
|
||||
|
||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -349,6 +369,26 @@ PCT2075: TelemetrySensorType.ValueType # 39
|
||||
"""
|
||||
PCT2075 Temperature Sensor
|
||||
"""
|
||||
ADS1X15: TelemetrySensorType.ValueType # 40
|
||||
"""
|
||||
ADS1X15 ADC
|
||||
"""
|
||||
ADS1X15_ALT: TelemetrySensorType.ValueType # 41
|
||||
"""
|
||||
ADS1X15 ADC_ALT
|
||||
"""
|
||||
SFA30: TelemetrySensorType.ValueType # 42
|
||||
"""
|
||||
Sensirion SFA30 Formaldehyde sensor
|
||||
"""
|
||||
SEN5X: TelemetrySensorType.ValueType # 43
|
||||
"""
|
||||
SEN5X PM SENSORS
|
||||
"""
|
||||
TSL2561: TelemetrySensorType.ValueType # 44
|
||||
"""
|
||||
TSL2561 light sensor
|
||||
"""
|
||||
global___TelemetrySensorType = TelemetrySensorType
|
||||
|
||||
@typing.final
|
||||
@@ -617,6 +657,16 @@ class PowerMetrics(google.protobuf.message.Message):
|
||||
CH2_CURRENT_FIELD_NUMBER: builtins.int
|
||||
CH3_VOLTAGE_FIELD_NUMBER: builtins.int
|
||||
CH3_CURRENT_FIELD_NUMBER: builtins.int
|
||||
CH4_VOLTAGE_FIELD_NUMBER: builtins.int
|
||||
CH4_CURRENT_FIELD_NUMBER: builtins.int
|
||||
CH5_VOLTAGE_FIELD_NUMBER: builtins.int
|
||||
CH5_CURRENT_FIELD_NUMBER: builtins.int
|
||||
CH6_VOLTAGE_FIELD_NUMBER: builtins.int
|
||||
CH6_CURRENT_FIELD_NUMBER: builtins.int
|
||||
CH7_VOLTAGE_FIELD_NUMBER: builtins.int
|
||||
CH7_CURRENT_FIELD_NUMBER: builtins.int
|
||||
CH8_VOLTAGE_FIELD_NUMBER: builtins.int
|
||||
CH8_CURRENT_FIELD_NUMBER: builtins.int
|
||||
ch1_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch1)
|
||||
@@ -641,6 +691,46 @@ class PowerMetrics(google.protobuf.message.Message):
|
||||
"""
|
||||
Current (Ch3)
|
||||
"""
|
||||
ch4_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch4)
|
||||
"""
|
||||
ch4_current: builtins.float
|
||||
"""
|
||||
Current (Ch4)
|
||||
"""
|
||||
ch5_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch5)
|
||||
"""
|
||||
ch5_current: builtins.float
|
||||
"""
|
||||
Current (Ch5)
|
||||
"""
|
||||
ch6_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch6)
|
||||
"""
|
||||
ch6_current: builtins.float
|
||||
"""
|
||||
Current (Ch6)
|
||||
"""
|
||||
ch7_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch7)
|
||||
"""
|
||||
ch7_current: builtins.float
|
||||
"""
|
||||
Current (Ch7)
|
||||
"""
|
||||
ch8_voltage: builtins.float
|
||||
"""
|
||||
Voltage (Ch8)
|
||||
"""
|
||||
ch8_current: builtins.float
|
||||
"""
|
||||
Current (Ch8)
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -650,9 +740,19 @@ class PowerMetrics(google.protobuf.message.Message):
|
||||
ch2_current: builtins.float | None = ...,
|
||||
ch3_voltage: builtins.float | None = ...,
|
||||
ch3_current: builtins.float | None = ...,
|
||||
ch4_voltage: builtins.float | None = ...,
|
||||
ch4_current: builtins.float | None = ...,
|
||||
ch5_voltage: builtins.float | None = ...,
|
||||
ch5_current: builtins.float | None = ...,
|
||||
ch6_voltage: builtins.float | None = ...,
|
||||
ch6_current: builtins.float | None = ...,
|
||||
ch7_voltage: builtins.float | None = ...,
|
||||
ch7_current: builtins.float | None = ...,
|
||||
ch8_voltage: builtins.float | None = ...,
|
||||
ch8_current: builtins.float | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_ch1_current", b"_ch1_current", "_ch1_voltage", b"_ch1_voltage", "_ch2_current", b"_ch2_current", "_ch2_voltage", b"_ch2_voltage", "_ch3_current", b"_ch3_current", "_ch3_voltage", b"_ch3_voltage", "ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_ch1_current", b"_ch1_current", "_ch1_voltage", b"_ch1_voltage", "_ch2_current", b"_ch2_current", "_ch2_voltage", b"_ch2_voltage", "_ch3_current", b"_ch3_current", "_ch3_voltage", b"_ch3_voltage", "ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_ch1_current", b"_ch1_current", "_ch1_voltage", b"_ch1_voltage", "_ch2_current", b"_ch2_current", "_ch2_voltage", b"_ch2_voltage", "_ch3_current", b"_ch3_current", "_ch3_voltage", b"_ch3_voltage", "_ch4_current", b"_ch4_current", "_ch4_voltage", b"_ch4_voltage", "_ch5_current", b"_ch5_current", "_ch5_voltage", b"_ch5_voltage", "_ch6_current", b"_ch6_current", "_ch6_voltage", b"_ch6_voltage", "_ch7_current", b"_ch7_current", "_ch7_voltage", b"_ch7_voltage", "_ch8_current", b"_ch8_current", "_ch8_voltage", b"_ch8_voltage", "ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage", "ch4_current", b"ch4_current", "ch4_voltage", b"ch4_voltage", "ch5_current", b"ch5_current", "ch5_voltage", b"ch5_voltage", "ch6_current", b"ch6_current", "ch6_voltage", b"ch6_voltage", "ch7_current", b"ch7_current", "ch7_voltage", b"ch7_voltage", "ch8_current", b"ch8_current", "ch8_voltage", b"ch8_voltage"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_ch1_current", b"_ch1_current", "_ch1_voltage", b"_ch1_voltage", "_ch2_current", b"_ch2_current", "_ch2_voltage", b"_ch2_voltage", "_ch3_current", b"_ch3_current", "_ch3_voltage", b"_ch3_voltage", "_ch4_current", b"_ch4_current", "_ch4_voltage", b"_ch4_voltage", "_ch5_current", b"_ch5_current", "_ch5_voltage", b"_ch5_voltage", "_ch6_current", b"_ch6_current", "_ch6_voltage", b"_ch6_voltage", "_ch7_current", b"_ch7_current", "_ch7_voltage", b"_ch7_voltage", "_ch8_current", b"_ch8_current", "_ch8_voltage", b"_ch8_voltage", "ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage", "ch4_current", b"ch4_current", "ch4_voltage", b"ch4_voltage", "ch5_current", b"ch5_current", "ch5_voltage", b"ch5_voltage", "ch6_current", b"ch6_current", "ch6_voltage", b"ch6_voltage", "ch7_current", b"ch7_current", "ch7_voltage", b"ch7_voltage", "ch8_current", b"ch8_current", "ch8_voltage", b"ch8_voltage"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch1_current", b"_ch1_current"]) -> typing.Literal["ch1_current"] | None: ...
|
||||
@typing.overload
|
||||
@@ -665,6 +765,26 @@ class PowerMetrics(google.protobuf.message.Message):
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch3_current", b"_ch3_current"]) -> typing.Literal["ch3_current"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch3_voltage", b"_ch3_voltage"]) -> typing.Literal["ch3_voltage"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch4_current", b"_ch4_current"]) -> typing.Literal["ch4_current"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch4_voltage", b"_ch4_voltage"]) -> typing.Literal["ch4_voltage"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch5_current", b"_ch5_current"]) -> typing.Literal["ch5_current"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch5_voltage", b"_ch5_voltage"]) -> typing.Literal["ch5_voltage"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch6_current", b"_ch6_current"]) -> typing.Literal["ch6_current"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch6_voltage", b"_ch6_voltage"]) -> typing.Literal["ch6_voltage"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch7_current", b"_ch7_current"]) -> typing.Literal["ch7_current"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch7_voltage", b"_ch7_voltage"]) -> typing.Literal["ch7_voltage"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch8_current", b"_ch8_current"]) -> typing.Literal["ch8_current"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_ch8_voltage", b"_ch8_voltage"]) -> typing.Literal["ch8_voltage"] | None: ...
|
||||
|
||||
global___PowerMetrics = PowerMetrics
|
||||
|
||||
@@ -689,58 +809,118 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
PARTICLES_50UM_FIELD_NUMBER: builtins.int
|
||||
PARTICLES_100UM_FIELD_NUMBER: builtins.int
|
||||
CO2_FIELD_NUMBER: builtins.int
|
||||
CO2_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
CO2_HUMIDITY_FIELD_NUMBER: builtins.int
|
||||
FORM_FORMALDEHYDE_FIELD_NUMBER: builtins.int
|
||||
FORM_HUMIDITY_FIELD_NUMBER: builtins.int
|
||||
FORM_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
PM40_STANDARD_FIELD_NUMBER: builtins.int
|
||||
PARTICLES_40UM_FIELD_NUMBER: builtins.int
|
||||
PM_TEMPERATURE_FIELD_NUMBER: builtins.int
|
||||
PM_HUMIDITY_FIELD_NUMBER: builtins.int
|
||||
PM_VOC_IDX_FIELD_NUMBER: builtins.int
|
||||
PM_NOX_IDX_FIELD_NUMBER: builtins.int
|
||||
PARTICLES_TPS_FIELD_NUMBER: builtins.int
|
||||
pm10_standard: builtins.int
|
||||
"""
|
||||
Concentration Units Standard PM1.0
|
||||
Concentration Units Standard PM1.0 in ug/m3
|
||||
"""
|
||||
pm25_standard: builtins.int
|
||||
"""
|
||||
Concentration Units Standard PM2.5
|
||||
Concentration Units Standard PM2.5 in ug/m3
|
||||
"""
|
||||
pm100_standard: builtins.int
|
||||
"""
|
||||
Concentration Units Standard PM10.0
|
||||
Concentration Units Standard PM10.0 in ug/m3
|
||||
"""
|
||||
pm10_environmental: builtins.int
|
||||
"""
|
||||
Concentration Units Environmental PM1.0
|
||||
Concentration Units Environmental PM1.0 in ug/m3
|
||||
"""
|
||||
pm25_environmental: builtins.int
|
||||
"""
|
||||
Concentration Units Environmental PM2.5
|
||||
Concentration Units Environmental PM2.5 in ug/m3
|
||||
"""
|
||||
pm100_environmental: builtins.int
|
||||
"""
|
||||
Concentration Units Environmental PM10.0
|
||||
Concentration Units Environmental PM10.0 in ug/m3
|
||||
"""
|
||||
particles_03um: builtins.int
|
||||
"""
|
||||
0.3um Particle Count
|
||||
0.3um Particle Count in #/0.1l
|
||||
"""
|
||||
particles_05um: builtins.int
|
||||
"""
|
||||
0.5um Particle Count
|
||||
0.5um Particle Count in #/0.1l
|
||||
"""
|
||||
particles_10um: builtins.int
|
||||
"""
|
||||
1.0um Particle Count
|
||||
1.0um Particle Count in #/0.1l
|
||||
"""
|
||||
particles_25um: builtins.int
|
||||
"""
|
||||
2.5um Particle Count
|
||||
2.5um Particle Count in #/0.1l
|
||||
"""
|
||||
particles_50um: builtins.int
|
||||
"""
|
||||
5.0um Particle Count
|
||||
5.0um Particle Count in #/0.1l
|
||||
"""
|
||||
particles_100um: builtins.int
|
||||
"""
|
||||
10.0um Particle Count
|
||||
10.0um Particle Count in #/0.1l
|
||||
"""
|
||||
co2: builtins.int
|
||||
"""
|
||||
CO2 concentration in ppm
|
||||
"""
|
||||
co2_temperature: builtins.float
|
||||
"""
|
||||
CO2 sensor temperature in degC
|
||||
"""
|
||||
co2_humidity: builtins.float
|
||||
"""
|
||||
CO2 sensor relative humidity in %
|
||||
"""
|
||||
form_formaldehyde: builtins.float
|
||||
"""
|
||||
Formaldehyde sensor formaldehyde concentration in ppb
|
||||
"""
|
||||
form_humidity: builtins.float
|
||||
"""
|
||||
Formaldehyde sensor relative humidity in %RH
|
||||
"""
|
||||
form_temperature: builtins.float
|
||||
"""
|
||||
Formaldehyde sensor temperature in degrees Celsius
|
||||
"""
|
||||
pm40_standard: builtins.int
|
||||
"""
|
||||
Concentration Units Standard PM4.0 in ug/m3
|
||||
"""
|
||||
particles_40um: builtins.int
|
||||
"""
|
||||
4.0um Particle Count in #/0.1l
|
||||
"""
|
||||
pm_temperature: builtins.float
|
||||
"""
|
||||
PM Sensor Temperature
|
||||
"""
|
||||
pm_humidity: builtins.float
|
||||
"""
|
||||
PM Sensor humidity
|
||||
"""
|
||||
pm_voc_idx: builtins.float
|
||||
"""
|
||||
PM Sensor VOC Index
|
||||
"""
|
||||
pm_nox_idx: builtins.float
|
||||
"""
|
||||
PM Sensor NOx Index
|
||||
"""
|
||||
particles_tps: builtins.float
|
||||
"""
|
||||
Typical Particle Size in um
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -757,12 +937,34 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
particles_50um: builtins.int | None = ...,
|
||||
particles_100um: builtins.int | None = ...,
|
||||
co2: builtins.int | None = ...,
|
||||
co2_temperature: builtins.float | None = ...,
|
||||
co2_humidity: builtins.float | None = ...,
|
||||
form_formaldehyde: builtins.float | None = ...,
|
||||
form_humidity: builtins.float | None = ...,
|
||||
form_temperature: builtins.float | None = ...,
|
||||
pm40_standard: builtins.int | None = ...,
|
||||
particles_40um: builtins.int | None = ...,
|
||||
pm_temperature: builtins.float | None = ...,
|
||||
pm_humidity: builtins.float | None = ...,
|
||||
pm_voc_idx: builtins.float | None = ...,
|
||||
pm_nox_idx: builtins.float | None = ...,
|
||||
particles_tps: builtins.float | None = ...,
|
||||
) -> None: ...
|
||||
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["_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: ...
|
||||
def HasField(self, field_name: typing.Literal["_co2", b"_co2", "_co2_humidity", b"_co2_humidity", "_co2_temperature", b"_co2_temperature", "_form_formaldehyde", b"_form_formaldehyde", "_form_humidity", b"_form_humidity", "_form_temperature", b"_form_temperature", "_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_40um", b"_particles_40um", "_particles_50um", b"_particles_50um", "_particles_tps", b"_particles_tps", "_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", "_pm40_standard", b"_pm40_standard", "_pm_humidity", b"_pm_humidity", "_pm_nox_idx", b"_pm_nox_idx", "_pm_temperature", b"_pm_temperature", "_pm_voc_idx", b"_pm_voc_idx", "co2", b"co2", "co2_humidity", b"co2_humidity", "co2_temperature", b"co2_temperature", "form_formaldehyde", b"form_formaldehyde", "form_humidity", b"form_humidity", "form_temperature", b"form_temperature", "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_40um", b"particles_40um", "particles_50um", b"particles_50um", "particles_tps", b"particles_tps", "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", "pm40_standard", b"pm40_standard", "pm_humidity", b"pm_humidity", "pm_nox_idx", b"pm_nox_idx", "pm_temperature", b"pm_temperature", "pm_voc_idx", b"pm_voc_idx"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_co2", b"_co2", "_co2_humidity", b"_co2_humidity", "_co2_temperature", b"_co2_temperature", "_form_formaldehyde", b"_form_formaldehyde", "_form_humidity", b"_form_humidity", "_form_temperature", b"_form_temperature", "_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_40um", b"_particles_40um", "_particles_50um", b"_particles_50um", "_particles_tps", b"_particles_tps", "_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", "_pm40_standard", b"_pm40_standard", "_pm_humidity", b"_pm_humidity", "_pm_nox_idx", b"_pm_nox_idx", "_pm_temperature", b"_pm_temperature", "_pm_voc_idx", b"_pm_voc_idx", "co2", b"co2", "co2_humidity", b"co2_humidity", "co2_temperature", b"co2_temperature", "form_formaldehyde", b"form_formaldehyde", "form_humidity", b"form_humidity", "form_temperature", b"form_temperature", "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_40um", b"particles_40um", "particles_50um", b"particles_50um", "particles_tps", b"particles_tps", "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", "pm40_standard", b"pm40_standard", "pm_humidity", b"pm_humidity", "pm_nox_idx", b"pm_nox_idx", "pm_temperature", b"pm_temperature", "pm_voc_idx", b"pm_voc_idx"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_co2", b"_co2"]) -> typing.Literal["co2"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_co2_humidity", b"_co2_humidity"]) -> typing.Literal["co2_humidity"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_co2_temperature", b"_co2_temperature"]) -> typing.Literal["co2_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_form_formaldehyde", b"_form_formaldehyde"]) -> typing.Literal["form_formaldehyde"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_form_humidity", b"_form_humidity"]) -> typing.Literal["form_humidity"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_form_temperature", b"_form_temperature"]) -> typing.Literal["form_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_particles_03um", b"_particles_03um"]) -> typing.Literal["particles_03um"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_particles_05um", b"_particles_05um"]) -> typing.Literal["particles_05um"] | None: ...
|
||||
@@ -773,8 +975,12 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_particles_25um", b"_particles_25um"]) -> typing.Literal["particles_25um"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_particles_40um", b"_particles_40um"]) -> typing.Literal["particles_40um"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_particles_50um", b"_particles_50um"]) -> typing.Literal["particles_50um"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_particles_tps", b"_particles_tps"]) -> typing.Literal["particles_tps"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm100_environmental", b"_pm100_environmental"]) -> typing.Literal["pm100_environmental"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm100_standard", b"_pm100_standard"]) -> typing.Literal["pm100_standard"] | None: ...
|
||||
@@ -786,6 +992,16 @@ class AirQualityMetrics(google.protobuf.message.Message):
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm25_environmental", b"_pm25_environmental"]) -> typing.Literal["pm25_environmental"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm25_standard", b"_pm25_standard"]) -> typing.Literal["pm25_standard"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm40_standard", b"_pm40_standard"]) -> typing.Literal["pm40_standard"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_humidity", b"_pm_humidity"]) -> typing.Literal["pm_humidity"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_nox_idx", b"_pm_nox_idx"]) -> typing.Literal["pm_nox_idx"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_temperature", b"_pm_temperature"]) -> typing.Literal["pm_temperature"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_pm_voc_idx", b"_pm_voc_idx"]) -> typing.Literal["pm_voc_idx"] | None: ...
|
||||
|
||||
global___AirQualityMetrics = AirQualityMetrics
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ from pubsub import pub # type: ignore[import-untyped]
|
||||
from meshtastic.protobuf import portnums_pb2, remote_hardware_pb2
|
||||
from meshtastic.util import our_exit
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def onGPIOreceive(packet, interface) -> None:
|
||||
"""Callback for received GPIO responses"""
|
||||
logging.debug(f"packet:{packet} interface:{interface}")
|
||||
logger.debug(f"packet:{packet} interface:{interface}")
|
||||
gpioValue = 0
|
||||
hw = packet["decoded"]["remotehw"]
|
||||
if "gpioValue" in hw:
|
||||
@@ -76,7 +77,7 @@ class RemoteHardwareClient:
|
||||
Write the specified vals bits to the device GPIOs. Only bits in mask that
|
||||
are 1 will be changed
|
||||
"""
|
||||
logging.debug(f"writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}")
|
||||
logger.debug(f"writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}")
|
||||
r = remote_hardware_pb2.HardwareMessage()
|
||||
r.type = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
|
||||
r.gpio_mask = mask
|
||||
@@ -85,7 +86,7 @@ class RemoteHardwareClient:
|
||||
|
||||
def readGPIOs(self, nodeid, mask, onResponse=None):
|
||||
"""Read the specified bits from GPIO inputs on the device"""
|
||||
logging.debug(f"readGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
logger.debug(f"readGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
r = remote_hardware_pb2.HardwareMessage()
|
||||
r.type = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
|
||||
r.gpio_mask = mask
|
||||
@@ -93,7 +94,7 @@ class RemoteHardwareClient:
|
||||
|
||||
def watchGPIOs(self, nodeid, mask):
|
||||
"""Watch the specified bits from GPIO inputs on the device for changes"""
|
||||
logging.debug(f"watchGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
logger.debug(f"watchGPIOs nodeid:{nodeid} mask:{mask}")
|
||||
r = remote_hardware_pb2.HardwareMessage()
|
||||
r.type = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
|
||||
r.gpio_mask = mask
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
"""
|
||||
# pylint: disable=R0917
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
from io import TextIOWrapper
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -12,9 +13,7 @@ import serial # type: ignore[import-untyped]
|
||||
import meshtastic.util
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
if platform.system() != "Windows":
|
||||
import termios
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SerialInterface(StreamInterface):
|
||||
"""Interface class for meshtastic devices over a serial link"""
|
||||
@@ -33,7 +32,7 @@ class SerialInterface(StreamInterface):
|
||||
|
||||
if self.devPath is None:
|
||||
ports: List[str] = meshtastic.util.findPorts(True)
|
||||
logging.debug(f"ports:{ports}")
|
||||
logger.debug(f"ports:{ports}")
|
||||
if len(ports) == 0:
|
||||
print("No Serial Meshtastic device detected, attempting TCP connection on localhost.")
|
||||
return
|
||||
@@ -44,16 +43,11 @@ class SerialInterface(StreamInterface):
|
||||
else:
|
||||
self.devPath = ports[0]
|
||||
|
||||
logging.debug(f"Connecting to {self.devPath}")
|
||||
logger.debug(f"Connecting to {self.devPath}")
|
||||
|
||||
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
|
||||
# see https://github.com/pyserial/pyserial/issues/124
|
||||
if platform.system() != "Windows":
|
||||
if sys.platform != "win32":
|
||||
with open(self.devPath, encoding="utf8") as f:
|
||||
attrs = termios.tcgetattr(f)
|
||||
attrs[2] = attrs[2] & ~termios.HUPCL
|
||||
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
|
||||
f.close()
|
||||
self._set_hupcl_with_termios(f)
|
||||
time.sleep(0.1)
|
||||
|
||||
self.stream = serial.Serial(
|
||||
@@ -66,6 +60,18 @@ class SerialInterface(StreamInterface):
|
||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
||||
)
|
||||
|
||||
def _set_hupcl_with_termios(self, f: TextIOWrapper):
|
||||
"""first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
|
||||
see https://github.com/pyserial/pyserial/issues/124
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
return
|
||||
|
||||
import termios # pylint: disable=C0415,E0401
|
||||
attrs = termios.tcgetattr(f)
|
||||
attrs[2] = attrs[2] & ~termios.HUPCL
|
||||
termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
|
||||
|
||||
def __repr__(self):
|
||||
rep = f"SerialInterface(devPath={self.devPath!r}"
|
||||
if hasattr(self, 'debugOut') and self.debugOut is not None:
|
||||
@@ -84,5 +90,5 @@ class SerialInterface(StreamInterface):
|
||||
time.sleep(0.1)
|
||||
self.stream.flush()
|
||||
time.sleep(0.1)
|
||||
logging.debug("Closing Serial stream")
|
||||
logger.debug("Closing Serial stream")
|
||||
StreamInterface.close(self)
|
||||
|
||||
@@ -10,6 +10,7 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
import parse # type: ignore[import-untyped]
|
||||
@@ -22,6 +23,7 @@ from meshtastic.powermon import PowerMeter
|
||||
|
||||
from .arrow import FeatherWriter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def root_dir() -> str:
|
||||
"""Return the root directory for slog files."""
|
||||
@@ -29,9 +31,9 @@ def root_dir() -> str:
|
||||
app_name = "meshtastic"
|
||||
app_author = "meshtastic"
|
||||
app_dir = platformdirs.user_data_dir(app_name, app_author)
|
||||
dir_name = f"{app_dir}/slogs"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
return dir_name
|
||||
dir_name = Path(app_dir, "slogs")
|
||||
dir_name.mkdir(exist_ok=True, parents=True)
|
||||
return str(dir_name)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
@@ -144,7 +146,7 @@ class StructuredLogger:
|
||||
self.power_logger = power_logger
|
||||
|
||||
# Setup the arrow writer (and its schema)
|
||||
self.writer = FeatherWriter(f"{dir_path}/slog")
|
||||
self.writer = FeatherWriter(os.path.join(dir_path, "slog"))
|
||||
all_fields = reduce(
|
||||
(lambda x, y: x + y), map(lambda x: x.fields, log_defs.values())
|
||||
)
|
||||
@@ -164,7 +166,7 @@ class StructuredLogger:
|
||||
self.raw_file: Optional[
|
||||
io.TextIOWrapper
|
||||
] = open( # pylint: disable=consider-using-with
|
||||
f"{dir_path}/raw.txt", "w", encoding="utf8"
|
||||
os.path.join(dir_path, "raw.txt"), "w", encoding="utf8"
|
||||
)
|
||||
|
||||
# We need a closure here because the subscription API is very strict about exact arg matching
|
||||
@@ -197,7 +199,7 @@ class StructuredLogger:
|
||||
if m:
|
||||
src = m.group(1)
|
||||
args = m.group(2)
|
||||
logging.debug(f"SLog {src}, args: {args}")
|
||||
logger.debug(f"SLog {src}, args: {args}")
|
||||
|
||||
d = log_defs.get(src)
|
||||
if d:
|
||||
@@ -219,9 +221,9 @@ class StructuredLogger:
|
||||
# If the last field is an empty string, remove it
|
||||
del di[last_field[0]]
|
||||
else:
|
||||
logging.warning(f"Failed to parse slog {line} with {d.format}")
|
||||
logger.warning(f"Failed to parse slog {line} with {d.format}")
|
||||
else:
|
||||
logging.warning(f"Unknown Structured Log: {line}")
|
||||
logger.warning(f"Unknown Structured Log: {line}")
|
||||
|
||||
# Store our structured log record
|
||||
if di or self.include_raw:
|
||||
@@ -256,23 +258,28 @@ class LogSet:
|
||||
|
||||
if not dir_name:
|
||||
app_dir = root_dir()
|
||||
dir_name = f"{app_dir}/{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
app_time_dir = Path(app_dir, datetime.now().strftime('%Y%m%d-%H%M%S'))
|
||||
app_time_dir.mkdir(exist_ok=True)
|
||||
dir_name = str(app_time_dir)
|
||||
|
||||
# Also make a 'latest' directory that always points to the most recent logs
|
||||
latest_dir = Path(app_dir, "latest")
|
||||
latest_dir.unlink(missing_ok=True)
|
||||
|
||||
# symlink might fail on some platforms, if it does fail silently
|
||||
if os.path.exists(f"{app_dir}/latest"):
|
||||
os.unlink(f"{app_dir}/latest")
|
||||
os.symlink(dir_name, f"{app_dir}/latest", target_is_directory=True)
|
||||
try:
|
||||
latest_dir.symlink_to(dir_name, target_is_directory=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.dir_name = dir_name
|
||||
|
||||
logging.info(f"Writing slogs to {dir_name}")
|
||||
logger.info(f"Writing slogs to {dir_name}")
|
||||
|
||||
self.power_logger: Optional[PowerLogger] = (
|
||||
None
|
||||
if not power_meter
|
||||
else PowerLogger(power_meter, f"{self.dir_name}/power")
|
||||
else PowerLogger(power_meter, os.path.join(self.dir_name, "power"))
|
||||
)
|
||||
|
||||
self.slog_logger: Optional[StructuredLogger] = StructuredLogger(
|
||||
@@ -286,7 +293,7 @@ class LogSet:
|
||||
"""Close the log set."""
|
||||
|
||||
if self.slog_logger:
|
||||
logging.info(f"Closing slogs in {self.dir_name}")
|
||||
logger.info(f"Closing slogs in {self.dir_name}")
|
||||
atexit.unregister(
|
||||
self.atexit_handler
|
||||
) # docs say it will silently ignore if not found
|
||||
|
||||
@@ -17,6 +17,7 @@ START1 = 0x94
|
||||
START2 = 0xC3
|
||||
HEADER_LEN = 4
|
||||
MAX_TO_FROM_RADIO_SIZE = 512
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StreamInterface(MeshInterface):
|
||||
@@ -82,7 +83,7 @@ class StreamInterface(MeshInterface):
|
||||
"""We override the superclass implementation to close our port"""
|
||||
MeshInterface._disconnected(self)
|
||||
|
||||
logging.debug("Closing our port")
|
||||
logger.debug("Closing our port")
|
||||
# pylint: disable=E0203
|
||||
if not self.stream is None:
|
||||
# pylint: disable=E0203
|
||||
@@ -111,17 +112,17 @@ class StreamInterface(MeshInterface):
|
||||
|
||||
def _sendToRadioImpl(self, toRadio) -> None:
|
||||
"""Send a ToRadio protobuf to the device"""
|
||||
logging.debug(f"Sending: {stripnl(toRadio)}")
|
||||
logger.debug(f"Sending: {stripnl(toRadio)}")
|
||||
b: bytes = toRadio.SerializeToString()
|
||||
bufLen: int = len(b)
|
||||
# We convert into a string, because the TCP code doesn't work with byte arrays
|
||||
header: bytes = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
|
||||
logging.debug(f"sending header:{header!r} b:{b!r}")
|
||||
logger.debug(f"sending header:{header!r} b:{b!r}")
|
||||
self._writeBytes(header + b)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing stream")
|
||||
logger.debug("Closing stream")
|
||||
MeshInterface.close(self)
|
||||
# pyserial cancel_read doesn't seem to work, therefore we ask the
|
||||
# reader thread to close things for us
|
||||
@@ -148,18 +149,18 @@ class StreamInterface(MeshInterface):
|
||||
|
||||
def __reader(self) -> None:
|
||||
"""The reader thread that reads bytes from our stream"""
|
||||
logging.debug("in __reader()")
|
||||
logger.debug("in __reader()")
|
||||
empty = bytes()
|
||||
|
||||
try:
|
||||
while not self._wantExit:
|
||||
# logging.debug("reading character")
|
||||
# logger.debug("reading character")
|
||||
b: Optional[bytes] = self._readBytes(1)
|
||||
# logging.debug("In reader loop")
|
||||
# logging.debug(f"read returned {b}")
|
||||
# logger.debug("In reader loop")
|
||||
# logger.debug(f"read returned {b}")
|
||||
if b is not None and len(cast(bytes, b)) > 0:
|
||||
c: int = b[0]
|
||||
# logging.debug(f'c:{c}')
|
||||
# logger.debug(f'c:{c}')
|
||||
ptr: int = len(self._rxBuf)
|
||||
|
||||
# Assume we want to append this byte, fixme use bytearray instead
|
||||
@@ -176,7 +177,7 @@ class StreamInterface(MeshInterface):
|
||||
if c != START2:
|
||||
self._rxBuf = empty # failed to find start2
|
||||
elif ptr >= HEADER_LEN - 1: # we've at least got a header
|
||||
# logging.debug('at least we received a header')
|
||||
# logger.debug('at least we received a header')
|
||||
# big endian length follows header
|
||||
packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
|
||||
|
||||
@@ -192,32 +193,32 @@ class StreamInterface(MeshInterface):
|
||||
try:
|
||||
self._handleFromRadio(self._rxBuf[HEADER_LEN:])
|
||||
except Exception as ex:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Error while handling message from radio {ex}"
|
||||
)
|
||||
traceback.print_exc()
|
||||
self._rxBuf = empty
|
||||
else:
|
||||
# logging.debug(f"timeout")
|
||||
# logger.debug(f"timeout")
|
||||
pass
|
||||
except serial.SerialException as ex:
|
||||
if (
|
||||
not self._wantExit
|
||||
): # We might intentionally get an exception during shutdown
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Meshtastic serial port disconnected, disconnecting... {ex}"
|
||||
)
|
||||
except OSError as ex:
|
||||
if (
|
||||
not self._wantExit
|
||||
): # We might intentionally get an exception during shutdown
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Unexpected OSError, terminating meshtastic reader... {ex}"
|
||||
)
|
||||
except Exception as ex:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Unexpected exception, terminating meshtastic reader... {ex}"
|
||||
)
|
||||
finally:
|
||||
logging.debug("reader is exiting")
|
||||
logger.debug("reader is exiting")
|
||||
self._disconnected()
|
||||
|
||||
@@ -207,6 +207,18 @@ nano_g1 = SupportedDevice(
|
||||
usb_product_id_in_hex="55d4",
|
||||
)
|
||||
|
||||
seeed_xiao_s3 = SupportedDevice(
|
||||
name = "Seeed Xiao ESP32-S3",
|
||||
version = "",
|
||||
for_firmware="seeed-xiao-esp32s3",
|
||||
baseport_on_linux="ttyACM",
|
||||
baseport_on_mac="cu.usbmodem",
|
||||
usb_vendor_id_in_hex="2886",
|
||||
usb_product_id_in_hex="0059",
|
||||
)
|
||||
|
||||
|
||||
|
||||
supported_devices = [
|
||||
tbeam_v0_7,
|
||||
tbeam_v1_1,
|
||||
@@ -226,4 +238,5 @@ supported_devices = [
|
||||
rak4631_19003,
|
||||
rak11200,
|
||||
nano_g1,
|
||||
seeed_xiao_s3,
|
||||
]
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import Optional
|
||||
from meshtastic.stream_interface import StreamInterface
|
||||
|
||||
DEFAULT_TCP_PORT = 4403
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TCPInterface(StreamInterface):
|
||||
"""Interface class for meshtastic devices over a TCP link"""
|
||||
@@ -67,13 +68,13 @@ class TCPInterface(StreamInterface):
|
||||
|
||||
def myConnect(self) -> None:
|
||||
"""Connect to socket"""
|
||||
logging.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe]
|
||||
logger.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe]
|
||||
server_address = (self.hostname, self.portNumber)
|
||||
self.socket = socket.create_connection(server_address)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close a connection to the device"""
|
||||
logging.debug("Closing TCP stream")
|
||||
logger.debug("Closing TCP stream")
|
||||
super().close()
|
||||
# Sometimes the socket read might be blocked in the reader thread.
|
||||
# Therefore we force the shutdown by closing the socket here
|
||||
@@ -97,7 +98,7 @@ class TCPInterface(StreamInterface):
|
||||
# empty byte indicates a disconnected socket,
|
||||
# we need to handle it to avoid an infinite loop reading from null socket
|
||||
if data == b'':
|
||||
logging.debug("dead socket, re-connecting")
|
||||
logger.debug("dead socket, re-connecting")
|
||||
# cleanup and reconnect socket without breaking reader thread
|
||||
with contextlib.suppress(Exception):
|
||||
self._socket_shutdown()
|
||||
|
||||
@@ -29,6 +29,7 @@ testNumber: int = 0
|
||||
|
||||
sendingInterface = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def onReceive(packet, interface) -> None:
|
||||
"""Callback invoked when a packet arrives"""
|
||||
@@ -79,7 +80,7 @@ def testSend(
|
||||
else:
|
||||
toNode = toInterface.myInfo.my_node_num
|
||||
|
||||
logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
logger.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
|
||||
# pylint: disable=W0603
|
||||
global sendingInterface
|
||||
sendingInterface = fromInterface
|
||||
@@ -98,7 +99,7 @@ def testSend(
|
||||
|
||||
def runTests(numTests: int=50, wantAck: bool=False, maxFailures: int=0) -> bool:
|
||||
"""Run the tests."""
|
||||
logging.info(f"Running {numTests} tests with wantAck={wantAck}")
|
||||
logger.info(f"Running {numTests} tests with wantAck={wantAck}")
|
||||
numFail: int = 0
|
||||
numSuccess: int = 0
|
||||
for _ in range(numTests):
|
||||
@@ -112,26 +113,26 @@ def runTests(numTests: int=50, wantAck: bool=False, maxFailures: int=0) -> bool:
|
||||
)
|
||||
if not success:
|
||||
numFail = numFail + 1
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)"
|
||||
)
|
||||
else:
|
||||
numSuccess = numSuccess + 1
|
||||
logging.info(
|
||||
logger.info(
|
||||
f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far"
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
if numFail > maxFailures:
|
||||
logging.error("Too many failures! Test failed!")
|
||||
logger.error("Too many failures! Test failed!")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def testThread(numTests=50) -> bool:
|
||||
"""Test thread"""
|
||||
logging.info("Found devices, starting tests...")
|
||||
logger.info("Found devices, starting tests...")
|
||||
result: bool = runTests(numTests, wantAck=True)
|
||||
if result:
|
||||
# Run another test
|
||||
@@ -148,7 +149,7 @@ def onConnection(topic=pub.AUTO_TOPIC) -> None:
|
||||
def openDebugLog(portName) -> io.TextIOWrapper:
|
||||
"""Open the debug log file"""
|
||||
debugname = "log" + portName.replace("/", "_")
|
||||
logging.info(f"Writing serial debugging to {debugname}")
|
||||
logger.info(f"Writing serial debugging to {debugname}")
|
||||
return open(debugname, "w+", buffering=1, encoding="utf8")
|
||||
|
||||
|
||||
@@ -177,7 +178,7 @@ def testAll(numTests: int=5) -> bool:
|
||||
)
|
||||
)
|
||||
|
||||
logging.info("Ports opened, starting test")
|
||||
logger.info("Ports opened, starting test")
|
||||
result: bool = testThread(numTests)
|
||||
|
||||
for i in interfaces:
|
||||
@@ -196,14 +197,14 @@ def testSimulator() -> None:
|
||||
python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
|
||||
"""
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.info("Connecting to simulator on localhost!")
|
||||
logger.info("Connecting to simulator on localhost!")
|
||||
try:
|
||||
iface: meshtastic.tcp_interface.TCPInterface = TCPInterface("localhost")
|
||||
iface.showInfo()
|
||||
iface.localNode.showInfo()
|
||||
iface.localNode.exitSimulator()
|
||||
iface.close()
|
||||
logging.info("Integration test successful!")
|
||||
logger.info("Integration test successful!")
|
||||
except:
|
||||
print("Error while testing simulator:", sys.exc_info()[0])
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -18,6 +18,7 @@ from meshtastic.__main__ import (
|
||||
onNode,
|
||||
onReceive,
|
||||
tunnelMain,
|
||||
set_missing_flags_false,
|
||||
)
|
||||
from meshtastic import mt_config
|
||||
|
||||
@@ -34,7 +35,6 @@ from ..tcp_interface import TCPInterface
|
||||
# from ..remote_hardware import onGPIOreceive
|
||||
# from ..config_pb2 import Config
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_init_parser_no_args(capsys):
|
||||
@@ -454,6 +454,37 @@ def test_main_set_owner_short_to_bob(capsys):
|
||||
assert err == ""
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_set_is_unmessageable_to_true(capsys):
|
||||
"""Test --set-is-unmessageable true"""
|
||||
sys.argv = ["", "--set-is-unmessageable", "true"]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||
main()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||
assert re.search(r"Setting device owner is_unmessageable to True", out, re.MULTILINE)
|
||||
assert err == ""
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_set_is_unmessagable_to_true(capsys):
|
||||
"""Test --set-is-unmessagable true"""
|
||||
sys.argv = ["", "--set-is-unmessagable", "true"]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||
main()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||
assert re.search(r"Setting device owner is_unmessageable to True", out, re.MULTILINE)
|
||||
assert err == ""
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@@ -494,6 +525,44 @@ def test_main_get_canned_messages(capsys, caplog, iface_with_nodes):
|
||||
assert err == ""
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_set_ringtone(capsys):
|
||||
"""Test --set-ringtone"""
|
||||
sys.argv = ["", "--set-ringtone", "foo,bar"]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||
main()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||
assert re.search(r"Setting ringtone to foo,bar", out, re.MULTILINE)
|
||||
assert err == ""
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_get_ringtone(capsys, caplog, iface_with_nodes):
|
||||
"""Test --get-ringtone"""
|
||||
sys.argv = ["", "--get-ringtone"]
|
||||
mt_config.args = sys.argv
|
||||
|
||||
iface = iface_with_nodes
|
||||
iface.devPath = "bar"
|
||||
|
||||
mocked_node = MagicMock(autospec=Node)
|
||||
mocked_node.get_ringtone.return_value = "foo,bar"
|
||||
iface.localNode = mocked_node
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||
main()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||
assert re.search(r"ringtone:foo,bar", out, re.MULTILINE)
|
||||
assert err == ""
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@@ -688,12 +757,11 @@ def test_main_sendtext_with_invalid_channel_nine(caplog, capsys):
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_get, mock_set, capsys, caplog, iface_with_nodes):
|
||||
def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_hupcl, capsys, caplog, iface_with_nodes):
|
||||
"""Test --sendtext with --dest"""
|
||||
sys.argv = ["", "--sendtext", "hello", "--dest", "foo"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -887,12 +955,11 @@ def test_main_seturl(capsys):
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_valid(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_set_valid(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --set with valid field"""
|
||||
sys.argv = ["", "--set", "network.wifi_ssid", "foo"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -912,12 +979,11 @@ def test_main_set_valid(mocked_findports, mocked_serial, mocked_open, mocked_get
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_valid_wifi_psk(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_set_valid_wifi_psk(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --set with valid field"""
|
||||
sys.argv = ["", "--set", "network.wifi_psk", "123456789"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -937,12 +1003,11 @@ def test_main_set_valid_wifi_psk(mocked_findports, mocked_serial, mocked_open, m
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_invalid_wifi_psk(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_set_invalid_wifi_psk(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --set with an invalid value (psk must be 8 or more characters)"""
|
||||
sys.argv = ["", "--set", "network.wifi_psk", "1234567"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -965,12 +1030,11 @@ def test_main_set_invalid_wifi_psk(mocked_findports, mocked_serial, mocked_open,
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_valid_camel_case(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_set_valid_camel_case(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --set with valid field"""
|
||||
sys.argv = ["", "--set", "network.wifi_ssid", "foo"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -991,12 +1055,11 @@ def test_main_set_valid_camel_case(mocked_findports, mocked_serial, mocked_open,
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_set_with_invalid(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_set_with_invalid(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --set with invalid field"""
|
||||
sys.argv = ["", "--set", "foo", "foo"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -1017,12 +1080,11 @@ def test_main_set_with_invalid(mocked_findports, mocked_serial, mocked_open, moc
|
||||
# TODO: write some negative --configure tests
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_configure_with_snake_case(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_configure_with_snake_case(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --configure with valid file"""
|
||||
sys.argv = ["", "--configure", "example_config.yaml"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -1050,12 +1112,11 @@ def test_main_configure_with_snake_case(mocked_findports, mocked_serial, mocked_
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_main_configure_with_camel_case_keys(mocked_findports, mocked_serial, mocked_open, mocked_get, mocked_set, capsys):
|
||||
def test_main_configure_with_camel_case_keys(mocked_findports, mocked_serial, mocked_open, mocked_hupcl, capsys):
|
||||
"""Test --configure with valid file"""
|
||||
sys.argv = ["", "--configure", "exampleConfig.yaml"]
|
||||
mt_config.args = sys.argv
|
||||
@@ -1724,6 +1785,8 @@ def test_main_export_config(capsys):
|
||||
mo.getLongName.return_value = "foo"
|
||||
mo.getShortName.return_value = "oof"
|
||||
mo.localNode.getURL.return_value = "bar"
|
||||
mo.getCannedMessage.return_value = "foo|bar"
|
||||
mo.getRingtone.return_value = "24:d=32,o=5"
|
||||
mo.getMyNodeInfo().get.return_value = {
|
||||
"latitudeI": 1100000000,
|
||||
"longitudeI": 1200000000,
|
||||
@@ -1738,7 +1801,8 @@ position_broadcast_smart: true
|
||||
fixed_position: true
|
||||
position_flags: 35"""
|
||||
export_config(mo)
|
||||
out, err = capsys.readouterr()
|
||||
out = export_config(mo)
|
||||
err = ""
|
||||
|
||||
# ensure we do not output this line
|
||||
assert not re.search(r"Connected to radio", out, re.MULTILINE)
|
||||
@@ -1825,6 +1889,41 @@ position_flags: 35"""
|
||||
# mo.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_set_missing_flags_false():
|
||||
"""Test set_missing_flags_false() function"""
|
||||
config = {
|
||||
"bluetooth": {
|
||||
"enabled": True
|
||||
},
|
||||
"lora": {
|
||||
"txEnabled": True
|
||||
}
|
||||
}
|
||||
|
||||
false_defaults = {
|
||||
("bluetooth", "enabled"),
|
||||
("lora", "sx126xRxBoostedGain"),
|
||||
("lora", "txEnabled"),
|
||||
("lora", "usePreset"),
|
||||
("position", "positionBroadcastSmartEnabled"),
|
||||
("security", "serialEnabled"),
|
||||
("mqtt", "encryptionEnabled"),
|
||||
}
|
||||
|
||||
set_missing_flags_false(config, false_defaults)
|
||||
|
||||
# Preserved
|
||||
assert config["bluetooth"]["enabled"] is True
|
||||
assert config["lora"]["txEnabled"] is True
|
||||
|
||||
# Added
|
||||
assert config["lora"]["usePreset"] is False
|
||||
assert config["lora"]["sx126xRxBoostedGain"] is False
|
||||
assert config["position"]["positionBroadcastSmartEnabled"] is False
|
||||
assert config["security"]["serialEnabled"] is False
|
||||
assert config["mqtt"]["encryptionEnabled"] is False
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_main_gpio_rd_no_gpio_channel(capsys):
|
||||
@@ -2614,16 +2713,16 @@ def test_tunnel_subnet_arg_with_no_devices(mock_platform_system, caplog, capsys)
|
||||
assert err == ""
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="on windows is no fcntl module")
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@patch("platform.system")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_tunnel_tunnel_arg(
|
||||
mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_platform_system, caplog, iface_with_nodes, capsys
|
||||
mocked_findPorts, mocked_serial, mocked_open, mock_hupcl, mock_platform_system, caplog, iface_with_nodes, capsys
|
||||
):
|
||||
"""Test tunnel with tunnel arg (act like we are on a linux system)"""
|
||||
|
||||
|
||||
@@ -525,6 +525,28 @@ def test_getMyNodeInfo():
|
||||
myinfo = iface.getMyNodeInfo()
|
||||
assert myinfo == anode
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_getCannedMessage():
|
||||
"""Test MeshInterface.getCannedMessage()"""
|
||||
iface = MeshInterface(noProto=True)
|
||||
node = MagicMock()
|
||||
node.get_canned_message.return_value = "Hi|Bye|Yes"
|
||||
iface.localNode = node
|
||||
result = iface.getCannedMessage()
|
||||
assert result == "Hi|Bye|Yes"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_getRingtone():
|
||||
"""Test MeshInterface.getRingtone()"""
|
||||
iface = MeshInterface(noProto=True)
|
||||
node = MagicMock()
|
||||
node.get_ringtone.return_value = "foo,bar"
|
||||
iface.localNode = node
|
||||
result = iface.getRingtone()
|
||||
assert result == "foo,bar"
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
@@ -543,7 +565,6 @@ def test_generatePacketId(capsys):
|
||||
assert err == ""
|
||||
assert pytest_wrapped_e.type == MeshInterface.MeshInterfaceError
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_mt_config")
|
||||
def test_fixupPosition_empty_pos():
|
||||
@@ -651,15 +672,21 @@ def test_getOrCreateByNum(iface_with_nodes):
|
||||
@pytest.mark.unit
|
||||
def test_exit_with_exception(caplog):
|
||||
"""Test __exit__()"""
|
||||
iface = MeshInterface(noProto=True)
|
||||
with caplog.at_level(logging.ERROR):
|
||||
iface.__exit__("foo", "bar", "baz")
|
||||
assert re.search(
|
||||
r"An exception of type foo with value bar has occurred",
|
||||
caplog.text,
|
||||
re.MULTILINE,
|
||||
)
|
||||
assert re.search(r"Traceback: baz", caplog.text, re.MULTILINE)
|
||||
try:
|
||||
with MeshInterface(noProto=True):
|
||||
raise ValueError("Something went wrong")
|
||||
except:
|
||||
assert re.search(
|
||||
r"An exception of type <class \'ValueError\'> with value Something went wrong has occurred",
|
||||
caplog.text,
|
||||
re.MULTILINE,
|
||||
)
|
||||
assert re.search(
|
||||
r"Traceback:\n.*in test_exit_with_exception\n {4}raise ValueError\(\"Something went wrong\"\)",
|
||||
caplog.text,
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# pylint: disable=R0917
|
||||
|
||||
import re
|
||||
import sys
|
||||
from unittest.mock import mock_open, patch
|
||||
|
||||
import pytest
|
||||
@@ -9,16 +10,14 @@ import pytest
|
||||
from ..serial_interface import SerialInterface
|
||||
from ..protobuf import config_pb2
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@patch("time.sleep")
|
||||
@patch("termios.tcsetattr")
|
||||
@patch("termios.tcgetattr")
|
||||
@patch("meshtastic.serial_interface.SerialInterface._set_hupcl_with_termios")
|
||||
@patch("builtins.open", new_callable=mock_open, read_data="data")
|
||||
@patch("serial.Serial")
|
||||
@patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
|
||||
def test_SerialInterface_single_port(
|
||||
mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys
|
||||
mocked_findPorts, mocked_serial, mocked_open, mock_hupcl, mock_sleep, capsys
|
||||
):
|
||||
"""Test that we can instantiate a SerialInterface with a single port"""
|
||||
iface = SerialInterface(noProto=True)
|
||||
@@ -28,9 +27,12 @@ def test_SerialInterface_single_port(
|
||||
iface.close()
|
||||
mocked_findPorts.assert_called()
|
||||
mocked_serial.assert_called()
|
||||
mocked_open.assert_called()
|
||||
mock_get.assert_called()
|
||||
mock_set.assert_called()
|
||||
|
||||
# doesn't get called in SerialInterface on windows
|
||||
if sys.platform != "win32":
|
||||
mocked_open.assert_called()
|
||||
mock_hupcl.assert_called()
|
||||
|
||||
mock_sleep.assert_called()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Nodes in mesh", out, re.MULTILINE)
|
||||
|
||||
@@ -563,7 +563,7 @@ def test_active_ports_on_supported_devices_mac_duplicates_check(mock_platform, m
|
||||
def test_message_to_json_shows_all():
|
||||
"""Test that message_to_json prints fields that aren't included in data passed in"""
|
||||
actual = json.loads(message_to_json(mesh_pb2.MyNodeInfo()))
|
||||
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0, "deviceId": "", "pioEnv": "" }
|
||||
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0, "deviceId": "", "pioEnv": "", 'firmwareEdition': 'VANILLA', 'nodedbCount': 0 }
|
||||
assert actual == expected
|
||||
|
||||
@pytest.mark.unit
|
||||
|
||||
@@ -26,10 +26,11 @@ from meshtastic.protobuf import portnums_pb2
|
||||
from meshtastic import mt_config
|
||||
from meshtastic.util import ipstr, readnet_u16
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def onTunnelReceive(packet, interface): # pylint: disable=W0613
|
||||
"""Callback for received tunneled messages from mesh."""
|
||||
logging.debug(f"in onTunnelReceive()")
|
||||
logger.debug(f"in onTunnelReceive()")
|
||||
tunnelInstance = mt_config.tunnelInstance
|
||||
tunnelInstance.onReceive(packet)
|
||||
|
||||
@@ -92,7 +93,7 @@ class Tunnel:
|
||||
self.LOG_TRACE = 5
|
||||
|
||||
# TODO: check if root?
|
||||
logging.info(
|
||||
logger.info(
|
||||
"Starting IP to mesh tunnel (you must be root for this *pre-alpha* "
|
||||
"feature to work). Mesh members:"
|
||||
)
|
||||
@@ -104,13 +105,13 @@ class Tunnel:
|
||||
for node in self.iface.nodes.values():
|
||||
nodeId = node["user"]["id"]
|
||||
ip = self._nodeNumToIp(node["num"])
|
||||
logging.info(f"Node { nodeId } has IP address { ip }")
|
||||
logger.info(f"Node { nodeId } has IP address { ip }")
|
||||
|
||||
logging.debug("creating TUN device with MTU=200")
|
||||
logger.debug("creating TUN device with MTU=200")
|
||||
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
|
||||
self.tun = None
|
||||
if self.iface.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Not creating a TapDevice() because it is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
@@ -120,11 +121,11 @@ class Tunnel:
|
||||
|
||||
self._rxThread = None
|
||||
if self.iface.noProto:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Not starting TUN reader because it is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
logging.debug(f"starting TUN reader, our IP address is {myAddr}")
|
||||
logger.debug(f"starting TUN reader, our IP address is {myAddr}")
|
||||
self._rxThread = threading.Thread(
|
||||
target=self.__tunReader, args=(), daemon=True
|
||||
)
|
||||
@@ -134,9 +135,9 @@ class Tunnel:
|
||||
"""onReceive"""
|
||||
p = packet["decoded"]["payload"]
|
||||
if packet["from"] == self.iface.myInfo.my_node_num:
|
||||
logging.debug("Ignoring message we sent")
|
||||
logger.debug("Ignoring message we sent")
|
||||
else:
|
||||
logging.debug(f"Received mesh tunnel message type={type(p)} len={len(p)}")
|
||||
logger.debug(f"Received mesh tunnel message type={type(p)} len={len(p)}")
|
||||
# we don't really need to check for filtering here (sender should have checked),
|
||||
# but this provides useful debug printing on types of packets received
|
||||
if not self.iface.noProto:
|
||||
@@ -152,7 +153,7 @@ class Tunnel:
|
||||
ignore = False # Assume we will be forwarding the packet
|
||||
if protocol in self.protocolBlacklist:
|
||||
ignore = True
|
||||
logging.log(
|
||||
logger.log(
|
||||
self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}"
|
||||
)
|
||||
elif protocol == 0x01: # ICMP
|
||||
@@ -160,7 +161,7 @@ class Tunnel:
|
||||
icmpCode = p[21]
|
||||
checksum = p[22:24]
|
||||
# pylint: disable=line-too-long
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}"
|
||||
)
|
||||
# reply to pings (swap src and dest but keep rest of packet unchanged)
|
||||
@@ -171,19 +172,19 @@ class Tunnel:
|
||||
destport = readnet_u16(p, subheader + 2)
|
||||
if destport in self.udpBlacklist:
|
||||
ignore = True
|
||||
logging.log(self.LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
|
||||
logger.log(self.LOG_TRACE, f"ignoring blacklisted UDP port {destport}")
|
||||
else:
|
||||
logging.debug(f"forwarding udp srcport={srcport}, destport={destport}")
|
||||
logger.debug(f"forwarding udp srcport={srcport}, destport={destport}")
|
||||
elif protocol == 0x06: # TCP
|
||||
srcport = readnet_u16(p, subheader)
|
||||
destport = readnet_u16(p, subheader + 2)
|
||||
if destport in self.tcpBlacklist:
|
||||
ignore = True
|
||||
logging.log(self.LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
|
||||
logger.log(self.LOG_TRACE, f"ignoring blacklisted TCP port {destport}")
|
||||
else:
|
||||
logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
|
||||
logger.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
|
||||
else:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"forwarding unexpected protocol 0x{protocol:02x}, "
|
||||
"src={ipstr(srcaddr)}, dest={ipstr(destAddr)}"
|
||||
)
|
||||
@@ -192,10 +193,10 @@ class Tunnel:
|
||||
|
||||
def __tunReader(self):
|
||||
tap = self.tun
|
||||
logging.debug("TUN reader running")
|
||||
logger.debug("TUN reader running")
|
||||
while True:
|
||||
p = tap.read()
|
||||
# logging.debug(f"IP packet received on TUN interface, type={type(p)}")
|
||||
# logger.debug(f"IP packet received on TUN interface, type={type(p)}")
|
||||
destAddr = p[16:20]
|
||||
|
||||
if not self._shouldFilterPacket(p):
|
||||
@@ -210,7 +211,7 @@ class Tunnel:
|
||||
|
||||
for node in self.iface.nodes.values():
|
||||
nodeNum = node["num"] & 0xFFFF
|
||||
# logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}")
|
||||
# logger.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}")
|
||||
if (nodeNum) == ipBits:
|
||||
return node["user"]["id"]
|
||||
return None
|
||||
@@ -222,12 +223,12 @@ class Tunnel:
|
||||
"""Forward the provided IP packet into the mesh"""
|
||||
nodeId = self._ipToNodeId(destAddr)
|
||||
if nodeId is not None:
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}"
|
||||
)
|
||||
self.iface.sendData(p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
|
||||
else:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"Dropping packet because no node found for destIP={ipstr(destAddr)}"
|
||||
)
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ blacklistVids: Dict = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
|
||||
0x303a Heltec tracker"""
|
||||
whitelistVids = dict.fromkeys([0x239a, 0x303a])
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def quoteBooleans(a_string: str) -> str:
|
||||
"""Quote booleans
|
||||
@@ -141,7 +142,7 @@ def catchAndIgnore(reason: str, closure) -> None:
|
||||
try:
|
||||
closure()
|
||||
except BaseException as ex:
|
||||
logging.error(f"Exception thrown in {reason}: {ex}")
|
||||
logger.error(f"Exception thrown in {reason}: {ex}")
|
||||
|
||||
|
||||
def findPorts(eliminate_duplicates: bool=False) -> List[str]:
|
||||
@@ -307,7 +308,7 @@ class DeferredExecution:
|
||||
o = self.queue.get()
|
||||
o()
|
||||
except:
|
||||
logging.error(
|
||||
logger.error(
|
||||
f"Unexpected error in deferred execution {sys.exc_info()[0]}"
|
||||
)
|
||||
print(traceback.format_exc())
|
||||
|
||||
Submodule protobufs updated: c758376d04...8caf423964
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "meshtastic"
|
||||
version = "2.6.4"
|
||||
version = "2.7.3"
|
||||
description = "Python API & client shell for talking to Meshtastic devices"
|
||||
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
Reference in New Issue
Block a user