Compare commits

...

35 Commits
2.5.0 ... 2.5.1

Author SHA1 Message Date
github-actions
3fb1e67357 bump version to 2.5.1 2024-09-29 21:30:36 +00:00
Ian McEwen
cbd3c119fe Fix pylint errors 2024-09-28 20:15:08 -07:00
Ian McEwen
bbd6d6a541 Change order of logging and parsing fromRadioBytes, and add a bit more traceback logging at that point 2024-09-28 20:11:52 -07:00
Ian McEwen
6e1217c7ca Merge pull request #677 from ianmcorvidae/pkiencrypted_admin
Default to pkiEncrypted always on for admin messages
2024-09-28 18:40:31 -07:00
Ian McEwen
81db38956b Silence pylint 2024-09-28 18:37:05 -07:00
Ian McEwen
27729995d2 Default to pkiEncrypted always on for admin messages 2024-09-28 11:13:04 -07:00
Ian McEwen
d875a574b6 Merge pull request #676 from ianmcorvidae/set-time
Add a --set-time command that set's the node time using a provided timestamp or the host system clock
2024-09-22 10:06:09 -07:00
Ian McEwen
40019a9712 Add a --set-time command that set's the node time using a provided timestamp or the host system clock. 2024-09-22 09:32:04 -07:00
Ian McEwen
de657bab24 Merge pull request #675 from djholt/feature/remote-config-position
Enable setting and removing fixed position via remote admin
2024-09-19 11:24:23 -07:00
DJ Holt
40353a387e Fix tests for remote position configs 2024-09-19 03:31:01 -06:00
DJ Holt
9949d144a1 Enable setting and removing fixed position via remote admin 2024-09-19 02:39:49 -06:00
Ian McEwen
48a06c6e1e protobufs & alpha version number: v2.5.1 2024-09-18 14:58:05 -07:00
Ian McEwen
c696d59b90 Set list-type keys differently, excluding 0-like values and resetting whole list 2024-09-17 21:52:57 -07:00
Ian McEwen
4fdbcb9679 Fix test_fuzz_fromStr for floats 2024-09-17 21:10:57 -07:00
Ian McEwen
5d6dfb877b Merge pull request #673 from djholt/feature/tcp-port
Allow port number to be specified with tcp hostname
2024-09-17 21:08:03 -07:00
Ian McEwen
951edfe27b Merge pull request #665 from lysol/remote-admin-retry
Retry admin channel setting retrieval and add configurable timeout
2024-09-17 21:06:38 -07:00
DJ Holt
5cc9627e21 Refactor default port number to variable 2024-09-16 23:41:44 -06:00
DJ Holt
bf904c6906 Allow port number to be specified with tcp hostname 2024-09-16 23:24:03 -06:00
Derek Arnold
2026212a00 please pylint with a docstring and newline 2024-09-15 16:27:40 -05:00
Derek Arnold
34f9be255e fix unrelated bug when fromStr input is short hex
For example, 0x0 will generate an unhandled ValueError. This was caught
during a random unit test run for 3.9, so I figure I'd fix it.

This is unrelated to the PR otherwise.
2024-09-15 16:20:50 -05:00
Derek Arnold
e561222ea7 add docstrings 2024-09-15 16:06:44 -05:00
Derek Arnold
73a1bbc7d5 add test coverage for changes to requestChannels 2024-09-15 16:02:59 -05:00
Derek Arnold
8f2c397fbf missed one reference for requestChannelRetries 🤡 2024-09-15 14:01:41 -05:00
Derek Arnold
62f5201a38 Add test covering retry logic 2024-09-15 12:04:22 -05:00
Derek Arnold
9e7d5e96ab Rename "retries" to "attempts"
Otherwise, semantically, it's off-by-one.
2024-09-15 12:03:51 -05:00
Derek Arnold
aa74db46cb update message check in test to reflect new message 2024-09-15 11:59:03 -05:00
Derek Arnold
1967519deb correct type issue during initial assignment 2024-09-15 11:52:27 -05:00
Derek Arnold
662aea049a Merge branch 'master' into remote-admin-retry 2024-09-15 11:46:32 -05:00
Derek Arnold
44cfd72a80 make sure new_index is always an int 2024-09-15 11:39:10 -05:00
Derek Arnold
abe1dd47ca add type to argument 2024-09-15 11:36:23 -05:00
Ian McEwen
584a14f578 Merge pull request #668 from ianmcorvidae/channel-preset-optimize
Change modem preset shortcuts to not request channels, and to request remote config when needed
2024-09-12 16:20:59 -07:00
Ian McEwen
8ba92da7cf Change modem preset shortcuts to not request channels, and to request remote config when needed 2024-09-06 23:38:52 -07:00
Derek Arnold
3811226a61 add a configurable timeout 2024-09-03 22:12:03 -05:00
Derek Arnold
b6547c9737 actually link up the retry args from the commandline to getNode 2024-09-03 22:11:52 -05:00
Derek Arnold
9612aea9b9 Add in a retry mechanism for channel settings
Attempts multiple times to fetch things over the admin channel
before giving up.
2024-09-03 21:58:16 -05:00
19 changed files with 408 additions and 195 deletions

View File

