Skip to content

Commit f449ff9

Browse files
committed
Add a variety of type annotations, primarily in mesh_interface
1 parent b280d0b commit f449ff9

3 files changed

Lines changed: 78 additions & 73 deletions

File tree

meshtastic/mesh_interface.py

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
message_to_json,
3838
)
3939

40+
from typing import Any, Callable, Dict, List, Optional, Union
41+
4042

4143
class MeshInterface:
4244
"""Interface class for meshtastic devices
@@ -54,35 +56,35 @@ def __init__(self, message):
5456
self.message = message
5557
super().__init__(self.message)
5658

57-
def __init__(self, debugOut=None, noProto=False):
59+
def __init__(self, debugOut=None, noProto: bool=False) -> None:
5860
"""Constructor
5961
6062
Keyword Arguments:
6163
noProto -- If True, don't try to run our protocol on the
6264
link - just be a dumb serial client.
6365
"""
6466
self.debugOut = debugOut
65-
self.nodes = None # FIXME
66-
self.isConnected = threading.Event()
67-
self.noProto = noProto
68-
self.localNode = meshtastic.node.Node(self, -1) # We fixup nodenum later
69-
self.myInfo = None # We don't have device info yet
70-
self.metadata = None # We don't have device metadata yet
71-
self.responseHandlers = {} # A map from request ID to the handler
67+
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
68+
self.isConnected: threading.Event = threading.Event()
69+
self.noProto: bool = noProto
70+
self.localNode: meshtastic.node.Node = meshtastic.node.Node(self, -1) # We fixup nodenum later
71+
self.myInfo: Optional[mesh_pb2.MyNodeInfo] = None # We don't have device info yet
72+
self.metadata: Optional[mesh_pb2.DeviceMetadata] = None # We don't have device metadata yet
73+
self.responseHandlers: Dict[int,ResponseHandler] = {} # A map from request ID to the handler
7274
self.failure = (
7375
None # If we've encountered a fatal exception it will be kept here
7476
)
75-
self._timeout = Timeout()
76-
self._acknowledgment = Acknowledgment()
77-
self.heartbeatTimer = None
77+
self._timeout: Timeout = Timeout()
78+
self._acknowledgment: Acknowledgment = Acknowledgment()
79+
self.heartbeatTimer: Optional[threading.Timer] = None
7880
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
79-
self.currentPacketId = random.randint(0, 0xFFFFFFFF)
80-
self.nodesByNum = None
81-
self.configId = None
82-
self.gotResponse = False # used in gpio read
83-
self.mask = None # used in gpio read and gpio watch
84-
self.queueStatus = None
85-
self.queue = collections.OrderedDict()
81+
self.currentPacketId: int = random.randint(0, 0xFFFFFFFF)
82+
self.nodesByNum: Optional[Dict[int, Dict]] = None
83+
self.configId: Optional[int] = None
84+
self.gotResponse: bool = False # used in gpio read
85+
self.mask: Optional[int] = None # used in gpio read and gpio watch
86+
self.queueStatus: Optional[mesh_pb2.QueueStatus] = None
87+
self.queue: collections.OrderedDict = collections.OrderedDict()
8688

8789
def close(self):
8890
"""Shutdown this interface"""
@@ -103,7 +105,7 @@ def __exit__(self, exc_type, exc_value, traceback):
103105
logging.error(f"Traceback: {traceback}")
104106
self.close()
105107

106-
def showInfo(self, file=sys.stdout): # pylint: disable=W0613
108+
def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613
107109
"""Show human readable summary about this object"""
108110
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
109111
myinfo = ""
@@ -135,28 +137,28 @@ def showInfo(self, file=sys.stdout): # pylint: disable=W0613
135137
print(infos)
136138
return infos
137139

138-
def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
140+
def showNodes(self, includeSelf: bool=True, file=sys.stdout) -> str: # pylint: disable=W0613
139141
"""Show table summary of nodes in mesh"""
140142

141-
def formatFloat(value, precision=2, unit=""):
143+
def formatFloat(value, precision=2, unit="") -> Optional[str]:
142144
"""Format a float value with precision."""
143145
return f"{value:.{precision}f}{unit}" if value else None
144146

145-
def getLH(ts):
147+
def getLH(ts) -> Optional[str]:
146148
"""Format last heard"""
147149
return (
148150
datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None
149151
)
150152

151-
def getTimeAgo(ts):
153+
def getTimeAgo(ts) -> Optional[str]:
152154
"""Format how long ago have we heard from this node (aka timeago)."""
153155
return (
154156
timeago.format(datetime.fromtimestamp(ts), datetime.now())
155157
if ts
156158
else None
157159
)
158160

159-
rows = []
161+
rows: List[Dict[str, Any]] = []
160162
if self.nodesByNum:
161163
logging.debug(f"self.nodes:{self.nodes}")
162164
for node in self.nodesByNum.values():
@@ -225,7 +227,7 @@ def getTimeAgo(ts):
225227
print(table)
226228
return table
227229

228-
def getNode(self, nodeId, requestChannels=True):
230+
def getNode(self, nodeId: str, requestChannels: bool=True) -> meshtastic.node.Node:
229231
"""Return a node object which contains device settings and channel info"""
230232
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
231233
return self.localNode
@@ -242,11 +244,11 @@ def getNode(self, nodeId, requestChannels=True):
242244
def sendText(
243245
self,
244246
text: str,
245-
destinationId=BROADCAST_ADDR,
246-
wantAck=False,
247-
wantResponse=False,
248-
onResponse=None,
249-
channelIndex=0,
247+
destinationId: Union[int, str]=BROADCAST_ADDR,
248+
wantAck: bool=False,
249+
wantResponse: bool=False,
250+
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
251+
channelIndex: int=0,
250252
):
251253
"""Send a utf8 string to some other node, if the node has a display it
252254
will also be shown on the device.
@@ -281,12 +283,12 @@ def sendText(
281283
def sendData(
282284
self,
283285
data,
284-
destinationId=BROADCAST_ADDR,
285-
portNum=portnums_pb2.PortNum.PRIVATE_APP,
286-
wantAck=False,
287-
wantResponse=False,
288-
onResponse=None,
289-
channelIndex=0,
286+
destinationId: Union[int, str]=BROADCAST_ADDR,
287+
portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
288+
wantAck: bool=False,
289+
wantResponse: bool=False,
290+
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
291+
channelIndex: int=0,
290292
):
291293
"""Send a data packet to some other node
292294
@@ -341,13 +343,13 @@ def sendData(
341343

342344
def sendPosition(
343345
self,
344-
latitude=0.0,
345-
longitude=0.0,
346-
altitude=0,
347-
timeSec=0,
348-
destinationId=BROADCAST_ADDR,
349-
wantAck=False,
350-
wantResponse=False,
346+
latitude: float=0.0,
347+
longitude: float=0.0,
348+
altitude: int=0,
349+
timeSec: int=0,
350+
destinationId: Union[int, str]=BROADCAST_ADDR,
351+
wantAck: bool=False,
352+
wantResponse: bool=False,
351353
):
352354
"""
353355
Send a position packet to some other node (normally a broadcast)
@@ -374,8 +376,8 @@ def sendPosition(
374376
logging.debug(f"p.altitude:{p.altitude}")
375377

376378
if timeSec == 0:
377-
timeSec = time.time() # returns unix timestamp in seconds
378-
p.time = int(timeSec)
379+
timeSec = int(time.time()) # returns unix timestamp in seconds
380+
p.time = timeSec
379381
logging.debug(f"p.time:{p.time}")
380382

381383
return self.sendData(
@@ -386,7 +388,7 @@ def sendPosition(
386388
wantResponse=wantResponse,
387389
)
388390

389-
def sendTraceRoute(self, dest, hopLimit):
391+
def sendTraceRoute(self, dest: Union[int, str], hopLimit: int):
390392
"""Send the trace route"""
391393
r = mesh_pb2.RouteDiscovery()
392394
self.sendData(
@@ -397,7 +399,7 @@ def sendTraceRoute(self, dest, hopLimit):
397399
onResponse=self.onResponseTraceRoute,
398400
)
399401
# extend timeout based on number of nodes, limit by configured hopLimit
400-
waitFactor = min(len(self.nodes) - 1, hopLimit)
402+
waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
401403
self.waitForTraceRoute(waitFactor)
402404

403405
def onResponseTraceRoute(self, p):
@@ -480,10 +482,10 @@ def onResponseTelemetry(self, p):
480482
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
481483
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
482484

483-
def _addResponseHandler(self, requestId, callback):
485+
def _addResponseHandler(self, requestId: int, callback: Callable):
484486
self.responseHandlers[requestId] = ResponseHandler(callback)
485487

486-
def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False):
488+
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
487489
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
488490
You probably don't want this - use sendData instead.
489491
@@ -497,7 +499,7 @@ def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False):
497499

498500
toRadio = mesh_pb2.ToRadio()
499501

500-
nodeNum = 0
502+
nodeNum: int = 0
501503
if destinationId is None:
502504
our_exit("Warning: destinationId must not be None")
503505
elif isinstance(destinationId, int):
@@ -515,9 +517,10 @@ def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False):
515517
else:
516518
if self.nodes:
517519
node = self.nodes.get(destinationId)
518-
if not node:
520+
if node is None:
519521
our_exit(f"Warning: NodeId {destinationId} not found in DB")
520-
nodeNum = node["num"]
522+
else:
523+
nodeNum = node["num"]
521524
else:
522525
logging.warning("Warning: There were no self.nodes.")
523526

