One of the lessons learned from Launch 3 was we needed way to verify all payloads were working before releasing the balloon. For an APRS transmitter, this means getting the packets to the APRS Internet Service, where packets can then be viewed on many different websites such as aprs.fi.
Our usual launch site is too far away from an APRS IGate, so we had no way to verify that Kazu's RP2040 APRS transmitter was working for Launch 3. This APRS transmitter only puts out around 5 mW (~6 dBm), which is not enough power to make it through the trees and buildings to the nearest IGate. Unfortunately, we never heard from it on the flight.
Previous Work
There are several projects for running an APRS IGate on a Raspberry Pi with a RTL-SDR dongle. However, all of these projects use the entire Pi and/or recommend the desktop GUI version of Raspberry Pi OS. Downloading some random ISO to flash on an SD card didn't seem too appealing to me either, what else is installed?
Since I'm a big fan of using docker containers to partition off different programs, and everything else was containerized in my mobile tracking station, I wanted this APRS receiver to also be in a docker container.
I searched the internets far and wide for a simple APRS IGate running inside a docker container, and didn't really find anything. John Boiles wrote pi-rtlsdr-igate-docker, but it didn't work and used environment variables for all the options (making it hard to quickly change configurations).
rtl-aprs-igate
Seeing that lack of containerized APRS IGates, I created the rtl-aprs-igate project. Fundamentally, it's just a docker container that has the RTL-SDR utilities for accessing a RTL-SDR dongle, the Direwolf software APRS demodulation, and a python script which reads the configuration file that sets everything up.
Every time you start or restart this container, the configuration file is re-read and applied, making it very easy to experiment with different options. From the README:
Hardware Required
I recommend the RTL-SDR Blog v3 or v4 dongles, they are only a few dollars more than the cheap Chinese knockoffs and they work so much better.
If you have more than one RTL-SDR dongle, set each one with a unique device_index (serial number). This is an 8-character string (such as SDR00005) set on the RTL-SDR dongle's firmware. Use rtl_eeprom
to program this (instructions below). DO NOT program a device_idx of 00000000 or 00000001 on any RTL-SDR, this causes confusion in the RTL-SDR utilities.
Programming RTL-SDR serial number
Make sure that only one RTL-SDR dongle is plugged into the computer. Jump into a new container to access the rtl_eeprom utility:$ docker run -it --rm --device=/dev/bus/usb ghcr.io/bklofas/rtl-aprs-igate:latest bash
root@2ca01d75885c:/# rtl_eeprom -s SDR00002
Found 1 device(s):
0: Generic RTL2832U OEM
Using device 0: Generic RTL2832U OEM
Found Rafael Micro R820T tuner
Current configuration:
__________________________________________
Vendor ID: 0x0bda
Product ID: 0x2838
Manufacturer: Realtek
Product: RTL2838UHIDIR
Serial number: 0
Serial number enabled: yes
Bias Tee always on: no
Remote wakeup enabled: no
__________________________________________
New configuration:
__________________________________________
Vendor ID: 0x0bda
Product ID: 0x2838
Manufacturer: Realtek
Product: RTL2838UHIDIR
Serial number: SDR00002
Serial number enabled: yes
Bias Tee always on: no
Remote wakeup enabled: no
__________________________________________
Write new configuration to device [y/n]? y
Configuration successfully written.
Please replug the device for changes to take effect.
root@2ca01d75885c:/#
Ensure the RTL-SDR dongle has adequate power. If using a hub, check that it is an active externally-powered hub, not powered from the main computer. Or better yet, plug the RTL-SDR dongle directly into the host USB port, don't use a hub. Raspberry Pi's (and other small embedded computers) have a lower power limit
You'll also need an antenna plugged into the dongle. For mobile use, any 1/4 wave mag mount works well. For home/stationary use, a J-pole or quarter-wave ground plane antenna works well.
Run This Project
This project installs the RTL-SDR drivers and Direwolf into a docker container. No dependencies to install. Total container size is 125 to 160 MB, depending on the host architecture.
1.1 Install Docker
Check to see if docker is already installed by running docker ps
. If this errors out, install Docker by using the convenience script:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
After docker installation, to be able to run docker commands as your non-root user (recommended!!), run:
sudo usermod -aG docker $(whoami)
Reboot your computer afterwards to pick up the changes to group membership.
1.2 RTL DVB Kernel Module Blacklisting
The RTL DVB kernel modules must first be blacklisted on the Docker host (the computer where the RTL-SDR dongle is plugged into). Otherwise your computer will think you want to watch TV with your dongle! RTL-SDR itself is not required on the Docker host. This can be accomplished using the following commands:
echo 'blacklist dvb_usb_rtl28xxu' | sudo tee /etc/modprobe.d/blacklist-dvb_usb_rtl28xxu.conf
sudo modprobe -r dvb_usb_rtl28xxu
If the modprobe -r
command errors, reboot the computer to unload the module.
1.3 Configuration file
The station.conf
file has all of the options for the RTL-SDR dongle and Direwolf.
mkdir -p ~/rtl-aprs-igate
curl -o ~/rtl-aprs-igate/station.conf https://raw.githubusercontent.com/bklofas/rtl-aprs-igate/master/station.conf
vim ~/rtl-aprs-igate/station.conf
Change your local APRS frequency, RTL-SDR device index, callsign, IGate server, etc.
1.4 Run The Container
-
Just to test things out:
docker run -it --rm --device=/dev/bus/usb -v ~/rtl-aprs-igate/station.conf:/station.conf:ro ghcr.io/bklofas/rtl-aprs-igate:latest
- This downloads a pre-built container from the Github container registry.
- Architectures supported are i386, amd64, arm32v6, arm32v7, and arm64. Tested on amd64, arm32v7, and arm64. arm packages run on all RaspberryPi flavors. Your client will automatically download the appropriate architecture.
- This image will start rtl_fm piping audio to direwolf, and show the received packets on STDOUT.
- Make sure at least one RTL-SDR dongle is connected. If you have a device_idx in your station.conf file, it will use that one. Otherwise, it will pick the first available dongle to use.
- Host networking (--network=host) is not required, since traffic is outbound only.
- The generated
rtl_fm
command and direwolf configuration will be displayed at the beginning. - Startup messages and decoded packets will display in the terminal.
- Using the --rm flag will delete the container when you kill it. Otherwise, it will stay around until you prune.
- Ctrl-C to kill.
- If something is broken, you can start the container (without running the rtl_fm command) by appending
bash
on the end of the docker run command
-
For a more permanent setup, run the container in the background:
docker run -d --name rtl-aprs-igate --restart=unless-stopped --log-driver=local --device=/dev/bus/usb -v ~/rtl-aprs-igate/station.conf:/station.conf:ro ghcr.io/bklofas/rtl-aprs-igate:latest
- -d: Start this container in daemon/background mode.
- --name: Name this anything you want.
- --restart=unless-stopped: Automatically restart the container if something happens (reboot, USB problem), unless you have manually stopped the container.
- --log-driver=local: By default, docker uses the json log driver which may fill up your harddrive, depending on how busy your station is. local log driver defaults to 100MB of saved logs, and automatically rotates them.
- --device=: Allows the container to talk to the USB bus to access the RTL-SDR dongle.
- -v: Mounts the config files inside the container.
- View the last 25 log lines with
docker logs -n 25 --follow rtl-aprs-igate
- Stop the container with
docker stop rtl-aprs-igate
- Use
docker restart rtl-aprs-igate
to reload the station.conf configuration file.
Jump Into the Container
If you just want to start a new container but not actually start rtl_fm and direwolf (such as for running the rtl_eeprom utility):
docker run -it --rm --device=/dev/bus/usb -v ~/rtl-aprs-igate/station.conf:/station.conf:ro ghcr.io/bklofas/rtl-aprs-igate:latest bash
If you're already running this container in background/daemon mode, you can jump into the running container with docker exec -it rtl-aprs-igate bash
Once you're inside the container, you can run any of the RTL-SDR utilities manually such as rtl_eeprom
or rtl_test
, or python3 ./run.sh
to run the script, or direwolf
and options to run direwolf.
Build Container Locally
To build this container locally, check out this repository and build with docker build -t rtl-aprs-igate .
Image size will be between 140 and 170 MB depending on your computer architecture.
Alternatively you can build the container with docker build -t rtl-aprs-igate https://github.com/bklofas/rtl-aprs-igate.git
If you'd like to build the container against a branch docker build -t rtl-aprs-igate-branch https://github.com/bklofas/rtl-aprs-igate.git#branch_name
Output
When you start the container, the run.py script prints a bunch of stuff to STDOUT:
- First it prints the RTL-SDR configuration settings. Verify these look good.
- Then it prints the Direwolf options, which is written into the direwolf.conf file into the container. Verify these look good.
- Then it prints the
rtl_fm | direwolf
command. - Then Direwolf and RTL-SDR will start up, and print all of their startup messages. Unfortunately, all of the messages are intertwined with each other.
- At the end, the APRS-IS server and server status link will be printed. This shows that you a
- If everything is working, APRS packets will be printed as they are received.
If you have a previous direwolf.conf file, you can bind-mount it into the container in / and that will be used.
user@laptop:~$ docker run -it --rm --device=/dev/bus/usb -v ~/rtl-aprs-igate/station.conf:/station.conf:ro ghcr.io/bklofas/rtl-aprs-igate:latest
Frequency: 144.390 MHz
Device_index: SDR00002
PPM: 0.0
Bias-Tee: False
Gain: -1.0
# Direwolf.conf file generated: 2025-03-07 22:18:04.289086
ADEVICE null null
CHANNEL 0
MYCALL KF6ZEO-10
IGSERVER noam.aprs2.net
IGLOGIN KF6ZEO-10 XXXXX
PBEACON sendto=IG delay=0:30 every=10 symbol="igate" overlay=R lat=37.7462 long=-122.4284 comment="Docker Direwolf + RTL-SDR"
command: rtl_fm -f 144.39M -d SDR00002 | direwolf -c direwolf.conf -r 24000 -
Dire Wolf version 1.7
Dire Wolf requires only privileges available to ordinary users.
Running this as root is an unnecessary security risk.
Reading config file direwolf.conf
Audio input device for receive: stdin (channel 0)
Audio out device for transmit: null (channel 0)
Channel 0: 1200 baud, AFSK 1200 & 2200 Hz, A+, 24000 sample rate.
Found 1 device(s):
Note: PTT not configured for channel 0. (Ignore this if using VOX.)
Ready to accept AGW client application 0 on port 8000 ...
Ready to accept KISS TCP client application 0 on port 8001 ...
Found 3 device(s):
0: Realtek, RTL2838UHIDIR, SN: 00000005
1: Realtek, RTL2838UHIDIR, SN: SDR00002
2: Realtek, RTL2838UHIDIR, SN: 00000006
Using device 1: Generic RTL2832U OEM
Found Rafael Micro R820T tuner
Tuner gain set to automatic.
Tuned to 144642000 Hz.
Oversampling input by: 42x.
Oversampling output by: 1x.
Buffer size: 8.13ms
Exact sample rate is: 1008000.009613 Hz
Sampling at 1008000 S/s.
Output at 24000 Hz.
Now connected to IGate server noam.aprs2.net (34.139.102.149)
Check server status here http://34.139.102.149:14501
Digipeater WIDE1 (probably BKELEY) audio level = 66(10/10) |||||____
[0.2] K2DEN-10>APDW16,BKELEY,WIDE1*,WIDE2-1:!3740.73NI12224.43W#PHG4540K2DEN-10 iGate Brisbane CA
Position, I-gate equipped digipeater, DireWolf, WB2OSZ, 16 W height(HAAT)=320ft=98m 4dBi omni
N 37 40.7300, W 122 24.4300
K2DEN-10 iGate Brisbane CA
[ig] # aprsc 2.1.19-g730c5c0
[ig] # logresp KF6ZEO-10 verified, server T2PR
Digipeater WIDE2 (probably BKELEY) audio level = 72(9/12) |||||____
[0.2] K6KKP-3>S7TUXP,WA6TOW-2,WIDE1,BKELEY,WIDE2*:`2++l E>/`"5w}_1<0x0d>
MIC-E, normal car (side view), Yaesu FTM-300D, In Service
N 37 45.8000, W 122 15.1500, 0 km/h (0 MPH), course 41, alt 187 m (614 ft)
[ig] KF6ZEO-10>APDW17:!3744.77NR12225.70W&Docker Direwolf + RTL-SDR
The error message usb_claim_interface error -6, Failed to open rtlsdr device #1.
means that this particular RTL-SDR dongle is already being used by another program. Check the serial number (device index) and make sure it is correct and not being used by something else.
If everything is working, you'll get a link to the APRS Core server that your station is sending data to. Click the link, scroll down, and look for your callsign near the top of the "Clients" list. This table is sorted by Uptime, so the longer your station is online, the further down the table it appears.
The "Packets Rx" column shows the total number of packets received, the number of duplicates, and the number of erroneous packets dropped. If you put pbeacon= true
in your configuration file, some of those "packets received" will be your location packets sent from this docker container to the APRS network (not over RF).
Results
I used this docker container on Launch 4, and it worked really well. The APRS packets transmitted were received by my mobile receive station, and sent to the APRS Internet Service, where people could view the location on aprs.fi.
Acknowledgments/Inspiration
- beardymcbeards for help on the docker build container setup.
- darksidelemm for the radiosonde_auto_rx project, where I stole a bunch of docker ideas and documentation.
- johnboiles and his pi-rtlsdr-igate-docker project, which has almost everything I wanted but uses environment variables instead of a config file.