@@ -253,7 +253,9 @@ def setPref(config, comp_name, valStr) -> bool:
del getattr(config_values, pref.name)[:] del getattr(config_values, pref.name)[:]
else: else:
print(f"Adding '{val}' to the {pref.name} list") print(f"Adding '{val}' to the {pref.name} list")
getattr(config_values, pref.name).append(val) cur_vals = [x for x in getattr(config_values, pref.name) if x not in [0, "", b""]]
cur_vals.append(val)
getattr(config_values, pref.name)[:] = cur_vals
prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else "" prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else ""
if mt_config.camel_case: if mt_config.camel_case:
@@ -273,24 +275,28 @@ def onConnected(interface):
try: try:
args = mt_config.args args = mt_config.args
# convenient place to store any keyword args we pass to getNode
getNode_kwargs = {
"requestChannelAttempts": args.channel_fetch_attempts,
"timeout": args.timeout
}
# do not print this line if we are exporting the config # do not print this line if we are exporting the config
if not args.export_config: if not args.export_config:
print("Connected to radio") print("Connected to radio")
if args.set_time is not None:
interface.getNode(args.dest, False, **getNode_kwargs).setTime(args.set_time)
if args.remove_position: if args.remove_position:
if args.dest != BROADCAST_ADDR:
print("Setting positions of remote nodes is not supported.")
return
closeNow = True closeNow = True
waitForAckNak = True
print("Removing fixed position and disabling fixed position setting") print("Removing fixed position and disabling fixed position setting")
interface.localNode.removeFixedPosition() interface.getNode(args.dest, False, **getNode_kwargs).removeFixedPosition()
elif args.setlat or args.setlon or args.setalt: elif args.setlat or args.setlon or args.setalt:
if args.dest != BROADCAST_ADDR:
print(
"Setting latitude, longitude, and altitude of remote nodes is not supported."
)
return
closeNow = True closeNow = True
waitForAckNak = True
alt = 0 alt = 0
lat = 0 lat = 0
@@ -313,7 +319,7 @@ def onConnected(interface):
print("Setting device position and enabling fixed position setting") print("Setting device position and enabling fixed position setting")
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1 # can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
interface.localNode.setFixedPosition(lat, lon, alt) 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:
closeNow = True closeNow = True
@@ -324,14 +330,14 @@ def onConnected(interface):
print(f"Setting device owner to {args.set_owner}") print(f"Setting device owner to {args.set_owner}")
else: # short name only else: # short name only
print(f"Setting device owner short to {args.set_owner_short}") print(f"Setting device owner short to {args.set_owner_short}")
interface.getNode(args.dest, False).setOwner(long_name=args.set_owner, short_name=args.set_owner_short) interface.getNode(args.dest, False, **getNode_kwargs).setOwner(long_name=args.set_owner, short_name=args.set_owner_short)
# TODO: add to export-config and configure # TODO: add to export-config and configure
if args.set_canned_message: if args.set_canned_message:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
print(f"Setting canned plugin message to {args.set_canned_message}") print(f"Setting canned plugin message to {args.set_canned_message}")
interface.getNode(args.dest, False).set_canned_message( interface.getNode(args.dest, False, **getNode_kwargs).set_canned_message(
args.set_canned_message args.set_canned_message
) )
@@ -340,12 +346,12 @@ def onConnected(interface):
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
print(f"Setting ringtone to {args.set_ringtone}") print(f"Setting ringtone to {args.set_ringtone}")
interface.getNode(args.dest, False).set_ringtone(args.set_ringtone) interface.getNode(args.dest, False, **getNode_kwargs).set_ringtone(args.set_ringtone)
if args.pos_fields: if args.pos_fields:
# If --pos-fields invoked with args, set position fields # If --pos-fields invoked with args, set position fields
closeNow = True closeNow = True
positionConfig = interface.getNode(args.dest).localConfig.position positionConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig.position
allFields = 0 allFields = 0
try: try:
@@ -364,12 +370,12 @@ def onConnected(interface):
print(f"Setting position fields to {allFields}") print(f"Setting position fields to {allFields}")
setPref(positionConfig, "position_flags", f"{allFields:d}") setPref(positionConfig, "position_flags", f"{allFields:d}")
print("Writing modified preferences to device") print("Writing modified preferences to device")
interface.getNode(args.dest).writeConfig("position") interface.getNode(args.dest, **getNode_kwargs).writeConfig("position")
elif args.pos_fields is not None: elif args.pos_fields is not None:
# If --pos-fields invoked without args, read and display current value # If --pos-fields invoked without args, read and display current value
closeNow = True closeNow = True
positionConfig = interface.getNode(args.dest).localConfig.position positionConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig.position
fieldNames = [] fieldNames = []
for bit in positionConfig.PositionFlags.values(): for bit in positionConfig.PositionFlags.values():
@@ -380,57 +386,58 @@ def onConnected(interface):
if args.set_ham: if args.set_ham:
closeNow = True closeNow = True
print(f"Setting Ham ID to {args.set_ham} and turning off encryption") print(f"Setting Ham ID to {args.set_ham} and turning off encryption")
interface.getNode(args.dest).setOwner(args.set_ham, is_licensed=True) interface.getNode(args.dest, **getNode_kwargs).setOwner(args.set_ham, is_licensed=True)
# Must turn off encryption on primary channel # Must turn off encryption on primary channel
interface.getNode(args.dest).turnOffEncryptionOnPrimaryChannel() interface.getNode(args.dest, **getNode_kwargs).turnOffEncryptionOnPrimaryChannel()
if args.reboot: if args.reboot:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).reboot() interface.getNode(args.dest, False, **getNode_kwargs).reboot()
if args.reboot_ota: if args.reboot_ota:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).rebootOTA() interface.getNode(args.dest, False, **getNode_kwargs).rebootOTA()
if args.enter_dfu: if args.enter_dfu:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).enterDFUMode() interface.getNode(args.dest, False, **getNode_kwargs).enterDFUMode()
if args.shutdown: if args.shutdown:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).shutdown() interface.getNode(args.dest, False, **getNode_kwargs).shutdown()
if args.device_metadata: if args.device_metadata:
closeNow = True closeNow = True
interface.getNode(args.dest, False).getMetadata() interface.getNode(args.dest, False, **getNode_kwargs).getMetadata()
if args.begin_edit: if args.begin_edit:
closeNow = True closeNow = True
interface.getNode(args.dest, False).beginSettingsTransaction() interface.getNode(args.dest, False, **getNode_kwargs).beginSettingsTransaction()
if args.commit_edit: if args.commit_edit:
closeNow = True closeNow = True
interface.getNode(args.dest, False).commitSettingsTransaction() interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
if args.factory_reset or args.factory_reset_device: if args.factory_reset or args.factory_reset_device:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
full = bool(args.factory_reset_device) full = bool(args.factory_reset_device)
interface.getNode(args.dest, False).factoryReset(full=full) interface.getNode(args.dest, False, **getNode_kwargs).factoryReset(full=full)
if args.remove_node: if args.remove_node:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).removeNode(args.remove_node) interface.getNode(args.dest, False, **getNode_kwargs).removeNode(args.remove_node)
if args.reset_nodedb: if args.reset_nodedb:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).resetNodeDb() interface.getNode(args.dest, False, **getNode_kwargs).resetNodeDb()
if args.sendtext: if args.sendtext:
closeNow = True closeNow = True
@@ -444,7 +451,7 @@ def onConnected(interface):
args.dest, args.dest,
wantAck=True, wantAck=True,
channelIndex=channelIndex, channelIndex=channelIndex,
onResponse=interface.getNode(args.dest, False).onAckNak, onResponse=interface.getNode(args.dest, False, **getNode_kwargs).onAckNak,
) )
else: else:
meshtastic.util.our_exit( meshtastic.util.our_exit(
@@ -535,7 +542,7 @@ def onConnected(interface):
if args.set: if args.set:
closeNow = True closeNow = True
waitForAckNak = True waitForAckNak = True
node = interface.getNode(args.dest, False) node = interface.getNode(args.dest, False, **getNode_kwargs)
# Handle the int/float/bool arguments # Handle the int/float/bool arguments
pref = None pref = None
@@ -574,19 +581,19 @@ def onConnected(interface):
configuration = yaml.safe_load(file) configuration = yaml.safe_load(file)
closeNow = True closeNow = True
interface.getNode(args.dest, False).beginSettingsTransaction() interface.getNode(args.dest, False, **getNode_kwargs).beginSettingsTransaction()
if "owner" in configuration: if "owner" in configuration:
print(f"Setting device owner to {configuration['owner']}") print(f"Setting device owner to {configuration['owner']}")
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).setOwner(configuration["owner"]) interface.getNode(args.dest, False, **getNode_kwargs).setOwner(configuration["owner"])
if "owner_short" in configuration: if "owner_short" in configuration:
print( print(
f"Setting device owner short to {configuration['owner_short']}" f"Setting device owner short to {configuration['owner_short']}"
) )
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).setOwner( interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
long_name=None, short_name=configuration["owner_short"] long_name=None, short_name=configuration["owner_short"]
) )
@@ -595,17 +602,17 @@ def onConnected(interface):
f"Setting device owner short to {configuration['ownerShort']}" f"Setting device owner short to {configuration['ownerShort']}"
) )
waitForAckNak = True waitForAckNak = True
interface.getNode(args.dest, False).setOwner( interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
long_name=None, short_name=configuration["ownerShort"] long_name=None, short_name=configuration["ownerShort"]
) )
if "channel_url" in configuration: if "channel_url" in configuration:
print("Setting channel url to", configuration["channel_url"]) print("Setting channel url to", configuration["channel_url"])
interface.getNode(args.dest).setURL(configuration["channel_url"]) interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channel_url"])
if "channelUrl" in configuration: if "channelUrl" in configuration:
print("Setting channel url to", configuration["channelUrl"]) print("Setting channel url to", configuration["channelUrl"])
interface.getNode(args.dest).setURL(configuration["channelUrl"]) interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channelUrl"])
if "location" in configuration: if "location" in configuration:
alt = 0 alt = 0
@@ -630,28 +637,28 @@ def onConnected(interface):
interface.localNode.writeConfig("position") interface.localNode.writeConfig("position")
if "config" in configuration: if "config" in configuration:
localConfig = interface.getNode(args.dest).localConfig localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
for section in configuration["config"]: for section in configuration["config"]:
traverseConfig( traverseConfig(
section, configuration["config"][section], localConfig section, configuration["config"][section], localConfig
) )
interface.getNode(args.dest).writeConfig( interface.getNode(args.dest, **getNode_kwargs).writeConfig(
meshtastic.util.camel_to_snake(section) meshtastic.util.camel_to_snake(section)
) )
if "module_config" in configuration: if "module_config" in configuration:
moduleConfig = interface.getNode(args.dest).moduleConfig moduleConfig = interface.getNode(args.dest, **getNode_kwargs).moduleConfig
for section in configuration["module_config"]: for section in configuration["module_config"]:
traverseConfig( traverseConfig(
section, section,
configuration["module_config"][section], configuration["module_config"][section],
moduleConfig, moduleConfig,
) )
interface.getNode(args.dest).writeConfig( interface.getNode(args.dest, **getNode_kwargs).writeConfig(
meshtastic.util.camel_to_snake(section) meshtastic.util.camel_to_snake(section)
) )
interface.getNode(args.dest, False).commitSettingsTransaction() interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
print("Writing modified configuration to device") print("Writing modified configuration to device")
if args.export_config: if args.export_config:
@@ -664,7 +671,7 @@ def onConnected(interface):
if args.seturl: if args.seturl:
closeNow = True closeNow = True
interface.getNode(args.dest).setURL(args.seturl) interface.getNode(args.dest, **getNode_kwargs).setURL(args.seturl)
# handle changing channels # handle changing channels
@@ -680,7 +687,7 @@ def onConnected(interface):
meshtastic.util.our_exit( meshtastic.util.our_exit(
"Warning: Channel name must be shorter. Channel not added." "Warning: Channel name must be shorter. Channel not added."
) )
n = interface.getNode(args.dest) n = interface.getNode(args.dest, **getNode_kwargs)
ch = n.getChannelByName(args.ch_add) ch = n.getChannelByName(args.ch_add)
if ch: if ch:
meshtastic.util.our_exit( meshtastic.util.our_exit(
@@ -719,7 +726,7 @@ def onConnected(interface):
) )
else: else:
print(f"Deleting channel {channelIndex}") print(f"Deleting channel {channelIndex}")
ch = interface.getNode(args.dest).deleteChannel(channelIndex) ch = interface.getNode(args.dest, **getNode_kwargs).deleteChannel(channelIndex)
def setSimpleConfig(modem_preset): def setSimpleConfig(modem_preset):
"""Set one of the simple modem_config""" """Set one of the simple modem_config"""
@@ -729,9 +736,11 @@ def onConnected(interface):
"Warning: Cannot set modem preset for non-primary channel", 1 "Warning: Cannot set modem preset for non-primary channel", 1
) )
# Overwrite modem_preset # Overwrite modem_preset
prefs = interface.getNode(args.dest).localConfig node = interface.getNode(args.dest, False, **getNode_kwargs)
prefs.lora.modem_preset = modem_preset if len(node.localConfig.ListFields()) == 0:
interface.getNode(args.dest).writeConfig("lora") node.requestConfig(node.localConfig.DESCRIPTOR.fields_by_name.get("lora"))
node.localConfig.lora.modem_preset = modem_preset
node.writeConfig("lora")
# handle the simple radio set commands # handle the simple radio set commands
if args.ch_vlongslow: if args.ch_vlongslow:
@@ -761,7 +770,7 @@ def onConnected(interface):
channelIndex = mt_config.channel_index channelIndex = mt_config.channel_index
if channelIndex is None: if channelIndex is None:
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1) meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
node = interface.getNode(args.dest) node = interface.getNode(args.dest, **getNode_kwargs)
ch = node.channels[channelIndex] ch = node.channels[channelIndex]
if args.ch_enable or args.ch_disable: if args.ch_enable or args.ch_disable:
@@ -825,12 +834,12 @@ def onConnected(interface):
if args.get_canned_message: if args.get_canned_message:
closeNow = True closeNow = True
print("") print("")
interface.getNode(args.dest).get_canned_message() interface.getNode(args.dest, **getNode_kwargs).get_canned_message()
if args.get_ringtone: if args.get_ringtone:
closeNow = True closeNow = True
print("") print("")
interface.getNode(args.dest).get_ringtone() interface.getNode(args.dest, **getNode_kwargs).get_ringtone()
if args.info: if args.info:
print("") print("")
@@ -838,7 +847,7 @@ def onConnected(interface):
if args.dest == BROADCAST_ADDR: if args.dest == BROADCAST_ADDR:
interface.showInfo() interface.showInfo()
print("") print("")
interface.getNode(args.dest).showInfo() interface.getNode(args.dest, **getNode_kwargs).showInfo()
closeNow = True closeNow = True
print("") print("")
pypi_version = meshtastic.util.check_if_newer_version() pypi_version = meshtastic.util.check_if_newer_version()
@@ -855,7 +864,7 @@ def onConnected(interface):
if args.get: if args.get:
closeNow = True closeNow = True
node = interface.getNode(args.dest, False) node = interface.getNode(args.dest, False, **getNode_kwargs)
for pref in args.get: for pref in args.get:
found = getPref(node, pref[0]) found = getPref(node, pref[0])
@@ -871,7 +880,7 @@ def onConnected(interface):
if args.qr or args.qr_all: if args.qr or args.qr_all:
closeNow = True closeNow = True
url = interface.getNode(args.dest, True).getURL(includeAll=args.qr_all) url = interface.getNode(args.dest, True, **getNode_kwargs).getURL(includeAll=args.qr_all)
if args.qr_all: if args.qr_all:
urldesc = "Complete URL (includes all channels)" urldesc = "Complete URL (includes all channels)"
else: else:
@@ -926,7 +935,7 @@ def onConnected(interface):
print( print(
f"Waiting for an acknowledgment from remote node (this could take a while)" f"Waiting for an acknowledgment from remote node (this could take a while)"
) )
interface.getNode(args.dest, False).iface.waitForAckNak() interface.getNode(args.dest, False, **getNode_kwargs).iface.waitForAckNak()
if args.wait_to_disconnect: if args.wait_to_disconnect:
print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting") print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting")
@@ -1153,8 +1162,14 @@ def common():
) )
elif args.host: elif args.host:
try: try:
if ":" in args.host:
tcp_hostname, tcp_port = args.host.split(':')
else:
tcp_hostname = args.host
tcp_port = meshtastic.tcp_interface.DEFAULT_TCP_PORT
client = meshtastic.tcp_interface.TCPInterface( client = meshtastic.tcp_interface.TCPInterface(
args.host, tcp_hostname,
portNumber=tcp_port,
debugOut=logfile, debugOut=logfile,
noProto=args.noproto, noProto=args.noproto,
noNodes=args.no_nodes, noNodes=args.no_nodes,
@@ -1406,6 +1421,20 @@ def initParser():
action="append", action="append",
) )
group.add_argument(
"--channel-fetch-attempts",
help=("Attempt to retrieve channel settings for --ch-set this many times before giving up."),
default=3,
type=int,
)
group.add_argument(
"--timeout",
help="How long to wait for replies",
default=300,
type=int,
)
group.add_argument( group.add_argument(
"--ch-vlongslow", "--ch-vlongslow",
help="Change to the very long-range and slow channel", help="Change to the very long-range and slow channel",
@@ -1571,6 +1600,16 @@ def initParser():
action="store_true", action="store_true",
) )
group.add_argument(
"--set-time",
help="Set the time to the provided unix epoch timestamp, or the system's current time if omitted or 0.",
action="store",
type=int,
nargs="?",
default=None,
const=0,
)
group.add_argument( group.add_argument(
"--reply", help="Reply to received messages", action="store_true" "--reply", help="Reply to received messages", action="store_true"
) )

View File

@@ -8,6 +8,7 @@ import random
import sys import sys
import threading import threading
import time import time
import traceback
from datetime import datetime from datetime import datetime
from decimal import Decimal from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Union from typing import Any, Callable, Dict, List, Optional, Union
@@ -139,13 +140,13 @@ class MeshInterface: # pylint: disable=R0902
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, trace):
if exc_type is not None and exc_value is not None: if exc_type is not None and exc_value is not None:
logging.error( logging.error(
f"An exception of type {exc_type} with value {exc_value} has occurred" f"An exception of type {exc_type} with value {exc_value} has occurred"
) )
if traceback is not None: if trace is not None:
logging.error(f"Traceback: {traceback}") logging.error(f"Traceback: {trace}")
self.close() self.close()
@staticmethod @staticmethod
@@ -315,19 +316,33 @@ class MeshInterface: # pylint: disable=R0902
return table return table
def getNode( def getNode(
self, nodeId: str, requestChannels: bool = True self, nodeId: str, requestChannels: bool = True, requestChannelAttempts: int = 3, timeout: int = 300
) -> meshtastic.node.Node: ) -> meshtastic.node.Node:
"""Return a node object which contains device settings and channel info""" """Return a node object which contains device settings and channel info"""
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR): if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
return self.localNode return self.localNode
else: else:
n = meshtastic.node.Node(self, nodeId) n = meshtastic.node.Node(self, nodeId, timeout=timeout)
# Only request device settings and channel info when necessary # Only request device settings and channel info when necessary
if requestChannels: if requestChannels:
logging.debug("About to requestChannels") logging.debug("About to requestChannels")
n.requestChannels() n.requestChannels()
if not n.waitForConfig(): retries_left = requestChannelAttempts
our_exit("Error: Timed out waiting for channels") last_index: int = 0
while retries_left > 0:
retries_left -= 1
if not n.waitForConfig():
new_index: int = len(n.partialChannels) if n.partialChannels else 0
# each time we get a new channel, reset the counter
if new_index != last_index:
retries_left = requestChannelAttempts - 1
if retries_left <= 0:
our_exit(f"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
else:
break
return n return n
def sendText( def sendText(
@@ -380,7 +395,9 @@ class MeshInterface: # pylint: disable=R0902
onResponseAckPermitted: bool=False, onResponseAckPermitted: bool=False,
channelIndex: int=0, channelIndex: int=0,
hopLimit: Optional[int]=None, hopLimit: Optional[int]=None,
): pkiEncrypted: Optional[bool]=False,
publicKey: Optional[bytes]=None,
): # pylint: disable=R0913
"""Send a data packet to some other node """Send a data packet to some other node
Keyword Arguments: Keyword Arguments:
@@ -435,7 +452,7 @@ class MeshInterface: # pylint: disable=R0902
if onResponse is not None: if onResponse is not None:
logging.debug(f"Setting a response handler for requestId {meshPacket.id}") logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted) self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted)
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit) p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit, pkiEncrypted=pkiEncrypted, publicKey=publicKey)
return p return p
def sendPosition( def sendPosition(
@@ -675,7 +692,9 @@ class MeshInterface: # pylint: disable=R0902
meshPacket: mesh_pb2.MeshPacket, meshPacket: mesh_pb2.MeshPacket,
destinationId: Union[int,str]=BROADCAST_ADDR, destinationId: Union[int,str]=BROADCAST_ADDR,
wantAck: bool=False, wantAck: bool=False,
hopLimit: Optional[int]=None hopLimit: Optional[int]=None,
pkiEncrypted: Optional[bool]=False,
publicKey: Optional[bytes]=None,
): ):
"""Send a MeshPacket to the specified node (or if unspecified, broadcast). """Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don't want this - use sendData instead. You probably don't want this - use sendData instead.
@@ -724,6 +743,12 @@ class MeshInterface: # pylint: disable=R0902
loraConfig = getattr(self.localNode.localConfig, "lora") loraConfig = getattr(self.localNode.localConfig, "lora")
meshPacket.hop_limit = getattr(loraConfig, "hop_limit") meshPacket.hop_limit = getattr(loraConfig, "hop_limit")
if pkiEncrypted:
meshPacket.pki_encrypted = True
if publicKey is not None:
meshPacket.public_key = publicKey
# if the user hasn't set an ID for this packet (likely and recommended), # if the user hasn't set an ID for this packet (likely and recommended),
# we should pick a new unique ID so the message can be tracked. # we should pick a new unique ID so the message can be tracked.
if meshPacket.id == 0: if meshPacket.id == 0:
@@ -999,10 +1024,17 @@ class MeshInterface: # pylint: disable=R0902
Called by subclasses.""" Called by subclasses."""
fromRadio = mesh_pb2.FromRadio() fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug( logging.debug(
f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}" f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}"
) )
try:
fromRadio.ParseFromString(fromRadioBytes)
except Exception as ex:
logging.error(
f"Error while parsing FromRadio bytes:{fromRadioBytes} {ex}"
)
traceback.print_exc()
raise ex
asDict = google.protobuf.json_format.MessageToDict(fromRadio) asDict = google.protobuf.json_format.MessageToDict(fromRadio)
logging.debug(f"Received from radio: {fromRadio}") logging.debug(f"Received from radio: {fromRadio}")
if fromRadio.HasField("my_info"): if fromRadio.HasField("my_info"):

