From e62ae04a21b78d2144fa67124a38a562ee222ae8 Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Thu, 21 Nov 2024 19:48:56 -0500 Subject: [PATCH] aprs-mqtt bridge --- docker/containers/aprs-to-mqtt.dockerfile | 13 ++++ ...qtt-ingest-radio-spots.docker-compose.yaml | 9 +++ scripts/aprs-to-mqtt | 65 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 docker/containers/aprs-to-mqtt.dockerfile create mode 100755 scripts/aprs-to-mqtt diff --git a/docker/containers/aprs-to-mqtt.dockerfile b/docker/containers/aprs-to-mqtt.dockerfile new file mode 100644 index 0000000..e36f4bd --- /dev/null +++ b/docker/containers/aprs-to-mqtt.dockerfile @@ -0,0 +1,13 @@ +FROM python:3.13 + +# External config +ENV MQTT_HOST= + +# Dependencies +RUN pip install paho-mqtt + +# The script itself +COPY ./scripts/aprs-to-mqtt /aprs-to-mqtt.py + +# Run config +CMD ["sh", "-c", "python /aprs-to-mqtt.py $MQTT_HOST"] \ No newline at end of file diff --git a/docker/stacks/mqtt-ingest-radio-spots.docker-compose.yaml b/docker/stacks/mqtt-ingest-radio-spots.docker-compose.yaml index f1a43e7..7de9ec3 100644 --- a/docker/stacks/mqtt-ingest-radio-spots.docker-compose.yaml +++ b/docker/stacks/mqtt-ingest-radio-spots.docker-compose.yaml @@ -25,5 +25,14 @@ services: dockerfile: docker/containers/pskreporter-to-mqtt.dockerfile restart: unless-stopped env_file: .env + environment: + - MQTT_HOST=${MQTT_HOST} + + aprs_to_mqtt: + build: + context: ../.. + dockerfile: docker/containers/aprs-to-mqtt.dockerfile + restart: unless-stopped + env_file: .env environment: - MQTT_HOST=${MQTT_HOST} \ No newline at end of file diff --git a/scripts/aprs-to-mqtt b/scripts/aprs-to-mqtt new file mode 100755 index 0000000..d4491c0 --- /dev/null +++ b/scripts/aprs-to-mqtt @@ -0,0 +1,65 @@ +#! /usr/bin/env python3 +import argparse +import sys +import logging +import socket +import re +import json +import paho.mqtt.client as mqtt # pip install paho-mqtt +from datetime import datetime, UTC + +logger = logging.getLogger(__name__) +def main() -> int: + # Handle program arguments + ap = argparse.ArgumentParser(prog='aprs-to-mqtt', description='Reflects APRS spots to an MQTT broker') + ap.add_argument("mqtt_broker", help="MQTT broker to connect to") + ap.add_argument('-v', '--verbose', help='Enable verbose logging', action='store_true') + args = ap.parse_args() + + # Configure logging + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format='%(levelname)s: %(message)s', + ) + + # Connect to the MQTT broker + mqtt_client = mqtt.Client() + mqtt_client.connect(args.mqtt_broker) + + # Connect to APRS-IS + aprs_socket = socket.create_connection(('rotate.aprs.net', 10152)) + + # Authenticate with the core server + aprs_socket.sendall(b'user n0call pass -1\r\n') + + # Handle incoming packets + buffer = b'' + while True: + + # Read incoming packet (may contain multiple actual APRS packets) + data = aprs_socket.recv(1024) + if not data: + break + + # Append to buffer + buffer += data + + # Split the packet into aprs packets + if b'\r\n' in buffer: + chunks = buffer.splitlines() + buffer = chunks.pop() # Store the un-finished chunk for later + for aprs_packet in chunks: + if aprs_packet.startswith(b'#'): + continue + + # Try to chop off the sender callsign + sender = aprs_packet.split(b'>', 1)[0] + + # Send over MQTT + logger.debug(f"Received APRS packet from {sender.decode('utf-8').upper()}: {aprs_packet}") + mqtt_client.publish(f"radio/aprs/raw/{sender.decode('utf-8').upper()}", aprs_packet) + + return 0 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file