diff --git a/README.md b/README.md
@@ -1,4 +1,4 @@
-# Meshtastic
+# Meshtastic Utilities
> Experiments with Meshtastic, MQTT, Lora, & more....
## WORK-IN-PROGRESS
@@ -25,6 +25,7 @@ The goal is to experiment with the possibilities of Python as a means of interfa
## Updates
- Threw in an IRC skeleton where the serial controller will interface with. Will have to consider how handle asyncronous comms over serial...
- Working reconnection on disconnection!
+- Most events are handled and outputted to stdout, debugging and testing finished
___
###### Mirrors for this repository: [acid.vegas](https://git.acid.vegas/meshtastic) • [SuperNETs](https://git.supernets.org/acidvegas/meshtastic) • [GitHub](https://github.com/acidvegas/meshtastic) • [GitLab](https://gitlab.com/acidvegas/meshtastic) • [Codeberg](https://codeberg.org/acidvegas/meshtastic)
\ No newline at end of file
diff --git a/meshtastic_serial.py b/meshtastic_serial.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-# Meshtastic Serial Interface - Developed by Acidvegas in Python (https://git.acid.vegas/meshtastic)
+# Meshtastic Serial Interface - Developed by Acidvegas in Python (https://git.acid.vegas)
import argparse
import logging
@@ -10,7 +10,7 @@ try:
import meshtastic
from meshtastic.serial_interface import SerialInterface
from meshtastic.util import findPorts
- from meshtastic.tcp_interface import TCPInterface
+ from meshtastic.tcp_interface import TCPInterface
except ImportError:
raise ImportError('meshtastic library not found (pip install meshtastic)')
@@ -21,7 +21,7 @@ except ImportError:
# Initialize logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)9s | %(funcName)s | %(message)s')
+logging.basicConfig(level=logging.INFO, format='%(asctime)s | %(levelname)9s | %(funcName)s | %(message)s', datefmt='%Y-%m-%d %I:%M:%S')
def now():
@@ -40,11 +40,11 @@ class MeshtasticClient(object):
def connect(self, option: str, value: str):
'''
Connect to the Meshtastic interface
-
- :param option: The interface option to connect to
+
+ :param option: The interface option to connect to
:param value: The value of the interface option
- '''
-
+ '''
+
while True:
try:
if option == 'serial':
@@ -54,7 +54,7 @@ class MeshtasticClient(object):
else:
raise Exception('No serial devices found')
self.interface = SerialInterface(value)
-
+
elif option == 'tcp':
self.interface = TCPInterface(value)
@@ -91,19 +91,16 @@ class MeshtasticClient(object):
'''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_packet, 'meshtastic.receive')
+ 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...')
-
- # The meshtastic.receive topics can be broken down further:
- # pub.subscribe(self.on_text, 'meshtastic.receive.text')
- # pub.subscribe(self.on_position, 'meshtastic.receive.position')
- # pub.subscribe(self.on_user, 'meshtastic.receive.user')
- # pub.subscribe(self.on_data, 'meshtastic.receive.data.portnum')
-
-
+
+
def event_connect(self, interface, topic=pub.AUTO_TOPIC):
'''
Callback function for connection established
@@ -116,6 +113,17 @@ class MeshtasticClient(object):
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
@@ -125,15 +133,13 @@ class MeshtasticClient(object):
'''
logging.warning('Lost connection to radio!')
- logging.info('Reconnecting in 10 seconds...')
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
@@ -146,30 +152,78 @@ class MeshtasticClient(object):
logging.info(f'Node found: {node["user"]["id"]} - {node["user"]["shortName"].ljust(4)} - {node["user"]["longName"]}')
- def event_packet(self, packet: dict):
+ 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
'''
- # Handle incoming text messages
- if packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP':
- sender = packet['from']
- msg = packet['decoded']['payload'].decode('utf-8')
+ sender = packet['from']
+ 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'
+ logging.info(f'{id} - {name}: {msg}')
- # Message from self
- if sender == self.interface.myInfo.my_node_num:
- print(f'{now()} {self.nodes[sender]}: {msg}') # Can do custom formatting here or ignore the message, just an example
- # Message from others
- if sender in self.nodes:
- print(f'{now()} {self.nodes[sender]}: {msg}')
+ def event_user(self, packet: dict, interface):
+ '''
+ Callback function for user updates
- # Unknown sender
- else:
- # TODO: Trigger request for node update here
- print(f'{now()} UNK: {msg}')
+ :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
@@ -178,11 +232,11 @@ if __name__ == '__main__':
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()
@@ -197,11 +251,10 @@ if __name__ == '__main__':
while True:
time.sleep(60)
except KeyboardInterrupt:
- pass # Exit the loop on Ctrl+C
+ try:
+ mesh.interface.close()
+ except:
+ pass
finally:
- if mesh.interface:
- try:
- mesh.interface.close()
- logging.info('Connection to radio interface closed!')
- except:
- pass
+ logging.info('Connection to radio lost')
+
| |