@@ -569,9 +572,9 @@ def waitForTelemetry(self):
569572
if not success:
570573
raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry")
571574

572-
def getMyNodeInfo(self):
575+
def getMyNodeInfo(self) -> Optional[Dict]:
573576
"""Get info about my node."""
574-
if self.myInfo is None:
577+
if self.myInfo is None or self.nodesByNum is None:
575578
return None
576579
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
577580
return self.nodesByNum.get(self.myInfo.my_node_num)
@@ -608,7 +611,7 @@ def _waitConnected(self, timeout=30.0):
608611
if self.failure:
609612
raise self.failure
610613

611-
def _generatePacketId(self):
614+
def _generatePacketId(self) -> int:
612615
"""Get a new unique packet ID"""
613616
if self.currentPacketId is None:
614617
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
@@ -670,18 +673,18 @@ def _sendDisconnect(self):
670673
m.disconnect = True
671674
self._sendToRadio(m)
672675

673-
def _queueHasFreeSpace(self):
676+
def _queueHasFreeSpace(self) -> bool:
674677
# We never got queueStatus, maybe the firmware is old
675678
if self.queueStatus is None:
676679
return True
677680
return self.queueStatus.free > 0
678681

679-
def _queueClaim(self):
682+
def _queueClaim(self) -> None:
680683
if self.queueStatus is None:
681684
return
682685
self.queueStatus.free -= 1
683686

