From d46d1d1c98e36372a35d2bcb3c0ccd1a4775005a Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 11:14:07 -0700 Subject: [PATCH] pubsub works --- TODO.md | 7 ++++++- bin/run.sh | 2 +- meshtastic/__main__.py | 25 ++++++++++++++++++++++++ meshtastic/interface.py | 43 +++++++++++++++++++++++++++++++++-------- setup.py | 2 +- 5 files changed, 68 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index 8cd10d2..5ee7618 100644 --- a/TODO.md +++ b/TODO.md @@ -2,12 +2,17 @@ ## Before initial release -- make pubsub work - make docs decent - document properties/fields - include examples in readme. hello.py, textchat.py, replymessage.py - DONE use port enumeration to find ports https://pyserial.readthedocs.io/en/latest/shortintro.html - DONE make serial debug output optional (by providing a null stream) +- DONE make pubsub work + +## Soon after initial release + +- keep nodedb up-to-date based on received MeshPackets +- handle radio reboots and redownload db when that happens. Look for a special FromRadio.rebooted packet ## Eventual diff --git a/bin/run.sh b/bin/run.sh index e6c25e0..e6dc377 100755 --- a/bin/run.sh +++ b/bin/run.sh @@ -1 +1 @@ -python3 -m meshtastic "$@" +python3 -m meshtastic "$@" --seriallog none diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 82598fb..c755512 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -4,6 +4,30 @@ import argparse from .interface import StreamInterface import logging import sys +from pubsub import pub +import google.protobuf.json_format + + +def onReceive(packet): + """Callback invoked when a packet arrives""" + print(f"Received: {packet}") + + +def onConnection(topic=pub.AUTO_TOPIC): + """Callback invoked when we connect/disconnect from a radio""" + print(f"Connection changed: {topic.getName()}") + + +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(onReceive, "meshtastic.receive") + pub.subscribe(onConnection, "meshtastic.connection") + pub.subscribe(onNode, "meshtastic.node") def main(): @@ -35,6 +59,7 @@ def main(): logging.info(f"Logging serial output to {args.seriallog}") logfile = open(args.seriallog, 'w+', buffering=1) # line buffering + subscribe() client = StreamInterface(args.device, debugOut=logfile) diff --git a/meshtastic/interface.py b/meshtastic/interface.py index 00ef54e..33a0b58 100644 --- a/meshtastic/interface.py +++ b/meshtastic/interface.py @@ -7,6 +7,7 @@ import logging import sys import traceback from . import mesh_pb2 +from pubsub import pub START1 = 0x94 START2 = 0xc3 @@ -34,12 +35,12 @@ Use a pubsub model to communicate events [https://pypubsub.readthedocs.io/en/v4. - meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB - meshtastic.connection.lost - published once we've lost our link to the radio -- meshtastic.receive.position(MeshPacket) -- meshtastic.receive.user(MeshPacket) -- meshtastic.receive.data(MeshPacket) -- meshtastic.node.updated(NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...) -- meshtastic.debug(string) -- meshtastic.send(MeshPacket) - Not yet implemented, instead call sendPacket(...) on MeshInterface +- meshtastic.receive.position(packet = MeshPacket) +- meshtastic.receive.user(packet = MeshPacket) +- meshtastic.receive.data(packet = MeshPacket) +- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...) +- meshtastic.debug(message = string) +- meshtastic.send(packet = MeshPacket) - Not yet implemented, instead call sendPacket(...) on MeshInterface """ @@ -76,6 +77,10 @@ class MeshInterface: toRadio.packet.CopyFrom(meshPacket) self._sendToRadio(toRadio) + def _disconnected(self): + """Called by subclasses to tell clients this interface has disconnected""" + pub.sendMessage("meshtastic.connection.lost") + def _startConfig(self): """Start device packets flowing""" self.myInfo = None @@ -102,7 +107,7 @@ class MeshInterface: logging.debug(f"Received: {json}") if fromRadio.HasField("my_info"): self.myInfo = fromRadio.my_info - if fromRadio.HasField("radio"): + elif fromRadio.HasField("radio"): self.radioConfig = fromRadio.radio elif fromRadio.HasField("node_info"): node = fromRadio.node_info @@ -110,10 +115,31 @@ 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 - pass + pub.sendMessage("meshtastic.connection.established") + elif fromRadio.HasField("packet"): + self._handlePacketFromRadio(self, fromRadio.packet) else: logging.warn("Unexpected FromRadio payload") + def _handlePacketFromRadio(self, meshPacket): + """Handle a MeshPacket that just arrived from the radio + + Will publish one of the following events: + - meshtastic.receive.position(packet = MeshPacket dictionary) + - 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) + if meshPacket.payload.HasField("position"): + pub.sendMessage("meshtastic.receive.position", packet=json) + if meshPacket.payload.HasField("user"): + pub.sendMessage("meshtastic.receive.user", + packet=json) + if meshPacket.payload.HasField("data"): + pub.sendMessage("meshtastic.receive.data", + packet=json) + class StreamInterface(MeshInterface): """Interface class for meshtastic devices over a stream link(serial, TCP, etc)""" @@ -201,3 +227,4 @@ class StreamInterface(MeshInterface): self._rxBuf = empty logging.debug("reader is exiting") self.stream.close() + self._disconnected() diff --git a/setup.py b/setup.py index a25ca04..9a0be3a 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ setup( ], packages=["meshtastic"], include_package_data=True, - install_requires=["pyserial>=3.4"], + install_requires=["pyserial>=3.4", "protobuf>=3.6.1", "pypubsub>=4.0.3"], python_requires='>=3', entry_points={ "console_scripts": [