refactor aprs for argus

This commit is contained in:
Craig McDaniel
2026-02-28 14:28:31 -06:00
parent d065942bb9
commit 8e9f396eff
6 changed files with 232 additions and 137 deletions
+21 -31
View File
@@ -1,10 +1,9 @@
# Role: scanner_airspy_aprs
# Configure AirSpy SDR for APRS reception on VHF 144.390 MHz
#
# This role sets up:
# - airspy-fmradion for FM demodulation
# - Direwolf for APRS packet decoding
# - Full AX.25 stack integration (receive-only)
# - Linux kernel AX.25 stack for receive only demodulation of AX.25 packets
#
# NOTE: This role depends on scanner_digirig having been run first to set up:
# - AX.25 packages and kernel modules
@@ -12,61 +11,52 @@
# - Base directory structure
#
- name: Create direwolf directories
- name: Create directories
file:
path: "{{item}}"
state: directory
owner: busnet
group: busnet
owner: argus
group: argus
mode: u=rwx,g=rwx,o=rx
with_items:
- /opt/busnet/direwolf
- /opt/busnet/direwolf/config
- /opt/busnet/direwolf/logs
###################################################################################################
# Package Dependencies
###################################################################################################
- name: Install socat for PTY bridging
ansible.builtin.apt:
name:
- socat
state: present
- /opt/argus/scripts
- /opt/argus/direwolf
- /opt/argus/direwolf/config
- /opt/argus/direwolf/logs
###################################################################################################
# AirSpy bash script and Direwolf config
###################################################################################################
- name: Copy airspy_aprs.sh AX.25 stack script
- name: Copy aprs_airspy.sh AX.25 stack script
copy:
src: "templates/airspy_aprs.sh"
dest: "/opt/busnet/direwolf/airspy_aprs.sh"
owner: busnet
group: busnet
src: "templates/aprs_airspy.sh"
dest: "/opt/argus/scripts/aprs_airspy.sh"
owner: argus
group: argus
mode: "0755"
- name: Copy Direwolf config for AirSpy APRS
template:
src: "templates/direwolf_airspy_aprs.conf"
dest: "/opt/busnet/direwolf/config/airspy-aprs.conf"
owner: busnet
group: busnet
dest: "/opt/argus/direwolf/config/airspy-aprs.conf"
owner: argus
group: argus
###################################################################################################
# SYSTEMD
###################################################################################################
- name: Install systemd unit file for AirSpy APRS
- name: Install systemd unit file for argus-aprs-airspy.service.j2
template:
src: "templates/airspy-aprs.service.j2"
dest: "/etc/systemd/system/airspy-aprs.service"
src: "templates/argus-aprs-airspy.service.j2"
dest: "/etc/systemd/system/argus-aprs-airspy.service"
owner: root
group: root
mode: "0644"
- name: Enable airspy-aprs service
- name: Enable argus-aprs-airspy.service
systemd:
name: "airspy-aprs"
name: "argus-aprs-airspy.service"
daemon_reload: true
enabled: true
@@ -1,18 +0,0 @@
[Unit]
Description=AirSpy APRS Receiver with Direwolf and AX.25 Stack
Documentation=https://github.com/wb2osz/direwolf
After=network.target
[Service]
Type=simple
ExecStart=/opt/busnet/direwolf/airspy_aprs.sh
Restart=on-failure
RestartSec=10
User=busnet
Group=busnet
# Give it time to start up properly
TimeoutStartSec=30
[Install]
WantedBy=multi-user.target
@@ -1,87 +0,0 @@
#!/bin/bash
# @author Craig McDaniel
#
# This script runs airspy-fmradion to demodulate FM signals from the AirSpy SDR, pipes the audio
# to Direwolf for APRS packet decoding, then sets up the AX.25 stack for kernel integration.
#
# This is receive-only APRS monitoring on VHF 144.390 MHz.
#
# Configuration
FMRADION_CMD="airspy-fmradion -m nbfm -t airspy -q -M -c freq=144390000,srate=2500000,lagc,magc,lgain=14,mgain=15,vgain=15"
DIREWOLF_CMD="direwolf -q d -c /opt/busnet/direwolf/config/airspy-aprs.conf -n 1 -r 48000 -b 16 -"
SOCAT_CMD="/usr/bin/socat PTY,raw,echo=0,link=/dev/radio/airspy-tnc TCP4:127.0.0.1:8003"
KISSATTACH_CMD="/usr/sbin/kissattach /dev/radio/airspy-tnc airspy"
KISSPARAMS_CMD="kissparms -c 5 -p airspy"
cleanup() {
echo
echo "Exit signal detected. Cleaning up..."
trap - SIGTERM # prevent recursion
kill 0
exit
}
# Trap exit signals
trap cleanup SIGINT SIGTERM
echo
echo "======================================================================"
echo "Starting AirSpy FM Demodulator → Direwolf pipeline..."
echo "======================================================================"
echo
# Start the pipeline: airspy-fmradion pipes audio to direwolf
$FMRADION_CMD | $DIREWOLF_CMD &
PIPELINE_PID=$!
sleep 1
if ! kill -0 $PIPELINE_PID 2>/dev/null; then
echo "Pipeline failed to start."
exit 1
fi
# Wait 3 seconds for Direwolf to initialize and open KISS TCP port 8003
sleep 3
echo
echo "======================================================================"
echo "Starting socat PTY bridge..."
echo "======================================================================"
echo
# Start this in the background
sudo $SOCAT_CMD &
SOCAT_PID=$!
if ! kill -0 $SOCAT_PID 2>/dev/null; then
echo "Socat failed to start."
kill $PIPELINE_PID
exit 1
fi
# Wait for PTY device to be created
sleep 1
echo
echo "======================================================================"
echo "Starting kissattach and setting kissparams..."
echo "======================================================================"
echo
# Kissattach will automatically fork itself in background.
sudo $KISSATTACH_CMD
if [ $? -ne 0 ]; then
echo "kissattach failed to start."
cleanup
fi
# Configure AX.25 parameters for receive only. This is probably not necessary?
# Maybe somebody will do a bunch of tests and see if it actually matters one day.
sudo $KISSPARAMS_CMD
echo
echo "AirSpy APRS stack online! Monitoring processes..."
# 'wait -n' waits for ANY background process to exit.
# It returns as soon as the pipeline or socat dies.
wait -n
# If we get here, one of the processes died.
cleanup
@@ -0,0 +1,193 @@
#!/bin/bash
# Start the APRS Receive-Only APRS AX.25 Stack
# @author Craig McDaniel
#
# This script runs airspy-fmradion to demodulate FM signals from the AirSpy SDR, pipes the audio
# to Direwolf for APRS packet decoding, then sets up the AX.25 stack for kernel integration.
#
# This is receive-only APRS monitoring on VHF 144.390 MHz.
#
#
# Here's a diagram of the stack this creates:
# -------------------------------------------------------------------------------------------------
#
# RF signals! _ RF signals!
# \ _/
# \ /
# +--------+
# | AirSpy | ----- RF signals!
# +--------+
# |
# | USB cable
# |
# +----|----------------------------------------------------------------------------+
# | | This is your computer. The things in here are virtual components. |
# | | |
# | +-----------------+ |
# | | airspy-fmradion | <--- software radio FM demodulator |
# | +-----------------+ |
# | | |
# | | <-- UNIX pipe! |
# | | |
# | +-----------+ +-------+ +------------+ Linux kernel |
# | | Direwolf | <---> | Socat | <----> | Kissattach | <------> ax1 interface |
# | +-----------+ +-------+ +------------+ / |
# | | /--------+ |
# | AGWPE port / |
# | available for +---------------+ |
# | receive only use. | mheard daemon | |
# | +---------------+ |
# +---------------------------------------------------------------------------------+
# This software demodulates the FM signal in software from the AirSpy receiver.
FMRADION_CMD="airspy-fmradion -m nbfm -t airspy -q -M -c freq=144390000,srate=2500000,lagc,magc,lgain=14,mgain=15,vgain=15"
# Command to execute direwolf including any options and location of config file.
DIREWOLF_CMD="direwolf -q d -c /opt/argus/direwolf/config/airspy-aprs.conf -n 1 -r 48000 -b 16 -"
# The AX.25 port for the radio defined in /etc/ax25/axports. Note that this is not the same thing as
# the kernel AX.25 interfaces, which are named things like ax0, ax1, etc...
AX25_PORT_NAME="airspy"
# The PTY/TTY (whatever it's called) that socat will create for kissattach
TNC_DEV="/dev/radio/airspy-tnc"
# This is the socat command to that connects one end of a virtual pipe to Direwolf's KISS TCP port,
# and the other end to the PTY/TTY. The IP and port specified here must match the Direwolf config
# file.
SOCAT_CMD="/usr/bin/socat PTY,raw,echo=0,link=${TNC_DEV} TCP4:127.0.0.1:8003"
# These are the commands to create the kernel AX.25 interface using the kissattach utility.
KISSATTACH_CMD="/usr/sbin/kissattach ${TNC_DEV} ${AX25_PORT_NAME}"
KISSPARAMS_CMD="kissparms -c 5 -p ${AX25_PORT_NAME}"
# Cleanup the AX.25 stack
cleanup_stack()
{
# Kill kissattach explicitly (it forks, so we can't track its PID)
sudo pkill -f "kissattach ${TNC_DEV} ${AX25_PORT_NAME}"
sleep 1
# Kill socat by PID
if [ -n "$SOCAT_PID" ]; then
sudo kill $SOCAT_PID 2>/dev/null
sleep 1
fi
# Kill demodulator/direwolf by PID and wait for it to finish
if [ -n "$PIPELINE_PID" ] && kill -0 $PIPELINE_PID 2>/dev/null; then
kill $PIPELINE_PID 2>/dev/null
wait $PIPELINE_PID 2>/dev/null
fi
}
# Start the AX.25 stack for APRS receive only.
start_stack()
{
# We start the software that demodulates the FM signal then pipes its output stream directly
# to Direwolf. Direwolf config says to read from stdin.
$FMRADION_CMD | $DIREWOLF_CMD &
PIPELINE_PID=$!
# NOTE: If Direwolf encounters an error, it will just WAIT something like 15 seconds before
# exiting. It does not fail fast. That means that the following check will pass, but then
# we go to move on to create socat which will fail. I don't have a good solution for this
# case just yet, but I'm recording this note here.
if ! kill -0 $PIPELINE_PID; then
echo "Direwolf failed to start."
cleanup_stack
exit 1
fi
echo "Fm Demodulator --> Direwolf started (PID: $PIPELINE_PID)"
# Start socat PTY bridge.
# socat creates a full duplex/bidirectional pipe between a serial port ${TNC_DEV} and Direwolf's
# KISS TCP interface, which is running on localhost:8001. The kissattach program needs a serial
# port to work, and this is how we accomplish connecting kissattach to direwolf.
sleep 3
sudo $SOCAT_CMD &
SOCAT_PID=$!
if ! kill -0 $SOCAT_PID; then
echo "Socat failed to start."
cleanup_stack
exit 1
fi
echo "socat started (PID: $SOCAT_PID)"
# Run kissatach to create an AX.25 network interface on the system.
# Kissattach annoyingly forks itself in the background, so we can't directly monitor the process.
# Kissattach cannot use KISS over TCP directly to Direwolf. It expects a serial terminal to exist,
# which is why we use socat to create the pipe between Direwolf's KISS TCP port and this serial
# device.
sleep 1
sudo $KISSATTACH_CMD
if [ $? -ne 0 ]; then
echo "kissattach failed to start."
cleanup_stack
fi
# Configure AX.25 parameters for receive only. This is probably not necessary?
# Maybe somebody will do a bunch of tests and see if it actually matters one day.
sudo $KISSPARAMS_CMD
# Restart mheardd daemon. If mheardd was started before ax1, ax2 or other AX.25 kernel interface
# was created, and now we receive AX.25 packets on this new interface we just created, mheardd
# just throws errors about a new, unknown interface. Ugh.
if sudo systemctl is-enabled --quiet mheardd.service; then
echo "Restarting mheardd..."
sudo systemctl restart mheardd.service
fi
}
# This is our POSIX signal handler so we cleanly shut down before exiting.
handle_signal()
{
echo "We received a SIGINT or SIGTERM signal. Shutting down APRS Receive-only AX.25 stack."
cleanup_stack
exit 0
}
###################################################################################################
#
# MAIN ENTRY POINT
#
###################################################################################################
echo ""
echo "======================================================================"
echo "APRS Receive-only AX.25 Stack Started"
echo "======================================================================"
echo ""
# This is the bash way of intercepting these signals. We execute the cleanup function when we receive
# either one.
trap handle_signal SIGINT SIGTERM
# Load required kernel modules
echo "Loading kernel modules..."
sudo modprobe ax25
if [ $? -ne 0 ]; then
echo "Failed to load ax25 kernel module"
exit 1
fi
# Clean up any orphaned kissattach processes from previous runs. We must do this because kissattach
# forks itself in the background, and we therefore cannot keep track of its PID. We use pkill to
# make sure no previous kissattach is running.
sudo pkill -f "kissattach ${TNC_DEV} ${AX25_PORT_NAME}"
sleep 1
# Start the AX.25 stack
start_stack
echo "All processes started. Monitoring via wait..."
# 'wait -n' waits for ANY background process to exit.
# It returns as soon as fmradion, direwolf, or socat exits. Kissattach forks itself so we can't wait on it.
wait -n
# If we get here, one of the processes died. Cleanup the stack and exit.
cleanup_stack
@@ -0,0 +1,17 @@
[Unit]
Description=APRS AirSpy Receive-Only AX.25 Stack
After=network.target
[Service]
Type=simple
ExecStart=/opt/argus/scripts/aprs_airspy.sh
Restart=on-failure
RestartSec=10
User=argus
Group=argus
# Give it time to start up properly
TimeoutStartSec=30
[Install]
WantedBy=multi-user.target
@@ -8,7 +8,7 @@
ACHANNELS 1
ADEVICE stdin null
ARATE 48000
MYCALL K0BIT-2
MYCALL N0CAL
MODEM 1200
AGWPORT 8002