684-
def _sendToRadio(self, toRadio):
687+
def _sendToRadio(self, toRadio: mesh_pb2.ToRadio) -> None:
685688
"""Send a ToRadio protobuf to the device"""
686689
if self.noProto:
687690
logging.warning(
@@ -730,18 +733,18 @@ def _sendToRadio(self, toRadio):
730733
self.queue[packetId] = packet
731734
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
732735

733-
def _sendToRadioImpl(self, toRadio):
736+
def _sendToRadioImpl(self, toRadio: mesh_pb2.ToRadio) -> None:
734737
"""Send a ToRadio protobuf to the device"""
735738
logging.error(f"Subclass must provide toradio: {toRadio}")
736739

737-
def _handleConfigComplete(self):
740+
def _handleConfigComplete(self) -> None:
738741
"""
739742
Done with initial config messages, now send regular MeshPackets
740743
to ask for settings and channels
741744
"""
742745
self.localNode.requestChannels()
743746

744-
def _handleQueueStatusFromRadio(self, queueStatus):
747+
def _handleQueueStatusFromRadio(self, queueStatus) -> None:
745748
self.queueStatus = queueStatus
746749
logging.debug(
747750
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
@@ -892,7 +895,7 @@ def _handleFromRadio(self, fromRadioBytes):
892895
else:
893896
logging.debug("Unexpected FromRadio payload")
894897

895-
def _fixupPosition(self, position):
898+
def _fixupPosition(self, position: Dict) -> Dict:
896899
"""Convert integer lat/lon into floats
897900
898901
Arguments:

meshtastic/node.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import time
77

8-
from typing import Union
8+
from typing import Optional, Union
99

1010
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
1111
from meshtastic.util import (
@@ -759,9 +759,9 @@ def _requestChannel(self, channelNum: int):
759759
def _sendAdmin(
760760
self,
761761
p: admin_pb2.AdminMessage,
762-
wantResponse=True,
762+
wantResponse: bool=True,
763763
onResponse=None,
764-
adminIndex=0,
764+
adminIndex: int=0,
765765
):
766766
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
767767

meshtastic/util.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from meshtastic.supported_device import supported_devices
2222
from meshtastic.version import get_active_version
2323

24+
from typing import Union
25+
2426
"""Some devices such as a seger jlink we never want to accidentally open"""
2527
blacklistVids = dict.fromkeys([0x1366])
2628

@@ -153,16 +155,16 @@ class dotdict(dict):
153155
class Timeout:
154156
"""Timeout class"""
155157

156-
def __init__(self, maxSecs=20):
157-
self.expireTime = 0
158-
self.sleepInterval = 0.1
159-
self.expireTimeout = maxSecs
158+
def __init__(self, maxSecs: int=20):
159+
self.expireTime: Union[int, float] = 0
160+
self.sleepInterval: float = 0.1
161+
self.expireTimeout: int = maxSecs
160162

161163
def reset(self):
162164
"""Restart the waitForSet timer"""
163165
self.expireTime = time.time() + self.expireTimeout
164166

165-
def waitForSet(self, target, attrs=()):
167+
def waitForSet(self, target, attrs=()) -> bool:
166168
"""Block until the specified attributes are set. Returns True if config has been received."""
167169
self.reset()
168170
while time.time() < self.expireTime:
@@ -173,7 +175,7 @@ def waitForSet(self, target, attrs=()):
173175

174176
def waitForAckNak(
175177
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
176-
):
178+
) -> bool:
177179
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
178180
self.reset()
179181
while time.time() < self.expireTime:
@@ -183,7 +185,7 @@ def waitForAckNak(
183185
time.sleep(self.sleepInterval)
184186
return False
185187

186-
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute"):
188+
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute") -> bool:
187189
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
188190
self.expireTimeout *= waitFactor
189191
self.reset()
@@ -194,7 +196,7 @@ def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute
194196
time.sleep(self.sleepInterval)
195197
return False
196198

197-
def waitForTelemetry(self, acknowledgment):
199+
def waitForTelemetry(self, acknowledgment) -> bool:
198200
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
199201
self.reset()
200202
while time.time() < self.expireTime:

0 commit comments

Comments
 (0)