From 53889e045c386a92e5de00156a4ee17e523c26a1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 3 May 2020 20:14:35 -0700 Subject: [PATCH] regen docs --- docs/meshtastic/index.html | 249 +++++++++++++++++---- docs/meshtastic/mesh_pb2.html | 69 +++--- docs/meshtastic/test.html | 400 ++++++++++++++++++++++++++++++++++ docs/meshtastic/util.html | 147 +++++++++++++ 4 files changed, 794 insertions(+), 71 deletions(-) create mode 100644 docs/meshtastic/test.html create mode 100644 docs/meshtastic/util.html diff --git a/docs/meshtastic/index.html b/docs/meshtastic/index.html index a59de7f..e44f687 100644 --- a/docs/meshtastic/index.html +++ b/docs/meshtastic/index.html @@ -32,7 +32,7 @@ the device. Includes always up-to-date location and username information for each node in the mesh. This is a read-only datastructure. -
  • myNodeInfo - You probably don't want this.
  • +
  • myNodeInfo - Contains read-only information about the local radio device (software version, hardware version, etc)
  • Published PubSub topics

    We use a publish-subscribe model to communicate asynchronous events. @@ -80,7 +80,7 @@ properties of StreamInterface: the device. - nodes - The database of received nodes. Includes always up-to-date location and username information for each node in the mesh. This is a read-only datastructure. -- myNodeInfo - You probably don't want this. +- myNodeInfo - Contains read-only information about the local radio device (software version, hardware version, etc) ## Published PubSub topics @@ -116,13 +116,14 @@ interface = meshtastic.StreamInterface() # By default will try to find a meshtas import google.protobuf.json_format import serial -import serial.tools.list_ports import threading import logging import sys import traceback from . import mesh_pb2 +from . import util from pubsub import pub +from dotmap import DotMap START1 = 0x94 START2 = 0xc3 @@ -130,19 +131,26 @@ HEADER_LEN = 4 MAX_TO_FROM_RADIO_SIZE = 512 BROADCAST_ADDR = "all" # A special ID that means broadcast - +BROADCAST_NUM = 255 MY_CONFIG_ID = 42 class MeshInterface: """Interface class for meshtastic devices + + Properties: + + isConnected + nodes + debugOut """ def __init__(self, debugOut=None): """Constructor""" self.debugOut = debugOut self.nodes = None # FIXME + self.isConnected = False self._startConfig() def sendText(self, text, destinationId=BROADCAST_ADDR): @@ -152,7 +160,7 @@ class MeshInterface: text {string} -- The text to send Keyword Arguments: - destinationId {nodeId} -- where to send this message (default: {BROADCAST_ADDR}) + destinationId {nodeId or nodeNum} -- where to send this message (default: {BROADCAST_ADDR}) """ self.sendData(text.encode("utf-8"), destinationId, dataType=mesh_pb2.Data.CLEAR_TEXT) @@ -169,13 +177,38 @@ class MeshInterface: You probably don't want this - use sendData instead.""" toRadio = mesh_pb2.ToRadio() # FIXME add support for non broadcast addresses - meshPacket.to = 255 + + if isinstance(destinationId, int): + nodeNum = destinationId + elif nodeNum == BROADCAST_ADDR: + nodeNum = 255 + else: + raise Exception( + "invalid destination addr, we don't yet support nodeid lookup") + + meshPacket.to = nodeNum toRadio.packet.CopyFrom(meshPacket) self._sendToRadio(toRadio) + def writeConfig(self): + """Write the current (edited) radioConfig to the device""" + if self.radioConfig == None: + raise Exception("No RadioConfig has been read") + + t = mesh_pb2.ToRadio() + t.set_radio.CopyFrom(self.radioConfig) + self._sendToRadio(t) + def _disconnected(self): """Called by subclasses to tell clients this interface has disconnected""" - pub.sendMessage("meshtastic.connection.lost") + self.isConnected = False + pub.sendMessage("meshtastic.connection.lost", interface=self) + + def _connected(self): + """Called by this class to tell clients we are now fully connected to a node + """ + self.isConnected = True + pub.sendMessage("meshtastic.connection.established", interface=self) def _startConfig(self): """Start device packets flowing""" @@ -211,12 +244,25 @@ class MeshInterface: self.nodes[node.user.id] = node elif fromRadio.config_complete_id == MY_CONFIG_ID: # we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id - pub.sendMessage("meshtastic.connection.established") + self._connected() elif fromRadio.HasField("packet"): self._handlePacketFromRadio(fromRadio.packet) + elif fromRadio.rebooted: + self._disconnected() + self._startConfig() # redownload the node db etc... else: logging.warn("Unexpected FromRadio payload") + def _nodeNumToId(self, num): + if num == BROADCAST_NUM: + return BROADCAST_ADDR + + try: + return self._nodesByNum[num].user.id + except: + logging.error("Node not found for fromId") + return None + def _handlePacketFromRadio(self, meshPacket): """Handle a MeshPacket that just arrived from the radio @@ -225,16 +271,29 @@ class MeshInterface: - meshtastic.receive.user(packet = MeshPacket dictionary) - meshtastic.receive.data(packet = MeshPacket dictionary) """ - # FIXME, update node DB as needed - json = google.protobuf.json_format.MessageToDict(meshPacket) + + asDict = google.protobuf.json_format.MessageToDict(meshPacket) + # /add fromId and toId fields based on the node ID + asDict["fromId"] = self._nodeNumToId(asDict["from"]) + asDict["toId"] = self._nodeNumToId(asDict["to"]) + + # We could provide our objects as DotMaps - which work with . notation or as dictionaries + #asObj = DotMap(asDict) + topic = None if meshPacket.payload.HasField("position"): - pub.sendMessage("meshtastic.receive.position", packet=json) + topic = "meshtastic.receive.position" + # FIXME, update node DB as needed if meshPacket.payload.HasField("user"): - pub.sendMessage("meshtastic.receive.user", - packet=json) + topic = "meshtastic.receive.user" + # FIXME, update node DB as needed if meshPacket.payload.HasField("data"): - pub.sendMessage("meshtastic.receive.data", - packet=json) + topic = "meshtastic.receive.data" + # For text messages, we go ahead and decode the text to ascii for our users + # if asObj.payload.data.typ == "CLEAR_TEXT": + # asObj.payload.data.text = asObj.payload.data.payload.decode( + # "utf-8") + + pub.sendMessage(topic, packet=asDict, interface=self) class StreamInterface(MeshInterface): @@ -254,17 +313,17 @@ class StreamInterface(MeshInterface): """ if devPath is None: - ports = list(filter(lambda port: port.vid != None, - serial.tools.list_ports.comports())) + ports = util.findPorts() if len(ports) == 0: raise Exception("No Meshtastic devices detected") elif len(ports) > 1: raise Exception( f"Multiple ports detected, you must specify a device, such as {ports[0].device}") else: - devPath = ports[0].device + devPath = ports[0] logging.debug(f"Connecting to {devPath}") + self.devPath = devPath self._rxBuf = bytes() # empty self._wantExit = False self.stream = serial.Serial( @@ -288,6 +347,8 @@ class StreamInterface(MeshInterface): logging.debug("Closing serial stream") # pyserial cancel_read doesn't seem to work, therefore we ask the reader thread to close things for us self._wantExit = True + if self._rxThread != threading.current_thread(): + self._rxThread.join() # wait for it to exit def __reader(self): """The reader thread that reads bytes from our stream""" @@ -327,10 +388,13 @@ class StreamInterface(MeshInterface): try: self._handleFromRadio(self._rxBuf[HEADER_LEN:]) except Exception as ex: - logging.warn( + logging.error( f"Error handling FromRadio, possibly corrupted? {ex}") traceback.print_exc() self._rxBuf = empty + else: + # logging.debug(f"timeout on {self.devPath}") + pass logging.debug("reader is exiting") self.stream.close() self._disconnected() @@ -343,6 +407,14 @@ class StreamInterface(MeshInterface):

    +
    meshtastic.test
    +
    +
    +
    +
    meshtastic.util
    +
    +
    +
    @@ -358,6 +430,10 @@ class StreamInterface(MeshInterface):

    Interface class for meshtastic devices

    +

    Properties:

    +

    isConnected +nodes +debugOut

    Constructor

    @@ -365,12 +441,19 @@ class StreamInterface(MeshInterface):
    class MeshInterface:
         """Interface class for meshtastic devices
    +
    +    Properties:
    +
    +    isConnected
    +    nodes
    +    debugOut
         """
     
         def __init__(self, debugOut=None):
             """Constructor"""
             self.debugOut = debugOut
             self.nodes = None  # FIXME
    +        self.isConnected = False
             self._startConfig()
     
         def sendText(self, text, destinationId=BROADCAST_ADDR):
    @@ -380,7 +463,7 @@ class StreamInterface(MeshInterface):
                 text {string} -- The text to send
     
             Keyword Arguments:
    -            destinationId {nodeId} -- where to send this message (default: {BROADCAST_ADDR})
    +            destinationId {nodeId or nodeNum} -- where to send this message (default: {BROADCAST_ADDR})
             """
             self.sendData(text.encode("utf-8"), destinationId,
                           dataType=mesh_pb2.Data.CLEAR_TEXT)
    @@ -397,13 +480,38 @@ class StreamInterface(MeshInterface):
             You probably don't want this - use sendData instead."""
             toRadio = mesh_pb2.ToRadio()
             # FIXME add support for non broadcast addresses
    -        meshPacket.to = 255
    +
    +        if isinstance(destinationId, int):
    +            nodeNum = destinationId
    +        elif nodeNum == BROADCAST_ADDR:
    +            nodeNum = 255
    +        else:
    +            raise Exception(
    +                "invalid destination addr, we don't yet support nodeid lookup")
    +
    +        meshPacket.to = nodeNum
             toRadio.packet.CopyFrom(meshPacket)
             self._sendToRadio(toRadio)
     
    +    def writeConfig(self):
    +        """Write the current (edited) radioConfig to the device"""
    +        if self.radioConfig == None:
    +            raise Exception("No RadioConfig has been read")
    +
    +        t = mesh_pb2.ToRadio()
    +        t.set_radio.CopyFrom(self.radioConfig)
    +        self._sendToRadio(t)
    +
         def _disconnected(self):
             """Called by subclasses to tell clients this interface has disconnected"""
    -        pub.sendMessage("meshtastic.connection.lost")
    +        self.isConnected = False
    +        pub.sendMessage("meshtastic.connection.lost", interface=self)
    +
    +    def _connected(self):
    +        """Called by this class to tell clients we are now fully connected to a node
    +        """
    +        self.isConnected = True
    +        pub.sendMessage("meshtastic.connection.established", interface=self)
     
         def _startConfig(self):
             """Start device packets flowing"""
    @@ -439,12 +547,25 @@ class StreamInterface(MeshInterface):
                 self.nodes[node.user.id] = node
             elif fromRadio.config_complete_id == MY_CONFIG_ID:
                 # we ignore the config_complete_id, it is unneeded for our stream API fromRadio.config_complete_id
    -            pub.sendMessage("meshtastic.connection.established")
    +            self._connected()
             elif fromRadio.HasField("packet"):
                 self._handlePacketFromRadio(fromRadio.packet)
    +        elif fromRadio.rebooted:
    +            self._disconnected()
    +            self._startConfig()  # redownload the node db etc...
             else:
                 logging.warn("Unexpected FromRadio payload")
     
    +    def _nodeNumToId(self, num):
    +        if num == BROADCAST_NUM:
    +            return BROADCAST_ADDR
    +
    +        try:
    +            return self._nodesByNum[num].user.id
    +        except:
    +            logging.error("Node not found for fromId")
    +            return None
    +
         def _handlePacketFromRadio(self, meshPacket):
             """Handle a MeshPacket that just arrived from the radio
     
    @@ -453,16 +574,29 @@ class StreamInterface(MeshInterface):
             - meshtastic.receive.user(packet = MeshPacket dictionary)
             - meshtastic.receive.data(packet = MeshPacket dictionary)
             """
    -        # FIXME, update node DB as needed
    -        json = google.protobuf.json_format.MessageToDict(meshPacket)
    +
    +        asDict = google.protobuf.json_format.MessageToDict(meshPacket)
    +        # /add fromId and toId fields based on the node ID
    +        asDict["fromId"] = self._nodeNumToId(asDict["from"])
    +        asDict["toId"] = self._nodeNumToId(asDict["to"])
    +
    +        # We could provide our objects as DotMaps - which work with . notation or as dictionaries
    +        #asObj = DotMap(asDict)
    +        topic = None
             if meshPacket.payload.HasField("position"):
    -            pub.sendMessage("meshtastic.receive.position", packet=json)
    +            topic = "meshtastic.receive.position"
    +            # FIXME, update node DB as needed
             if meshPacket.payload.HasField("user"):
    -            pub.sendMessage("meshtastic.receive.user",
    -                            packet=json)
    +            topic = "meshtastic.receive.user"
    +            # FIXME, update node DB as needed
             if meshPacket.payload.HasField("data"):
    -            pub.sendMessage("meshtastic.receive.data",
    -                            packet=json)
    + topic = "meshtastic.receive.data" + # For text messages, we go ahead and decode the text to ascii for our users + # if asObj.payload.data.typ == "CLEAR_TEXT": + # asObj.payload.data.text = asObj.payload.data.payload.decode( + # "utf-8") + + pub.sendMessage(topic, packet=asDict, interface=self)

    Subclasses

      @@ -502,7 +636,16 @@ You probably don't want this - use sendData instead.

      You probably don't want this - use sendData instead.""" toRadio = mesh_pb2.ToRadio() # FIXME add support for non broadcast addresses - meshPacket.to = 255 + + if isinstance(destinationId, int): + nodeNum = destinationId + elif nodeNum == BROADCAST_ADDR: + nodeNum = 255 + else: + raise Exception( + "invalid destination addr, we don't yet support nodeid lookup") + + meshPacket.to = nodeNum toRadio.packet.CopyFrom(meshPacket) self._sendToRadio(toRadio) @@ -515,7 +658,7 @@ You probably don't want this - use sendData instead.

      Arguments

      text {string} – The text to send

      Keyword Arguments: -destinationId {nodeId} – where to send this message (default: {BROADCAST_ADDR})

      +destinationId {nodeId or nodeNum} – where to send this message (default: {BROADCAST_ADDR})

      Expand source code @@ -527,12 +670,31 @@ destinationId {nodeId} – where to send this message (default: {BROADCAST_A text {string} -- The text to send Keyword Arguments: - destinationId {nodeId} -- where to send this message (default: {BROADCAST_ADDR}) + destinationId {nodeId or nodeNum} -- where to send this message (default: {BROADCAST_ADDR}) """ self.sendData(text.encode("utf-8"), destinationId, dataType=mesh_pb2.Data.CLEAR_TEXT)
    +
    +def writeConfig(self) +
    +
    +

    Write the current (edited) radioConfig to the device

    +
    + +Expand source code + +
    def writeConfig(self):
    +    """Write the current (edited) radioConfig to the device"""
    +    if self.radioConfig == None:
    +        raise Exception("No RadioConfig has been read")
    +
    +    t = mesh_pb2.ToRadio()
    +    t.set_radio.CopyFrom(self.radioConfig)
    +    self._sendToRadio(t)
    +
    +
    @@ -574,17 +736,17 @@ debugOut {stream} – If a stream is provided, any debug serial output from """ if devPath is None: - ports = list(filter(lambda port: port.vid != None, - serial.tools.list_ports.comports())) + ports = util.findPorts() if len(ports) == 0: raise Exception("No Meshtastic devices detected") elif len(ports) > 1: raise Exception( f"Multiple ports detected, you must specify a device, such as {ports[0].device}") else: - devPath = ports[0].device + devPath = ports[0] logging.debug(f"Connecting to {devPath}") + self.devPath = devPath self._rxBuf = bytes() # empty self._wantExit = False self.stream = serial.Serial( @@ -608,6 +770,8 @@ debugOut {stream} – If a stream is provided, any debug serial output from logging.debug("Closing serial stream") # pyserial cancel_read doesn't seem to work, therefore we ask the reader thread to close things for us self._wantExit = True + if self._rxThread != threading.current_thread(): + self._rxThread.join() # wait for it to exit def __reader(self): """The reader thread that reads bytes from our stream""" @@ -647,10 +811,13 @@ debugOut {stream} – If a stream is provided, any debug serial output from try: self._handleFromRadio(self._rxBuf[HEADER_LEN:]) except Exception as ex: - logging.warn( + logging.error( f"Error handling FromRadio, possibly corrupted? {ex}") traceback.print_exc() self._rxBuf = empty + else: + # logging.debug(f"timeout on {self.devPath}") + pass logging.debug("reader is exiting") self.stream.close() self._disconnected() @@ -674,7 +841,9 @@ debugOut {stream} – If a stream is provided, any debug serial output from """Close a connection to the device""" logging.debug("Closing serial stream") # pyserial cancel_read doesn't seem to work, therefore we ask the reader thread to close things for us - self._wantExit = True + self._wantExit = True + if self._rxThread != threading.current_thread(): + self._rxThread.join() # wait for it to exit @@ -685,6 +854,7 @@ debugOut {stream} – If a stream is provided, any debug serial output from
  • sendData
  • sendPacket
  • sendText
  • +
  • writeConfig
  • @@ -705,6 +875,8 @@ debugOut {stream} – If a stream is provided, any debug serial output from
  • Sub-modules

  • Classes

    @@ -715,6 +887,7 @@ debugOut {stream} – If a stream is provided, any debug serial output from
  • sendData
  • sendPacket
  • sendText
  • +
  • writeConfig
  • diff --git a/docs/meshtastic/mesh_pb2.html b/docs/meshtastic/mesh_pb2.html index b69077c..f9c96b6 100644 --- a/docs/meshtastic/mesh_pb2.html +++ b/docs/meshtastic/mesh_pb2.html @@ -46,7 +46,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='', syntax='proto3', serialized_options=_b('\n\023com.geeksville.meshB\nMeshProtos'), - serialized_pb=_b('\n\nmesh.proto\"f\n\x08Position\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x15\n\rbattery_level\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x06 \x01(\r\"g\n\x04\x44\x61ta\x12\x17\n\x03typ\x18\x01 \x01(\x0e\x32\n.Data.Type\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"5\n\x04Type\x12\n\n\x06OPAQUE\x10\x00\x12\x0e\n\nCLEAR_TEXT\x10\x01\x12\x11\n\rCLEAR_READACK\x10\x02\"J\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x0f\n\x07macaddr\x18\x04 \x01(\x0c\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x02 \x03(\x05\"i\n\tSubPacket\x12\x1b\n\x08position\x18\x01 \x01(\x0b\x32\t.Position\x12\x13\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x05.Data\x12\x13\n\x04user\x18\x04 \x01(\x0b\x32\x05.User\x12\x15\n\rwant_response\x18\x05 \x01(\x08\"p\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x05\x12\n\n\x02to\x18\x02 \x01(\x05\x12\x1b\n\x07payload\x18\x03 \x01(\x0b\x32\n.SubPacket\x12\x0f\n\x07rx_time\x18\x04 \x01(\r\x12\x0e\n\x06rx_snr\x18\x05 \x01(\x11\x12\n\n\x02id\x18\x06 \x01(\r\"\xd4\x01\n\x0f\x43hannelSettings\x12\x10\n\x08tx_power\x18\x01 \x01(\x05\x12\x32\n\x0cmodem_config\x18\x03 \x01(\x0e\x32\x1c.ChannelSettings.ModemConfig\x12\x0b\n\x03psk\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\"`\n\x0bModemConfig\x12\x12\n\x0e\x42w125Cr45Sf128\x10\x00\x12\x12\n\x0e\x42w500Cr45Sf128\x10\x01\x12\x14\n\x10\x42w31_25Cr48Sf512\x10\x02\x12\x13\n\x0f\x42w125Cr48Sf4096\x10\x03\"\xd7\x03\n\x0bRadioConfig\x12\x31\n\x0bpreferences\x18\x01 \x01(\x0b\x32\x1c.RadioConfig.UserPreferences\x12*\n\x10\x63hannel_settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x1a\xe8\x02\n\x0fUserPreferences\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12\x1b\n\x13send_owner_interval\x18\x02 \x01(\r\x12\x1a\n\x12num_missed_to_fail\x18\x03 \x01(\r\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x16\n\x0escreen_on_secs\x18\x05 \x01(\r\x12\x1a\n\x12phone_timeout_secs\x18\x06 \x01(\r\x12\x1d\n\x15phone_sds_timeout_sec\x18\x07 \x01(\r\x12\x1d\n\x15mesh_sds_timeout_secs\x18\x08 \x01(\r\x12\x10\n\x08sds_secs\x18\t \x01(\r\x12\x0f\n\x07ls_secs\x18\n \x01(\r\x12\x15\n\rmin_wake_secs\x18\x0b \x01(\r\x12\x18\n\x10keep_all_packets\x18\x64 \x01(\x08\x12\x18\n\x10promiscuous_mode\x18\x65 \x01(\x08\"o\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\x05\x12\x13\n\x04user\x18\x02 \x01(\x0b\x32\x05.User\x12\x1b\n\x08position\x18\x03 \x01(\x0b\x32\t.Position\x12\x0b\n\x03snr\x18\x05 \x01(\x05\x12\x17\n\x0f\x66requency_error\x18\x06 \x01(\x05\"\xc4\x01\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\x05\x12\x0f\n\x07has_gps\x18\x02 \x01(\x08\x12\x14\n\x0cnum_channels\x18\x03 \x01(\x05\x12\x0e\n\x06region\x18\x04 \x01(\t\x12\x10\n\x08hw_model\x18\x05 \x01(\t\x12\x18\n\x10\x66irmware_version\x18\x06 \x01(\t\x12\x12\n\nerror_code\x18\x07 \x01(\r\x12\x15\n\rerror_address\x18\x08 \x01(\r\x12\x13\n\x0b\x65rror_count\x18\t \x01(\r\"\xd5\x01\n\x0b\x44\x65viceState\x12\x1b\n\x05radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\"\x1e\n\x0b\x44\x65\x62ugString\x12\x0f\n\x07message\x18\x01 \x01(\t\"\xe5\x01\n\tFromRadio\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x1d\n\x06packet\x18\x02 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x1e\n\x07my_info\x18\x03 \x01(\x0b\x32\x0b.MyNodeInfoH\x00\x12\x1e\n\tnode_info\x18\x04 \x01(\x0b\x32\t.NodeInfoH\x00\x12\x1d\n\x05radio\x18\x06 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12$\n\x0c\x64\x65\x62ug_string\x18\x07 \x01(\x0b\x32\x0c.DebugStringH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x08 \x01(\rH\x00\x42\t\n\x07variant\"\x8c\x01\n\x07ToRadio\x12\x1d\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x18\n\x0ewant_config_id\x18\x64 \x01(\rH\x00\x12!\n\tset_radio\x18\x65 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x66 \x01(\x0b\x32\x05.UserH\x00\x42\t\n\x07variant*\x17\n\tConstants\x12\n\n\x06Unused\x10\x00\x42!\n\x13\x63om.geeksville.meshB\nMeshProtosb\x06proto3') + serialized_pb=_b('\n\nmesh.proto\"f\n\x08Position\x12\x10\n\x08latitude\x18\x01 \x01(\x01\x12\x11\n\tlongitude\x18\x02 \x01(\x01\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\x15\n\rbattery_level\x18\x04 \x01(\x05\x12\x0c\n\x04time\x18\x06 \x01(\r\"g\n\x04\x44\x61ta\x12\x17\n\x03typ\x18\x01 \x01(\x0e\x32\n.Data.Type\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"5\n\x04Type\x12\n\n\x06OPAQUE\x10\x00\x12\x0e\n\nCLEAR_TEXT\x10\x01\x12\x11\n\rCLEAR_READACK\x10\x02\"J\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x0f\n\x07macaddr\x18\x04 \x01(\x0c\"\x1f\n\x0eRouteDiscovery\x12\r\n\x05route\x18\x02 \x03(\x05\"i\n\tSubPacket\x12\x1b\n\x08position\x18\x01 \x01(\x0b\x32\t.Position\x12\x13\n\x04\x64\x61ta\x18\x03 \x01(\x0b\x32\x05.Data\x12\x13\n\x04user\x18\x04 \x01(\x0b\x32\x05.User\x12\x15\n\rwant_response\x18\x05 \x01(\x08\"p\n\nMeshPacket\x12\x0c\n\x04\x66rom\x18\x01 \x01(\x05\x12\n\n\x02to\x18\x02 \x01(\x05\x12\x1b\n\x07payload\x18\x03 \x01(\x0b\x32\n.SubPacket\x12\x0f\n\x07rx_time\x18\x04 \x01(\r\x12\n\n\x02id\x18\x06 \x01(\r\x12\x0e\n\x06rx_snr\x18\x07 \x01(\x02\"\xd4\x01\n\x0f\x43hannelSettings\x12\x10\n\x08tx_power\x18\x01 \x01(\x05\x12\x32\n\x0cmodem_config\x18\x03 \x01(\x0e\x32\x1c.ChannelSettings.ModemConfig\x12\x0b\n\x03psk\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\"`\n\x0bModemConfig\x12\x12\n\x0e\x42w125Cr45Sf128\x10\x00\x12\x12\n\x0e\x42w500Cr45Sf128\x10\x01\x12\x14\n\x10\x42w31_25Cr48Sf512\x10\x02\x12\x13\n\x0f\x42w125Cr48Sf4096\x10\x03\"\xd7\x03\n\x0bRadioConfig\x12\x31\n\x0bpreferences\x18\x01 \x01(\x0b\x32\x1c.RadioConfig.UserPreferences\x12*\n\x10\x63hannel_settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x1a\xe8\x02\n\x0fUserPreferences\x12\x1f\n\x17position_broadcast_secs\x18\x01 \x01(\r\x12\x1b\n\x13send_owner_interval\x18\x02 \x01(\r\x12\x1a\n\x12num_missed_to_fail\x18\x03 \x01(\r\x12\x1b\n\x13wait_bluetooth_secs\x18\x04 \x01(\r\x12\x16\n\x0escreen_on_secs\x18\x05 \x01(\r\x12\x1a\n\x12phone_timeout_secs\x18\x06 \x01(\r\x12\x1d\n\x15phone_sds_timeout_sec\x18\x07 \x01(\r\x12\x1d\n\x15mesh_sds_timeout_secs\x18\x08 \x01(\r\x12\x10\n\x08sds_secs\x18\t \x01(\r\x12\x0f\n\x07ls_secs\x18\n \x01(\r\x12\x15\n\rmin_wake_secs\x18\x0b \x01(\r\x12\x18\n\x10keep_all_packets\x18\x64 \x01(\x08\x12\x18\n\x10promiscuous_mode\x18\x65 \x01(\x08\"V\n\x08NodeInfo\x12\x0b\n\x03num\x18\x01 \x01(\x05\x12\x13\n\x04user\x18\x02 \x01(\x0b\x32\x05.User\x12\x1b\n\x08position\x18\x03 \x01(\x0b\x32\t.Position\x12\x0b\n\x03snr\x18\x07 \x01(\x02\"\xc4\x01\n\nMyNodeInfo\x12\x13\n\x0bmy_node_num\x18\x01 \x01(\x05\x12\x0f\n\x07has_gps\x18\x02 \x01(\x08\x12\x14\n\x0cnum_channels\x18\x03 \x01(\x05\x12\x0e\n\x06region\x18\x04 \x01(\t\x12\x10\n\x08hw_model\x18\x05 \x01(\t\x12\x18\n\x10\x66irmware_version\x18\x06 \x01(\t\x12\x12\n\nerror_code\x18\x07 \x01(\r\x12\x15\n\rerror_address\x18\x08 \x01(\r\x12\x13\n\x0b\x65rror_count\x18\t \x01(\r\"\xd5\x01\n\x0b\x44\x65viceState\x12\x1b\n\x05radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\"\x1e\n\x0b\x44\x65\x62ugString\x12\x0f\n\x07message\x18\x01 \x01(\t\"\xf9\x01\n\tFromRadio\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x1d\n\x06packet\x18\x02 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x1e\n\x07my_info\x18\x03 \x01(\x0b\x32\x0b.MyNodeInfoH\x00\x12\x1e\n\tnode_info\x18\x04 \x01(\x0b\x32\t.NodeInfoH\x00\x12\x1d\n\x05radio\x18\x06 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12$\n\x0c\x64\x65\x62ug_string\x18\x07 \x01(\x0b\x32\x0c.DebugStringH\x00\x12\x1c\n\x12\x63onfig_complete_id\x18\x08 \x01(\rH\x00\x12\x12\n\x08rebooted\x18\t \x01(\x08H\x00\x42\t\n\x07variant\"\x8c\x01\n\x07ToRadio\x12\x1d\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacketH\x00\x12\x18\n\x0ewant_config_id\x18\x64 \x01(\rH\x00\x12!\n\tset_radio\x18\x65 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x66 \x01(\x0b\x32\x05.UserH\x00\x42\t\n\x07variant*\x17\n\tConstants\x12\n\n\x06Unused\x10\x00\x42!\n\x13\x63om.geeksville.meshB\nMeshProtosb\x06proto3') ) _CONSTANTS = _descriptor.EnumDescriptor( @@ -62,8 +62,8 @@ _CONSTANTS = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=2177, - serialized_end=2200, + serialized_start=2172, + serialized_end=2195, ) _sym_db.RegisterEnumDescriptor(_CONSTANTS) @@ -397,16 +397,16 @@ _MESHPACKET = _descriptor.Descriptor( is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='rx_snr', full_name='MeshPacket.rx_snr', index=4, - number=5, type=17, cpp_type=1, label=1, + name='id', full_name='MeshPacket.id', index=4, + number=6, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( - name='id', full_name='MeshPacket.id', index=5, - number=6, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, + name='rx_snr', full_name='MeshPacket.rx_snr', index=5, + number=7, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -662,15 +662,8 @@ _NODEINFO = _descriptor.Descriptor( serialized_options=None, file=DESCRIPTOR), _descriptor.FieldDescriptor( name='snr', full_name='NodeInfo.snr', index=3, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='frequency_error', full_name='NodeInfo.frequency_error', index=4, - number=6, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, + number=7, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), @@ -687,7 +680,7 @@ _NODEINFO = _descriptor.Descriptor( oneofs=[ ], serialized_start=1242, - serialized_end=1353, + serialized_end=1328, ) @@ -773,8 +766,8 @@ _MYNODEINFO = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1356, - serialized_end=1552, + serialized_start=1331, + serialized_end=1527, ) @@ -846,8 +839,8 @@ _DEVICESTATE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1555, - serialized_end=1768, + serialized_start=1530, + serialized_end=1743, ) @@ -877,8 +870,8 @@ _DEBUGSTRING = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1770, - serialized_end=1800, + serialized_start=1745, + serialized_end=1775, ) @@ -938,6 +931,13 @@ _FROMRADIO = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='rebooted', full_name='FromRadio.rebooted', index=7, + number=9, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -953,8 +953,8 @@ _FROMRADIO = _descriptor.Descriptor( name='variant', full_name='FromRadio.variant', index=0, containing_type=None, fields=[]), ], - serialized_start=1803, - serialized_end=2032, + serialized_start=1778, + serialized_end=2027, ) @@ -1008,8 +1008,8 @@ _TORADIO = _descriptor.Descriptor( name='variant', full_name='ToRadio.variant', index=0, containing_type=None, fields=[]), ], - serialized_start=2035, - serialized_end=2175, + serialized_start=2030, + serialized_end=2170, ) _DATA.fields_by_name['typ'].enum_type = _DATA_TYPE @@ -1054,6 +1054,9 @@ _FROMRADIO.fields_by_name['debug_string'].containing_oneof = _FROMRADIO. _FROMRADIO.oneofs_by_name['variant'].fields.append( _FROMRADIO.fields_by_name['config_complete_id']) _FROMRADIO.fields_by_name['config_complete_id'].containing_oneof = _FROMRADIO.oneofs_by_name['variant'] +_FROMRADIO.oneofs_by_name['variant'].fields.append( + _FROMRADIO.fields_by_name['rebooted']) +_FROMRADIO.fields_by_name['rebooted'].containing_oneof = _FROMRADIO.oneofs_by_name['variant'] _TORADIO.fields_by_name['packet'].message_type = _MESHPACKET _TORADIO.fields_by_name['set_radio'].message_type = _RADIOCONFIG _TORADIO.fields_by_name['set_owner'].message_type = _USER @@ -1419,6 +1422,10 @@ DESCRIPTOR._options = None
    +
    var REBOOTED_FIELD_NUMBER
    +
    +
    +
    @@ -1536,10 +1543,6 @@ DESCRIPTOR._options = None
    -
    var FREQUENCY_ERROR_FIELD_NUMBER
    -
    -
    -
    var NUM_FIELD_NUMBER
    @@ -1830,6 +1833,7 @@ DESCRIPTOR._options = None
  • NUM_FIELD_NUMBER
  • PACKET_FIELD_NUMBER
  • RADIO_FIELD_NUMBER
  • +
  • REBOOTED_FIELD_NUMBER
  • @@ -1863,7 +1867,6 @@ DESCRIPTOR._options = None

    NodeInfo

    • DESCRIPTOR
    • -
    • FREQUENCY_ERROR_FIELD_NUMBER
    • NUM_FIELD_NUMBER
    • POSITION_FIELD_NUMBER
    • SNR_FIELD_NUMBER
    • diff --git a/docs/meshtastic/test.html b/docs/meshtastic/test.html new file mode 100644 index 0000000..1f28d9e --- /dev/null +++ b/docs/meshtastic/test.html @@ -0,0 +1,400 @@ + + + + + + +meshtastic.test API documentation + + + + + + + + + +
      +
      +
      +

      Module meshtastic.test

      +
      +
      +
      + +Expand source code + +
      import logging
      +from . import util
      +from . import StreamInterface
      +from pubsub import pub
      +import time
      +import sys
      +import threading
      +from dotmap import DotMap
      +
      +"""The interfaces we are using for our tests"""
      +interfaces = None
      +
      +"""A list of all packets we received while the current test was running"""
      +receivedPackets = None
      +
      +testsRunning = False
      +
      +testNumber = 0
      +
      +
      +def onReceive(packet, interface):
      +    """Callback invoked when a packet arrives"""
      +    print(f"From {interface.devPath}: {packet}")
      +    p = DotMap(packet)
      +
      +    if p.payload.data.typ == "CLEAR_TEXT":
      +        # We only care a about clear text packets
      +        receivedPackets.append(p)
      +
      +
      +def onNode(node):
      +    """Callback invoked when the node DB changes"""
      +    print(f"Node changed: {node}")
      +
      +
      +def subscribe():
      +    """Subscribe to the topics the user probably wants to see, prints output to stdout"""
      +
      +    pub.subscribe(onNode, "meshtastic.node")
      +
      +
      +def testSend(fromInterface, toInterface):
      +    """
      +    Sends one test packet between two nodes and then returns success or failure
      +
      +    Arguments:
      +        fromInterface {[type]} -- [description]
      +        toInterface {[type]} -- [description]
      +
      +    Returns:
      +        boolean -- True for success
      +    """
      +    global receivedPackets
      +    receivedPackets = []
      +    fromNode = fromInterface.myInfo.my_node_num
      +    toNode = toInterface.myInfo.my_node_num
      +
      +    # FIXME, hack to test broadcast
      +    toNode = 255
      +
      +    logging.info(f"Sending test packet from {fromNode} to {toNode}")
      +    fromInterface.sendText(f"Test {testNumber}", toNode)
      +    time.sleep(40)
      +    return (len(receivedPackets) >= 1)
      +
      +
      +def testThread():
      +    logging.info("Found devices, starting tests...")
      +    numFail = 0
      +    numSuccess = 0
      +    while True:
      +        global testNumber
      +        testNumber = testNumber + 1
      +        success = testSend(interfaces[0], interfaces[1])
      +        if not success:
      +            numFail = numFail + 1
      +            logging.error(
      +                f"Test failed, expected packet not received ({numFail} failures so far)")
      +        else:
      +            numSuccess = numSuccess + 1
      +            logging.info(f"Test succeeded ({numSuccess} successes so far)")
      +
      +        if numFail >= 3:
      +            for i in interfaces:
      +                i.close()
      +            return
      +
      +        time.sleep(1)
      +
      +
      +def onConnection(topic=pub.AUTO_TOPIC):
      +    """Callback invoked when we connect/disconnect from a radio"""
      +    print(f"Connection changed: {topic.getName()}")
      +
      +    global testsRunning
      +    if (all(iface.isConnected for iface in interfaces) and not testsRunning):
      +        testsRunning = True
      +        t = threading.Thread(target=testThread, args=())
      +        t.start()
      +
      +
      +def openDebugLog(portName):
      +    debugname = "log" + portName.replace("/", "_")
      +    logging.info(f"Writing serial debugging to {debugname}")
      +    return open(debugname, 'w+', buffering=1)
      +
      +
      +def testAll():
      +    """
      +    Run a series of tests using devices we can find.
      +
      +    Raises:
      +        Exception: If not enough devices are found
      +    """
      +    ports = util.findPorts()
      +    if (len(ports) < 2):
      +        raise Exception("Must have at least two devices connected to USB")
      +
      +    pub.subscribe(onConnection, "meshtastic.connection")
      +    pub.subscribe(onReceive, "meshtastic.receive")
      +    global interfaces
      +    interfaces = list(map(lambda port: StreamInterface(
      +        port, debugOut=openDebugLog(port)), ports))
      +    logging.info("Ports opened, waiting for device to complete connection")
      +
      +
      +
      +
      +
      +

      Global variables

      +
      +
      var interfaces
      +
      +

      A list of all packets we received while the current test was running

      +
      +
      +
      +
      +

      Functions

      +
      +
      +def onConnection(topic=pubsub.core.callables.AUTO_TOPIC) +
      +
      +

      Callback invoked when we connect/disconnect from a radio

      +
      + +Expand source code + +
      def onConnection(topic=pub.AUTO_TOPIC):
      +    """Callback invoked when we connect/disconnect from a radio"""
      +    print(f"Connection changed: {topic.getName()}")
      +
      +    global testsRunning
      +    if (all(iface.isConnected for iface in interfaces) and not testsRunning):
      +        testsRunning = True
      +        t = threading.Thread(target=testThread, args=())
      +        t.start()
      +
      +
      +
      +def onNode(node) +
      +
      +

      Callback invoked when the node DB changes

      +
      + +Expand source code + +
      def onNode(node):
      +    """Callback invoked when the node DB changes"""
      +    print(f"Node changed: {node}")
      +
      +
      +
      +def onReceive(packet, interface) +
      +
      +

      Callback invoked when a packet arrives

      +
      + +Expand source code + +
      def onReceive(packet, interface):
      +    """Callback invoked when a packet arrives"""
      +    print(f"From {interface.devPath}: {packet}")
      +    p = DotMap(packet)
      +
      +    if p.payload.data.typ == "CLEAR_TEXT":
      +        # We only care a about clear text packets
      +        receivedPackets.append(p)
      +
      +
      +
      +def openDebugLog(portName) +
      +
      +
      +
      + +Expand source code + +
      def openDebugLog(portName):
      +    debugname = "log" + portName.replace("/", "_")
      +    logging.info(f"Writing serial debugging to {debugname}")
      +    return open(debugname, 'w+', buffering=1)
      +
      +
      +
      +def subscribe() +
      +
      +

      Subscribe to the topics the user probably wants to see, prints output to stdout

      +
      + +Expand source code + +
      def subscribe():
      +    """Subscribe to the topics the user probably wants to see, prints output to stdout"""
      +
      +    pub.subscribe(onNode, "meshtastic.node")
      +
      +
      +
      +def testAll() +
      +
      +

      Run a series of tests using devices we can find.

      +

      Raises

      +
      +
      Exception
      +
      If not enough devices are found
      +
      +
      + +Expand source code + +
      def testAll():
      +    """
      +    Run a series of tests using devices we can find.
      +
      +    Raises:
      +        Exception: If not enough devices are found
      +    """
      +    ports = util.findPorts()
      +    if (len(ports) < 2):
      +        raise Exception("Must have at least two devices connected to USB")
      +
      +    pub.subscribe(onConnection, "meshtastic.connection")
      +    pub.subscribe(onReceive, "meshtastic.receive")
      +    global interfaces
      +    interfaces = list(map(lambda port: StreamInterface(
      +        port, debugOut=openDebugLog(port)), ports))
      +    logging.info("Ports opened, waiting for device to complete connection")
      +
      +
      +
      +def testSend(fromInterface, toInterface) +
      +
      +

      Sends one test packet between two nodes and then returns success or failure

      +

      Arguments

      +

      fromInterface {[type]} – [description] +toInterface {[type]} – [description]

      +

      Returns

      +
      +
      boolean -- True for success
      +
       
      +
      +
      + +Expand source code + +
      def testSend(fromInterface, toInterface):
      +    """
      +    Sends one test packet between two nodes and then returns success or failure
      +
      +    Arguments:
      +        fromInterface {[type]} -- [description]
      +        toInterface {[type]} -- [description]
      +
      +    Returns:
      +        boolean -- True for success
      +    """
      +    global receivedPackets
      +    receivedPackets = []
      +    fromNode = fromInterface.myInfo.my_node_num
      +    toNode = toInterface.myInfo.my_node_num
      +
      +    # FIXME, hack to test broadcast
      +    toNode = 255
      +
      +    logging.info(f"Sending test packet from {fromNode} to {toNode}")
      +    fromInterface.sendText(f"Test {testNumber}", toNode)
      +    time.sleep(40)
      +    return (len(receivedPackets) >= 1)
      +
      +
      +
      +def testThread() +
      +
      +
      +
      + +Expand source code + +
      def testThread():
      +    logging.info("Found devices, starting tests...")
      +    numFail = 0
      +    numSuccess = 0
      +    while True:
      +        global testNumber
      +        testNumber = testNumber + 1
      +        success = testSend(interfaces[0], interfaces[1])
      +        if not success:
      +            numFail = numFail + 1
      +            logging.error(
      +                f"Test failed, expected packet not received ({numFail} failures so far)")
      +        else:
      +            numSuccess = numSuccess + 1
      +            logging.info(f"Test succeeded ({numSuccess} successes so far)")
      +
      +        if numFail >= 3:
      +            for i in interfaces:
      +                i.close()
      +            return
      +
      +        time.sleep(1)
      +
      +
      +
      +
      +
      +
      +
      + +
      + + + + + \ No newline at end of file diff --git a/docs/meshtastic/util.html b/docs/meshtastic/util.html new file mode 100644 index 0000000..78fcc66 --- /dev/null +++ b/docs/meshtastic/util.html @@ -0,0 +1,147 @@ + + + + + + +meshtastic.util API documentation + + + + + + + + + +
      +
      +
      +

      Module meshtastic.util

      +
      +
      +
      + +Expand source code + +
      from collections import defaultdict
      +import serial
      +import serial.tools.list_ports
      +
      +
      +def findPorts():
      +    """Find all ports that might have meshtastic devices
      +
      +    Returns:
      +        list -- a list of device paths
      +    """
      +    l = list(map(lambda port: port.device,
      +                 filter(lambda port: port.vid != None,
      +                        serial.tools.list_ports.comports())))
      +    l.sort()
      +    return l
      +
      +
      +class dotdict(dict):
      +    """dot.notation access to dictionary attributes"""
      +    __getattr__ = dict.get
      +    __setattr__ = dict.__setitem__
      +    __delattr__ = dict.__delitem__
      +
      +
      +
      +
      +
      +
      +
      +

      Functions

      +
      +
      +def findPorts() +
      +
      +

      Find all ports that might have meshtastic devices

      +

      Returns

      +
      +
      list -- a list of device paths
      +
       
      +
      +
      + +Expand source code + +
      def findPorts():
      +    """Find all ports that might have meshtastic devices
      +
      +    Returns:
      +        list -- a list of device paths
      +    """
      +    l = list(map(lambda port: port.device,
      +                 filter(lambda port: port.vid != None,
      +                        serial.tools.list_ports.comports())))
      +    l.sort()
      +    return l
      +
      +
      +
      +
      +
      +

      Classes

      +
      +
      +class dotdict +(...) +
      +
      +

      dot.notation access to dictionary attributes

      +
      + +Expand source code + +
      class dotdict(dict):
      +    """dot.notation access to dictionary attributes"""
      +    __getattr__ = dict.get
      +    __setattr__ = dict.__setitem__
      +    __delattr__ = dict.__delitem__
      +
      +

      Ancestors

      +
        +
      • builtins.dict
      • +
      +
      +
      +
      +
      + +
      + + + + + \ No newline at end of file