Added dynamic interface with tcp and upcomming mqtt hooking. add outgoing messages aswell.

This commit is contained in:
Dionysus 2024-04-27 05:13:26 -04:00
parent 1b0616daca
commit 8eb954124f
Signed by: acidvegas
GPG Key ID: EF4B922DB85DC9DE
2 changed files with 118 additions and 59 deletions

View File

@ -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

View File

@ -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: