commit
8c7895ea92
4 changed files with 266 additions and 0 deletions
@ -0,0 +1,13 @@ |
||||
# Changelog |
||||
|
||||
All notable changes to this project will be documented in this file. |
||||
|
||||
Added : for new features. |
||||
Changed : for changes in existing functionality. |
||||
Deprecated: for soon-to-be removed features. |
||||
Removed : for now removed features. |
||||
Fixed : for any bug fixes. |
||||
Security : in case of vulnerabilities. |
||||
|
||||
## [1.0.0] - 2023-12-08 |
||||
First working version. |
@ -0,0 +1,49 @@ |
||||
# APRS telemetry to MQTT bridge |
||||
|
||||
The APRS telemetry to MQTT bridge can relay PE1RXF telemetry to an MQTT broker. |
||||
|
||||
This program is a utility for the APRS telemetry system used by PE1RXF. The telemetry is embedded in an APRS message which can travel over the existing APRS network. For more information about this open protocol visit this link: https://www.meezenest.nl/mees-elektronica/projects/aprs_telemetry/APRS_protocol_nodes_PE1RXF.pdf |
||||
|
||||
## Configuration |
||||
|
||||
This first version reads the telemetry files as generated by APRS server software. This software can be found here: https://git.meezenest.nl/marcel/pe1rxf-aprs-server |
||||
|
||||
It would be nice when future versions would use the ax.25 stack instead of the APRS server software (like the utility "aprs-mqtt-bridge" already does). This would make it more universal. But for now, it works just fine! |
||||
|
||||
The program is configured via a YAML file. The global section defines the MQTT broker to which to publish the data. |
||||
|
||||
The topic section specifies the telemetry files to read. The descriptions of the fields are defined here as well. |
||||
|
||||
``` |
||||
# Global settings apply to all other entries |
||||
global: |
||||
broker: pe1rxf.ampr.org # The MQTT broker we are going to use |
||||
port: 1883 # The tcp port of the MQTT broker |
||||
topic_root: hamnet_aprs_nodes # MQTT topic root |
||||
poll_rate: 300 # Number of second between polling |
||||
topics: |
||||
# MQTT topic: each telemtry node has its own name (sub root) and must be unique |
||||
- name: solar_generator |
||||
telemetry_file: /home/marcel/ham/aprs_utils/aprs_log/latest_telemetry_PE1RXF-9.dat |
||||
# Defines the names of the values in the telemetry_file. Also defines the number of entries in this file. |
||||
# So make sure the number of descriptions match the number of values in the telemetry_file! |
||||
description: |
||||
- soc |
||||
- voltage |
||||
- power |
||||
- temperature |
||||
|
||||
``` |
||||
|
||||
## Requirements |
||||
|
||||
- Python3 |
||||
- pathlib |
||||
- yaml |
||||
- paho-mqtt |
||||
|
||||
Future versions probably will also need: |
||||
|
||||
- Python AX.25 Module for Python3 (https://github.com/ha5di/pyax25) |
||||
- Linux AX.25 stack |
||||
|
@ -0,0 +1,187 @@ |
||||
#!/usr/bin/python3 |
||||
""" |
||||
A bridge between APRS messaging and MQTT, designed to control my lora_aprs_node_pico |
||||
|
||||
(C)2022-2023 M.T. Konstapel https://meezenest.nl/mees |
||||
|
||||
This file is part of aprs-mqtt-bridge. |
||||
|
||||
aprs-mqtt-bridge is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
aprs-mqtt-bridge is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with aprs-mqtt-bridge. If not, see <https://www.gnu.org/licenses/>. |
||||
""" |
||||
import sys |
||||
import random |
||||
import time |
||||
import os |
||||
from pathlib import Path |
||||
import yaml |
||||
from yaml.loader import SafeLoader |
||||
from paho.mqtt import client as mqtt_client |
||||
import csv |
||||
|
||||
configuration_file = "aprs_telemetry_to_mqtt.yml" |
||||
|
||||
# This is where we keep our settings |
||||
class mqtt_settings: |
||||
#broker |
||||
#topic_root |
||||
#port |
||||
#client_id |
||||
#transmit_rate |
||||
#retry |
||||
#topics |
||||
#poll |
||||
state = 'ready' |
||||
aprs_state = 'idle' |
||||
pass |
||||
mqtt = mqtt_settings() |
||||
|
||||
def connect_mqtt(): |
||||
def on_connect(client, userdata, flags, rc): |
||||
if rc == 0: |
||||
print("Connected to MQTT Broker!") |
||||
else: |
||||
print("Failed to connect, return code %d\n", rc) |
||||
# Set Connecting Client ID |
||||
client = mqtt_client.Client(mqtt.client_id) |
||||
#client.username_pw_set(username, password) |
||||
client.on_connect = on_connect |
||||
client.connect(mqtt.broker, mqtt.port) |
||||
return client |
||||
|
||||
def publish(client, topic, message): |
||||
result = client.publish(topic, message) |
||||
status = result[0] |
||||
if status == 0: |
||||
print(f"Send `{message}` to topic `{topic}`") |
||||
else: |
||||
print(f"Failed to send message to topic {topic}") |
||||
|
||||
def subscribe(client: mqtt_client, topic): |
||||
def on_message(client, userdata, message): |
||||
received_payload = message.payload.decode() |
||||
received_topic = Path(message.topic).name |
||||
print(f"Received `{received_payload}` from `{message.topic}` topic") |
||||
# Find corresponding topic in configuration-file and send this to the next function |
||||
for topics in mqtt.topics: |
||||
if received_topic == topics['name']: |
||||
#print ('Found topic in list!') |
||||
#print (topics) |
||||
process_message(topics, received_payload) |
||||
break |
||||
# print(topic['name']) |
||||
# print(topic['command']) |
||||
|
||||
client.subscribe(topic) |
||||
client.on_message = on_message |
||||
|
||||
def read_config(): |
||||
try: |
||||
with open(configuration_file) as f: |
||||
cfg = yaml.load(f, Loader=SafeLoader) |
||||
|
||||
mqtt.topics = cfg['topics'] |
||||
#print(mqtt.topics) |
||||
#for topic in mqtt.topics: |
||||
# print(topic['name']) |
||||
# print(topic['command']) |
||||
except: |
||||
print ("Configuration file ./" + configuration_file + " not found.") |
||||
sys.exit(1) |
||||
|
||||
try: |
||||
mqtt.broker = cfg['global']['broker'] |
||||
except: |
||||
print ("Error in configuration file: no broker defined.") |
||||
sys.exit(1) |
||||
try: |
||||
mqtt.port = cfg['global']['port'] |
||||
except: |
||||
print ("Error in configuration file: no port defined.") |
||||
sys.exit(1) |
||||
try: |
||||
mqtt.topic_root = cfg['global']['topic_root'] |
||||
except: |
||||
print ("Error in configuration file: no topic defined.") |
||||
sys.exit(1) |
||||
try: |
||||
mqtt.poll_rate = cfg['global']['poll_rate'] |
||||
except: |
||||
print ("Error in configuration file: no poll_rate defined.") |
||||
sys.exit(1) |
||||
|
||||
mqtt.client_id = f'{mqtt.topic_root}-{random.randint(0, 1000)}' |
||||
|
||||
print (mqtt.broker) |
||||
print (mqtt.topic_root) |
||||
print (mqtt.port) |
||||
print (mqtt.client_id) |
||||
|
||||
# Loop through all topics and activate them |
||||
def add_subscribtions_from_configfile(client): |
||||
for topics in mqtt.topics: |
||||
current_topic = mqtt.topic_root + '/' + topics['name'] |
||||
subscribe(client,current_topic) |
||||
print('Topic ' + topics['name'] + ' added') |
||||
|
||||
# Loop through all topics and send telemtry to broker |
||||
def send_telemetry_to_broker(client): |
||||
for topics in mqtt.topics: |
||||
|
||||
#for topics in topics.description: |
||||
#print('Description ' + topics['description'] + ' added') |
||||
#print(topics['description']) |
||||
# Loop through descriptions and send values from telemtry file along with it |
||||
#for descr in topics['description']: |
||||
for index, descr in enumerate(topics['description'], start=0): |
||||
|
||||
#print(descr) |
||||
|
||||
# Read telemetry data |
||||
with open(topics['telemetry_file'], newline='') as csvfile: |
||||
telemetry_reader = csv.reader(csvfile, delimiter=',', quotechar='|') |
||||
# there should only be one row in the telemetry file, but try to read all lines anyway |
||||
for row in telemetry_reader: |
||||
current_topic = mqtt.topic_root + '/' + topics['name'] + '/' + descr |
||||
publish(client,current_topic,row[index]) |
||||
#print(current_topic + '=' + row[index]) |
||||
|
||||
|
||||
def run(): |
||||
read_config() |
||||
|
||||
client = connect_mqtt() |
||||
|
||||
add_subscribtions_from_configfile(client) |
||||
#topic = mqtt.topic_root + '/set' |
||||
#subscribe(client,topic) |
||||
|
||||
client.loop_start() |
||||
|
||||
while True: |
||||
|
||||
#topic = mqtt.topic_root + '/test' |
||||
#publish(client,topic,mqtt.aprs_state) |
||||
|
||||
send_telemetry_to_broker(client) |
||||
|
||||
time.sleep(mqtt.poll_rate) # Sleep (time defined in yml file) |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
#sys.stdout = sys.stderr = open('debug.log', 'w') |
||||
run() |
||||
|
@ -0,0 +1,17 @@ |
||||
# Global settings apply to all other entries |
||||
global: |
||||
broker: pe1rxf.ampr.org # The MQTT broker we are going to use |
||||
port: 1883 # The tcp port of the MQTT broker |
||||
topic_root: hamnet_aprs_nodes # MQTT topic root |
||||
poll_rate: 300 # Number of second between polling |
||||
topics: |
||||
# MQTT topic: each telemtry node has its own name (sub root) and must be unique |
||||
- name: solar_generator |
||||
telemetry_file: /home/marcel/ham/aprs_utils/aprs_log/latest_telemetry_PE1RXF-9.dat |
||||
# Defines the names of the values in the telemetry_file. Also defines the number of entries in this file. |
||||
# So make sure the number of descriptions match the number of values in the telemetry_file! |
||||
description: |
||||
- soc |
||||
- voltage |
||||
- power |
||||
- temperature |
Loading…
Reference in new issue