View File

@@ -5,7 +5,7 @@ import base64
import logging import logging
import time import time
from typing import Optional, Union from typing import Optional, Union, List
from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2 from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2
from meshtastic.util import ( from meshtastic.util import (
@@ -25,15 +25,15 @@ class Node:
Includes methods for localConfig, moduleConfig and channels Includes methods for localConfig, moduleConfig and channels
""" """
def __init__(self, iface, nodeNum, noProto=False): def __init__(self, iface, nodeNum, noProto=False, timeout: int = 300):
"""Constructor""" """Constructor"""
self.iface = iface self.iface = iface
self.nodeNum = nodeNum self.nodeNum = nodeNum
self.localConfig = localonly_pb2.LocalConfig() self.localConfig = localonly_pb2.LocalConfig()
self.moduleConfig = localonly_pb2.LocalModuleConfig() self.moduleConfig = localonly_pb2.LocalModuleConfig()
self.channels = None self.channels = None
self._timeout = Timeout(maxSecs=300) self._timeout = Timeout(maxSecs=timeout)
self.partialChannels = None self.partialChannels: Optional[List] = None
self.noProto = noProto self.noProto = noProto
self.cannedPluginMessage = None self.cannedPluginMessage = None
self.cannedPluginMessageMessages = None self.cannedPluginMessageMessages = None
@@ -77,13 +77,14 @@ class Node:
self.channels = channels self.channels = channels
self._fixupChannels() self._fixupChannels()
def requestChannels(self): def requestChannels(self, startingIndex: int = 0):
"""Send regular MeshPackets to ask channels.""" """Send regular MeshPackets to ask channels."""
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}") logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
self.channels = None # only initialize if we're starting out fresh
self.partialChannels = [] # We keep our channels in a temp array until finished if startingIndex == 0:
self.channels = None
self._requestChannel(0) self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestChannel(startingIndex)
def onResponseRequestSettings(self, p): def onResponseRequestSettings(self, p):
"""Handle the response packets for requesting settings _requestSettings()""" """Handle the response packets for requesting settings _requestSettings()"""
@@ -684,9 +685,6 @@ class Node:
def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int): def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int):
"""Tell the node to set fixed position to the provided value and enable the fixed position setting""" """Tell the node to set fixed position to the provided value and enable the fixed position setting"""
self.ensureSessionKey() self.ensureSessionKey()
if self != self.iface.localNode:
logging.error("Setting position of remote nodes is not supported.")
return None
p = mesh_pb2.Position() p = mesh_pb2.Position()
if isinstance(lat, float) and lat != 0.0: if isinstance(lat, float) and lat != 0.0:
@@ -704,7 +702,12 @@ class Node:
a = admin_pb2.AdminMessage() a = admin_pb2.AdminMessage()
a.set_fixed_position.CopyFrom(p) a.set_fixed_position.CopyFrom(p)
return self._sendAdmin(a)
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(a, onResponse=onResponse)
def removeFixedPosition(self): def removeFixedPosition(self):
"""Tell the node to remove the fixed position and set the fixed position setting to false""" """Tell the node to remove the fixed position and set the fixed position setting to false"""
@@ -713,7 +716,26 @@ class Node:
p.remove_fixed_position = True p.remove_fixed_position = True
logging.info(f"Telling node to remove fixed position") logging.info(f"Telling node to remove fixed position")
return self._sendAdmin(p) if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def setTime(self, timeSec: int = 0):
"""Tell the node to set its time to the provided timestamp, or the system's current time if not provided or 0."""
self.ensureSessionKey()
if timeSec == 0:
timeSec = int(time.time())
p = admin_pb2.AdminMessage()
p.set_time_only = timeSec
logging.info(f"Setting node time to {timeSec}")
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def _fixupChannels(self): def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed""" """Fixup indexes and add disabled channels as needed"""
@@ -872,6 +894,7 @@ class Node:
wantResponse=wantResponse, wantResponse=wantResponse,
onResponse=onResponse, onResponse=onResponse,
channelIndex=adminIndex, channelIndex=adminIndex,
pkiEncrypted=True,
) )
def ensureSessionKey(self): def ensureSessionKey(self):

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\x93\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\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\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42_\n\x13\x63om.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\xa5\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x12\x10\n\x06\x64\x65tail\x18\x07 \x01(\x0cH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\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\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42_\n\x13\x63om.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -21,20 +21,20 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_TEAM']._serialized_start=703 _globals['_TEAM']._serialized_start=721
_globals['_TEAM']._serialized_end=895 _globals['_TEAM']._serialized_end=913
_globals['_MEMBERROLE']._serialized_start=897 _globals['_MEMBERROLE']._serialized_start=915
_globals['_MEMBERROLE']._serialized_end=1024 _globals['_MEMBERROLE']._serialized_end=1042
_globals['_TAKPACKET']._serialized_start=56 _globals['_TAKPACKET']._serialized_start=56
_globals['_TAKPACKET']._serialized_end=331 _globals['_TAKPACKET']._serialized_end=349
_globals['_GEOCHAT']._serialized_start=333 _globals['_GEOCHAT']._serialized_start=351
_globals['_GEOCHAT']._serialized_end=425 _globals['_GEOCHAT']._serialized_end=443
_globals['_GROUP']._serialized_start=427 _globals['_GROUP']._serialized_start=445
_globals['_GROUP']._serialized_end=522 _globals['_GROUP']._serialized_end=540
_globals['_STATUS']._serialized_start=524 _globals['_STATUS']._serialized_start=542
_globals['_STATUS']._serialized_end=549 _globals['_STATUS']._serialized_end=567
_globals['_CONTACT']._serialized_start=551 _globals['_CONTACT']._serialized_start=569
_globals['_CONTACT']._serialized_end=603 _globals['_CONTACT']._serialized_end=621
_globals['_PLI']._serialized_start=605 _globals['_PLI']._serialized_start=623
_globals['_PLI']._serialized_end=700 _globals['_PLI']._serialized_end=718
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -248,10 +248,16 @@ class TAKPacket(google.protobuf.message.Message):
STATUS_FIELD_NUMBER: builtins.int STATUS_FIELD_NUMBER: builtins.int
PLI_FIELD_NUMBER: builtins.int PLI_FIELD_NUMBER: builtins.int
CHAT_FIELD_NUMBER: builtins.int CHAT_FIELD_NUMBER: builtins.int
DETAIL_FIELD_NUMBER: builtins.int
is_compressed: builtins.bool is_compressed: builtins.bool
""" """
Are the payloads strings compressed for LoRA transport? Are the payloads strings compressed for LoRA transport?
""" """
detail: builtins.bytes
"""
Generic CoT detail XML
May be compressed / truncated by the sender
"""
@property @property
def contact(self) -> global___Contact: def contact(self) -> global___Contact:
""" """
@@ -291,10 +297,11 @@ class TAKPacket(google.protobuf.message.Message):
status: global___Status | None = ..., status: global___Status | None = ...,
pli: global___PLI | None = ..., pli: global___PLI | None = ...,
chat: global___GeoChat | None = ..., chat: global___GeoChat | None = ...,
detail: builtins.bytes = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "group", b"group", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "detail", b"detail", "group", b"group", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "group", b"group", "is_compressed", b"is_compressed", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> None: ... def ClearField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "detail", b"detail", "group", b"group", "is_compressed", b"is_compressed", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["pli", "chat"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["pli", "chat", "detail"] | None: ...
global___TAKPacket = TAKPacket global___TAKPacket = TAKPacket

View File

@@ -12,9 +12,10 @@ _sym_db = _symbol_database.Default()
from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__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
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\"\x9f\x02\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"\xc4\x03\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x15\n\x08ringtone\x18\x07 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -22,6 +23,6 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cliento
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_DEVICEPROFILE']._serialized_start=99 _globals['_DEVICEPROFILE']._serialized_start=131
_globals['_DEVICEPROFILE']._serialized_end=386 _globals['_DEVICEPROFILE']._serialized_end=583
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -7,6 +7,7 @@ import builtins
import google.protobuf.descriptor import google.protobuf.descriptor
import google.protobuf.message import google.protobuf.message
import meshtastic.protobuf.localonly_pb2 import meshtastic.protobuf.localonly_pb2
import meshtastic.protobuf.mesh_pb2
import typing import typing
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@@ -25,6 +26,9 @@ class DeviceProfile(google.protobuf.message.Message):
CHANNEL_URL_FIELD_NUMBER: builtins.int CHANNEL_URL_FIELD_NUMBER: builtins.int
CONFIG_FIELD_NUMBER: builtins.int CONFIG_FIELD_NUMBER: builtins.int
MODULE_CONFIG_FIELD_NUMBER: builtins.int MODULE_CONFIG_FIELD_NUMBER: builtins.int
FIXED_POSITION_FIELD_NUMBER: builtins.int
RINGTONE_FIELD_NUMBER: builtins.int
CANNED_MESSAGES_FIELD_NUMBER: builtins.int
long_name: builtins.str long_name: builtins.str
""" """
Long name for the node Long name for the node
@@ -37,6 +41,14 @@ class DeviceProfile(google.protobuf.message.Message):
""" """
The url of the channels from our node The url of the channels from our node
""" """
ringtone: builtins.str
"""
Ringtone for ExternalNotification
"""
canned_messages: builtins.str
"""
Predefined messages for CannedMessage
"""
@property @property
def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig: def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
""" """
@@ -49,6 +61,12 @@ class DeviceProfile(google.protobuf.message.Message):
The ModuleConfig of the node The ModuleConfig of the node
""" """
@property
def fixed_position(self) -> meshtastic.protobuf.mesh_pb2.Position:
"""
Fixed position data
"""
def __init__( def __init__(
self, self,
*, *,
@@ -57,18 +75,27 @@ class DeviceProfile(google.protobuf.message.Message):
channel_url: builtins.str | None = ..., channel_url: builtins.str | None = ...,
config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ..., config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ..., module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
ringtone: builtins.str | None = ...,
canned_messages: builtins.str | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["_channel_url", b"_channel_url", "_config", b"_config", "_long_name", b"_long_name", "_module_config", b"_module_config", "_short_name", b"_short_name", "channel_url", b"channel_url", "config", b"config", "long_name", b"long_name", "module_config", b"module_config", "short_name", b"short_name"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_channel_url", b"_channel_url", "_config", b"_config", "_long_name", b"_long_name", "_module_config", b"_module_config", "_short_name", b"_short_name", "channel_url", b"channel_url", "config", b"config", "long_name", b"long_name", "module_config", b"module_config", "short_name", b"short_name"]) -> None: ... def ClearField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_canned_messages", b"_canned_messages"]) -> typing.Literal["canned_messages"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_channel_url", b"_channel_url"]) -> typing.Literal["channel_url"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_channel_url", b"_channel_url"]) -> typing.Literal["channel_url"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_config", b"_config"]) -> typing.Literal["config"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_config", b"_config"]) -> typing.Literal["config"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_fixed_position", b"_fixed_position"]) -> typing.Literal["fixed_position"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_long_name", b"_long_name"]) -> typing.Literal["long_name"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_long_name", b"_long_name"]) -> typing.Literal["long_name"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ringtone", b"_ringtone"]) -> typing.Literal["ringtone"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_short_name", b"_short_name"]) -> typing.Literal["short_name"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_short_name", b"_short_name"]) -> typing.Literal["short_name"] | None: ...
global___DeviceProfile = DeviceProfile global___DeviceProfile = DeviceProfile

View File

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

View File

@@ -248,11 +248,12 @@ class NodeInfoLite(google.protobuf.message.Message):
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ..., device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
channel: builtins.int = ..., channel: builtins.int = ...,
via_mqtt: builtins.bool = ..., via_mqtt: builtins.bool = ...,
hops_away: builtins.int = ..., hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ..., is_favorite: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "position", b"position", "user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ... def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
global___NodeInfoLite = NodeInfoLite global___NodeInfoLite = NodeInfoLite

View File

File diff suppressed because one or more lines are too long

View File

@@ -1389,6 +1389,14 @@ class Routing(google.protobuf.message.Message):
""" """
The receiving node does not have a Public Key to decode with The receiving node does not have a Public Key to decode with
""" """
ADMIN_BAD_SESSION_KEY: Routing._Error.ValueType # 36
"""
Admin packet otherwise checks out, but uses a bogus or expired session key
"""
ADMIN_PUBLIC_KEY_UNAUTHORIZED: Routing._Error.ValueType # 37
"""
Admin packet sent using PKC, but not from a public key on the admin key list
"""
class Error(_Error, metaclass=_ErrorEnumTypeWrapper): class Error(_Error, metaclass=_ErrorEnumTypeWrapper):
""" """
@@ -1454,6 +1462,14 @@ class Routing(google.protobuf.message.Message):
""" """
The receiving node does not have a Public Key to decode with The receiving node does not have a Public Key to decode with
""" """
ADMIN_BAD_SESSION_KEY: Routing.Error.ValueType # 36
"""
Admin packet otherwise checks out, but uses a bogus or expired session key
"""
ADMIN_PUBLIC_KEY_UNAUTHORIZED: Routing.Error.ValueType # 37
"""
Admin packet sent using PKC, but not from a public key on the admin key list
"""
ROUTE_REQUEST_FIELD_NUMBER: builtins.int ROUTE_REQUEST_FIELD_NUMBER: builtins.int
ROUTE_REPLY_FIELD_NUMBER: builtins.int ROUTE_REPLY_FIELD_NUMBER: builtins.int
@@ -2080,11 +2096,12 @@ class NodeInfo(google.protobuf.message.Message):
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ..., device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
channel: builtins.int = ..., channel: builtins.int = ...,
via_mqtt: builtins.bool = ..., via_mqtt: builtins.bool = ...,
hops_away: builtins.int = ..., hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ..., is_favorite: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "position", b"position", "user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ... def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
global___NodeInfo = NodeInfo global___NodeInfo = NodeInfo
@@ -2695,6 +2712,7 @@ class DeviceMetadata(google.protobuf.message.Message):
POSITION_FLAGS_FIELD_NUMBER: builtins.int POSITION_FLAGS_FIELD_NUMBER: builtins.int
HW_MODEL_FIELD_NUMBER: builtins.int HW_MODEL_FIELD_NUMBER: builtins.int
HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int
HASPKC_FIELD_NUMBER: builtins.int
firmware_version: builtins.str firmware_version: builtins.str
""" """
Device firmware version string Device firmware version string
@@ -2735,6 +2753,10 @@ class DeviceMetadata(google.protobuf.message.Message):
""" """
Has Remote Hardware enabled Has Remote Hardware enabled
""" """
hasPKC: builtins.bool
"""
Has PKC capabilities
"""
def __init__( def __init__(
self, self,
*, *,
@@ -2748,8 +2770,9 @@ class DeviceMetadata(google.protobuf.message.Message):
position_flags: builtins.int = ..., position_flags: builtins.int = ...,
hw_model: global___HardwareModel.ValueType = ..., hw_model: global___HardwareModel.ValueType = ...,
hasRemoteHardware: builtins.bool = ..., hasRemoteHardware: builtins.bool = ...,
hasPKC: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ... def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasPKC", b"hasPKC", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ...
global___DeviceMetadata = DeviceMetadata global___DeviceMetadata = DeviceMetadata

View File

@@ -6,6 +6,7 @@ from typing import Optional
from meshtastic.stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
DEFAULT_TCP_PORT = 4403
class TCPInterface(StreamInterface): class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link""" """Interface class for meshtastic devices over a TCP link"""
@@ -16,7 +17,7 @@ class TCPInterface(StreamInterface):
debugOut=None, debugOut=None,
noProto=False, noProto=False,
connectNow=True, connectNow=True,
portNumber=4403, portNumber=DEFAULT_TCP_PORT,
noNodes:bool=False, noNodes:bool=False,
): ):
"""Constructor, opens a connection to a specified IP address/hostname """Constructor, opens a connection to a specified IP address/hostname

View File

@@ -726,8 +726,8 @@ def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config") @pytest.mark.usefixtures("reset_mt_config")
def test_main_removeposition_invalid(capsys): def test_main_removeposition_remote(capsys):
"""Test --remove-position with an invalid dest""" """Test --remove-position with a remote dest"""
sys.argv = ["", "--remove-position", "--dest", "!12345678"] sys.argv = ["", "--remove-position", "--dest", "!12345678"]
mt_config.args = sys.argv mt_config.args = sys.argv
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
@@ -735,14 +735,15 @@ def test_main_removeposition_invalid(capsys):
main() main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"remote nodes is not supported", out, re.MULTILINE) assert re.search(r"Removing fixed position and disabling fixed position setting", out, re.MULTILINE)
assert re.search(r"Waiting for an acknowledgment from remote node", out, re.MULTILINE)
assert err == "" assert err == ""
mo.assert_called() mo.assert_called()
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config") @pytest.mark.usefixtures("reset_mt_config")
def test_main_setlat_invalid(capsys): def test_main_setlat_remote(capsys):
"""Test --setlat with an invalid dest""" """Test --setlat with a remote dest"""
sys.argv = ["", "--setlat", "37.5", "--dest", "!12345678"] sys.argv = ["", "--setlat", "37.5", "--dest", "!12345678"]
mt_config.args = sys.argv mt_config.args = sys.argv
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
@@ -750,7 +751,8 @@ def test_main_setlat_invalid(capsys):
main() main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"remote nodes is not supported", out, re.MULTILINE) assert re.search(r"Setting device position and enabling fixed position setting", out, re.MULTILINE)
assert re.search(r"Waiting for an acknowledgment from remote node", out, re.MULTILINE)
assert err == "" assert err == ""
mo.assert_called() mo.assert_called()
@@ -769,7 +771,7 @@ def test_main_removeposition(capsys):
mocked_node.removeFixedPosition.side_effect = mock_removeFixedPosition mocked_node.removeFixedPosition.side_effect = mock_removeFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()
@@ -796,7 +798,7 @@ def test_main_setlat(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()
@@ -825,7 +827,7 @@ def test_main_setlon(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()
@@ -854,7 +856,7 @@ def test_main_setalt(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()

View File

@@ -173,7 +173,23 @@ def test_getNode_not_local_timeout(capsys):
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.match(r"Error: Timed out waiting for channels", out) assert re.match(r"Timed out trying to retrieve channel info, retrying", out)
assert err == ""
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_getNode_not_local_timeout_attempts(capsys):
"""Test getNode not local, simulate timeout"""
iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node)
anode.waitForConfig.return_value = False
with patch("meshtastic.node.Node", return_value=anode):
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.getNode("bar2", requestChannelAttempts=2)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert out == 'Timed out trying to retrieve channel info, retrying\nError: Timed out waiting for channels, giving up\n'
assert err == "" assert err == ""

View File

@@ -842,6 +842,34 @@ def test_requestChannel_localNode(caplog):
assert re.search(r"Requesting channel 0", caplog.text, re.MULTILINE) assert re.search(r"Requesting channel 0", caplog.text, re.MULTILINE)
assert not re.search(r"from remote node", caplog.text, re.MULTILINE) assert not re.search(r"from remote node", caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_requestChannels_non_localNode(caplog):
"""Test requestChannels() with a starting index of 0"""
iface = MagicMock(autospec=SerialInterface)
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, "bar", noProto=True)
anode.partialChannels = ['0']
with caplog.at_level(logging.DEBUG):
anode.requestChannels(0)
assert re.search(f"Requesting channel 0 info from remote node", caplog.text, re.MULTILINE)
assert anode.partialChannels == []
@pytest.mark.unit
def test_requestChannels_non_localNode_starting_index(caplog):
"""Test requestChannels() with a starting index of non-0"""
iface = MagicMock(autospec=SerialInterface)
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, "bar", noProto=True)
anode.partialChannels = ['1']
with caplog.at_level(logging.DEBUG):
anode.requestChannels(3)
assert re.search(f"Requesting channel 3 info from remote node", caplog.text, re.MULTILINE)
# make sure it hasn't been initialized
assert anode.partialChannels == ['1']
# @pytest.mark.unit # @pytest.mark.unit
# def test_onResponseRequestCannedMessagePluginMesagePart1(caplog): # def test_onResponseRequestCannedMessagePluginMesagePart1(caplog):

View File

@@ -649,4 +649,17 @@ def test_fuzz_fromStr(valstr):
int(valstr) int(valstr)
assert isinstance(result, int) assert isinstance(result, int)
except ValueError: except ValueError:
assert isinstance(result, str) try:
float(valstr)
assert isinstance(result, float)
except ValueError:
assert isinstance(result, str)
def test_shorthex():
"""Test the shortest hex string representations"""
result = fromStr('0x0')
assert result == b'\x00'
result = fromStr('0x5')
assert result == b'\x05'
result = fromStr('0xffff')
assert result == b'\xff\xff'

View File

@@ -82,7 +82,7 @@ def fromStr(valstr):
val = bytes() val = bytes()
elif valstr.startswith("0x"): elif valstr.startswith("0x"):
# if needed convert to string with asBytes.decode('utf-8') # if needed convert to string with asBytes.decode('utf-8')
val = bytes.fromhex(valstr[2:]) val = bytes.fromhex(valstr[2:].zfill(2))
elif valstr.startswith("base64:"): elif valstr.startswith("base64:"):
val = base64.b64decode(valstr[7:]) val = base64.b64decode(valstr[7:])
elif valstr.lower() in {"t", "true", "yes"}: elif valstr.lower() in {"t", "true", "yes"}:

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "meshtastic" name = "meshtastic"
version = "2.5.0" version = "2.5.1"
description = "Python API & client shell for talking to Meshtastic devices" description = "Python API & client shell for talking to Meshtastic devices"
authors = ["Meshtastic Developers <contact@meshtastic.org>"] authors = ["Meshtastic Developers <contact@meshtastic.org>"]
license = "GPL-3.0-only" license = "GPL-3.0-only"