Beispiel #1
0
    def setURL(self, url):
        """Set mesh network URL"""
        if self.radioConfig is None:
            our_exit("Warning: No RadioConfig has been read")

        # URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set}
        # Split on '/#' to find the base64 encoded channel settings
        splitURL = url.split("/#")
        b64 = splitURL[-1]

        # We normally strip padding to make for a shorter URL, but the python parser doesn't like
        # that.  So add back any missing padding
        # per https://stackoverflow.com/a/9807138
        missing_padding = len(b64) % 4
        if missing_padding:
            b64 += '=' * (4 - missing_padding)

        decodedURL = base64.urlsafe_b64decode(b64)
        channelSet = apponly_pb2.ChannelSet()
        channelSet.ParseFromString(decodedURL)

        if len(channelSet.settings) == 0:
            our_exit("Warning: There were no settings.")

        i = 0
        for chs in channelSet.settings:
            ch = channel_pb2.Channel()
            ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY
            ch.index = i
            ch.settings.CopyFrom(chs)
            self.channels[ch.index] = ch
            logging.debug(f'Channel i:{i} ch:{ch}')
            self.writeChannel(ch.index)
            i = i + 1
Beispiel #2
0
    def set_canned_message(self, message):
        """Set the canned message. Split into parts of 200 chars each."""

        if len(message) > 800:
            our_exit(
                "Warning: The canned message must be less than 800 characters."
            )

        # split into chunks
        chunks = []
        chunks_size = 200
        for i in range(0, len(message), chunks_size):
            chunks.append(message[i:i + chunks_size])

        # for each chunk, send a message to set the values
        #for i in range(0, len(chunks)):
        for i, chunk in enumerate(chunks):
            p = admin_pb2.AdminMessage()

            # TODO: should be a way to improve this
            if i == 0:
                p.set_canned_message_plugin_part1 = chunk
            elif i == 1:
                p.set_canned_message_plugin_part2 = chunk
            elif i == 2:
                p.set_canned_message_plugin_part3 = chunk
            elif i == 3:
                p.set_canned_message_plugin_part4 = chunk

            logging.debug(f"Setting canned message '{chunk}' part {i+1}")
            self._sendAdmin(p)
Beispiel #3
0
    def deleteChannel(self, channelIndex):
        """Delete the specifed channelIndex and shift other channels up"""
        ch = self.channels[channelIndex]
        if ch.role not in (channel_pb2.Channel.Role.SECONDARY,
                           channel_pb2.Channel.Role.DISABLED):
            our_exit("Warning: Only SECONDARY channels can be deleted")

        # we are careful here because if we move the "admin" channel the channelIndex we need to use
        # for sending admin channels will also change
        adminIndex = self.iface.localNode._getAdminChannelIndex()

        self.channels.pop(channelIndex)
        self._fixupChannels()  # expand back to 8 channels

        index = channelIndex
        while index < self.iface.myInfo.max_channels:
            self.writeChannel(index, adminIndex=adminIndex)
            index += 1

            # if we are updating the local node, we might end up
            # *moving* the admin channel index as we are writing
            if (self.iface.localNode == self) and index >= adminIndex:
                # We've now passed the old location for admin index
                # (and written it), so we can start finding it by name again
                adminIndex = 0
Beispiel #4
0
def test_our_exit_non_zero_return_value(capsys):
    """Test our_exit with a non-zero return value"""
    with pytest.raises(SystemExit) as pytest_wrapped_e:
        our_exit("Error: Some message", 1)
    out, err = capsys.readouterr()
    assert re.search(r'Error: Some message', out, re.MULTILINE)
    assert err == ''
    assert pytest_wrapped_e.type == SystemExit
    assert pytest_wrapped_e.value.code == 1
Beispiel #5
0
    def writeConfig(self):
        """Write the current (edited) radioConfig to the device"""
        if self.radioConfig is None:
            our_exit("Error: No RadioConfig has been read")

        p = admin_pb2.AdminMessage()
        p.set_radio.CopyFrom(self.radioConfig)

        self._sendAdmin(p)
        logging.debug("Wrote config")
 def getNode(self, nodeId):
     """Return a node object which contains device settings and channel info"""
     if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
         return self.localNode
     else:
         n = meshtastic.node.Node(self, nodeId)
         logging.debug("About to requestConfig")
         n.requestConfig()
         if not n.waitForConfig():
             our_exit("Error: Timed out waiting for node config")
         return n
Beispiel #7
0
 def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
     if not nodeid:
         our_exit(
             r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)"
         )
     return self.iface.sendData(r,
                                nodeid,
                                portnums_pb2.REMOTE_HARDWARE_APP,
                                wantAck=True,
                                channelIndex=self.channelIndex,
                                wantResponse=wantResponse,
                                onResponse=onResponse)
