mirror of
https://github.com/meshtastic/python.git
synced 2025-12-26 01:17:51 -05:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2c9c1315e | ||
|
|
f41ef042a9 | ||
|
|
84bec5a7c4 | ||
|
|
985366c812 | ||
|
|
3954fbd404 | ||
|
|
5f174b2850 | ||
|
|
23ea19c00b | ||
|
|
acc47146c9 | ||
|
|
dd8803793d | ||
|
|
68ec588804 | ||
|
|
2f48594dc3 | ||
|
|
c7c3c69fc3 | ||
|
|
53e40d5aab | ||
|
|
dd3da6a670 | ||
|
|
e500b399f4 | ||
|
|
27ac28e300 | ||
|
|
6bc5f5e305 | ||
|
|
c844e4e0fe | ||
|
|
060df86bb6 | ||
|
|
7d87d5037e | ||
|
|
4ec7698d94 | ||
|
|
7cc65aa08a | ||
|
|
cc411ce0bb | ||
|
|
edff956f9d | ||
|
|
bd68739158 | ||
|
|
530d92ead2 | ||
|
|
60f9dc6266 | ||
|
|
f9ae021e43 | ||
|
|
317d81c983 | ||
|
|
5837bd0172 | ||
|
|
5487f7a791 | ||
|
|
c6d8a540eb | ||
|
|
0962c9b058 | ||
|
|
4f98602ac2 | ||
|
|
6ebddb67c0 | ||
|
|
82554a1f18 | ||
|
|
8c115dc636 | ||
|
|
e2fe359527 | ||
|
|
5600ce92b0 | ||
|
|
efb848adf9 | ||
|
|
0d8646189f | ||
|
|
d0023df8ca | ||
|
|
b522abf33e | ||
|
|
c086b6372e | ||
|
|
6ec506fe3b | ||
|
|
fc3b81dfde | ||
|
|
9c53ea017c | ||
|
|
1e6625d062 | ||
|
|
0487ce5e1a | ||
|
|
aac19b2ecc | ||
|
|
0fb72b8ad1 | ||
|
|
872fbef5d6 | ||
|
|
ec4fbe3a59 | ||
|
|
6bab385380 | ||
|
|
b8178d513a | ||
|
|
f4c085fc50 | ||
|
|
57f0598082 | ||
|
|
55d3188408 | ||
|
|
7b64fbb71b | ||
|
|
7f85eb0285 | ||
|
|
d05ef17ab3 | ||
|
|
d161291ca4 | ||
|
|
4e267c75b0 | ||
|
|
7cc18e9df6 | ||
|
|
9284a848f2 | ||
|
|
74c911cb75 | ||
|
|
579383cd5a |
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
@@ -245,6 +245,23 @@
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--debug", "--nodes"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic nodes table",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--nodes"]
|
||||
},
|
||||
{
|
||||
"name": "meshtastic nodes table with show-fields",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "meshtastic",
|
||||
"justMyCode": true,
|
||||
"args": ["--nodes", "--show-fields", "AKA,Pubkey,Role,Role,Role,Latitude,Latitude,deviceMetrics.voltage"]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
55
examples/waypoint.py
Normal file
55
examples/waypoint.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Program to create and delete waypoint
|
||||
To run:
|
||||
python3 examples/waypoint.py --port /dev/ttyUSB0 create 45 test the_desc_2 '2024-12-18T23:05:23' 48.74 7.35
|
||||
python3 examples/waypoint.py delete 45
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
import meshtastic
|
||||
import meshtastic.serial_interface
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='waypoint',
|
||||
description='Create and delete Meshtastic waypoint')
|
||||
parser.add_argument('--port', default=None)
|
||||
parser.add_argument('--debug', default=False, action='store_true')
|
||||
|
||||
subparsers = parser.add_subparsers(dest='cmd')
|
||||
parser_delete = subparsers.add_parser('delete', help='Delete a waypoint')
|
||||
parser_delete.add_argument('id', help="id of the waypoint")
|
||||
|
||||
parser_create = subparsers.add_parser('create', help='Create a new waypoint')
|
||||
parser_create.add_argument('id', help="id of the waypoint")
|
||||
parser_create.add_argument('name', help="name of the waypoint")
|
||||
parser_create.add_argument('description', help="description of the waypoint")
|
||||
parser_create.add_argument('expire', help="expiration date of the waypoint as interpreted by datetime.fromisoformat")
|
||||
parser_create.add_argument('latitude', help="latitude of the waypoint")
|
||||
parser_create.add_argument('longitude', help="longitude of the waypoint")
|
||||
|
||||
args = parser.parse_args()
|
||||
print(args)
|
||||
|
||||
# By default will try to find a meshtastic device,
|
||||
# otherwise provide a device path like /dev/ttyUSB0
|
||||
if args.debug:
|
||||
d = sys.stderr
|
||||
else:
|
||||
d = None
|
||||
with meshtastic.serial_interface.SerialInterface(args.port, debugOut=d) as iface:
|
||||
if args.cmd == 'create':
|
||||
p = iface.sendWaypoint(
|
||||
waypoint_id=int(args.id),
|
||||
name=args.name,
|
||||
description=args.description,
|
||||
expire=int(datetime.datetime.fromisoformat(args.expire).timestamp()),
|
||||
latitude=float(args.latitude),
|
||||
longitude=float(args.longitude),
|
||||
)
|
||||
else:
|
||||
p = iface.deleteWaypoint(int(args.id))
|
||||
print(p)
|
||||
|
||||
# iface.close()
|
||||
@@ -11,7 +11,7 @@ from types import ModuleType
|
||||
import argparse
|
||||
argcomplete: Union[None, ModuleType] = None
|
||||
try:
|
||||
import argcomplete
|
||||
import argcomplete # type: ignore
|
||||
except ImportError as e:
|
||||
pass # already set to None by default above
|
||||
|
||||
@@ -74,7 +74,7 @@ def onReceive(packet, interface) -> None:
|
||||
args
|
||||
and args.sendtext
|
||||
and packet["to"] == interface.myInfo.my_node_num
|
||||
and d["portnum"] == portnums_pb2.PortNum.TEXT_MESSAGE_APP
|
||||
and d.get("portnum", portnums_pb2.PortNum.UNKNOWN_APP) == portnums_pb2.PortNum.TEXT_MESSAGE_APP
|
||||
):
|
||||
interface.close() # after running command then exit
|
||||
|
||||
@@ -90,7 +90,7 @@ def onReceive(packet, interface) -> None:
|
||||
interface.sendText(reply)
|
||||
|
||||
except Exception as ex:
|
||||
print(f"Warning: There is no field {ex} in the packet.")
|
||||
print(f"Warning: Error processing received packet: {ex}.")
|
||||
|
||||
|
||||
def onConnection(interface, topic=pub.AUTO_TOPIC) -> None: # pylint: disable=W0613
|
||||
@@ -226,7 +226,7 @@ def setPref(config, comp_name, raw_val) -> bool:
|
||||
logging.debug(f"valStr:{raw_val} val:{val}")
|
||||
|
||||
if snake_name == "wifi_psk" and len(str(raw_val)) < 8:
|
||||
print(f"Warning: network.wifi_psk must be 8 or more characters.")
|
||||
print("Warning: network.wifi_psk must be 8 or more characters.")
|
||||
return False
|
||||
|
||||
enumType = pref.enum_type
|
||||
@@ -483,6 +483,7 @@ def onConnected(interface):
|
||||
if checkChannel(interface, channelIndex):
|
||||
print(
|
||||
f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}"
|
||||
f" {'using PRIVATE_APP port' if args.private else ''}"
|
||||
)
|
||||
interface.sendText(
|
||||
args.sendtext,
|
||||
@@ -490,6 +491,7 @@ def onConnected(interface):
|
||||
wantAck=True,
|
||||
channelIndex=channelIndex,
|
||||
onResponse=interface.getNode(args.dest, False, **getNode_kwargs).onAckNak,
|
||||
portNum=portnums_pb2.PortNum.PRIVATE_APP if args.private else portnums_pb2.PortNum.TEXT_MESSAGE_APP
|
||||
)
|
||||
else:
|
||||
meshtastic.util.our_exit(
|
||||
@@ -714,12 +716,16 @@ def onConnected(interface):
|
||||
closeNow = True
|
||||
export_config(interface)
|
||||
|
||||
if args.seturl:
|
||||
if args.ch_set_url:
|
||||
closeNow = True
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(args.seturl)
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(args.ch_set_url, addOnly=False)
|
||||
|
||||
# handle changing channels
|
||||
|
||||
if args.ch_add_url:
|
||||
closeNow = True
|
||||
interface.getNode(args.dest, **getNode_kwargs).setURL(args.ch_add_url, addOnly=True)
|
||||
|
||||
if args.ch_add:
|
||||
channelIndex = mt_config.channel_index
|
||||
if channelIndex is not None:
|
||||
@@ -921,7 +927,11 @@ def onConnected(interface):
|
||||
if args.dest != BROADCAST_ADDR:
|
||||
print("Showing node list of a remote node is not supported.")
|
||||
return
|
||||
interface.showNodes()
|
||||
interface.showNodes(True, args.show_fields)
|
||||
|
||||
if args.show_fields and not args.nodes:
|
||||
print("--show-fields can only be used with --nodes")
|
||||
return
|
||||
|
||||
if args.qr or args.qr_all:
|
||||
closeNow = True
|
||||
@@ -1245,6 +1255,19 @@ def common():
|
||||
noProto=args.noproto,
|
||||
noNodes=args.no_nodes,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
# Handle the case where the serial device is not found
|
||||
message = (
|
||||
f"File Not Found Error:\n"
|
||||
)
|
||||
message += f" The serial device at '{args.port}' was not found.\n"
|
||||
message += " Please check the following:\n"
|
||||
message += " 1. Is the device connected properly?\n"
|
||||
message += " 2. Is the correct serial port specified?\n"
|
||||
message += " 3. Are the necessary drivers installed?\n"
|
||||
message += " 4. Are you using a **power-only USB cable**? A power-only cable cannot transmit data.\n"
|
||||
message += " Ensure you are using a **data-capable USB cable**.\n"
|
||||
meshtastic.util.our_exit(message, 1)
|
||||
except PermissionError as ex:
|
||||
username = os.getlogin()
|
||||
message = "Permission Error:\n"
|
||||
@@ -1255,6 +1278,12 @@ def common():
|
||||
message += " After running that command, log out and re-login for it to take effect.\n"
|
||||
message += f"Error was:{ex}"
|
||||
meshtastic.util.our_exit(message)
|
||||
except OSError as ex:
|
||||
message = f"OS Error:\n"
|
||||
message += " The serial device couldn't be opened, it might be in use by another process.\n"
|
||||
message += " Please close any applications or webpages that may be using the device and try again.\n"
|
||||
message += f"\nOriginal error: {ex}"
|
||||
meshtastic.util.our_exit(message)
|
||||
if client.devPath is None:
|
||||
try:
|
||||
client = meshtastic.tcp_interface.TCPInterface(
|
||||
@@ -1342,7 +1371,8 @@ def addSelectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser
|
||||
|
||||
group.add_argument(
|
||||
"--dest",
|
||||
help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate",
|
||||
help="The destination node id for any sent commands. If not set '^all' or '^local' is assumed."
|
||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||
default=None,
|
||||
metavar="!xxxxxxxx",
|
||||
)
|
||||
@@ -1489,7 +1519,20 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
"--set-ham", help="Set licensed Ham ID and turn off encryption", action="store"
|
||||
)
|
||||
|
||||
group.add_argument("--seturl", help="Set a channel URL", action="store")
|
||||
group.add_argument(
|
||||
"--ch-set-url", "--seturl",
|
||||
help="Set all channels and set LoRa config from a supplied URL",
|
||||
metavar="URL",
|
||||
action="store"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--ch-add-url",
|
||||
help="Add secondary channels and set LoRa config from a supplied URL",
|
||||
metavar="URL",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
return parser
|
||||
|
||||
@@ -1627,6 +1670,13 @@ def addLocalActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--show-fields",
|
||||
help="Specify fields to show (comma-separated) when using --nodes",
|
||||
type=lambda s: s.split(','),
|
||||
default=None
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
def addRemoteActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
||||
@@ -1638,15 +1688,21 @@ def addRemoteActionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar
|
||||
|
||||
group.add_argument(
|
||||
"--sendtext",
|
||||
help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.",
|
||||
help="Send a text message. Can specify a destination '--dest', use of PRIVATE_APP port '--private', and/or channel index '--ch-index'.",
|
||||
metavar="TEXT",
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--private",
|
||||
help="Optional argument for sending text messages to the PRIVATE_APP port. Use in combination with --sendtext.",
|
||||
action="store_true"
|
||||
)
|
||||
|
||||
group.add_argument(
|
||||
"--traceroute",
|
||||
help="Traceroute from connected node to a destination. "
|
||||
"You need pass the destination ID as argument, like "
|
||||
"this: '--traceroute !ba4bf9d0' "
|
||||
"this: '--traceroute !ba4bf9d0' | '--traceroute 0xba4bf9d0'"
|
||||
"Only nodes with a shared channel can be traced.",
|
||||
metavar="!xxxxxxxx",
|
||||
)
|
||||
@@ -1727,27 +1783,32 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
|
||||
|
||||
group.add_argument(
|
||||
"--remove-node",
|
||||
help="Tell the destination node to remove a specific node from its DB, by node number or ID",
|
||||
help="Tell the destination node to remove a specific node from its NodeDB. "
|
||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||
metavar="!xxxxxxxx"
|
||||
)
|
||||
group.add_argument(
|
||||
"--set-favorite-node",
|
||||
help="Tell the destination node to set the specified node to be favorited on the NodeDB on the devicein its DB, by number or ID",
|
||||
help="Tell the destination node to set the specified node to be favorited on the NodeDB. "
|
||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||
metavar="!xxxxxxxx"
|
||||
)
|
||||
group.add_argument(
|
||||
"--remove-favorite-node",
|
||||
help="Tell the destination node to set the specified node to be un-favorited on the NodeDB on the device, by number or ID",
|
||||
help="Tell the destination node to set the specified node to be un-favorited on the NodeDB. "
|
||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||
metavar="!xxxxxxxx"
|
||||
)
|
||||
group.add_argument(
|
||||
"--set-ignored-node",
|
||||
help="Tell the destination node to set the specified node to be ignored on the NodeDB on the devicein its DB, by number or ID",
|
||||
help="Tell the destination node to set the specified node to be ignored on the NodeDB. "
|
||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||
metavar="!xxxxxxxx"
|
||||
)
|
||||
group.add_argument(
|
||||
"--remove-ignored-node",
|
||||
help="Tell the destination node to set the specified node to be un-ignored on the NodeDB on the device, by number or ID",
|
||||
help="Tell the destination node to set the specified node to be un-ignored on the NodeDB. "
|
||||
"Use the node ID with a '!' or '0x' prefix or the node number.",
|
||||
metavar="!xxxxxxxx"
|
||||
)
|
||||
group.add_argument(
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
import collections
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
import secrets
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
@@ -220,9 +222,42 @@ class MeshInterface: # pylint: disable=R0902
|
||||
return infos
|
||||
|
||||
def showNodes(
|
||||
self, includeSelf: bool = True
|
||||
self, includeSelf: bool = True, showFields: Optional[List[str]] = None
|
||||
) -> str: # pylint: disable=W0613
|
||||
"""Show table summary of nodes in mesh"""
|
||||
"""Show table summary of nodes in mesh
|
||||
|
||||
Args:
|
||||
includeSelf (bool): Include ourself in the output?
|
||||
showFields (List[str]): List of fields to show in output
|
||||
"""
|
||||
|
||||
def get_human_readable(name):
|
||||
name_map = {
|
||||
"user.longName": "User",
|
||||
"user.id": "ID",
|
||||
"user.shortName": "AKA",
|
||||
"user.hwModel": "Hardware",
|
||||
"user.publicKey": "Pubkey",
|
||||
"user.role": "Role",
|
||||
"position.latitude": "Latitude",
|
||||
"position.longitude": "Longitude",
|
||||
"position.altitude": "Altitude",
|
||||
"deviceMetrics.batteryLevel": "Battery",
|
||||
"deviceMetrics.channelUtilization": "Channel util.",
|
||||
"deviceMetrics.airUtilTx": "Tx air util.",
|
||||
"snr": "SNR",
|
||||
"hopsAway": "Hops",
|
||||
"channel": "Channel",
|
||||
"lastHeard": "LastHeard",
|
||||
"since": "Since",
|
||||
|
||||
}
|
||||
|
||||
if name in name_map:
|
||||
return name_map.get(name) # Default to a formatted guess
|
||||
else:
|
||||
return name
|
||||
|
||||
|
||||
def formatFloat(value, precision=2, unit="") -> Optional[str]:
|
||||
"""Format a float value with precision."""
|
||||
@@ -244,6 +279,29 @@ class MeshInterface: # pylint: disable=R0902
|
||||
return None # not handling a timestamp from the future
|
||||
return _timeago(delta_secs)
|
||||
|
||||
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.")
|
||||
return None
|
||||
keys = key_path.split(".")
|
||||
value: Optional[Union[str, dict]] = node_dict
|
||||
for key in keys:
|
||||
if isinstance(value, dict):
|
||||
value = value.get(key)
|
||||
else:
|
||||
return None
|
||||
return value
|
||||
|
||||
if showFields is None or len(showFields) == 0:
|
||||
# The default set of fields to show (e.g., the status quo)
|
||||
showFields = ["N", "user.longName", "user.id", "user.shortName", "user.hwModel", "user.publicKey",
|
||||
"user.role", "position.latitude", "position.longitude", "position.altitude",
|
||||
"deviceMetrics.batteryLevel", "deviceMetrics.channelUtilization",
|
||||
"deviceMetrics.airUtilTx", "snr", "hopsAway", "channel", "lastHeard", "since"]
|
||||
else:
|
||||
# Always at least include the row number.
|
||||
showFields.insert(0, "N")
|
||||
|
||||
rows: List[Dict[str, Any]] = []
|
||||
if self.nodesByNum:
|
||||
logging.debug(f"self.nodes:{self.nodes}")
|
||||
@@ -252,65 +310,60 @@ class MeshInterface: # pylint: disable=R0902
|
||||
continue
|
||||
|
||||
presumptive_id = f"!{node['num']:08x}"
|
||||
row = {
|
||||
"N": 0,
|
||||
"User": f"Meshtastic {presumptive_id[-4:]}",
|
||||
"ID": presumptive_id,
|
||||
}
|
||||
|
||||
user = node.get("user")
|
||||
if user:
|
||||
row.update(
|
||||
{
|
||||
"User": user.get("longName", "N/A"),
|
||||
"AKA": user.get("shortName", "N/A"),
|
||||
"ID": user["id"],
|
||||
"Hardware": user.get("hwModel", "UNSET"),
|
||||
"Pubkey": user.get("publicKey", "UNSET"),
|
||||
}
|
||||
)
|
||||
|
||||
pos = node.get("position")
|
||||
if pos:
|
||||
row.update(
|
||||
{
|
||||
"Latitude": formatFloat(pos.get("latitude"), 4, "°"),
|
||||
"Longitude": formatFloat(pos.get("longitude"), 4, "°"),
|
||||
"Altitude": formatFloat(pos.get("altitude"), 0, " m"),
|
||||
}
|
||||
)
|
||||
|
||||
metrics = node.get("deviceMetrics")
|
||||
if metrics:
|
||||
batteryLevel = metrics.get("batteryLevel")
|
||||
if batteryLevel is not None:
|
||||
if batteryLevel == 0:
|
||||
batteryString = "Powered"
|
||||
# This allows the user to specify fields that wouldn't otherwise be included.
|
||||
fields = {}
|
||||
for field in showFields:
|
||||
if "." in field:
|
||||
raw_value = getNestedValue(node, field)
|
||||
else:
|
||||
# The "since" column is synthesized, it's not retrieved from the device. Get the
|
||||
# lastHeard value here, and then we'll format it properly below.
|
||||
if field == "since":
|
||||
raw_value = node.get("lastHeard")
|
||||
else:
|
||||
batteryString = str(batteryLevel) + "%"
|
||||
row.update({"Battery": batteryString})
|
||||
row.update(
|
||||
{
|
||||
"Channel util.": formatFloat(
|
||||
metrics.get("channelUtilization"), 2, "%"
|
||||
),
|
||||
"Tx air util.": formatFloat(
|
||||
metrics.get("airUtilTx"), 2, "%"
|
||||
),
|
||||
}
|
||||
)
|
||||
raw_value = node.get(field)
|
||||
|
||||
row.update(
|
||||
{
|
||||
"SNR": formatFloat(node.get("snr"), 2, " dB"),
|
||||
"Hops": node.get("hopsAway", "?"),
|
||||
"Channel": node.get("channel", 0),
|
||||
"LastHeard": getLH(node.get("lastHeard")),
|
||||
"Since": getTimeAgo(node.get("lastHeard")),
|
||||
}
|
||||
)
|
||||
formatted_value: Optional[str] = ""
|
||||
|
||||
rows.append(row)
|
||||
# Some of these need special formatting or processing.
|
||||
if field == "channel":
|
||||
if raw_value is None:
|
||||
formatted_value = "0"
|
||||
elif field == "deviceMetrics.channelUtilization":
|
||||
formatted_value = formatFloat(raw_value, 2, "%")
|
||||
elif field == "deviceMetrics.airUtilTx":
|
||||
formatted_value = formatFloat(raw_value, 2, "%")
|
||||
elif field == "deviceMetrics.batteryLevel":
|
||||
if raw_value in (0, 101):
|
||||
formatted_value = "Powered"
|
||||
else:
|
||||
formatted_value = formatFloat(raw_value, 0, "%")
|
||||
elif field == "lastHeard":
|
||||
formatted_value = getLH(raw_value)
|
||||
elif field == "position.latitude":
|
||||
formatted_value = formatFloat(raw_value, 4, "°")
|
||||
elif field == "position.longitude":
|
||||
formatted_value = formatFloat(raw_value, 4, "°")
|
||||
elif field == "position.altitude":
|
||||
formatted_value = formatFloat(raw_value, 0, "m")
|
||||
elif field == "since":
|
||||
formatted_value = getTimeAgo(raw_value) or "N/A"
|
||||
elif field == "snr":
|
||||
formatted_value = formatFloat(raw_value, 0, " dB")
|
||||
elif field == "user.shortName":
|
||||
formatted_value = raw_value if raw_value is not None else f'Meshtastic {presumptive_id[-4:]}'
|
||||
elif field == "user.id":
|
||||
formatted_value = raw_value if raw_value is not None else presumptive_id
|
||||
else:
|
||||
formatted_value = raw_value # No special formatting
|
||||
|
||||
fields[field] = formatted_value
|
||||
|
||||
# Filter out any field in the data set that was not specified.
|
||||
filteredData = {get_human_readable(k): v for k, v in fields.items() if k in showFields}
|
||||
filteredData.update({get_human_readable(k): v for k, v in fields.items()})
|
||||
rows.append(filteredData)
|
||||
|
||||
rows.sort(key=lambda r: r.get("LastHeard") or "0000", reverse=True)
|
||||
for i, row in enumerate(rows):
|
||||
@@ -342,7 +395,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
if new_index != last_index:
|
||||
retries_left = requestChannelAttempts - 1
|
||||
if retries_left <= 0:
|
||||
our_exit(f"Error: Timed out waiting for channels, giving up")
|
||||
our_exit("Error: Timed out waiting for channels, giving up")
|
||||
print("Timed out trying to retrieve channel info, retrying")
|
||||
n.requestChannels(startingIndex=new_index)
|
||||
last_index = new_index
|
||||
@@ -358,6 +411,7 @@ 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
|
||||
):
|
||||
"""Send a utf8 string to some other node, if the node has a display it
|
||||
will also be shown on the device.
|
||||
@@ -368,12 +422,12 @@ class MeshInterface: # pylint: disable=R0902
|
||||
Keyword Arguments:
|
||||
destinationId {nodeId or nodeNum} -- where to send this
|
||||
message (default: {BROADCAST_ADDR})
|
||||
portNum -- the application portnum (similar to IP port numbers)
|
||||
of the destination, see portnums.proto for a list
|
||||
wantAck -- True if you want the message sent in a reliable manner
|
||||
(with retries and ack/nak provided for delivery)
|
||||
wantResponse -- True if you want the service on the other side to
|
||||
send an application layer response
|
||||
portNum -- the application portnum (similar to IP port numbers)
|
||||
of the destination, see portnums.proto for a list
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet
|
||||
and can be used to track future message acks/naks.
|
||||
@@ -382,13 +436,47 @@ class MeshInterface: # pylint: disable=R0902
|
||||
return self.sendData(
|
||||
text.encode("utf-8"),
|
||||
destinationId,
|
||||
portNum=portnums_pb2.PortNum.TEXT_MESSAGE_APP,
|
||||
portNum=portNum,
|
||||
wantAck=wantAck,
|
||||
wantResponse=wantResponse,
|
||||
onResponse=onResponse,
|
||||
channelIndex=channelIndex,
|
||||
)
|
||||
|
||||
|
||||
def sendAlert(
|
||||
self,
|
||||
text: str,
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
onResponse: Optional[Callable[[dict], Any]] = None,
|
||||
channelIndex: int = 0,
|
||||
):
|
||||
"""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.
|
||||
|
||||
Arguments:
|
||||
text {string} -- The text of the alert to send
|
||||
|
||||
Keyword Arguments:
|
||||
destinationId {nodeId or nodeNum} -- where to send this
|
||||
message (default: {BROADCAST_ADDR})
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet
|
||||
and can be used to track future message acks/naks.
|
||||
"""
|
||||
|
||||
return self.sendData(
|
||||
text.encode("utf-8"),
|
||||
destinationId,
|
||||
portNum=portnums_pb2.PortNum.ALERT_APP,
|
||||
wantAck=False,
|
||||
wantResponse=False,
|
||||
onResponse=onResponse,
|
||||
channelIndex=channelIndex,
|
||||
priority=mesh_pb2.MeshPacket.Priority.ALERT
|
||||
)
|
||||
|
||||
def sendData(
|
||||
self,
|
||||
data,
|
||||
@@ -402,6 +490,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
hopLimit: Optional[int]=None,
|
||||
pkiEncrypted: Optional[bool]=False,
|
||||
publicKey: Optional[bytes]=None,
|
||||
priority: mesh_pb2.MeshPacket.Priority.ValueType=mesh_pb2.MeshPacket.Priority.RELIABLE,
|
||||
): # pylint: disable=R0913
|
||||
"""Send a data packet to some other node
|
||||
|
||||
@@ -453,6 +542,8 @@ class MeshInterface: # pylint: disable=R0902
|
||||
meshPacket.decoded.portnum = portNum
|
||||
meshPacket.decoded.want_response = wantResponse
|
||||
meshPacket.id = self._generatePacketId()
|
||||
if priority is not None:
|
||||
meshPacket.priority = priority
|
||||
|
||||
if onResponse is not None:
|
||||
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
||||
@@ -700,6 +791,113 @@ class MeshInterface: # pylint: disable=R0902
|
||||
"No response from node. At least firmware 2.1.22 is required on the destination node."
|
||||
)
|
||||
|
||||
def onResponseWaypoint(self, p: dict):
|
||||
"""on response for waypoint"""
|
||||
if p["decoded"]["portnum"] == "WAYPOINT_APP":
|
||||
self._acknowledgment.receivedWaypoint = True
|
||||
w = mesh_pb2.Waypoint()
|
||||
w.ParseFromString(p["decoded"]["payload"])
|
||||
print(f"Waypoint received: {w}")
|
||||
elif p["decoded"]["portnum"] == "ROUTING_APP":
|
||||
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":
|
||||
our_exit(
|
||||
"No response from node. At least firmware 2.1.22 is required on the destination node."
|
||||
)
|
||||
|
||||
def sendWaypoint(
|
||||
self,
|
||||
name,
|
||||
description,
|
||||
expire: int,
|
||||
waypoint_id: Optional[int] = None,
|
||||
latitude: float = 0.0,
|
||||
longitude: float = 0.0,
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
wantAck: bool = True,
|
||||
wantResponse: bool = False,
|
||||
channelIndex: int = 0,
|
||||
): # pylint: disable=R0913
|
||||
"""
|
||||
Send a waypoint packet to some other node (normally a broadcast)
|
||||
|
||||
Returns the sent packet. The id field will be populated in this packet and
|
||||
can be used to track future message acks/naks.
|
||||
"""
|
||||
w = mesh_pb2.Waypoint()
|
||||
w.name = name
|
||||
w.description = description
|
||||
w.expire = expire
|
||||
if waypoint_id is None:
|
||||
# Generate a waypoint's id, NOT a packet ID.
|
||||
# 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}")
|
||||
else:
|
||||
w.id = waypoint_id
|
||||
if latitude != 0.0:
|
||||
w.latitude_i = int(latitude * 1e7)
|
||||
logging.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}")
|
||||
|
||||
if wantResponse:
|
||||
onResponse = self.onResponseWaypoint
|
||||
else:
|
||||
onResponse = None
|
||||
|
||||
d = self.sendData(
|
||||
w,
|
||||
destinationId,
|
||||
portNum=portnums_pb2.PortNum.WAYPOINT_APP,
|
||||
wantAck=wantAck,
|
||||
wantResponse=wantResponse,
|
||||
onResponse=onResponse,
|
||||
channelIndex=channelIndex,
|
||||
)
|
||||
if wantResponse:
|
||||
self.waitForWaypoint()
|
||||
return d
|
||||
|
||||
def deleteWaypoint(
|
||||
self,
|
||||
waypoint_id: int,
|
||||
destinationId: Union[int, str] = BROADCAST_ADDR,
|
||||
wantAck: bool = True,
|
||||
wantResponse: bool = False,
|
||||
channelIndex: int = 0,
|
||||
):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
p = mesh_pb2.Waypoint()
|
||||
p.id = waypoint_id
|
||||
p.expire = 0
|
||||
|
||||
if wantResponse:
|
||||
onResponse = self.onResponseWaypoint
|
||||
else:
|
||||
onResponse = None
|
||||
|
||||
d = self.sendData(
|
||||
p,
|
||||
destinationId,
|
||||
portNum=portnums_pb2.PortNum.WAYPOINT_APP,
|
||||
wantAck=wantAck,
|
||||
wantResponse=wantResponse,
|
||||
onResponse=onResponse,
|
||||
channelIndex=channelIndex,
|
||||
)
|
||||
if wantResponse:
|
||||
self.waitForWaypoint()
|
||||
return d
|
||||
|
||||
def _addResponseHandler(
|
||||
self,
|
||||
requestId: int,
|
||||
@@ -745,8 +943,10 @@ class MeshInterface: # pylint: disable=R0902
|
||||
else:
|
||||
our_exit("Warning: No myInfo found.")
|
||||
# A simple hex style nodeid - we can parse this without needing the DB
|
||||
elif destinationId.startswith("!"):
|
||||
nodeNum = int(destinationId[1:], 16)
|
||||
elif isinstance(destinationId, str) and len(destinationId) >= 8:
|
||||
# assuming some form of node id string such as !1234578 or 0x12345678
|
||||
# always grab the last 8 items of the hexadecimal id str and parse to integer
|
||||
nodeNum = int(destinationId[-8:], 16)
|
||||
else:
|
||||
if self.nodes:
|
||||
node = self.nodes.get(destinationId)
|
||||
@@ -780,7 +980,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
toRadio.packet.CopyFrom(meshPacket)
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
f"Not sending packet because protocol use is disabled by noProto"
|
||||
"Not sending packet because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
logging.debug(f"Sending packet: {stripnl(meshPacket)}")
|
||||
@@ -824,6 +1024,12 @@ class MeshInterface: # pylint: disable=R0902
|
||||
if not success:
|
||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for position")
|
||||
|
||||
def waitForWaypoint(self):
|
||||
"""Wait for waypoint"""
|
||||
success = self._timeout.waitForWaypoint(self._acknowledgment)
|
||||
if not success:
|
||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for waypoint")
|
||||
|
||||
def getMyNodeInfo(self) -> Optional[Dict]:
|
||||
"""Get info about my node."""
|
||||
if self.myInfo is None or self.nodesByNum is None:
|
||||
@@ -963,7 +1169,7 @@ class MeshInterface: # pylint: disable=R0902
|
||||
"""Send a ToRadio protobuf to the device"""
|
||||
if self.noProto:
|
||||
logging.warning(
|
||||
f"Not sending packet because protocol use is disabled by noProto"
|
||||
"Not sending packet because protocol use is disabled by noProto"
|
||||
)
|
||||
else:
|
||||
# logging.debug(f"Sending toRadio: {stripnl(toRadio)}")
|
||||
|
||||
@@ -337,14 +337,19 @@ class Node:
|
||||
s = s.replace("=", "").replace("+", "-").replace("/", "_")
|
||||
return f"https://meshtastic.org/e/#{s}"
|
||||
|
||||
def setURL(self, url):
|
||||
def setURL(self, url: str, addOnly: bool = False):
|
||||
"""Set mesh network URL"""
|
||||
if self.localConfig is None:
|
||||
our_exit("Warning: No Config has been read")
|
||||
if self.localConfig is None or self.channels is None:
|
||||
our_exit("Warning: config or channels not loaded")
|
||||
|
||||
# URLs are of the form https://meshtastic.org/d/#{base64_channel_set}
|
||||
# Split on '/#' to find the base64 encoded channel settings
|
||||
splitURL = url.split("/#")
|
||||
if addOnly:
|
||||
splitURL = url.split("/?add=true#")
|
||||
else:
|
||||
splitURL = url.split("/#")
|
||||
if len(splitURL) == 1:
|
||||
our_exit(f"Warning: Invalid URL '{url}'")
|
||||
b64 = splitURL[-1]
|
||||
|
||||
# We normally strip padding to make for a shorter URL, but the python parser doesn't like
|
||||
@@ -361,20 +366,36 @@ class Node:
|
||||
if len(channelSet.settings) == 0:
|
||||
our_exit("Warning: There were no settings.")
|
||||
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
ch = channel_pb2.Channel()
|
||||
ch.role = (
|
||||
channel_pb2.Channel.Role.PRIMARY
|
||||
if i == 0
|
||||
else channel_pb2.Channel.Role.SECONDARY
|
||||
)
|
||||
ch.index = i
|
||||
ch.settings.CopyFrom(chs)
|
||||
self.channels[ch.index] = ch
|
||||
logging.debug(f"Channel i:{i} ch:{ch}")
|
||||
self.writeChannel(ch.index)
|
||||
i = i + 1
|
||||
if addOnly:
|
||||
# Add new channels with names not already present
|
||||
# Don't change existing channels
|
||||
for chs in channelSet.settings:
|
||||
channelExists = self.getChannelByName(chs.name)
|
||||
if channelExists or chs.name == "":
|
||||
print(f"Ignoring existing or empty channel \"{chs.name}\" from add URL")
|
||||
continue
|
||||
ch = self.getDisabledChannel()
|
||||
if not ch:
|
||||
our_exit("Warning: No free channels were found")
|
||||
ch.settings.CopyFrom(chs)
|
||||
ch.role = channel_pb2.Channel.Role.SECONDARY
|
||||
print(f"Adding new channel '{chs.name}' to device")
|
||||
self.writeChannel(ch.index)
|
||||
else:
|
||||
i = 0
|
||||
for chs in channelSet.settings:
|
||||
ch = channel_pb2.Channel()
|
||||
ch.role = (
|
||||
channel_pb2.Channel.Role.PRIMARY
|
||||
if i == 0
|
||||
else channel_pb2.Channel.Role.SECONDARY
|
||||
)
|
||||
ch.index = i
|
||||
ch.settings.CopyFrom(chs)
|
||||
self.channels[ch.index] = ch
|
||||
logging.debug(f"Channel i:{i} ch:{ch}")
|
||||
self.writeChannel(ch.index)
|
||||
i = i + 1
|
||||
|
||||
p = admin_pb2.AdminMessage()
|
||||
p.set_config.lora.CopyFrom(channelSet.lora_config)
|
||||
|
||||
94
meshtastic/protobuf/config_pb2.py
generated
94
meshtastic/protobuf/config_pb2.py
generated
File diff suppressed because one or more lines are too long
62
meshtastic/protobuf/config_pb2.pyi
generated
62
meshtastic/protobuf/config_pb2.pyi
generated
@@ -108,6 +108,14 @@ class Config(google.protobuf.message.Message):
|
||||
and automatic TAK PLI (position location information) broadcasts.
|
||||
Uses position module configuration to determine TAK PLI broadcast interval.
|
||||
"""
|
||||
ROUTER_LATE: Config.DeviceConfig._Role.ValueType # 11
|
||||
"""
|
||||
Description: Will always rebroadcast packets, but will do so after all other modes.
|
||||
Technical Details: Used for router nodes that are intended to provide additional coverage
|
||||
in areas not already covered by other routers, or to bridge around problematic terrain,
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops.
|
||||
"""
|
||||
|
||||
class Role(_Role, metaclass=_RoleEnumTypeWrapper):
|
||||
"""
|
||||
@@ -184,6 +192,14 @@ class Config(google.protobuf.message.Message):
|
||||
and automatic TAK PLI (position location information) broadcasts.
|
||||
Uses position module configuration to determine TAK PLI broadcast interval.
|
||||
"""
|
||||
ROUTER_LATE: Config.DeviceConfig.Role.ValueType # 11
|
||||
"""
|
||||
Description: Will always rebroadcast packets, but will do so after all other modes.
|
||||
Technical Details: Used for router nodes that are intended to provide additional coverage
|
||||
in areas not already covered by other routers, or to bridge around problematic terrain,
|
||||
but should not be given priority over other routers in order to avoid unnecessaraily
|
||||
consuming hops.
|
||||
"""
|
||||
|
||||
class _RebroadcastMode:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
@@ -693,6 +709,35 @@ class Config(google.protobuf.message.Message):
|
||||
use static ip address
|
||||
"""
|
||||
|
||||
class _ProtocolFlags:
|
||||
ValueType = typing.NewType("ValueType", builtins.int)
|
||||
V: typing_extensions.TypeAlias = ValueType
|
||||
|
||||
class _ProtocolFlagsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[Config.NetworkConfig._ProtocolFlags.ValueType], builtins.type):
|
||||
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
|
||||
NO_BROADCAST: Config.NetworkConfig._ProtocolFlags.ValueType # 0
|
||||
"""
|
||||
Do not broadcast packets over any network protocol
|
||||
"""
|
||||
UDP_BROADCAST: Config.NetworkConfig._ProtocolFlags.ValueType # 1
|
||||
"""
|
||||
Enable broadcasting packets via UDP over the local network
|
||||
"""
|
||||
|
||||
class ProtocolFlags(_ProtocolFlags, metaclass=_ProtocolFlagsEnumTypeWrapper):
|
||||
"""
|
||||
Available flags auxiliary network protocols
|
||||
"""
|
||||
|
||||
NO_BROADCAST: Config.NetworkConfig.ProtocolFlags.ValueType # 0
|
||||
"""
|
||||
Do not broadcast packets over any network protocol
|
||||
"""
|
||||
UDP_BROADCAST: Config.NetworkConfig.ProtocolFlags.ValueType # 1
|
||||
"""
|
||||
Enable broadcasting packets via UDP over the local network
|
||||
"""
|
||||
|
||||
@typing.final
|
||||
class IpV4Config(google.protobuf.message.Message):
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
@@ -735,6 +780,7 @@ class Config(google.protobuf.message.Message):
|
||||
ADDRESS_MODE_FIELD_NUMBER: builtins.int
|
||||
IPV4_CONFIG_FIELD_NUMBER: builtins.int
|
||||
RSYSLOG_SERVER_FIELD_NUMBER: builtins.int
|
||||
ENABLED_PROTOCOLS_FIELD_NUMBER: builtins.int
|
||||
wifi_enabled: builtins.bool
|
||||
"""
|
||||
Enable WiFi (disables Bluetooth)
|
||||
@@ -764,6 +810,10 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
rsyslog Server and Port
|
||||
"""
|
||||
enabled_protocols: builtins.int
|
||||
"""
|
||||
Flags for enabling/disabling network protocols
|
||||
"""
|
||||
@property
|
||||
def ipv4_config(self) -> global___Config.NetworkConfig.IpV4Config:
|
||||
"""
|
||||
@@ -781,9 +831,10 @@ class Config(google.protobuf.message.Message):
|
||||
address_mode: global___Config.NetworkConfig.AddressMode.ValueType = ...,
|
||||
ipv4_config: global___Config.NetworkConfig.IpV4Config | None = ...,
|
||||
rsyslog_server: builtins.str = ...,
|
||||
enabled_protocols: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["ipv4_config", b"ipv4_config"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["address_mode", b"address_mode", "eth_enabled", b"eth_enabled", "ipv4_config", b"ipv4_config", "ntp_server", b"ntp_server", "rsyslog_server", b"rsyslog_server", "wifi_enabled", b"wifi_enabled", "wifi_psk", b"wifi_psk", "wifi_ssid", b"wifi_ssid"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["address_mode", b"address_mode", "enabled_protocols", b"enabled_protocols", "eth_enabled", b"eth_enabled", "ipv4_config", b"ipv4_config", "ntp_server", b"ntp_server", "rsyslog_server", b"rsyslog_server", "wifi_enabled", b"wifi_enabled", "wifi_psk", b"wifi_psk", "wifi_ssid", b"wifi_ssid"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class DisplayConfig(google.protobuf.message.Message):
|
||||
@@ -1067,6 +1118,7 @@ class Config(google.protobuf.message.Message):
|
||||
HEADING_BOLD_FIELD_NUMBER: builtins.int
|
||||
WAKE_ON_TAP_OR_MOTION_FIELD_NUMBER: builtins.int
|
||||
COMPASS_ORIENTATION_FIELD_NUMBER: builtins.int
|
||||
USE_12H_CLOCK_FIELD_NUMBER: builtins.int
|
||||
screen_on_secs: builtins.int
|
||||
"""
|
||||
Number of seconds the screen stays on after pressing the user button or receiving a message
|
||||
@@ -1114,6 +1166,11 @@ class Config(google.protobuf.message.Message):
|
||||
"""
|
||||
Indicates how to rotate or invert the compass output to accurate display on the display.
|
||||
"""
|
||||
use_12h_clock: builtins.bool
|
||||
"""
|
||||
If false (default), the device will display the time in 24-hour format on screen.
|
||||
If true, the device will display the time in 12-hour format on screen.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1128,8 +1185,9 @@ class Config(google.protobuf.message.Message):
|
||||
heading_bold: builtins.bool = ...,
|
||||
wake_on_tap_or_motion: builtins.bool = ...,
|
||||
compass_orientation: global___Config.DisplayConfig.CompassOrientation.ValueType = ...,
|
||||
use_12h_clock: builtins.bool = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["auto_screen_carousel_secs", b"auto_screen_carousel_secs", "compass_north_top", b"compass_north_top", "compass_orientation", b"compass_orientation", "displaymode", b"displaymode", "flip_screen", b"flip_screen", "gps_format", b"gps_format", "heading_bold", b"heading_bold", "oled", b"oled", "screen_on_secs", b"screen_on_secs", "units", b"units", "use_12h_clock", b"use_12h_clock", "wake_on_tap_or_motion", b"wake_on_tap_or_motion"]) -> None: ...
|
||||
|
||||
@typing.final
|
||||
class LoRaConfig(google.protobuf.message.Message):
|
||||
|
||||
16
meshtastic/protobuf/device_ui_pb2.py
generated
16
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\"\xbf\x03\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\"\x96\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xfc\x01\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x63\n\x13\x63om.geeksville.meshB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xbf\x03\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\"\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\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\x8b\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\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,14 +21,14 @@ _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=791
|
||||
_globals['_THEME']._serialized_end=828
|
||||
_globals['_LANGUAGE']._serialized_start=831
|
||||
_globals['_LANGUAGE']._serialized_end=1083
|
||||
_globals['_THEME']._serialized_start=808
|
||||
_globals['_THEME']._serialized_end=845
|
||||
_globals['_LANGUAGE']._serialized_start=848
|
||||
_globals['_LANGUAGE']._serialized_end=1115
|
||||
_globals['_DEVICEUICONFIG']._serialized_start=61
|
||||
_globals['_DEVICEUICONFIG']._serialized_end=508
|
||||
_globals['_NODEFILTER']._serialized_start=511
|
||||
_globals['_NODEFILTER']._serialized_end=661
|
||||
_globals['_NODEHIGHLIGHT']._serialized_start=663
|
||||
_globals['_NODEHIGHLIGHT']._serialized_end=789
|
||||
_globals['_NODEFILTER']._serialized_end=678
|
||||
_globals['_NODEHIGHLIGHT']._serialized_start=680
|
||||
_globals['_NODEHIGHLIGHT']._serialized_end=806
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
||||
16
meshtastic/protobuf/device_ui_pb2.pyi
generated
16
meshtastic/protobuf/device_ui_pb2.pyi
generated
@@ -118,6 +118,10 @@ class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumT
|
||||
"""
|
||||
Norwegian
|
||||
"""
|
||||
SLOVENIAN: _Language.ValueType # 15
|
||||
"""
|
||||
Slovenian
|
||||
"""
|
||||
SIMPLIFIED_CHINESE: _Language.ValueType # 30
|
||||
"""
|
||||
Simplified Chinese (experimental)
|
||||
@@ -192,6 +196,10 @@ NORWEGIAN: Language.ValueType # 14
|
||||
"""
|
||||
Norwegian
|
||||
"""
|
||||
SLOVENIAN: Language.ValueType # 15
|
||||
"""
|
||||
Slovenian
|
||||
"""
|
||||
SIMPLIFIED_CHINESE: Language.ValueType # 30
|
||||
"""
|
||||
Simplified Chinese (experimental)
|
||||
@@ -305,6 +313,7 @@ class NodeFilter(google.protobuf.message.Message):
|
||||
HOPS_AWAY_FIELD_NUMBER: builtins.int
|
||||
POSITION_SWITCH_FIELD_NUMBER: builtins.int
|
||||
NODE_NAME_FIELD_NUMBER: builtins.int
|
||||
CHANNEL_FIELD_NUMBER: builtins.int
|
||||
unknown_switch: builtins.bool
|
||||
"""
|
||||
Filter unknown nodes
|
||||
@@ -329,6 +338,10 @@ class NodeFilter(google.protobuf.message.Message):
|
||||
"""
|
||||
Filter nodes by matching name string
|
||||
"""
|
||||
channel: builtins.int
|
||||
"""
|
||||
Filter based on channel
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -338,8 +351,9 @@ class NodeFilter(google.protobuf.message.Message):
|
||||
hops_away: builtins.int = ...,
|
||||
position_switch: builtins.bool = ...,
|
||||
node_name: builtins.str = ...,
|
||||
channel: builtins.int = ...,
|
||||
) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["hops_away", b"hops_away", "node_name", b"node_name", "offline_switch", b"offline_switch", "position_switch", b"position_switch", "public_key_switch", b"public_key_switch", "unknown_switch", b"unknown_switch"]) -> None: ...
|
||||
def ClearField(self, field_name: typing.Literal["channel", b"channel", "hops_away", b"hops_away", "node_name", b"node_name", "offline_switch", b"offline_switch", "position_switch", b"position_switch", "public_key_switch", b"public_key_switch", "unknown_switch", b"unknown_switch"]) -> None: ...
|
||||
|
||||
global___NodeFilter = NodeFilter
|
||||
|
||||
|
||||
100
meshtastic/protobuf/mesh_pb2.py
generated
100
meshtastic/protobuf/mesh_pb2.py
generated
File diff suppressed because one or more lines are too long
20
meshtastic/protobuf/mesh_pb2.pyi
generated
20
meshtastic/protobuf/mesh_pb2.pyi
generated
@@ -397,6 +397,11 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
||||
Mesh-Tab, esp32 based
|
||||
https://github.com/valzzu/Mesh-Tab
|
||||
"""
|
||||
MESHLINK: _HardwareModel.ValueType # 87
|
||||
"""
|
||||
MeshLink board developed by LoraItalia. NRF52840, eByte E22900M22S (Will also come with other frequencies), 25w MPPT solar charger (5v,12v,18v selectable), support for gps, buzzer, oled or e-ink display, 10 gpios, hardware watchdog
|
||||
https://www.loraitalia.it
|
||||
"""
|
||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -777,6 +782,11 @@ MESH_TAB: HardwareModel.ValueType # 86
|
||||
Mesh-Tab, esp32 based
|
||||
https://github.com/valzzu/Mesh-Tab
|
||||
"""
|
||||
MESHLINK: HardwareModel.ValueType # 87
|
||||
"""
|
||||
MeshLink board developed by LoraItalia. NRF52840, eByte E22900M22S (Will also come with other frequencies), 25w MPPT solar charger (5v,12v,18v selectable), support for gps, buzzer, oled or e-ink display, 10 gpios, hardware watchdog
|
||||
https://www.loraitalia.it
|
||||
"""
|
||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||
"""
|
||||
------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2083,6 +2093,7 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
PKI_ENCRYPTED_FIELD_NUMBER: builtins.int
|
||||
NEXT_HOP_FIELD_NUMBER: builtins.int
|
||||
RELAY_NODE_FIELD_NUMBER: builtins.int
|
||||
TX_AFTER_FIELD_NUMBER: builtins.int
|
||||
to: builtins.int
|
||||
"""
|
||||
The (immediate) destination for this packet
|
||||
@@ -2184,6 +2195,12 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
Last byte of the node number of the node that will relay/relayed this packet.
|
||||
Set by the firmware internally, clients are not supposed to set this.
|
||||
"""
|
||||
tx_after: builtins.int
|
||||
"""
|
||||
*Never* sent over the radio links.
|
||||
Timestamp after which this packet may be sent.
|
||||
Set by the firmware internally, clients are not supposed to set this.
|
||||
"""
|
||||
@property
|
||||
def decoded(self) -> global___Data:
|
||||
"""
|
||||
@@ -2211,9 +2228,10 @@ class MeshPacket(google.protobuf.message.Message):
|
||||
pki_encrypted: builtins.bool = ...,
|
||||
next_hop: builtins.int = ...,
|
||||
relay_node: builtins.int = ...,
|
||||
tx_after: builtins.int = ...,
|
||||
) -> 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", "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", "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
|
||||
|
||||
2
meshtastic/protobuf/module_config_pb2.pyi
generated
2
meshtastic/protobuf/module_config_pb2.pyi
generated
@@ -927,7 +927,7 @@ class ModuleConfig(google.protobuf.message.Message):
|
||||
@typing.final
|
||||
class CannedMessageConfig(google.protobuf.message.Message):
|
||||
"""
|
||||
TODO: REPLACE
|
||||
Canned Messages Module Config
|
||||
"""
|
||||
|
||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||
|
||||
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
28
meshtastic/protobuf/telemetry_pb2.pyi
generated
28
meshtastic/protobuf/telemetry_pb2.pyi
generated
@@ -163,6 +163,10 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
||||
"""
|
||||
High accuracy current and voltage
|
||||
"""
|
||||
DFROBOT_RAIN: _TelemetrySensorType.ValueType # 35
|
||||
"""
|
||||
DFRobot Gravity tipping bucket rain gauge
|
||||
"""
|
||||
|
||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||
"""
|
||||
@@ -309,6 +313,10 @@ INA226: TelemetrySensorType.ValueType # 34
|
||||
"""
|
||||
High accuracy current and voltage
|
||||
"""
|
||||
DFROBOT_RAIN: TelemetrySensorType.ValueType # 35
|
||||
"""
|
||||
DFRobot Gravity tipping bucket rain gauge
|
||||
"""
|
||||
global___TelemetrySensorType = TelemetrySensorType
|
||||
|
||||
@typing.final
|
||||
@@ -394,6 +402,8 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
WIND_GUST_FIELD_NUMBER: builtins.int
|
||||
WIND_LULL_FIELD_NUMBER: builtins.int
|
||||
RADIATION_FIELD_NUMBER: builtins.int
|
||||
RAINFALL_1H_FIELD_NUMBER: builtins.int
|
||||
RAINFALL_24H_FIELD_NUMBER: builtins.int
|
||||
temperature: builtins.float
|
||||
"""
|
||||
Temperature measured
|
||||
@@ -468,6 +478,14 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
"""
|
||||
Radiation in µR/h
|
||||
"""
|
||||
rainfall_1h: builtins.float
|
||||
"""
|
||||
Rainfall in the last hour in mm
|
||||
"""
|
||||
rainfall_24h: builtins.float
|
||||
"""
|
||||
Rainfall in the last 24 hours in mm
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -489,9 +507,11 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
wind_gust: builtins.float | None = ...,
|
||||
wind_lull: builtins.float | None = ...,
|
||||
radiation: builtins.float | None = ...,
|
||||
rainfall_1h: builtins.float | None = ...,
|
||||
rainfall_24h: builtins.float | None = ...,
|
||||
) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
|
||||
def HasField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_rainfall_1h", b"_rainfall_1h", "_rainfall_24h", b"_rainfall_24h", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "rainfall_1h", b"rainfall_1h", "rainfall_24h", b"rainfall_24h", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ...
|
||||
def ClearField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_rainfall_1h", b"_rainfall_1h", "_rainfall_24h", b"_rainfall_24h", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "rainfall_1h", b"rainfall_1h", "rainfall_24h", b"rainfall_24h", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_barometric_pressure", b"_barometric_pressure"]) -> typing.Literal["barometric_pressure"] | None: ...
|
||||
@typing.overload
|
||||
@@ -509,6 +529,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_radiation", b"_radiation"]) -> typing.Literal["radiation"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_rainfall_1h", b"_rainfall_1h"]) -> typing.Literal["rainfall_1h"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_rainfall_24h", b"_rainfall_24h"]) -> typing.Literal["rainfall_24h"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_relative_humidity", b"_relative_humidity"]) -> typing.Literal["relative_humidity"] | None: ...
|
||||
@typing.overload
|
||||
def WhichOneof(self, oneof_group: typing.Literal["_temperature", b"_temperature"]) -> typing.Literal["temperature"] | None: ...
|
||||
|
||||
@@ -408,8 +408,8 @@ def test_main_nodes(capsys):
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
|
||||
def mock_showNodes():
|
||||
print("inside mocked showNodes")
|
||||
def mock_showNodes(includeSelf, showFields):
|
||||
print(f"inside mocked showNodes: {includeSelf} {showFields}")
|
||||
|
||||
iface.showNodes.side_effect = mock_showNodes
|
||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||
@@ -593,10 +593,10 @@ def test_main_sendtext(capsys):
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
|
||||
def mock_sendText(
|
||||
text, dest, wantAck=False, wantResponse=False, onResponse=None, channelIndex=0
|
||||
text, dest, wantAck=False, wantResponse=False, onResponse=None, channelIndex=0, portNum=0
|
||||
):
|
||||
print("inside mocked sendText")
|
||||
print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex}")
|
||||
print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex} {portNum}")
|
||||
|
||||
iface.sendText.side_effect = mock_sendText
|
||||
|
||||
@@ -620,10 +620,10 @@ def test_main_sendtext_with_channel(capsys):
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
|
||||
def mock_sendText(
|
||||
text, dest, wantAck=False, wantResponse=False, onResponse=None, channelIndex=0
|
||||
text, dest, wantAck=False, wantResponse=False, onResponse=None, channelIndex=0, portNum=0
|
||||
):
|
||||
print("inside mocked sendText")
|
||||
print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex}")
|
||||
print(f"{text} {dest} {wantAck} {wantResponse} {channelIndex} {portNum}")
|
||||
|
||||
iface.sendText.side_effect = mock_sendText
|
||||
|
||||
@@ -1608,7 +1608,7 @@ def test_main_onReceive_empty(caplog, capsys):
|
||||
assert re.search(r"in onReceive", caplog.text, re.MULTILINE)
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(
|
||||
r"Warning: There is no field 'to' in the packet.", out, re.MULTILINE
|
||||
r"Warning: Error processing received packet: 'to'.", out, re.MULTILINE
|
||||
)
|
||||
assert err == ""
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ def test_setURL_empty_url(capsys):
|
||||
assert pytest_wrapped_e.type == SystemExit
|
||||
assert pytest_wrapped_e.value.code == 1
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Warning: There were no settings.", out, re.MULTILINE)
|
||||
assert re.search(r"Warning: config or channels not loaded", out, re.MULTILINE)
|
||||
assert err == ""
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ def test_setURL_valid_URL_but_no_settings(capsys):
|
||||
assert pytest_wrapped_e.type == SystemExit
|
||||
assert pytest_wrapped_e.value.code == 1
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r"Warning: There were no settings", out, re.MULTILINE)
|
||||
assert re.search(r"Warning: config or channels not loaded", out, re.MULTILINE)
|
||||
assert err == ""
|
||||
|
||||
|
||||
|
||||
@@ -254,6 +254,16 @@ class Timeout:
|
||||
time.sleep(self.sleepInterval)
|
||||
return False
|
||||
|
||||
def waitForWaypoint(self, acknowledgment) -> bool:
|
||||
"""Block until waypoint response is received. Returns True if waypoint response has been received."""
|
||||
self.reset()
|
||||
while time.time() < self.expireTime:
|
||||
if getattr(acknowledgment, "receivedWaypoint", None):
|
||||
acknowledgment.reset()
|
||||
return True
|
||||
time.sleep(self.sleepInterval)
|
||||
return False
|
||||
|
||||
class Acknowledgment:
|
||||
"A class that records which type of acknowledgment was just received, if any."
|
||||
|
||||
@@ -265,6 +275,7 @@ class Acknowledgment:
|
||||
self.receivedTraceRoute = False
|
||||
self.receivedTelemetry = False
|
||||
self.receivedPosition = False
|
||||
self.receivedWaypoint = False
|
||||
|
||||
def reset(self) -> None:
|
||||
"""reset"""
|
||||
@@ -274,6 +285,7 @@ class Acknowledgment:
|
||||
self.receivedTraceRoute = False
|
||||
self.receivedTelemetry = False
|
||||
self.receivedPosition = False
|
||||
self.receivedWaypoint = False
|
||||
|
||||
|
||||
class DeferredExecution:
|
||||
|
||||
8
poetry.lock
generated
8
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "altgraph"
|
||||
@@ -1459,13 +1459,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
version = "3.1.5"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
||||
Submodule protobufs updated: 2cffaf53e3...068646653e
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "meshtastic"
|
||||
version = "2.5.7"
|
||||
version = "2.5.12"
|
||||
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