First version

master v1.0.0
marcel 1 year ago
commit 8c7895ea92
  1. 13
      CHANGELOG.md
  2. 49
      README.md
  3. 187
      aprs_telemetry_to_mqtt.py
  4. 17
      aprs_telemetry_to_mqtt.yml

@ -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…
Cancel
Save