From 8eb954124f33845286e0f3bcb942ac42dd541ada Mon Sep 17 00:00:00 2001 From: acidvegas Date: Sat, 27 Apr 2024 05:13:26 -0400 Subject: [PATCH] Added dynamic interface with tcp and upcomming mqtt hooking. add outgoing messages aswell. --- meshirc.py | 7 +- meshtastic_serial.py | 170 +++++++++++++++++++++++++++++-------------- 2 files changed, 118 insertions(+), 59 deletions(-) diff --git a/meshirc.py b/meshirc.py index 2baa7b1..71d995b 100644 --- a/meshirc.py +++ b/meshirc.py @@ -171,9 +171,10 @@ class Bot(): await self.action(target, 'explodes') elif msg == '!ping': await self.sendmsg(target, 'Pong!') - elif msg.startswith('!say') and len(msg.split()) > 1: # Only allow !say if there is something to say - option = ' '.join(msg.split()[1:]) # Everything after !say is stored here - await self.sendmsg(target, option) + elif msg.startswith('!mesh') and len(msg.split()) > 1: + message = ' '.join(msg.split()[1:]) + # Implement outgoing meshtastic message here + #await self.sendmesh(message) self.last = time.time() # Update the last command time if it starts with ! character to prevent command flooding diff --git a/meshtastic_serial.py b/meshtastic_serial.py index 78142a8..0f82b6c 100644 --- a/meshtastic_serial.py +++ b/meshtastic_serial.py @@ -10,6 +10,7 @@ try: import meshtastic from meshtastic.serial_interface import SerialInterface from meshtastic.util import findPorts + from meshtastic.tcp_interface import TCPInterface except ImportError: raise ImportError('meshtastic library not found (pip install meshtastic)') @@ -29,12 +30,41 @@ def now(): return time.strftime('%Y-%m-%d %H:%M:%S') -class Meshtastic(object): - def __init__(self, serial: str): - self.interface = None # We will define the interface in the run() function - self.nodes = {} # Nodes will populate with the on_node() callback - self.serial = serial # Serial device to use for the Meshtastic interface - +class MeshtasticClient(object): + def __init__(self): + self.interface = None # We will define the interface in the connect() function + self.nodes = {} # Nodes will populate with the event_node() callback + + + def connect(self, option: str, value: str): + ''' + Connect to the Meshtastic interface + + :param option: The interface option to connect to + :param value: The value of the interface option + ''' + + if option == 'serial': + if devices := findPorts(): + if not os.path.exists(args.serial) or not args.serial in devices: + raise SystemExit(f'Invalid serial device: {args.serial} (Available: {devices})') # Show available devices if the specified device is invalid + else: + raise SystemExit('No serial devices found') + self.interface = SerialInterface(value) + + elif option == 'tcp': + self.interface = TCPInterface(value) + + elif option == 'mqtt': + raise NotImplementedError('MQTT interface not implemented yet') + + else: + raise SystemExit('Invalid interface option') + + logging.info(f'Connected to radio over {option} from {value}:') + + logging.debug(self.interface.nodes[self.interface.myInfo.my_node_num]) # Print the node info of the connected radio + def disconnect(self): '''Disconnect from the Meshtastic interface''' @@ -50,35 +80,33 @@ class Meshtastic(object): logging.info('Meshtastic interface closed') else: logging.warning('No Meshtastic interface to close') + + logging.info('Disconnected from radio') + + + def send(self, message: str): + ''' + Send a message to the Meshtastic interface + + :param message: The message to send + ''' + + if len(message) > 255: + logging.warning('Message exceeds 255 characters') + message = message[:255] + + self.interface.sendText(message) + + logging.info(f'Sent broadcast message: {message}') - def run(self): - '''Start the Meshtastic interface and subscribe to the callback functions''' - - if devices := findPorts(): - if not os.path.exists(args.serial) or not args.serial in devices: - raise SystemExit(f'Invalid serial device: {args.serial} (Available: {devices})') # Show available devices if the specified device is invalid - else: - raise SystemExit('No serial devices found') + def listen(self): + '''Create the Meshtastic callback subscriptions''' - # Initialize the Meshtastic interface - self.interface = SerialInterface(self.serial) - - # Interface over TCP instead of serial: - #from meshtastic.tcp_interface import TCPInterface - #self.interface = TCPInterface(args.tcp) - - logging.info('Meshtastic interface started over serial on {self.serial}') - - # Get the current node information - me = self.interface.nodes[self.interface.myInfo.my_node_num] - logging.debug(me) - - # Create the Meshtastic callback subscriptions pub.subscribe(self.event_connect, 'meshtastic.connection.established') pub.subscribe(self.event_disconnect, 'meshtastic.connection.lost') - pub.subscribe(self.on_node, 'meshtastic.node.updated') - pub.subscribe(self.on_packet, 'meshtastic.receive') + pub.subscribe(self.event_node, 'meshtastic.node.updated') + pub.subscribe(self.event_packet, 'meshtastic.receive') logging.debug('Listening for Meshtastic events...') @@ -97,7 +125,9 @@ class Meshtastic(object): :param topic: PubSub topic ''' - logging.info('Connection established') + me = interface.nodes[interface.myInfo.my_node_num]['user']['longName'] + + logging.info(f'Connected to \'{me}\' radio') def event_disconnect(self, interface, topic=pub.AUTO_TOPIC): @@ -108,10 +138,33 @@ class Meshtastic(object): :param topic: PubSub topic ''' - logging.warning('Connection lost') + logging.warning('Lost connection to radio') + + + def event_node(self, interface, topic=pub.AUTO_TOPIC): + ''' + Callback function for node updates + + :param interface: Meshtastic interface + :param topic: PubSub topic + ''' + + if not interface.nodes: + logging.warning('No nodes found') + return + + for node in interface.nodes.values(): + short = node['user']['shortName'] + long = node['user']['longName'].encode('ascii', 'ignore').decode().rstrip() + num = node['num'] + id = node['user']['id'] + mac = node['user']['macaddr'] + hw = node['user']['hwModel'] + + self.nodes[num] = long # we store the node updates in a dictionary so we can parse the names of who sent incomming messages - def on_packet(self, packet: dict): + def event_packet(self, packet: dict): ''' Callback function for received packets @@ -135,43 +188,48 @@ class Meshtastic(object): else: # TODO: Trigger request for node update here print(f'{now()} UNK: {msg}') - - - def on_node(self, interface, topic=pub.AUTO_TOPIC): + + + def event_position(self, packet: dict): ''' - Callback function for node updates + Callback function for received position packets - :param interface: Meshtastic interface - :param topic: PubSub topic + :param packet: Packet received ''' - if not interface.nodes: - logging.warning('No nodes found') - return + # Handle incoming position messages + pass - for node in interface.nodes.values(): - short = node['user']['shortName'] - long = node['user']['longName'].encode('ascii', 'ignore').decode().rstrip() - num = node['num'] - id = node['user']['id'] - mac = node['user']['macaddr'] - hw = node['user']['hwModel'] - self.nodes[num] = long # we store the node updates in a dictionary so we can parse the names of who sent incomming messages + if __name__ == '__main__': parser = argparse.ArgumentParser(description='Meshtastic Interface') - parser.add_argument('--serial', default='/dev/ttyACM0', help='Use serial interface') - parser.add_argument('--tcp', default='meshtastic.local'. help='Use TCP interface') + + # Interface options + parser.add_argument('--serial', help='Use serial interface') + parser.add_argument('--tcp', help='Use TCP interface') + parser.add_argument('--mqtt', help='Use MQTT interface') + args = parser.parse_args() + + if not args.serial and not args.tcp and not args.mqtt: + raise SystemExit('No interface specified') - # Define the Meshtastic client - mesh = Meshtastic(args.serial) + if (args.serial and args.tcp) or (args.serial and args.mqtt) or (args.tcp and args.mqtt): + raise SystemExit('Only one interface option can be specified (--serial, --tcp, or --mqtt)') - # Initialize the Meshtastic interface - mesh.run() + # Initialize the Meshtastic client + mesh = MeshtasticClient() + + # Determine the interface option and value + option = 'serial' if args.serial else 'tcp' if args.tcp else 'mqtt' + value = args.serial if args.serial else args.tcp if args.tcp else args.mqtt + + # Start the Meshtastic interface + mesh.connect(option, value) # Keep-alive loop try: