mirror of
https://github.com/meshtastic/python.git
synced 2026-01-03 13:28:02 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddad5f08b3 | ||
|
|
6e7933a3ce | ||
|
|
f449ff9506 | ||
|
|
b280d0ba23 | ||
|
|
439b1ade2e | ||
|
|
9f2b54eb98 | ||
|
|
278ca74a70 | ||
|
|
1c93b7bd52 | ||
|
|
2d4be347e9 | ||
|
|
26f024dc11 | ||
|
|
2b8348ea05 |
@@ -365,6 +365,11 @@ def onConnected(interface):
|
|||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
interface.getNode(args.dest, False).factoryReset()
|
interface.getNode(args.dest, False).factoryReset()
|
||||||
|
|
||||||
|
if args.remove_node:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
interface.getNode(args.dest, False).removeNode(args.remove_node)
|
||||||
|
|
||||||
if args.reset_nodedb:
|
if args.reset_nodedb:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
@@ -1332,9 +1337,13 @@ def initParser():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--remove-node",
|
||||||
|
help="Tell the destination node to remove a specific node from its DB, by node number or ID"
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--reset-nodedb",
|
"--reset-nodedb",
|
||||||
help="Tell the destination node clear its list of nodes",
|
help="Tell the destination node to clear its list of nodes",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
import google.protobuf.json_format
|
import google.protobuf.json_format
|
||||||
import timeago # type: ignore[import-untyped]
|
import timeago # type: ignore[import-untyped]
|
||||||
from pubsub import pub # type: ignore[import-untyped]
|
from pubsub import pub # type: ignore[import-untyped]
|
||||||
@@ -54,7 +56,7 @@ class MeshInterface:
|
|||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
def __init__(self, debugOut=None, noProto=False):
|
def __init__(self, debugOut=None, noProto: bool=False) -> None:
|
||||||
"""Constructor
|
"""Constructor
|
||||||
|
|
||||||
Keyword Arguments:
|
Keyword Arguments:
|
||||||
@@ -62,27 +64,27 @@ class MeshInterface:
|
|||||||
link - just be a dumb serial client.
|
link - just be a dumb serial client.
|
||||||
"""
|
"""
|
||||||
self.debugOut = debugOut
|
self.debugOut = debugOut
|
||||||
self.nodes = None # FIXME
|
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
|
||||||
self.isConnected = threading.Event()
|
self.isConnected: threading.Event = threading.Event()
|
||||||
self.noProto = noProto
|
self.noProto: bool = noProto
|
||||||
self.localNode = meshtastic.node.Node(self, -1) # We fixup nodenum later
|
self.localNode: meshtastic.node.Node = meshtastic.node.Node(self, -1) # We fixup nodenum later
|
||||||
self.myInfo = None # We don't have device info yet
|
self.myInfo: Optional[mesh_pb2.MyNodeInfo] = None # We don't have device info yet
|
||||||
self.metadata = None # We don't have device metadata yet
|
self.metadata: Optional[mesh_pb2.DeviceMetadata] = None # We don't have device metadata yet
|
||||||
self.responseHandlers = {} # A map from request ID to the handler
|
self.responseHandlers: Dict[int,ResponseHandler] = {} # A map from request ID to the handler
|
||||||
self.failure = (
|
self.failure = (
|
||||||
None # If we've encountered a fatal exception it will be kept here
|
None # If we've encountered a fatal exception it will be kept here
|
||||||
)
|
)
|
||||||
self._timeout = Timeout()
|
self._timeout: Timeout = Timeout()
|
||||||
self._acknowledgment = Acknowledgment()
|
self._acknowledgment: Acknowledgment = Acknowledgment()
|
||||||
self.heartbeatTimer = None
|
self.heartbeatTimer: Optional[threading.Timer] = None
|
||||||
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
|
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
|
||||||
self.currentPacketId = random.randint(0, 0xFFFFFFFF)
|
self.currentPacketId: int = random.randint(0, 0xFFFFFFFF)
|
||||||
self.nodesByNum = None
|
self.nodesByNum: Optional[Dict[int, Dict]] = None
|
||||||
self.configId = None
|
self.configId: Optional[int] = None
|
||||||
self.gotResponse = False # used in gpio read
|
self.gotResponse: bool = False # used in gpio read
|
||||||
self.mask = None # used in gpio read and gpio watch
|
self.mask: Optional[int] = None # used in gpio read and gpio watch
|
||||||
self.queueStatus = None
|
self.queueStatus: Optional[mesh_pb2.QueueStatus] = None
|
||||||
self.queue = collections.OrderedDict()
|
self.queue: collections.OrderedDict = collections.OrderedDict()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Shutdown this interface"""
|
"""Shutdown this interface"""
|
||||||
@@ -103,7 +105,7 @@ class MeshInterface:
|
|||||||
logging.error(f"Traceback: {traceback}")
|
logging.error(f"Traceback: {traceback}")
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def showInfo(self, file=sys.stdout): # pylint: disable=W0613
|
def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613
|
||||||
"""Show human readable summary about this object"""
|
"""Show human readable summary about this object"""
|
||||||
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
|
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
|
||||||
myinfo = ""
|
myinfo = ""
|
||||||
@@ -135,20 +137,20 @@ class MeshInterface:
|
|||||||
print(infos)
|
print(infos)
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
|
def showNodes(self, includeSelf: bool=True, file=sys.stdout) -> str: # pylint: disable=W0613
|
||||||
"""Show table summary of nodes in mesh"""
|
"""Show table summary of nodes in mesh"""
|
||||||
|
|
||||||
def formatFloat(value, precision=2, unit=""):
|
def formatFloat(value, precision=2, unit="") -> Optional[str]:
|
||||||
"""Format a float value with precision."""
|
"""Format a float value with precision."""
|
||||||
return f"{value:.{precision}f}{unit}" if value else None
|
return f"{value:.{precision}f}{unit}" if value else None
|
||||||
|
|
||||||
def getLH(ts):
|
def getLH(ts) -> Optional[str]:
|
||||||
"""Format last heard"""
|
"""Format last heard"""
|
||||||
return (
|
return (
|
||||||
datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None
|
datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None
|
||||||
)
|
)
|
||||||
|
|
||||||
def getTimeAgo(ts):
|
def getTimeAgo(ts) -> Optional[str]:
|
||||||
"""Format how long ago have we heard from this node (aka timeago)."""
|
"""Format how long ago have we heard from this node (aka timeago)."""
|
||||||
return (
|
return (
|
||||||
timeago.format(datetime.fromtimestamp(ts), datetime.now())
|
timeago.format(datetime.fromtimestamp(ts), datetime.now())
|
||||||
@@ -156,7 +158,7 @@ class MeshInterface:
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
rows = []
|
rows: List[Dict[str, Any]] = []
|
||||||
if self.nodesByNum:
|
if self.nodesByNum:
|
||||||
logging.debug(f"self.nodes:{self.nodes}")
|
logging.debug(f"self.nodes:{self.nodes}")
|
||||||
for node in self.nodesByNum.values():
|
for node in self.nodesByNum.values():
|
||||||
@@ -208,6 +210,7 @@ class MeshInterface:
|
|||||||
row.update(
|
row.update(
|
||||||
{
|
{
|
||||||
"SNR": formatFloat(node.get("snr"), 2, " dB"),
|
"SNR": formatFloat(node.get("snr"), 2, " dB"),
|
||||||
|
"Hops Away": node.get("hopsAway", "unknown"),
|
||||||
"Channel": node.get("channel"),
|
"Channel": node.get("channel"),
|
||||||
"LastHeard": getLH(node.get("lastHeard")),
|
"LastHeard": getLH(node.get("lastHeard")),
|
||||||
"Since": getTimeAgo(node.get("lastHeard")),
|
"Since": getTimeAgo(node.get("lastHeard")),
|
||||||
@@ -224,7 +227,7 @@ class MeshInterface:
|
|||||||
print(table)
|
print(table)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
def getNode(self, nodeId, requestChannels=True):
|
def getNode(self, nodeId: str, requestChannels: bool=True) -> 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
|
||||||
@@ -241,11 +244,11 @@ class MeshInterface:
|
|||||||
def sendText(
|
def sendText(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
destinationId=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
wantAck=False,
|
wantAck: bool=False,
|
||||||
wantResponse=False,
|
wantResponse: bool=False,
|
||||||
onResponse=None,
|
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
|
||||||
channelIndex=0,
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send a utf8 string to some other node, if the node has a display it
|
"""Send a utf8 string to some other node, if the node has a display it
|
||||||
will also be shown on the device.
|
will also be shown on the device.
|
||||||
@@ -280,12 +283,12 @@ class MeshInterface:
|
|||||||
def sendData(
|
def sendData(
|
||||||
self,
|
self,
|
||||||
data,
|
data,
|
||||||
destinationId=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
portNum=portnums_pb2.PortNum.PRIVATE_APP,
|
portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
|
||||||
wantAck=False,
|
wantAck: bool=False,
|
||||||
wantResponse=False,
|
wantResponse: bool=False,
|
||||||
onResponse=None,
|
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
|
||||||
channelIndex=0,
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send a data packet to some other node
|
"""Send a data packet to some other node
|
||||||
|
|
||||||
@@ -340,13 +343,13 @@ class MeshInterface:
|
|||||||
|
|
||||||
def sendPosition(
|
def sendPosition(
|
||||||
self,
|
self,
|
||||||
latitude=0.0,
|
latitude: float=0.0,
|
||||||
longitude=0.0,
|
longitude: float=0.0,
|
||||||
altitude=0,
|
altitude: int=0,
|
||||||
timeSec=0,
|
timeSec: int=0,
|
||||||
destinationId=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
wantAck=False,
|
wantAck: bool=False,
|
||||||
wantResponse=False,
|
wantResponse: bool=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Send a position packet to some other node (normally a broadcast)
|
Send a position packet to some other node (normally a broadcast)
|
||||||
@@ -373,8 +376,8 @@ class MeshInterface:
|
|||||||
logging.debug(f"p.altitude:{p.altitude}")
|
logging.debug(f"p.altitude:{p.altitude}")
|
||||||
|
|
||||||
if timeSec == 0:
|
if timeSec == 0:
|
||||||
timeSec = time.time() # returns unix timestamp in seconds
|
timeSec = int(time.time()) # returns unix timestamp in seconds
|
||||||
p.time = int(timeSec)
|
p.time = timeSec
|
||||||
logging.debug(f"p.time:{p.time}")
|
logging.debug(f"p.time:{p.time}")
|
||||||
|
|
||||||
return self.sendData(
|
return self.sendData(
|
||||||
@@ -385,7 +388,7 @@ class MeshInterface:
|
|||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
def sendTraceRoute(self, dest, hopLimit):
|
def sendTraceRoute(self, dest: Union[int, str], hopLimit: int):
|
||||||
"""Send the trace route"""
|
"""Send the trace route"""
|
||||||
r = mesh_pb2.RouteDiscovery()
|
r = mesh_pb2.RouteDiscovery()
|
||||||
self.sendData(
|
self.sendData(
|
||||||
@@ -396,7 +399,7 @@ class MeshInterface:
|
|||||||
onResponse=self.onResponseTraceRoute,
|
onResponse=self.onResponseTraceRoute,
|
||||||
)
|
)
|
||||||
# extend timeout based on number of nodes, limit by configured hopLimit
|
# extend timeout based on number of nodes, limit by configured hopLimit
|
||||||
waitFactor = min(len(self.nodes) - 1, hopLimit)
|
waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
|
||||||
self.waitForTraceRoute(waitFactor)
|
self.waitForTraceRoute(waitFactor)
|
||||||
|
|
||||||
def onResponseTraceRoute(self, p):
|
def onResponseTraceRoute(self, p):
|
||||||
@@ -479,10 +482,10 @@ class MeshInterface:
|
|||||||
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
||||||
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
||||||
|
|
||||||
def _addResponseHandler(self, requestId, callback):
|
def _addResponseHandler(self, requestId: int, callback: Callable):
|
||||||
self.responseHandlers[requestId] = ResponseHandler(callback)
|
self.responseHandlers[requestId] = ResponseHandler(callback)
|
||||||
|
|
||||||
def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False):
|
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
|
||||||
"""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.
|
||||||
|
|
||||||
@@ -496,7 +499,7 @@ class MeshInterface:
|
|||||||
|
|
||||||
toRadio = mesh_pb2.ToRadio()
|
toRadio = mesh_pb2.ToRadio()
|
||||||
|
|
||||||
nodeNum = 0
|
nodeNum: int = 0
|
||||||
if destinationId is None:
|
if destinationId is None:
|
||||||
our_exit("Warning: destinationId must not be None")
|
our_exit("Warning: destinationId must not be None")
|
||||||
elif isinstance(destinationId, int):
|
elif isinstance(destinationId, int):
|
||||||
@@ -514,8 +517,9 @@ class MeshInterface:
|
|||||||
else:
|
else:
|
||||||
if self.nodes:
|
if self.nodes:
|
||||||
node = self.nodes.get(destinationId)
|
node = self.nodes.get(destinationId)
|
||||||
if not node:
|
if node is None:
|
||||||
our_exit(f"Warning: NodeId {destinationId} not found in DB")
|
our_exit(f"Warning: NodeId {destinationId} not found in DB")
|
||||||
|
else:
|
||||||
nodeNum = node["num"]
|
nodeNum = node["num"]
|
||||||
else:
|
else:
|
||||||
logging.warning("Warning: There were no self.nodes.")
|
logging.warning("Warning: There were no self.nodes.")
|
||||||
@@ -568,9 +572,9 @@ class MeshInterface:
|
|||||||
if not success:
|
if not success:
|
||||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry")
|
raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry")
|
||||||
|
|
||||||
def getMyNodeInfo(self):
|
def getMyNodeInfo(self) -> Optional[Dict]:
|
||||||
"""Get info about my node."""
|
"""Get info about my node."""
|
||||||
if self.myInfo is None:
|
if self.myInfo is None or self.nodesByNum is None:
|
||||||
return None
|
return None
|
||||||
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
|
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
|
||||||
return self.nodesByNum.get(self.myInfo.my_node_num)
|
return self.nodesByNum.get(self.myInfo.my_node_num)
|
||||||
@@ -607,7 +611,7 @@ class MeshInterface:
|
|||||||
if self.failure:
|
if self.failure:
|
||||||
raise self.failure
|
raise self.failure
|
||||||
|
|
||||||
def _generatePacketId(self):
|
def _generatePacketId(self) -> int:
|
||||||
"""Get a new unique packet ID"""
|
"""Get a new unique packet ID"""
|
||||||
if self.currentPacketId is None:
|
if self.currentPacketId is None:
|
||||||
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
|
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
|
||||||
@@ -669,18 +673,18 @@ class MeshInterface:
|
|||||||
m.disconnect = True
|
m.disconnect = True
|
||||||
self._sendToRadio(m)
|
self._sendToRadio(m)
|
||||||
|
|
||||||
def _queueHasFreeSpace(self):
|
def _queueHasFreeSpace(self) -> bool:
|
||||||
# We never got queueStatus, maybe the firmware is old
|
# We never got queueStatus, maybe the firmware is old
|
||||||
if self.queueStatus is None:
|
if self.queueStatus is None:
|
||||||
return True
|
return True
|
||||||
return self.queueStatus.free > 0
|
return self.queueStatus.free > 0
|
||||||
|
|
||||||
def _queueClaim(self):
|
def _queueClaim(self) -> None:
|
||||||
if self.queueStatus is None:
|
if self.queueStatus is None:
|
||||||
return
|
return
|
||||||
self.queueStatus.free -= 1
|
self.queueStatus.free -= 1
|
||||||
|
|
||||||
def _sendToRadio(self, toRadio):
|
def _sendToRadio(self, toRadio: mesh_pb2.ToRadio) -> None:
|
||||||
"""Send a ToRadio protobuf to the device"""
|
"""Send a ToRadio protobuf to the device"""
|
||||||
if self.noProto:
|
if self.noProto:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
@@ -729,18 +733,18 @@ class MeshInterface:
|
|||||||
self.queue[packetId] = packet
|
self.queue[packetId] = packet
|
||||||
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
|
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||||
|
|
||||||
def _sendToRadioImpl(self, toRadio):
|
def _sendToRadioImpl(self, toRadio: mesh_pb2.ToRadio) -> None:
|
||||||
"""Send a ToRadio protobuf to the device"""
|
"""Send a ToRadio protobuf to the device"""
|
||||||
logging.error(f"Subclass must provide toradio: {toRadio}")
|
logging.error(f"Subclass must provide toradio: {toRadio}")
|
||||||
|
|
||||||
def _handleConfigComplete(self):
|
def _handleConfigComplete(self) -> None:
|
||||||
"""
|
"""
|
||||||
Done with initial config messages, now send regular MeshPackets
|
Done with initial config messages, now send regular MeshPackets
|
||||||
to ask for settings and channels
|
to ask for settings and channels
|
||||||
"""
|
"""
|
||||||
self.localNode.requestChannels()
|
self.localNode.requestChannels()
|
||||||
|
|
||||||
def _handleQueueStatusFromRadio(self, queueStatus):
|
def _handleQueueStatusFromRadio(self, queueStatus) -> None:
|
||||||
self.queueStatus = queueStatus
|
self.queueStatus = queueStatus
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
|
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
|
||||||
@@ -891,7 +895,7 @@ class MeshInterface:
|
|||||||
else:
|
else:
|
||||||
logging.debug("Unexpected FromRadio payload")
|
logging.debug("Unexpected FromRadio payload")
|
||||||
|
|
||||||
def _fixupPosition(self, position):
|
def _fixupPosition(self, position: Dict) -> Dict:
|
||||||
"""Convert integer lat/lon into floats
|
"""Convert integer lat/lon into floats
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
|
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
|
||||||
from meshtastic.util import (
|
from meshtastic.util import (
|
||||||
Timeout,
|
Timeout,
|
||||||
@@ -603,6 +605,23 @@ class Node:
|
|||||||
onResponse = self.onAckNak
|
onResponse = self.onAckNak
|
||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
|
def removeNode(self, nodeId: Union[int, str]):
|
||||||
|
"""Tell the node to remove a specific node by ID"""
|
||||||
|
if isinstance(nodeId, str):
|
||||||
|
if nodeId.startswith("!"):
|
||||||
|
nodeId = int(nodeId[1:], 16)
|
||||||
|
else:
|
||||||
|
nodeId = int(nodeId)
|
||||||
|
|
||||||
|
p = admin_pb2.AdminMessage()
|
||||||
|
p.remove_by_nodenum = nodeId
|
||||||
|
|
||||||
|
if self == self.iface.localNode:
|
||||||
|
onResponse = None
|
||||||
|
else:
|
||||||
|
onResponse = self.onAckNak
|
||||||
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
def resetNodeDb(self):
|
def resetNodeDb(self):
|
||||||
"""Tell the node to reset its list of nodes."""
|
"""Tell the node to reset its list of nodes."""
|
||||||
p = admin_pb2.AdminMessage()
|
p = admin_pb2.AdminMessage()
|
||||||
@@ -740,9 +759,9 @@ class Node:
|
|||||||
def _sendAdmin(
|
def _sendAdmin(
|
||||||
self,
|
self,
|
||||||
p: admin_pb2.AdminMessage,
|
p: admin_pb2.AdminMessage,
|
||||||
wantResponse=True,
|
wantResponse: bool=True,
|
||||||
onResponse=None,
|
onResponse=None,
|
||||||
adminIndex=0,
|
adminIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"i\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\"\x9b\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xe0\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"\x81\x01\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\x12\x16\n\x0euptime_seconds\x18\x05 \x01(\r\"\xa8\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x12\x0b\n\x03iaq\x18\x07 \x01(\r\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xe0\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
|
||||||
@@ -21,16 +21,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_TELEMETRYSENSORTYPE._serialized_start=1041
|
_TELEMETRYSENSORTYPE._serialized_start=1079
|
||||||
_TELEMETRYSENSORTYPE._serialized_end=1265
|
_TELEMETRYSENSORTYPE._serialized_end=1303
|
||||||
_DEVICEMETRICS._serialized_start=42
|
_DEVICEMETRICS._serialized_start=43
|
||||||
_DEVICEMETRICS._serialized_end=147
|
_DEVICEMETRICS._serialized_end=172
|
||||||
_ENVIRONMENTMETRICS._serialized_start=150
|
_ENVIRONMENTMETRICS._serialized_start=175
|
||||||
_ENVIRONMENTMETRICS._serialized_end=305
|
_ENVIRONMENTMETRICS._serialized_end=343
|
||||||
_POWERMETRICS._serialized_start=308
|
_POWERMETRICS._serialized_start=346
|
||||||
_POWERMETRICS._serialized_end=448
|
_POWERMETRICS._serialized_end=486
|
||||||
_AIRQUALITYMETRICS._serialized_start=451
|
_AIRQUALITYMETRICS._serialized_start=489
|
||||||
_AIRQUALITYMETRICS._serialized_end=770
|
_AIRQUALITYMETRICS._serialized_end=808
|
||||||
_TELEMETRY._serialized_start=773
|
_TELEMETRY._serialized_start=811
|
||||||
_TELEMETRY._serialized_end=1038
|
_TELEMETRY._serialized_end=1076
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ class DeviceMetrics(google.protobuf.message.Message):
|
|||||||
VOLTAGE_FIELD_NUMBER: builtins.int
|
VOLTAGE_FIELD_NUMBER: builtins.int
|
||||||
CHANNEL_UTILIZATION_FIELD_NUMBER: builtins.int
|
CHANNEL_UTILIZATION_FIELD_NUMBER: builtins.int
|
||||||
AIR_UTIL_TX_FIELD_NUMBER: builtins.int
|
AIR_UTIL_TX_FIELD_NUMBER: builtins.int
|
||||||
|
UPTIME_SECONDS_FIELD_NUMBER: builtins.int
|
||||||
battery_level: builtins.int
|
battery_level: builtins.int
|
||||||
"""
|
"""
|
||||||
0-100 (>100 means powered)
|
0-100 (>100 means powered)
|
||||||
@@ -186,6 +187,10 @@ class DeviceMetrics(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Percent of airtime for transmission used within the last hour.
|
Percent of airtime for transmission used within the last hour.
|
||||||
"""
|
"""
|
||||||
|
uptime_seconds: builtins.int
|
||||||
|
"""
|
||||||
|
How long the device has been running since the last reboot (in seconds)
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -193,8 +198,9 @@ class DeviceMetrics(google.protobuf.message.Message):
|
|||||||
voltage: builtins.float = ...,
|
voltage: builtins.float = ...,
|
||||||
channel_utilization: builtins.float = ...,
|
channel_utilization: builtins.float = ...,
|
||||||
air_util_tx: builtins.float = ...,
|
air_util_tx: builtins.float = ...,
|
||||||
|
uptime_seconds: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "voltage", b"voltage"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "uptime_seconds", b"uptime_seconds", "voltage", b"voltage"]) -> None: ...
|
||||||
|
|
||||||
global___DeviceMetrics = DeviceMetrics
|
global___DeviceMetrics = DeviceMetrics
|
||||||
|
|
||||||
@@ -212,6 +218,7 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
GAS_RESISTANCE_FIELD_NUMBER: builtins.int
|
GAS_RESISTANCE_FIELD_NUMBER: builtins.int
|
||||||
VOLTAGE_FIELD_NUMBER: builtins.int
|
VOLTAGE_FIELD_NUMBER: builtins.int
|
||||||
CURRENT_FIELD_NUMBER: builtins.int
|
CURRENT_FIELD_NUMBER: builtins.int
|
||||||
|
IAQ_FIELD_NUMBER: builtins.int
|
||||||
temperature: builtins.float
|
temperature: builtins.float
|
||||||
"""
|
"""
|
||||||
Temperature measured
|
Temperature measured
|
||||||
@@ -236,6 +243,11 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
||||||
"""
|
"""
|
||||||
|
iaq: builtins.int
|
||||||
|
"""
|
||||||
|
relative scale IAQ value as measured by Bosch BME680 . value 0-500.
|
||||||
|
Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here.
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -245,8 +257,9 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
gas_resistance: builtins.float = ...,
|
gas_resistance: builtins.float = ...,
|
||||||
voltage: builtins.float = ...,
|
voltage: builtins.float = ...,
|
||||||
current: builtins.float = ...,
|
current: builtins.float = ...,
|
||||||
|
iaq: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "gas_resistance", b"gas_resistance", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage"]) -> None: ...
|
||||||
|
|
||||||
global___EnvironmentMetrics = EnvironmentMetrics
|
global___EnvironmentMetrics = EnvironmentMetrics
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from google.protobuf.json_format import MessageToJson
|
from google.protobuf.json_format import MessageToJson
|
||||||
|
|
||||||
import packaging.version as pkg_version
|
import packaging.version as pkg_version
|
||||||
@@ -153,16 +155,16 @@ class dotdict(dict):
|
|||||||
class Timeout:
|
class Timeout:
|
||||||
"""Timeout class"""
|
"""Timeout class"""
|
||||||
|
|
||||||
def __init__(self, maxSecs=20):
|
def __init__(self, maxSecs: int=20):
|
||||||
self.expireTime = 0
|
self.expireTime: Union[int, float] = 0
|
||||||
self.sleepInterval = 0.1
|
self.sleepInterval: float = 0.1
|
||||||
self.expireTimeout = maxSecs
|
self.expireTimeout: int = maxSecs
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Restart the waitForSet timer"""
|
"""Restart the waitForSet timer"""
|
||||||
self.expireTime = time.time() + self.expireTimeout
|
self.expireTime = time.time() + self.expireTimeout
|
||||||
|
|
||||||
def waitForSet(self, target, attrs=()):
|
def waitForSet(self, target, attrs=()) -> bool:
|
||||||
"""Block until the specified attributes are set. Returns True if config has been received."""
|
"""Block until the specified attributes are set. Returns True if config has been received."""
|
||||||
self.reset()
|
self.reset()
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
@@ -173,7 +175,7 @@ class Timeout:
|
|||||||
|
|
||||||
def waitForAckNak(
|
def waitForAckNak(
|
||||||
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
|
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
|
||||||
):
|
) -> bool:
|
||||||
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
|
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
|
||||||
self.reset()
|
self.reset()
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
@@ -183,7 +185,7 @@ class Timeout:
|
|||||||
time.sleep(self.sleepInterval)
|
time.sleep(self.sleepInterval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute"):
|
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute") -> bool:
|
||||||
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
|
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
|
||||||
self.expireTimeout *= waitFactor
|
self.expireTimeout *= waitFactor
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -194,7 +196,7 @@ class Timeout:
|
|||||||
time.sleep(self.sleepInterval)
|
time.sleep(self.sleepInterval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def waitForTelemetry(self, acknowledgment):
|
def waitForTelemetry(self, acknowledgment) -> bool:
|
||||||
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
|
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
|
||||||
self.reset()
|
self.reset()
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
|
|||||||
Submodule protobufs updated: 68720ed8db...f92900c5f8
@@ -1,6 +1,6 @@
|
|||||||
markdown
|
markdown
|
||||||
pyserial
|
pyserial
|
||||||
protobuf
|
protobuf>=5.26.0
|
||||||
dotmap
|
dotmap
|
||||||
pexpect
|
pexpect
|
||||||
pyqrcode
|
pyqrcode
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -13,7 +13,7 @@ with open("README.md", "r") as fh:
|
|||||||
# This call to setup() does all the work
|
# This call to setup() does all the work
|
||||||
setup(
|
setup(
|
||||||
name="meshtastic",
|
name="meshtastic",
|
||||||
version="2.3.3",
|
version="2.3.4",
|
||||||
description="Python API & client shell for talking to Meshtastic devices",
|
description="Python API & client shell for talking to Meshtastic devices",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
@@ -34,7 +34,7 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"pyserial>=3.4",
|
"pyserial>=3.4",
|
||||||
"protobuf>=3.13.0",
|
"protobuf>=5.26.0",
|
||||||
"requests>=2.25.0",
|
"requests>=2.25.0",
|
||||||
"pypubsub>=4.0.3",
|
"pypubsub>=4.0.3",
|
||||||
"dotmap>=1.3.14",
|
"dotmap>=1.3.14",
|
||||||
|
|||||||
Reference in New Issue
Block a user