Compare commits

...

8 Commits

Author SHA1 Message Date
Jonathan Bennett
54ecb3feb8 Rough key-verification implementation 2025-06-10 10:06:39 -05:00
Ian McEwen
0deb98b4c6 Merge pull request #785 from grleblanc/master
fix(util): update waitForTraceRoute reset logic
2025-06-09 16:22:45 -07:00
grleblanc
04a0ff6322 fix(util): fix typo 2025-06-09 15:50:21 -04:00
grleblanc
b4764d3bc3 fix(util): update waitForTraceRoute reset logic 2025-06-09 15:05:53 -04:00
Ian McEwen
9281c4a335 Correctly call self.close() in disconnected_callback for bleak (fixes #770) 2025-06-09 11:52:23 -07:00
Ben Meadors
3c2dd6f4ff Merge pull request #783 from pdxlocations/config-work
Fix --configure by adding delays after sections
2025-06-09 05:37:25 -05:00
pdxlocations
8e48d141c8 add sleeps 2025-06-08 23:32:32 -07:00
Ian McEwen
622a435465 Add __repr__ methods to interface types and Node, for nicer printing/logging 2025-05-08 21:23:00 -07:00
6 changed files with 138 additions and 5 deletions

View File

@@ -472,6 +472,21 @@ def onConnected(interface):
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).removeIgnored(args.remove_ignored_node)
if args.key_verification:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).keyVerification(args.key_verification)
if args.key_verification_number:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).keyVerificationNumber(args.key_verification_number, args.key_verification_nonce)
if args.key_verification_confirm:
closeNow = True
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).keyVerificationConfirm(args.key_verification_confirm)
if args.reset_nodedb:
closeNow = True
waitForAckNak = True
@@ -647,6 +662,7 @@ def onConnected(interface):
print(f"Setting device owner to {configuration['owner']}")
waitForAckNak = True
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(configuration["owner"])
time.sleep(0.5)
if "owner_short" in configuration:
print(
@@ -656,6 +672,7 @@ def onConnected(interface):
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
long_name=None, short_name=configuration["owner_short"]
)
time.sleep(0.5)
if "ownerShort" in configuration:
print(
@@ -665,14 +682,17 @@ def onConnected(interface):
interface.getNode(args.dest, False, **getNode_kwargs).setOwner(
long_name=None, short_name=configuration["ownerShort"]
)
time.sleep(0.5)
if "channel_url" in configuration:
print("Setting channel url to", configuration["channel_url"])
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channel_url"])
time.sleep(0.5)
if "channelUrl" in configuration:
print("Setting channel url to", configuration["channelUrl"])
interface.getNode(args.dest, **getNode_kwargs).setURL(configuration["channelUrl"])
time.sleep(0.5)
if "location" in configuration:
alt = 0
@@ -691,6 +711,7 @@ def onConnected(interface):
print(f"Fixing longitude at {lon} degrees")
print("Setting device position")
interface.localNode.setFixedPosition(lat, lon, alt)
time.sleep(0.5)
if "config" in configuration:
localConfig = interface.getNode(args.dest, **getNode_kwargs).localConfig
@@ -701,6 +722,7 @@ def onConnected(interface):
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
meshtastic.util.camel_to_snake(section)
)
time.sleep(0.5)
if "module_config" in configuration:
moduleConfig = interface.getNode(args.dest, **getNode_kwargs).moduleConfig
@@ -713,6 +735,7 @@ def onConnected(interface):
interface.getNode(args.dest, **getNode_kwargs).writeConfig(
meshtastic.util.camel_to_snake(section)
)
time.sleep(0.5)
interface.getNode(args.dest, False, **getNode_kwargs).commitSettingsTransaction()
print("Writing modified configuration to device")
@@ -1824,6 +1847,22 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars
"Use the node ID with a '!' or '0x' prefix or the node number.",
metavar="!xxxxxxxx"
)
group.add_argument(
"--key-verification",
help="start key verification. "
"Use the node ID with a '!' or '0x' prefix or the node number.",
metavar="!xxxxxxxx"
)
group.add_argument(
"--key-verification-number",
help="start key verification. "
"Use the node ID with a '!' or '0x' prefix or the node number.",
)
group.add_argument(
"--key-verification-confirm",
help="start key verification. "
"Use the node ID with a '!' or '0x' prefix or the node number.",
)
group.add_argument(
"--reset-nodedb",
help="Tell the destination node to clear its list of nodes",
@@ -1953,6 +1992,12 @@ def initParser():
action="store_true",
)
group.add_argument(
"--key-verification-nonce",
help="start key verification. "
"Use the node ID with a '!' or '0x' prefix or the node number.",
)
power_group = parser.add_argument_group(
"Power Testing", "Options for power testing/logging."
)

View File

