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

This commit is contained in:
Dionysus 2024-04-29 20:03:38 -04:00
parent e0754f1f02
commit d9ec08e912
Signed by: acidvegas
GPG Key ID: EF4B922DB85DC9DE
5 changed files with 165 additions and 25 deletions

View File

@ -28,7 +28,6 @@
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.

View File

@ -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)*
___

41
SETUP.md Normal file
View File

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

View File

@ -8,9 +8,6 @@ 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)')
@ -24,12 +21,27 @@ except ImportError:
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):
def __init__(self, option: str, value: str):
self.interface = None
self.me = {}
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):
'''
@ -56,16 +68,16 @@ class MeshtasticClient(object):
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)
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 sendmsg(self, message: str, destination: int, channelIndex: int = 0):
def send(self, message: str):
'''
Send a message to the Meshtastic interface
@ -76,7 +88,7 @@ class MeshtasticClient(object):
logging.warning('Message exceeds 255 characters')
message = message[:255]
self.interface.sendText(message, destination, wantAck=True, channelIndex=channelIndex) # Do we need wantAck?
self.interface.sendText(message)
logging.info(f'Sent broadcast message: {message}')
@ -115,7 +127,7 @@ class MeshtasticClient(object):
:param interface: Meshtastic interface
'''
logging.info(f'Data update: {packet}')
logging.info(f'Data update: {data}')
def event_disconnect(self, interface, topic=pub.AUTO_TOPIC):
@ -141,9 +153,12 @@ class MeshtasticClient(object):
:param node: Node information
'''
# Node ID Formula = f'!{hex(node_num)[2:]}'
self.nodes[node['num']] = node
logging.info(f'Node recieved: {node["user"]["id"]} - {node["user"]["shortName"].ljust(4)} - {node["user"]["longName"]}')
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):
@ -155,7 +170,7 @@ class MeshtasticClient(object):
'''
sender = packet['from']
msg = packet['decoded']['payload'].hex() # What exactly is contained in this payload?
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
@ -164,7 +179,7 @@ class MeshtasticClient(object):
snr = packet['rxSnr']
rssi = packet['rxRssi']
logging.info(f'Position recieved: {id} - {name}: {longitude}, {latitude}, {altitude}m (SNR: {snr}, RSSI: {rssi}) - {msg}')
logging.info(f'{id} - {name}: {longitude}, {latitude}, {altitude}m (SNR: {snr}, RSSI: {rssi}) - {msg}')
def event_text(self, packet: dict, interface):
@ -181,7 +196,7 @@ class MeshtasticClient(object):
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}')
logging.info(f'{id} {name} -> {target}: {msg}')
print(packet)

88
meshmqtt.py Normal file
View File

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