Beispiel #8
0
    def __init__(self, iface):
        """
        Constructor

        iface is the already open MeshInterface instance
        """
        self.iface = iface
        ch = iface.localNode.getChannelByName("gpio")
        if not ch:
            our_exit(
                "Warning: No channel named 'gpio' was found.\n"\
                "On the sending and receive nodes create a channel named 'gpio'.\n"\
                "For example, run '--ch-add gpio' on one device, then '--seturl' on\n"\
                "the other devices using the url from the device where the channel was added.")
        self.channelIndex = ch.index

        pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw")
    def __init__(self, address, noProto=False, debugOut=None):
        if platform.system() != 'Linux':
            our_exit(
                "Linux is the only platform with experimental BLE support.", 1)
        self.address = address
        if not noProto:
            self.adapter = pygatt.GATTToolBackend()  # BGAPIBackend()
            self.adapter.start()
            logging.debug(f"Connecting to {self.address}")
            self.device = self.adapter.connect(address)
        else:
            self.adapter = None
            self.device = None
        logging.debug("Connected to device")
        # fromradio = self.device.char_read(FROMRADIO_UUID)
        MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)

        self._readFromRadio()  # read the initial responses

        def handle_data(handle, data):  # pylint: disable=W0613
            self._handleFromRadio(data)

        if self.device:
            self.device.subscribe(FROMNUM_UUID, callback=handle_data)
    def _sendPacket(self,
                    meshPacket,
                    destinationId=BROADCAST_ADDR,
                    wantAck=False,
                    hopLimit=None):
        """Send a MeshPacket to the specified node (or if unspecified, broadcast).
        You probably don't want this - use sendData instead.

        Returns the sent packet. The id field will be populated in this packet and
        can be used to track future message acks/naks.
        """
        if hopLimit is None:
            hopLimit = self.defaultHopLimit

        # We allow users to talk to the local node before we've completed the full connection flow...
        if (self.myInfo is not None
                and destinationId != self.myInfo.my_node_num):
            self._waitConnected()

        toRadio = mesh_pb2.ToRadio()

        nodeNum = 0
        if destinationId is None:
            our_exit("Warning: destinationId must not be None")
        elif isinstance(destinationId, int):
            nodeNum = destinationId
        elif destinationId == BROADCAST_ADDR:
            nodeNum = BROADCAST_NUM
        elif destinationId == LOCAL_ADDR:
            if self.myInfo:
                nodeNum = self.myInfo.my_node_num
            else:
                our_exit("Warning: No myInfo found.")
        # A simple hex style nodeid - we can parse this without needing the DB
        elif destinationId.startswith("!"):
            nodeNum = int(destinationId[1:], 16)
        else:
            if self.nodes:
                node = self.nodes.get(destinationId)
                if not node:
                    our_exit(
                        f"Warning: NodeId {destinationId} not found in DB")
                nodeNum = node['num']
            else:
                logging.warning("Warning: There were no self.nodes.")

        meshPacket.to = nodeNum
        meshPacket.want_ack = wantAck
        meshPacket.hop_limit = hopLimit

        # if the user hasn't set an ID for this packet (likely and recommended),
        # we should pick a new unique ID so the message can be tracked.
        if meshPacket.id == 0:
            meshPacket.id = self._generatePacketId()

        toRadio.packet.CopyFrom(meshPacket)
        if self.noProto:
            logging.warning(
                f"Not sending packet because protocol use is disabled by noProto"
            )
        else:
            logging.debug(f"Sending packet: {stripnl(meshPacket)}")
            self._sendToRadio(toRadio)
        return meshPacket
    def sendData(self,
                 data,
                 destinationId=BROADCAST_ADDR,
                 portNum=portnums_pb2.PortNum.PRIVATE_APP,
                 wantAck=False,
                 wantResponse=False,
                 hopLimit=None,
                 onResponse=None,
                 channelIndex=0):
        """Send a data packet to some other node

        Keyword Arguments:
            data -- the data to send, either as an array of bytes or
                    as a protobuf (which will be automatically
                    serialized to bytes)
            destinationId {nodeId or nodeNum} -- where to send this
                    message (default: {BROADCAST_ADDR})
            portNum -- the application portnum (similar to IP port numbers)
                    of the destination, see portnums.proto for a list
            wantAck -- True if you want the message sent in a reliable
                    manner (with retries and ack/nak provided for delivery)
            wantResponse -- True if you want the service on the other
                    side to send an application layer response
            onResponse -- A closure of the form funct(packet), that will be
                    called when a response packet arrives (or the transaction
                    is NAKed due to non receipt)
            channelIndex - channel number to use

        Returns the sent packet. The id field will be populated in this packet
        and can be used to track future message acks/naks.
        """
        if hopLimit is None:
            hopLimit = self.defaultHopLimit

        if getattr(data, "SerializeToString", None):
            logging.debug(f"Serializing protobuf as data: {stripnl(data)}")
            data = data.SerializeToString()

        logging.debug(f"len(data): {len(data)}")
        logging.debug(
            f"mesh_pb2.Constants.DATA_PAYLOAD_LEN: {mesh_pb2.Constants.DATA_PAYLOAD_LEN}"
        )
        if len(data) > mesh_pb2.Constants.DATA_PAYLOAD_LEN:
            raise Exception("Data payload too big")

        if portNum == portnums_pb2.PortNum.UNKNOWN_APP:  # we are now more strict wrt port numbers
            our_exit("Warning: A non-zero port number must be specified")

        meshPacket = mesh_pb2.MeshPacket()
        meshPacket.channel = channelIndex
        meshPacket.decoded.payload = data
        meshPacket.decoded.portnum = portNum
        meshPacket.decoded.want_response = wantResponse
        meshPacket.id = self._generatePacketId()

        if onResponse is not None:
            self._addResponseHandler(meshPacket.id, onResponse)
        p = self._sendPacket(meshPacket,
                             destinationId,
                             wantAck=wantAck,
                             hopLimit=hopLimit)
        return p
def test_our_exit_non_zero_return_value():
    """Test our_exit with a non-zero return value"""
    with pytest.raises(SystemExit) as pytest_wrapped_e:
        our_exit("Error: Some message", 1)
    assert pytest_wrapped_e.type == SystemExit
    assert pytest_wrapped_e.value.code == 1
def test_our_exit_zero_return_value():
    """Test our_exit with a zero return value"""
    with pytest.raises(SystemExit) as pytest_wrapped_e:
        our_exit("Warning: Some message", 0)
    assert pytest_wrapped_e.type == SystemExit
    assert pytest_wrapped_e.value.code == 0