Added documentation on firmware hacking & hardware provisioning, started working on the MQTT interface
This commit is contained in:
parent
e0754f1f02
commit
d9ec08e912
@ -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...
|
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
|
## Compile & flash firmware
|
||||||
- Select `PlatformIO: Pick Project Environment` & select your board.
|
- Select `PlatformIO: Pick Project Environment` & select your board.
|
||||||
- Run `PLatformIO: Build` to compile the firmware.
|
- Run `PLatformIO: Build` to compile the firmware.
|
15
README.md
15
README.md
@ -1,28 +1,25 @@
|
|||||||
# Meshtastic Utilities
|
# 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).
|
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 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)
|
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
|
## Documentation
|
||||||
- The node id formula is: f'!{hex(node_num)[2:]}'
|
- [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 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)*
|
- 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.
|
- `event_node` event is called **AS** we are defining the interface, so using `self.interface` in that callback will error.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- Asyncronous meshtastic interface *(Priority)*
|
- Asyncronous meshtastic interface
|
||||||
- MQTT interface with working decryption
|
- MQTT interface with working decryption
|
||||||
- Documentation on MQTT bridging for high availability
|
- 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)*
|
- 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
41
SETUP.md
Normal 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.
|
@ -8,9 +8,6 @@ import time
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import meshtastic
|
import meshtastic
|
||||||
from meshtastic.serial_interface import SerialInterface
|
|
||||||
from meshtastic.util import findPorts
|
|
||||||
from meshtastic.tcp_interface import TCPInterface
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError('meshtastic library not found (pip install meshtastic)')
|
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')
|
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):
|
class MeshtasticClient(object):
|
||||||
def __init__(self):
|
def __init__(self, option: str, value: str):
|
||||||
self.interface = None
|
self.interface = None
|
||||||
self.me = {}
|
self.me = {}
|
||||||
self.nodes = {}
|
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):
|
def connect(self, option: str, value: str):
|
||||||
'''
|
'''
|
||||||
@ -56,16 +68,16 @@ class MeshtasticClient(object):
|
|||||||
raise SystemExit('Invalid interface option')
|
raise SystemExit('Invalid interface option')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'Failed to connect to the Meshtastic interface: {e}')
|
logging.error(f'Failed to connect to the radio: {e}')
|
||||||
logging.error('Retrying in 10 seconds...')
|
logging.error('Retrying in 15 seconds...')
|
||||||
time.sleep(10)
|
time.sleep(15)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.me = self.interface.getMyNodeInfo()
|
self.me = self.interface.getMyNodeInfo()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def sendmsg(self, message: str, destination: int, channelIndex: int = 0):
|
def send(self, message: str):
|
||||||
'''
|
'''
|
||||||
Send a message to the Meshtastic interface
|
Send a message to the Meshtastic interface
|
||||||
|
|
||||||
@ -76,7 +88,7 @@ class MeshtasticClient(object):
|
|||||||
logging.warning('Message exceeds 255 characters')
|
logging.warning('Message exceeds 255 characters')
|
||||||
message = message[:255]
|
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}')
|
logging.info(f'Sent broadcast message: {message}')
|
||||||
|
|
||||||
@ -115,7 +127,7 @@ class MeshtasticClient(object):
|
|||||||
:param interface: Meshtastic interface
|
: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):
|
def event_disconnect(self, interface, topic=pub.AUTO_TOPIC):
|
||||||
@ -141,9 +153,12 @@ class MeshtasticClient(object):
|
|||||||
:param node: Node information
|
:param node: Node information
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
# Node ID Formula = f'!{hex(node_num)[2:]}'
|
||||||
|
|
||||||
self.nodes[node['num']] = node
|
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):
|
def event_position(self, packet: dict, interface):
|
||||||
@ -155,7 +170,7 @@ class MeshtasticClient(object):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
sender = packet['from']
|
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 '
|
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'
|
name = self.nodes[sender]['user']['longName'] if sender in self.nodes else 'UNK'
|
||||||
longitude = packet['decoded']['position']['longitudeI'] / 1e7
|
longitude = packet['decoded']['position']['longitudeI'] / 1e7
|
||||||
@ -164,7 +179,7 @@ class MeshtasticClient(object):
|
|||||||
snr = packet['rxSnr']
|
snr = packet['rxSnr']
|
||||||
rssi = packet['rxRssi']
|
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):
|
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'
|
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'
|
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)
|
print(packet)
|
||||||
|
|
||||||
|
|
88
meshmqtt.py
Normal file
88
meshmqtt.py
Normal 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
|
Loading…
Reference in New Issue
Block a user