meshtastic

- Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.acid.vegas/-c.git
Log | Files | Refs | Archive | README | LICENSE

commit d9ec08e9128fdf456f96d9b000115b222cf03e45
parent e0754f1f0258008961720a3bacefd582555c1fcf
Author: acidvegas <acid.vegas@acid.vegas>
Date: Mon, 29 Apr 2024 20:03:38 -0400

Added documentation on firmware hacking & hardware provisioning, started working on the MQTT interface

Diffstat:
AFIRMWARE.md | 39+++++++++++++++++++++++++++++++++++++++
DFIRMWARE_HACKS.md | 40----------------------------------------
MREADME.md | 15++++++---------
ASETUP.md | 41+++++++++++++++++++++++++++++++++++++++++
Ameshapi.py | 280+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ameshmqtt.py | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmeshtastic_serial.py | 265-------------------------------------------------------------------------------

7 files changed, 455 insertions(+), 314 deletions(-)

diff --git a/FIRMWARE.md b/FIRMWARE.md
@@ -0,0 +1,38 @@
+# Meshtastic Firmware Hacks
+
+### Custom Boot Logo & Message
+- Download & install [PlatformIO](https://platformio.org/platformio-ide)
+
+- `git clone https://github.com/meshtastic/firmware.git`
+
+- `cd firmware && git submodule update --init`
+
+- Use [XMB Viewer](https://windows87.github.io/xbm-viewer-converter/) to convert an image to XMB
+
+- The data from this goes in `firmware/src/graphics/img/icon.xbm`
+
+### Custom boot message
+- Navigate to `firmware/src/graphics/Screen.cpp`
+
+- Find & replace `const char *title = "meshtastic.org";` with your custom message.
+
+### Custom screen color
+ - Navigate to `src/graphics/TFTDisplay.cpp`
+
+ - Find & replace `#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)` with your custom color.
+
+ ### Custom alert sound (for T-Deck & devices with a buzzer)
+ - From the mobile app, click the 3 dots on the top right, and select `Radio configuration`
+ - Under `Module configuration`, select `External Notification`
+- Scroll down & you will see a `Ringtone` option that takes [RTTTL](https://en.wikipedia.org/wiki/Ring_Tone_Text_Transfer_Language) formatted tones.
+
+As far as I know, at the time of writing this, the onyl way to change the Ringtone is from the App...
+
+ ## Compile & flash firmware
+ - Select `PlatformIO: Pick Project Environment` & select your board.
+ - Run `PLatformIO: Build` to compile the firmware.
+ - Place device in DFU mode & plug in to the computer
+ - Do `PlatformIO: Upload` to send the firmware to the device
+ - Press the RESET button or reboot your device.
+
+ See [here](https://meshtastic.org/docs/development/firmware/build/) for more information building & flashing custom firmware.
+\ No newline at end of file
diff --git a/FIRMWARE_HACKS.md b/FIRMWARE_HACKS.md
@@ -1,39 +0,0 @@
-# Meshtastic Firmware Hacks
-
-### Custom Boot Logo & Message
-- Download & install [PlatformIO](https://platformio.org/platformio-ide)
-
-- `git clone https://github.com/meshtastic/firmware.git`
-
-- `cd firmware && git submodule update --init`
-
-- Use [XMB Viewer](https://windows87.github.io/xbm-viewer-converter/) to convert an image to XMB
-
-- The data from this goes in `firmware/src/graphics/img/icon.xbm`
-
-### Custom boot message
-- Navigate to `firmware/src/graphics/Screen.cpp`
-
-- Find & replace `const char *title = "meshtastic.org";` with your custom message.
-
-### Custom screen color
- - Navigate to `src/graphics/TFTDisplay.cpp`
-
- - Find & replace `#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)` with your custom color.
-
- ### Custom alert sound (for T-Deck & devices with a buzzer)
- - From the mobile app, click the 3 dots on the top right, and select `Radio configuration`
- - Under `Module configuration`, select `External Notification`
-- Scroll down & you will see a `Ringtone` option that takes [RTTTL](https://en.wikipedia.org/wiki/Ring_Tone_Text_Transfer_Language) formatted tones.
-
-As far as I know, at the time of writing this, the onyl way to change the Ringtone is from the App...
-
-
- ## Compile & flash firmware
- - Select `PlatformIO: Pick Project Environment` & select your board.
- - Run `PLatformIO: Build` to compile the firmware.
- - Place device in DFU mode & plug in to the computer
- - Do `PlatformIO: Upload` to send the firmware to the device
- - Press the RESET button or reboot your device.
-
- See [here](https://meshtastic.org/docs/development/firmware/build/) for more information building & flashing custom firmware.
-\ No newline at end of file
diff --git a/README.md b/README.md
@@ -1,28 +1,25 @@
 # Meshtastic Utilities
-> Experiments with Meshtastic, MQTT, Lora, & more....
 
-## WORK-IN-PROGRESS
-
-## Information
 This repository serves as a collection of resources created in my journey to learn & utilize [LoRa](https://en.wikipedia.org/wiki/LoRa) based communications with [Meshtastic](https://meshtastic.org).
 
 The goal here is to create simple & clean modules to interface with the hardware in a way that can be used to expand the possibilities of the devices capabilities.
 
 The hardware I am experimenting with: [Lilygo T-Deck](https://www.lilygo.cc/products/t-deck), [Lilygo T-Beam](https://www.lilygo.cc/products/t-beam-v1-1-esp32-lora-module), [Heltec Lora 32 v3](https://heltec.org/project/wifi-lora-32-v3/), and [RAK Wireless 4631](https://store.rakwireless.com/products/wisblock-core-modules?variant=42440631419078)
 
-## Notes to self & developers
-- The node id formula is: f'!{hex(node_num)[2:]}'
+## Documentation
+- [Firmware Hacks & Customization](./FIRMWARE.md)
+- [Setup Hardware](./SETUP.md)
+
 
-## Hardware & Software related Issues
+## Bugs & Issues
 - T-Deck must have Wifi turned off when going mobile. Upon leaving my house with WiFi still enabled, the UI & connection was EXTREMELY laggy & poor. Couldn't even type well...
 - T-Deck using a custom MQTT with TLS & auth will cause a reboot loop *(Need to fix this ASAP)*
 - `event_node` event is called **AS** we are defining the interface, so using `self.interface` in that callback will error.
 
 ## Roadmap
-- Asyncronous meshtastic interface *(Priority)*
+- Asyncronous meshtastic interface
 - MQTT interface with working decryption
 - Documentation on MQTT bridging for high availability
-- Create a simple setup script to provision new devices over serial
 - Bridge for IRC to allow channel messages to relay over Meshtastic & all Meshtastic events to relay into IRC. *(IRC to Meshtastic will require a command like `!mesh <message here>` to avoid overloading the traffic over LoRa)*
 
 ___
diff --git a/SETUP.md b/SETUP.md
@@ -0,0 +1,41 @@
+# Setup Hardware
+
+It is recommended that you provision your hardware using the serial interface over USB by using the [Meshtastic CLI Tool](https://pypi.org/project/meshtastic/). This is very specific because currently, at the time of writing this repository, changes made via the [web interface](https://client.meshtastic.org) do not work sometimes. When you "save" your settings, the device will reboot with the old settings still. I have had zero issues making changes over serial with the CLI interface.
+
+- `pip install meshtastic` to install the CLI tool
+- Plug in your device *(Make sure the USB cable you are using allows data transfer & not just power)*
+- Run the commands below *(Each command will make the device reboot after setting it)*
+
+###### NAME
+```
+meshtastic --set-owner 'CHANGEME' --set-owner-short 'CHNG'
+```
+
+**Note:** Short name can only be 4 alphanumeric characters.
+
+###### LORA
+```
+meshtastic --set lora.region US 
+```
+
+###### CHANNEL
+```
+meshtastic --ch-set name "SUPERNETS" --ch-set psk "CHANGEME" --ch-set uplink_enabled true --ch-set downlink_enabled true --ch-index 0
+```
+
+###### WIFI
+```
+meshtastic --set network.wifi_enabled true --set network.wifi_ssid "CHANGEME" --set network.wifi_psk "CHANGEME"
+```
+
+###### MQTT
+```
+meshtastic --set mqtt.enabled true --set mqtt.address "CHANGEME" --set mqtt.username "CHANGEME" --set mqtt.password "CHANGEME" --set mqtt.tls_enabled true --set mqtt.root "msh/CHANGEME" --set mqtt.encryption_enabled true
+```
+
+###### BLUETOOTH
+```
+meshtastic --set bluetooth.enabled true
+```
+
+**Note:** Only enable this on devices are not using Wifi *(mobile devices)* because with ESP32 chips, I don't think Wifi & Bluetooth can function side-by-side together.
diff --git a/meshapi.py b/meshapi.py
@@ -0,0 +1,279 @@
+#!/usr/bin/env python
+# Meshtastic Serial Interface - Developed by Acidvegas in Python (https://git.acid.vegas)
+
+import argparse
+import logging
+import os
+import time
+
+try:
+	import meshtastic
+except ImportError:
+	raise ImportError('meshtastic library not found (pip install meshtastic)')
+
+try:
+	from pubsub import pub
+except ImportError:
+	raise ImportError('pubsub library not found (pip install pypubsub)')
+
+
+# Initialize logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)9s | %(funcName)s | %(message)s', datefmt='%Y-%m-%d %I:%M:%S')
+
+
+def now():
+	'''Returns the current date and time in a formatted string'''
+
+	return time.strftime('%Y-%m-%d %H:%M:%S')
+
+
+class MeshtasticClient(object):
+	def __init__(self, option: str, value: str):
+		self.interface = None
+		self.me        = {}
+		self.nodes     = {}
+
+		self.interface_option = option
+		self.interface_value  = value
+
+		if self.interface_option == 'serial':
+			from meshtastic.serial_interface import SerialInterface as MeshInterface
+			from meshtastic.util import findPorts
+		elif self.interface_option == 'tcp':
+			from meshtastic.tcp_interface import TCPInterface as MeshInterface
+
+
+	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
+     	'''
+  
+		while True:
+			try:
+				if option == 'serial':
+					if devices := findPorts():
+						if not os.path.exists(args.serial) or not args.serial in devices:
+							raise Exception(f'Invalid serial port specified: {args.serial} (Available: {devices})')
+					else:
+						raise Exception('No serial devices found')
+					self.interface = SerialInterface(value)
+		
+				elif option == 'tcp':
+					self.interface = TCPInterface(value)
+
+				else:
+					raise SystemExit('Invalid interface option')
+
+			except Exception as e:
+				logging.error(f'Failed to connect to the radio: {e}')
+				logging.error('Retrying in 15 seconds...')
+				time.sleep(15)
+
+			else:
+				self.me = self.interface.getMyNodeInfo()
+				break
+
+
+	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 listen(self):
+		'''Create the Meshtastic callback subscriptions'''
+
+		pub.subscribe(self.event_connect,    'meshtastic.connection.established')
+		pub.subscribe(self.event_data,       'meshtastic.receive.data.portnum')
+		pub.subscribe(self.event_disconnect, 'meshtastic.connection.lost')
+		pub.subscribe(self.event_node,       'meshtastic.node')
+		pub.subscribe(self.event_position,   'meshtastic.receive.position')
+		pub.subscribe(self.event_text,       'meshtastic.receive.text')
+		pub.subscribe(self.event_user,       'meshtastic.receive.user')
+
+		logging.debug('Listening for Meshtastic events...')
+		
+		
+	def event_connect(self, interface, topic=pub.AUTO_TOPIC):
+		'''
+		Callback function for connection established
+
+		:param interface: Meshtastic interface
+		:param topic:     PubSub topic
+		'''
+
+		logging.info(f'Connected to the {self.me["user"]["longName"]} radio on {self.me["user"]["hwModel"]} hardware')
+		logging.info(f'Found a total of {len(self.nodes):,} nodes')
+
+
+	def event_data(self, packet: dict, interface):
+		'''
+		Callback function for data updates
+
+		:param packet: Data information
+		:param interface: Meshtastic interface
+		'''
+
+		logging.info(f'Data update: {data}')
+
+
+	def event_disconnect(self, interface, topic=pub.AUTO_TOPIC):
+		'''
+		Callback function for connection lost
+
+		:param interface: Meshtastic interface
+		:param topic:     PubSub topic
+		'''
+
+		logging.warning('Lost connection to radio!')
+
+		time.sleep(10)
+
+		 # TODO: Consider storing the interface option and value in a class variable since we don't want to reference the args object inside the class
+		self.connect('serial' if args.serial else 'tcp', args.serial if args.serial else args.tcp)
+
+	
+	def event_node(self, node):
+		'''
+		Callback function for node updates
+
+		:param node: Node information
+		'''
+
+		# Node ID Formula = f'!{hex(node_num)[2:]}'
+
+		self.nodes[node['num']] = node
+
+		logging.info(f'Node found: {node["user"]["id"]} - {node["user"]["shortName"].ljust(4)} - {node["user"]["longName"]}')
+		print(node)
+
+
+	def event_position(self, packet: dict, interface):
+		'''
+		Callback function for position updates
+
+		:param packet: Position information
+		:param interface: Meshtastic interface
+		'''
+
+		sender    = packet['from']
+		msg       = packet['decoded']['payload'].hex()
+		id        = self.nodes[sender]['user']['id']       if sender in self.nodes else '!unk   '
+		name      = self.nodes[sender]['user']['longName'] if sender in self.nodes else 'UNK'
+		longitude = packet['decoded']['position']['longitudeI'] / 1e7
+		latitude  = packet['decoded']['position']['latitudeI'] / 1e7
+		altitude  = packet['decoded']['position']['altitude']
+		snr	      = packet['rxSnr']
+		rssi	  = packet['rxRssi']
+
+		logging.info(f'{id} - {name}: {longitude}, {latitude}, {altitude}m (SNR: {snr}, RSSI: {rssi}) - {msg}')
+
+
+	def event_text(self, packet: dict, interface):
+		'''
+		Callback function for received packets
+
+		:param packet: Packet received
+		'''
+
+		sender = packet['from']
+		to     = packet['to'] 
+		msg    = packet['decoded']['payload'].decode('utf-8')
+		id     = self.nodes[sender]['user']['id']       if sender in self.nodes else '!unk   '
+		name   = self.nodes[sender]['user']['longName'] if sender in self.nodes else 'UNK'
+		target = self.nodes[to]['user']['longName']     if to     in self.nodes else 'UNK'
+
+		logging.info(f'{id} {name} -> {target}: {msg}')
+		print(packet)
+
+
+	def event_user(self, packet: dict, interface):
+		'''
+		Callback function for user updates
+
+		:param user: User information
+		'''
+
+		'''
+		{
+			'from' : 862341900,
+			'to'   : 4294967295,
+			'decoded' : {
+				'portnum'      : 'NODEINFO_APP',
+				'payload'      : b'\n\t!33664b0c\x12\x08HELLDIVE\x1a\x04H3LL"\x06d\xe83fK\x0c(+8\x03',
+				'wantResponse' : True,
+				'user' : {
+					'id'        : '!33664b0c',
+					'longName'  : 'HELLDIVE',
+					'shortName' : 'H3LL',
+					'macaddr'   : 'ZOgzZksM',
+					'hwModel'   : 'HELTEC_V3',
+					'role'      : 'ROUTER_CLIENT',
+					'raw'       : 'rm this'
+				}
+			},
+			'id'       : 1612906268,
+			'rxTime'   : 1714279638,
+			'rxSnr'    : 6.25,
+			'hopLimit' : 3,
+			'rxRssi'   : -38,
+			'hopStart' : 3,
+			'raw'      : 'rm this'
+		}
+		'''
+
+		# Not sure what to do with this yet...
+		pass
+
+
+
+if __name__ == '__main__':
+	parser = argparse.ArgumentParser(description='Meshtastic Interfacing Tool')
+	parser.add_argument('--serial', help='Use serial interface') # Typically /dev/ttyUSB0 or /dev/ttyACM0
+	parser.add_argument('--tcp',    help='Use TCP interface')    # Can be an IP address or hostname (meshtastic.local)
+	args = parser.parse_args()
+ 
+	# Ensure one interface is specified
+	if (not args.serial and not args.tcp) or (args.serial and args.tcp):
+		raise SystemExit('Must specify either --serial or --tcp interface')
+	
+	# Initialize the Meshtastic client
+	mesh = MeshtasticClient()
+
+	# Listen for Meshtastic events
+	mesh.listen()
+
+	# Connect to the Meshtastic interface
+	mesh.connect('serial' if args.serial else 'tcp', args.serial if args.serial else args.tcp)
+
+	# Keep-alive loop
+	try:
+		while True:
+			time.sleep(60)
+	except KeyboardInterrupt:
+		try:
+			mesh.interface.close()
+		except:
+			pass
+	finally:
+		logging.info('Connection to radio lost')
+
+'''
+Notes:
+		conf = self.interface.localNode.localConfig
+		ok = interface.getNode('^local')
+		print(ok.channels)
+'''
+\ No newline at end of file
diff --git a/meshmqtt.py b/meshmqtt.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# Meshtastic MQTT Interface - Developed by Acidvegas in Python (https://git.acid.vegas/meshtastic)
+
+import base64
+import random
+
+try:
+    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+    from cryptography.hazmat.backends           import default_backend
+except ImportError:
+    raise ImportError('cryptography library not found (pip install cryptography)')
+
+try:
+    from meshtastic import mesh_pb2, mqtt_pb2, portnums_pb2, telemetry_pb2
+except ImportError:
+    raise ImportError('meshtastic library not found (pip install meshtastic)')
+
+try:
+    import paho.mqtt.client as mqtt
+except ImportError:
+    raise ImportError('paho-mqtt library not found (pip install paho-mqtt)')
+
+
+# MQTT Configuration
+MQTT_BROKER     = 'localhost'
+MQTT_PORT       = 1883
+MQTT_USERNAME   = 'username'
+MQTT_PASSWORD   = 'password'
+MQTT_ROOT_TOPIC = 'msh/US/2/c/'
+CHANNEL_KEY     = 'channel_key'
+
+
+def on_connect(client, userdata, flags, rc, properties):
+    '''
+    Callback for when the client receives a CONNACK response from the server.
+    
+    :param client:     The client instance for this callback
+    :param userdata:   The private user data as set in Client() or user_data_set()
+    :param flags:      Response flags sent by the broker
+    :param rc:         The connection result
+    :param properties: The properties returned by the broker
+    '''
+
+    if rc == 0:
+        print('Connected to MQTT broker')
+
+    else:
+        print(f"Failed to connect to MQTT broker with result code {str(rc)}")
+
+
+def on_message(client, userdata, msg):
+    '''
+    Callback for when a PUBLISH message is received from the server.
+    
+    :param client:    The client instance for this callback
+    :param userdata:  The private user data as set in Client() or user_data_set()
+    :param msg:       An instance of MQTTMessage. This is a class with members topic, payload, qos, retain.
+    '''
+
+    service_envelope = mqtt_pb2.ServiceEnvelope()
+
+    try:
+        service_envelope.ParseFromString(msg.payload)
+        print(service_envelope)
+
+        message_packet = service_envelope.packet
+        print(message_packet)
+
+    except Exception as e:
+        print(f'error on message: {e}')
+
+    else:
+        if message_packet.HasField('encrypted') and not message_packet.HasField('decoded'): # Do we need to check for both?
+            pass # Need to finish this
+
+
+
+if __name__ == '__main__':
+    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
+    client.on_connect = on_connect
+    client.username_pw_set(username=MQTT_USERNAME, password=MQTT_PASSWORD)
+    client.connect(MQTT_BROKER, MQTT_PORT, 60)
+    client.on_message = on_message
+    client.subscribe(MQTT_ROOT_TOPIC, 0) # This is the topic that the Meshtastic device is publishing to
+
+    # Keep-alive loop
+    while client.loop() == 0:
+        pass
+\ No newline at end of file
diff --git a/meshtastic_serial.py b/meshtastic_serial.py
@@ -1,264 +0,0 @@
-#!/usr/bin/env python
-# Meshtastic Serial Interface - Developed by Acidvegas in Python (https://git.acid.vegas)
-
-import argparse
-import logging
-import os
-import time
-
-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)')
-
-try:
-	from pubsub import pub
-except ImportError:
-	raise ImportError('pubsub library not found (pip install pypubsub)')
-
-
-# Initialize logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)9s | %(funcName)s | %(message)s', datefmt='%Y-%m-%d %I:%M:%S')
-
-
-class MeshtasticClient(object):
-	def __init__(self):
-		self.interface = None
-		self.me        = {} 
-		self.nodes     = {}
-
-
-	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
-     	'''
-  
-		while True:
-			try:
-				if option == 'serial':
-					if devices := findPorts():
-						if not os.path.exists(args.serial) or not args.serial in devices:
-							raise Exception(f'Invalid serial port specified: {args.serial} (Available: {devices})')
-					else:
-						raise Exception('No serial devices found')
-					self.interface = SerialInterface(value)
-		
-				elif option == 'tcp':
-					self.interface = TCPInterface(value)
-
-				else:
-					raise SystemExit('Invalid interface option')
-
-			except Exception as e:
-				logging.error(f'Failed to connect to the Meshtastic interface: {e}')
-				logging.error('Retrying in 10 seconds...')
-				time.sleep(10)
-
-			else:
-				self.me = self.interface.getMyNodeInfo()
-				break
-
-
-	def sendmsg(self, message: str, destination: int, channelIndex: int = 0):
-		'''
-		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, destination, wantAck=True, channelIndex=channelIndex) # Do we need wantAck?
-
-		logging.info(f'Sent broadcast message: {message}')
-
-
-	def listen(self):
-		'''Create the Meshtastic callback subscriptions'''
-
-		pub.subscribe(self.event_connect,    'meshtastic.connection.established')
-		pub.subscribe(self.event_data,       'meshtastic.receive.data.portnum')
-		pub.subscribe(self.event_disconnect, 'meshtastic.connection.lost')
-		pub.subscribe(self.event_node,       'meshtastic.node')
-		pub.subscribe(self.event_position,   'meshtastic.receive.position')
-		pub.subscribe(self.event_text,       'meshtastic.receive.text')
-		pub.subscribe(self.event_user,       'meshtastic.receive.user')
-
-		logging.debug('Listening for Meshtastic events...')
-		
-		
-	def event_connect(self, interface, topic=pub.AUTO_TOPIC):
-		'''
-		Callback function for connection established
-
-		:param interface: Meshtastic interface
-		:param topic:     PubSub topic
-		'''
-
-		logging.info(f'Connected to the {self.me["user"]["longName"]} radio on {self.me["user"]["hwModel"]} hardware')
-		logging.info(f'Found a total of {len(self.nodes):,} nodes')
-
-
-	def event_data(self, packet: dict, interface):
-		'''
-		Callback function for data updates
-
-		:param packet: Data information
-		:param interface: Meshtastic interface
-		'''
-
-		logging.info(f'Data update: {packet}')
-
-
-	def event_disconnect(self, interface, topic=pub.AUTO_TOPIC):
-		'''
-		Callback function for connection lost
-
-		:param interface: Meshtastic interface
-		:param topic:     PubSub topic
-		'''
-
-		logging.warning('Lost connection to radio!')
-
-		time.sleep(10)
-
-		 # TODO: Consider storing the interface option and value in a class variable since we don't want to reference the args object inside the class
-		self.connect('serial' if args.serial else 'tcp', args.serial if args.serial else args.tcp)
-
-	
-	def event_node(self, node):
-		'''
-		Callback function for node updates
-
-		:param node: Node information
-		'''
-
-		self.nodes[node['num']] = node
-
-		logging.info(f'Node recieved: {node["user"]["id"]} - {node["user"]["shortName"].ljust(4)} - {node["user"]["longName"]}')
-
-
-	def event_position(self, packet: dict, interface):
-		'''
-		Callback function for position updates
-
-		:param packet: Position information
-		:param interface: Meshtastic interface
-		'''
-
-		sender    = packet['from']
-		msg       = packet['decoded']['payload'].hex() # What exactly is contained in this payload?
-		id        = self.nodes[sender]['user']['id']       if sender in self.nodes else '!unk   '
-		name      = self.nodes[sender]['user']['longName'] if sender in self.nodes else 'UNK'
-		longitude = packet['decoded']['position']['longitudeI'] / 1e7
-		latitude  = packet['decoded']['position']['latitudeI'] / 1e7
-		altitude  = packet['decoded']['position']['altitude']
-		snr	      = packet['rxSnr']
-		rssi	  = packet['rxRssi']
-
-		logging.info(f'Position recieved: {id} - {name}: {longitude}, {latitude}, {altitude}m (SNR: {snr}, RSSI: {rssi}) - {msg}')
-
-
-	def event_text(self, packet: dict, interface):
-		'''
-		Callback function for received packets
-
-		:param packet: Packet received
-		'''
-
-		sender = packet['from']
-		to     = packet['to'] 
-		msg    = packet['decoded']['payload'].decode('utf-8')
-		id     = self.nodes[sender]['user']['id']       if sender in self.nodes else '!unk   '
-		name   = self.nodes[sender]['user']['longName'] if sender in self.nodes else 'UNK'
-		target = self.nodes[to]['user']['longName']     if to     in self.nodes else 'UNK'
-
-		logging.info(f'Message recieved: {id} {name} -> {target}: {msg}')
-		print(packet)
-
-
-	def event_user(self, packet: dict, interface):
-		'''
-		Callback function for user updates
-
-		:param user: User information
-		'''
-
-		'''
-		{
-			'from' : 862341900,
-			'to'   : 4294967295,
-			'decoded' : {
-				'portnum'      : 'NODEINFO_APP',
-				'payload'      : b'\n\t!33664b0c\x12\x08HELLDIVE\x1a\x04H3LL"\x06d\xe83fK\x0c(+8\x03',
-				'wantResponse' : True,
-				'user' : {
-					'id'        : '!33664b0c',
-					'longName'  : 'HELLDIVE',
-					'shortName' : 'H3LL',
-					'macaddr'   : 'ZOgzZksM',
-					'hwModel'   : 'HELTEC_V3',
-					'role'      : 'ROUTER_CLIENT',
-					'raw'       : 'rm this'
-				}
-			},
-			'id'       : 1612906268,
-			'rxTime'   : 1714279638,
-			'rxSnr'    : 6.25,
-			'hopLimit' : 3,
-			'rxRssi'   : -38,
-			'hopStart' : 3,
-			'raw'      : 'rm this'
-		}
-		'''
-
-		# Not sure what to do with this yet...
-		pass
-
-
-
-if __name__ == '__main__':
-	parser = argparse.ArgumentParser(description='Meshtastic Interfacing Tool')
-	parser.add_argument('--serial', help='Use serial interface') # Typically /dev/ttyUSB0 or /dev/ttyACM0
-	parser.add_argument('--tcp',    help='Use TCP interface')    # Can be an IP address or hostname (meshtastic.local)
-	args = parser.parse_args()
- 
-	# Ensure one interface is specified
-	if (not args.serial and not args.tcp) or (args.serial and args.tcp):
-		raise SystemExit('Must specify either --serial or --tcp interface')
-	
-	# Initialize the Meshtastic client
-	mesh = MeshtasticClient()
-
-	# Listen for Meshtastic events
-	mesh.listen()
-
-	# Connect to the Meshtastic interface
-	mesh.connect('serial' if args.serial else 'tcp', args.serial if args.serial else args.tcp)
-
-	# Keep-alive loop
-	try:
-		while True:
-			time.sleep(60)
-	except KeyboardInterrupt:
-		try:
-			mesh.interface.close()
-		except:
-			pass
-	finally:
-		logging.info('Connection to radio lost')
-
-'''
-Notes:
-		conf = self.interface.localNode.localConfig
-		ok = interface.getNode('^local')
-		print(ok.channels)
-'''
-\ No newline at end of file