@@ -83,6 +83,17 @@ class BLEInterface(MeshInterface):
# Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
self._exit_handler = atexit.register(self.client.disconnect)
def __repr__(self):
rep = f"BLEInterface(address={self.client.address if self.client else None!r}"
if self.debugOut is not None:
rep += f", debugOut={self.debugOut!r}"
if self.noProto:
rep += ", noProto=True"
if self.noNodes:
rep += ", noNodes=True"
rep += ")"
return rep
def from_num_handler(self, _, b: bytes) -> None: # pylint: disable=C0116
"""Handle callbacks for fromnum notify.
Note: this method does not need to be async because it is just setting a bool.
@@ -163,7 +174,7 @@ class BLEInterface(MeshInterface):
# Bleak docs recommend always doing a scan before connecting (even if we know addr)
device = self.find_device(address)
client = BLEClient(device.address, disconnected_callback=lambda _: self.close)
client = BLEClient(device.address, disconnected_callback=lambda _: self.close())
client.connect()
client.discover()
return client

View File

@@ -42,6 +42,15 @@ class Node:
self.gotResponse = None
def __repr__(self):
r = f"Node({self.iface!r}, 0x{self.nodeNum:08x}"
if self.noProto:
r += ", noProto=True"
if self._timeout.expireTimeout != 300:
r += ", timeout={self._timeout.expireTimeout!r}"
r += ")"
return r
def showChannels(self):
"""Show human readable description of our channels."""
print("Channels:")
@@ -761,6 +770,49 @@ class Node:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def keyVerification(self, nodeId: Union[int, str]):
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.KeyVerificationAdmin()
p.message_type = p.MessageType.INITIATE_VERIFICATION
p.remote_nodenum = nodeId
p.nonce = 0
a = admin_pb2.AdminMessage()
a.key_verification_admin.CopyFrom(p)
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(a, onResponse=onResponse)
def keyVerificationNumber(self, number: int, nonce: int,):
print(int(number))
print(int(nonce))
p = admin_pb2.KeyVerificationAdmin()
p.message_type = p.MessageType.PROVIDE_SECURITY_NUMBER
p.nonce = int(nonce)
p.security_number = int(number)
a = admin_pb2.AdminMessage()
a.key_verification_admin.CopyFrom(p)
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(a, onResponse=onResponse)
def keyVerificationConfirm(self, nonce: int,):
print(int(nonce))
p = admin_pb2.KeyVerificationAdmin()
p.message_type = p.MessageType.DO_VERIFY
p.nonce = int(nonce)
a = admin_pb2.AdminMessage()
a.key_verification_admin.CopyFrom(p)
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(a, onResponse=onResponse)
def resetNodeDb(self):
"""Tell the node to reset its list of nodes."""
self.ensureSessionKey()

View File

@@ -66,6 +66,17 @@ class SerialInterface(StreamInterface):
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
)
def __repr__(self):
rep = f"SerialInterface(devPath={self.devPath!r}"
if hasattr(self, 'debugOut') and self.debugOut is not None:
rep += f", debugOut={self.debugOut!r}"
if self.noProto:
rep += ", noProto=True"
if hasattr(self, 'noNodes') and self.noNodes:
rep += ", noNodes=True"
rep += ")"
return rep
def close(self) -> None:
"""Close a connection to the device"""
if self.stream: # Stream can be null if we were already closed

View File

@@ -43,6 +43,21 @@ class TCPInterface(StreamInterface):
super().__init__(debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes)
def __repr__(self):
rep = f"TCPInterface({self.hostname!r}"
if self.debugOut is not None:
rep += f", debugOut={self.debugOut!r}"
if self.noProto:
rep += ", noProto=True"
if self.socket is None:
rep += ", connectNow=False"
if self.portNumber != DEFAULT_TCP_PORT:
rep += f", portNumber={self.portNumber!r}"
if self.noNodes:
rep += ", noNodes=True"
rep += ")"
return rep
def _socket_shutdown(self) -> None:
"""Shutdown the socket.
Note: Broke out this line so the exception could be unit tested.

View File

@@ -198,9 +198,9 @@ class Timeout:
self.sleepInterval: float = 0.1
self.expireTimeout: int = maxSecs
def reset(self) -> None:
def reset(self, expireTimeout=None):
"""Restart the waitForSet timer"""
self.expireTime = time.time() + self.expireTimeout
self.expireTime = time.time() + (self.expireTimeout if expireTimeout is None else expireTimeout)
def waitForSet(self, target, attrs=()) -> bool:
"""Block until the specified attributes are set. Returns True if config has been received."""
@@ -225,8 +225,7 @@ class Timeout:
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute") -> bool:
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
self.expireTimeout *= waitFactor
self.reset()
self.reset(self.expireTimeout * waitFactor)
while time.time() < self.expireTime:
if getattr(acknowledgment, attr, None):
acknowledgment.reset()