From 59517b4b49292917dd81d1317c9a8b49a87b9404 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 26 Feb 2021 18:44:02 +0800 Subject: [PATCH] use protocol decoders for all message types (even custom ones) --- meshtastic/__init__.py | 80 ++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index 59e2e93..2908413 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -101,12 +101,46 @@ class KnownProtocol(NamedTuple): """Used to automatically decode known protocol payloads""" name: str # portnum: int, now a key - protobufFactory: Callable + protobufFactory: Callable = None # If set, will be called to prase as a protocol buffer + onReceive: Callable = None # If set, invoked as onReceive(interface, packet) + +def _onTextReceive(iface, asDict): + """Special text auto parsing for received messages""" + # We don't throw if the utf8 is invalid in the text message. Instead we just don't populate + # the decoded.data.text and we log an error message. This at least allows some delivery to + # the app and the app can deal with the missing decoded representation. + # + # Usually btw this problem is caused by apps sending binary data but setting the payload type to + # text. + try: + asDict["decoded"]["text"] = meshPacket.decoded.payload.decode( + "utf-8") + except Exception as ex: + logging.error(f"Malformatted utf8 in text message: {ex}") + +def _onPositionReceive(iface, asDict): + """Special auto parsing for received messages""" + p = asDict["decoded"]["position"] + iface._fixupPosition(p) + # update node DB as needed + iface._getOrCreateByNum(asDict["from"])["position"] = p + +def _onNodeInfoReceive(iface, asDict): + """Special auto parsing for received messages""" + p = asDict["decoded"]["user"] + # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg + # update node DB as needed + n = iface._getOrCreateByNum(asDict["from"]) + n["user"] = p + # We now have a node ID, make sure it is uptodate in that table + iface.nodes[p["id"]] = n + """Well known message payloads can register decoders for automatic protobuf parsing""" protocols = { - portnums_pb2.PortNum.POSITION_APP: KnownProtocol("position", mesh_pb2.Position), - portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol("user", mesh_pb2.User), + portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol("text", onReceive=_onTextReceive), + portnums_pb2.PortNum.POSITION_APP: KnownProtocol("position", mesh_pb2.Position, _onPositionReceive), + portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol("user", mesh_pb2.User, _onNodeInfoReceive), portnums_pb2.PortNum.ADMIN_APP: KnownProtocol("admin", admin_pb2.AdminMessage), portnums_pb2.PortNum.ENVIRONMENTAL_MEASUREMENT_APP: KnownProtocol("environmental", environmental_measurement_pb2.EnvironmentalMeasurement), portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol("remotehw", remote_hardware_pb2.HardwareMessage) @@ -541,22 +575,6 @@ class MeshInterface: topic = f"meshtastic.receive.data.{portnum}" - # For text messages, we go ahead and decode the text to ascii for our users - if portnum == portnums_pb2.PortNum.Name(portnums_pb2.PortNum.TEXT_MESSAGE_APP): - topic = "meshtastic.receive.text" - - # We don't throw if the utf8 is invalid in the text message. Instead we just don't populate - # the decoded.data.text and we log an error message. This at least allows some delivery to - # the app and the app can deal with the missing decoded representation. - # - # Usually btw this problem is caused by apps sending binary data but setting the payload type to - # text. - try: - asDict["decoded"]["text"] = meshPacket.decoded.payload.decode( - "utf-8") - except Exception as ex: - logging.error(f"Malformatted utf8 in text message: {ex}") - # decode position protobufs and update nodedb, provide decoded version as "position" in the published msg # move the following into a 'decoders' API that clients could register? portNumInt = meshPacket.decoded.portnum # we want portnum as an int @@ -564,23 +582,17 @@ class MeshInterface: p = None # The decoded protobuf as a dictionary (if we understand this message) if handler is not None: topic = f"meshtastic.receive.{handler.name}" - pb = handler.protobufFactory() - pb.ParseFromString(meshPacket.decoded.payload) - p = google.protobuf.json_format.MessageToDict(pb) - asDict["decoded"][handler.name] = p - if portNumInt == portnums_pb2.PortNum.POSITION_APP: - self._fixupPosition(p) - # update node DB as needed - self._getOrCreateByNum(asDict["from"])["position"] = p + # Convert to protobuf if possible + if handler.protobufFactory is not None: + pb = handler.protobufFactory() + pb.ParseFromString(meshPacket.decoded.payload) + p = google.protobuf.json_format.MessageToDict(pb) + asDict["decoded"][handler.name] = p - # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg - if portNumInt == portnums_pb2.PortNum.NODEINFO_APP: - # update node DB as needed - n = self._getOrCreateByNum(asDict["from"]) - n["user"] = p - # We now have a node ID, make sure it is uptodate in that table - self.nodes[p["id"]] = n + # Call specialized onReceive if necessary + if handler.onReceive is not None: + handler.onReceive(self, asDict) logging.debug(f"Publishing topic {topic}") catchAndIgnore(f"publishing {topic}", lambda: pub.sendMessage(