Direwolf on a Raspberry Pi with RTL-SDR
Introduction
One of the first things I experimented with after getting my HAM license was APRS (Automatic Packet Reporting System). It was created more than 30 years ago by Bob Bruninga, call sign WB4APR, as one of the few positive things out of the Naval Academy. A HAM can do lots of things with APRS like sending position, exchanging messages, providing weather reports, etc.
I found on long road trips that while my general route was picked up by digipeaters and IGates near large cities, there were huge swaths of travel where no APRS radios heard me. I wasn't even picked up by digipeaters. Fortunately, creating my own, portable solution was not hard and using a Raspberry Pi allows for a lot of flexibility using Direwolf. Not only could Direwolf handle digipeating for any drivers local to me with handhelds, but if I patched the Raspberry Pi's WiFi adapter into my phone, then I could provide mobile an IGate for the quiet areas I drove through.
Credit
This article is based primarily as a clone of my original article "Direwolf on a Raspberry Pi for Mobile APRS" but modified for using a simple RTL-SDR as a receive-only APRS station as opposed to a full RX/TX capable station.
The original article is based on the "APRS Digipeater/iGate with Direwolf (Entry Level)" article by KI5GTB and "Raspberry Pi based APRS Tracker/Digipeater/iGate" by VK3IL. Additionally, the Direwolf user manual expounds on all the configuration options that are possible and allowed me to easily adapt from KI5GTB's guide into a dynamic solution for mobile use.
Additional resources for the RTL2832U version came from "A Guide To Setting Up an APRS Receive Only IGate Using a Raspberry Pi and an RTL-SDR Dongle" by G6NHU. "Raspberry Pi SDR IGate" by WB2OSZ was also used.
Requisite Hardware
Not a lot of equipment is necessary for creating a mobile APRS station capable of serving as an IGate. For this application, I used a Raspberry Pi 4, a GPS dongle, and a simple RTL2832U Software Defined Radio (SDR). The better RTL2832U's often have an SMA antenna jack allowing you to optimize reception but they also typically come with an antenna that does fine. For the sake of this guide, the base installation of the Raspberry Pi will be assumed.
- Raspberry Pi
- RTL23823U RTL-SDR
- GPS Dongle
- Portable Battery
- [recommended] USB Hub
Image linked from: Adafruit product website |
Raspberry Pis are easily acquired from a variety of locations like Amazon or Adafruit. Running the "lite" distributions without the graphical interface reduces the processing demands and a Model 3+ or better Pi will be more than sufficient. Make sure to get a portable USB battery charger of some kind otherwise you'll have a lot of inert equipment that looks cool.
It's optional, but highly recommended, to get a USB hub that attaches devices vertically. Due to the compact nature of the Raspberry Pi, the plug ends of the Easy Digi and the USB sound card "bleed over" and block use of the ports above/below and to their sides. A vertical hub allows these devices to not interfere with one another.
RTL2832U from RTL-SDR |
The RTL2832U was originally meant as a TV tuner but had the ability to receive a much wider frequency range than just broadcast television. RTL2832Us are easy to come by, but it is recommended to buy official units from RTL-SDR to avoid low quality knock-offs that underperform.
USB GPS Dongle |
There are a variety of NMEA-0183 compatible GPS dongles available that plug directly into a USB port. Although the "thumbdrive form factor" UBlox units are extremely popular, there are two reasons not to use it. The first is the dongle's size may inhibit the installation of other USB plugs. The second is that if the Raspberry Pi is under a seat, for instance, it may struggle to receive the GPS signal. A UBlox GPS receiver on a cable like the VK-162 G-Mouse offers unobstructed plugs and the ability to place the antenna in a more optimal location using its magnetic mount. NOTE: Many of these GPS dongles also receive position data from the Russian GLONASS constellation of satellites as well.
Installing the GPS
Just plug it in. Once attached, verify the device is seen by the Raspberry Pi. Generally, it can be found by looking at the devices for one named like ttyACM0
, looking for it's activation in the system message queue, or by listing the attached USB devices.
pi@APRS:~ $ls /dev/ttyA*
/dev/ttyACM0
pi@APRS:~ $ lsusb
Bus 001 Device 004: ID 1546:01a7 U-Blox AG [u-blox 7]
pi@APRS:~ $ dmesg
[146215.390810] usb 1-2: new full-speed USB device number 4 using ohci-pci
[146215.930468] usb 1-2: New USB device found, idVendor=1546, idProduct=01a7, bcdDevice= 1.00
[146215.930476] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[146215.930480] usb 1-2: Product: u-blox 7 - GPS/GNSS Receiver
[146215.930482] usb 1-2: Manufacturer: u-blox AG - www.u-blox.com
[146215.943836] cdc_acm 1-2:1.0: ttyACM0: USB ACM device
You can watch the NMEA-0183 strings scroll past by monitoring the output, usually on /dev/ttyACM0
.
pi@APRS:~ $ sudo cat /dev/ttyACM0
$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50
$GPTXT,01,01,02,HW UBX-G70xx 00070000 FF7FFFFFo*69
$GPTXT,01,01,02,ROM CORE 1.00 (59842) Jun 27 2012 17:43:52*59
$GPTXT,01,01,02,PROTVER 14.00*1E
$GPTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*20
$GPTXT,01,01,02,ANTSTATUS=OK*3B
$GPTXT,01,01,02,LLC FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFF-FFFFFFFD*2C
$GPRMC,014346.00,A,3919.62422,N,09454.98659,W,0.098,,300422,,,D*60
$GPVTG,,T,,M,0.098,N,0.182,K,D*2C
NOTE: Sometimes the UBlox GPS dongles get "stuck" on a position fix. A few forum posts make note of the peculiar behavior where the module has a solid 2D or 3D fix on plenty of satellites and is updating time but not the position. This can often be resolved with a reset of the module using the ubxtool
from the utilities included with gpsd
. After performing a reset, many UBlox tinkerers advise to enable their binary protocol and disable the NMEA output.
pi@APRS:~ $ubxtool -p RESET; sleep 5; ubxtool -e BINARY; ubxtool -d NMEA
UBX-ACK-ACK:
ACK to Class x06 (CFG) ID x09 (CFG)
... more status messages ...
Installing GPSd
The GPS service daemon makes everything extremely easy. Instead of having to manually interpret the NMEA-0183 strings or identify changed device names, the daemon basically handles everything. Downstream software that uses gpsd can make immediate use of location and timing data.
pi@APRS:~ $ sudo apt install gpsd gpsd-tools
The chrony daemon can handle getting the Raspberry Pi's clock synchronized properly. With an Internet connection, it will grab from NTP servers. With gpsd, it can set the clock from the GPS.
pi@APRS:~ $ sudo apt install chrony
Edit the /etc/chrony/chrony.conf
file and add this line to the end in order to pull time from the GPS.
refclock SHM 0 offset 0.5 delay 0.2 refid NMEA
Finally, make sure both the chrony and gpsd services automatically run.
pi@APRS:~ $ sudo systemctl enable gpsd
Synchronizing state of gpsd.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable gpsd
pi@APRS:~ $ sudo systemctl enable chrony
Synchronizing state of chrony.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable chrony
Created symlink /etc/systemd/system/chronyd.service → /lib/systemd/system/chrony.service.
pi@APRS:~ $ systemctl is-active gpsd
active
pi@APRS:~ $ systemctl is-active chrony
active
Now confirm that the GPS is being interpreted properly using cgps
utility. It may take awhile for the dongle to begin displaying location information if it does not have a recent cache of ephemeris data.
pi@APRS:~ $ cgps -s
┌───────────────────────────────────────────┐┌──────────────────Seen 19/Used 7┐
│ Time: 2022-04-30T01:49:43.000Z (18)││GNSS PRN Elev Azim SNR Use│
│ Latitude: 39.32718100 N ││GP 5 5 33.0 56.0 33.0 Y │
│ Longitude: 94.91645280 W ││GP 10 10 12.0 251.0 34.0 Y │
│ Alt (HAE, MSL): 731.138, 825.614 ft ││GP 13 13 45.0 69.0 32.0 Y │
│ Speed: 0.09 mph ││GP 15 15 67.0 131.0 28.0 Y │
│ Track (true, var): 0.0, 1.7 deg ││GP 18 18 62.0 325.0 39.0 Y │
│ Climb: 0.00 ft/min ││GP 23 23 41.0 262.0 30.0 Y │
│ Status: 3D FIX (1 secs) ││GP 29 29 55.0 190.0 24.0 Y │
│ Long Err (XDOP, EPX): 0.80, +/- 39.5 ft ││GP 2 2 6.0 115.0 21.0 N │
│ Lat Err (YDOP, EPY): 1.21, +/- 59.3 ft ││GP 11 11 0.0 110.0 21.0 N │
│ Alt Err (VDOP, EPV): 1.83, +/- 36.5 ft ││GP 16 16 9.0 307.0 0.0 N │
│ 2D Err (HDOP, CEP): 1.45, +/- 27.8 ft ││GP 20 20 8.0 68.0 0.0 N │
│ 3D Err (PDOP, SEP): 2.34, +/- 109 ft ││GP 24 24 3.0 150.0 25.0 N │
│ Time Err (TDOP): 1.31 ││GP 30 30 0.0 37.0 0.0 N │
│ Geo Err (GDOP): 2.68 ││SB133 46 32.0 227.0 0.0 N │
│ ECEF X, VX: -423422.940 m -0.010 m/s ││SB135 48 35.0 222.0 0.0 N │
│ ECEF Y, VY: -4922385.560 m -0.010 m/s ││SB138 51 43.0 199.0 32.0 N │
│ ECEF Z, VZ: 4020624.050 m 0.010 m/s ││QZ 1 193 n/a 0.0 0.0 N │
│ Speed Err (EPS): +/- 1.0 mph ││QZ 2 194 n/a 0.0 0.0 N │
│ Track Err (EPD): n/a ││QZ 3 195 n/a 0.0 0.0 N │
│ Time offset: 0.024121106 s ││ │
│ Grid Square: EM29nh08 ││ │
└───────────────────────────────────────────┘└─────────────────────────────────┘
Another handy trick for scripting is to look for the NMEA string $GPGGA
which details the GPS fix status. Specifically, the field about the "Fix Quality" will indicate whether the location data is valid and useful which will be a value of 1
or 2
.
pi@APRS:~ $ gpspipe --nmea --count 10 | grep -m 1 -i gpgga
$GPGGA,124049.00,3901.35298,N,07640.65124,W,1,04,2.09,30.2,M,-34.8,M,,*57
pi@APRS:~ $ gpspipe --nmea --count 10 | grep -m 1 -i gpgga | awk -F',' '{print $7}'
1
Installing the RTL2832U
Just plug it in. Once attached, verify the device is seen by the Raspberry Pi. Generally, it can be found by looking at the devices for one named like swradio0
, looking for it's activation in the system message queue, or by listing the attached USB devices.
pi@APRS:~ $ls /dev/swradio*
/dev/swradio0
pi@APRS:~ $ lsusb
Bus 001 Device 007: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T
pi@APRS:~ $ dmesg
[ 8.148607] rtl2832_sdr rtl2832_sdr.0.auto: Registered as swradio0
[ 8.148655] rtl2832_sdr rtl2832_sdr.0.auto: Realtek RTL2832 SDR attached
[ 8.148677] rtl2832_sdr rtl2832_sdr.0.auto: SDR API is still slightly experimental and functionality changes may follow
[ 8.172828] Registered IR keymap rc-empty
[ 8.176491] rc rc0: Realtek RTL2832U reference design as /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1.4/rc/rc0
[ 8.177460] rc rc0: lirc_dev: driver dvb_usb_rtl28xxu registered at minor = 0, raw IR receiver, no transmitter
[ 8.177672] input: Realtek RTL2832U reference design as /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1.4/rc/rc0/input1
[ 8.185244] usb 1-1.1.4: dvb_usb_v2: schedule remote query interval to 200 msecs
[ 8.195739] usb 1-1.1.4: dvb_usb_v2: 'Realtek RTL2832U reference design' successfully initialized and connected
[ 8.196286] usbcore: registered new interface driver dvb_usb_rtl28xxu
Installing the RTL SDR Software
The rtl_fm
package makes everything extremely easy. This package includes utilities to interact with and process radio frequencies received by the RTL2832U.
pi@APRS:~ $ sudo apt install rtl_fm
Installing Direwolf
There are two fundamental ways to install Direwolf. The first is directly from the source code repository and the second is from the package repository. For brevity, the output from these commands is omitted. In summary, the source is downloaded from github, configured via cmake, built with make and installed.
pi@APRS:~ $ git clone https://github.com/wb2osz/direwolf.git/
pi@APRS:~ $ cd direwolf
pi@APRS:~ $ mkdir build && cd build
pi@APRS:~ $ cmake ..
pi@APRS:~ $ make -j4
pi@APRS:~ $ sudo make install
pi@APRS:~ $ make install-conf
For the less sadist HAM radio operators, just install it from the package repository. It may not be the most bleeding edge version, but unless you are doing something extremely fancy, you won't need bleeding edge features.
pi@APRS:~ $ sudo apt install direwolf
Configuring Direwolf
When installing Direwolf from repositories, it often does not include a baseline configuration file. The following can be pasted into /etc/direwolf.conf
to get started.
#############################################################
# #
# Configuration file for Dire Wolf #
# #
# Linux version #
# #
#############################################################
#############################################################
# #
# FIRST AUDIO DEVICE PROPERTIES #
# (Channel 0 + 1 if in stereo) #
# #
#############################################################
ADEVICE null null
#############################################################
# #
# GPS Device PROPERTIES #
# #
#############################################################
GPSD
#############################################################
# #
# CHANNEL 0 PROPERTIES #
# #
#############################################################
CHANNEL 0
MYCALL KD3BUG-10
#############################################################
# #
# BEACONING PROPERTIES #
# #
#############################################################
#############################################################
# #
# DIGIPEATER PROPERTIES #
# #
#############################################################
#############################################################
# #
# INTERNET GATEWAY #
# #
#############################################################
IGSERVER noam.aprs2.net
IGLOGIN KD3BUG-10 5-digit code
TBEACON SENDTO=IG EVERY=15 SYMBOL=R& COMMENT="Direwolf IGATE on RTL2832U"
#############################################################
# #
# LOGGGING PROPERTIES #
# #
#############################################################
LOGDIR /var/log/direwolf/
What do all of these configurations mean?
ADEVICE null null
This section relates to the audio interface and is normally used when connected via analog speaker/mic cables to a radio. Direwolf is a program that converts digital data into AFSK audio and therefore it needs to know where to send those sounds. However, with the RTL2832U, the audio is already digitized. The ADEVICE
directive informs Direwolf of the card number and device number which in this case is null
.
GPSD
This GPSD
directive is quite simple and directs Direwolf to obtain location information from the GPS daemon configured earlier. If Direwolf was installed from the package repository, there should be no issues. There is a chance that compiling from source may not include the necessary gpsd interfaces. If errors arise, reference this guide "Adding a USB GPS Receiver to Direwolf to create an APRS Tracker" for recompilation.
CHANNEL 0
MYCALL KD3BUG-10
Normally this section handles the audio modem and PTT triggers for Direwolf to control the radio. An RTL2832U cannot transmit so none of those directives are relevant.
The MYCALL
directive is set to your callsign and SSID. The SSID typically ranges from 0-15 where the numbers had legacy meanings from the early days of APRS. It is common for handhelds to use -7
, mobiles to use -9
, and IGates to use -10
.
BEACONING PROPERTIES
This section is for Direwolf to transmit its own location and APRS messages via a connected radio. Once again, for an RTL2832U this is irrelevant since the device is receive-only.
DIGIPEATER PROPERTIES
Unless you are a wizard with Regular Expressions and really know what you're doing, utilize the default DIGIPEAT
directive. This line will match APRS paths and repeat the received packets from your station. However, since the RTL2832U cannot transmit, you can't digipeat anyway so the directives are not necessary.
IGSERVER noam.aprs2.net
IGLOGIN KD3BUG-10 5-digit code
These two directives control Direwolf's access to the Internet APRS databases allowing it to serve as an IGate. Each user needs their own five digit access code which can be obtained from http://apps.magicbug.co.uk/passcode/. The first directive instructs Direwolf which of the various APRS regional servers to interact with. The second directive authenticates Direwolf's access.
TBEACON SENDTO=IG EVERY=15 SYMBOL=R& COMMENT="Direwolf IGATE on RTL2832U"
This TBEACON
is different than what would have been used in the regular BEACONING PROPERTIES
entry. It includes SENDTO=IG
which instructs Direwolf to send items to the IGate instead of only out the radio interface. From the prior GPSD
directive, this one will also utilize the GPS daemon to obtain geographic coordinates for advertising itself. The EVERY=15
transmits the station's information to the APRS-IS network every 15 minutes. The SYMBOL=R&
utilizes the icon for a receive-only IGate.
LOGDIR /var/log/direwolf/
This final directive instructs Direwolf where to save it's activity log. By default, the files will be named in accordance with a YYYY-MM-DD.log convention. At this time, it's not possible to set the name to anything else but you can direct where to save them. Make sure the directory exists and you have write permissions to that path. You may have to adjust the permissions of this location for logging.
sudo chown USERNAME:USERNAME -R /var/log/direwolf
Configuring Wireless Connection
Your IGate will be useless if the Raspberry Pi is not connected to the Internet. Fortunately, you only need to add one or two entries to the /etc/wpa_supplicant/wpa_supplicant.conf
file to allow the Raspberry Pi to connect to available WiFi stations. Add lines similar to the following but tailor them to match your own mobile hotspot.
network={
ssid="KD3BUG iPhone"
key_mgmt=WPA-PSK
psk="NotMyRealPasswordSilly"
}
It makes sense to add an additional set of lines for the Raspberry Pi to connect to any open WiFi. Setting the key_mgmnt=NONE
directive matches non-WEP, non-WPA SSIDs. The priority=-999
directive just makes it the last one used in case a preferred access point, like your mobile hotspot, is available.
network={
key_mgmt=NONE
priority=-999
}
Running Direwolf
With the hardware installed and the configuration file prepared, it's time to run Direwolf. Using the -c
directive instructs Direwolf which configuration file to load. This can be handy for having multiple configurations for different use cases - just transmitting, just digipeating, just IGating, etc. The -t 0
directive tells the application not to use fancy colors in output which can help to avoid the Raspberry Pi's display getting funky.
The rtl_fm
application tunes the RTL2832U to the APRS frequency with the -f 144.39M
directive. It's digitized output is sent to STDOUT from the trailing -
and is passed directly into Direwolf as STDIN via a pipe.
For Direwolf to handle the digital stream from the RTL2832U, it needs the -r 24000
and -D 1
directives. These tell the application to sample at 24Khz and divide the first channel by 1 which will match the output from the RTL2832U. Lastly, the directives -dgitm
include debugging information in Direwolf's output stream to show GPS activity, IGate updates, beacons, and observed stations. The output from Direwolf is more informative than what it logs which makes it handy to direct it to STDOUT via the trailing -
which can be redirected into a file (I typically choose /var/lib/direwolf.log
).
pi@APRS:~ $ sudo rtl_fm -f 144.39M - | direwolf -t 0 -c /etc/direwolf.conf -r 24000 -D 1 -dgitm -
Scripting Direwolf to Self-Heal
Of course, for a mobile application, you're unlikely to have a keyboard and monitor attached to the Raspberry Pi inside your vehicle. I put together a script to only run Direwolf if all of the requisite components were ready - e.g. a RTL2832U present, a GPS fix, network connectivity confirmed, etc. This script is run every five minutes from a cronjob to restore it in the event of a crash so that no interaction is necessary while driving.
#! /bin/bash
varFLAG=false
## check network access ##
echo "[.] TESTING network connectivity"
if ping -c 1 noam.aprs2.net | grep "bytes.from"; then
echo "[*] network connectivity OK"
else
echo "[!] network connectivity FAIL"
varFLAG=true
fi
## validate name resolution
echo "[.] TESTING name resolution"
nslookup noam.aprs2.net
if tail -150 /var/log/direwolf.log | grep "Temporary failure in name resolution"; then
echo "[!] name resolution FAIL"
varFLAG=true
else
echo "[*] name resolution OK"
fi
## validate RTL-SDR
echo "[.] TESTING RTL2832U"
if pgrep rtl_fm; then
echo "[*] RTL2832U in use OK"
elif ls /dev | grep "swradio0" || lsmod | grep -i "rtl2832_sdr"; then
echo "[*] RTL2832U detected OK"
else
echo "[!] RTL2832U missing FAIL"
varFLAG=true
fi
## validate GPS lock
echo "[.] TESTING GPS lock"
gpspipe --nmea --count 5
varGPS=`gpspipe --nmea --count 50 | grep -i gpgga | awk -F',' '{print $7}' | egrep -m 1 "1|2"`
if [ "$varGPS" = "1" ] || [ "$varGPS" = "2" ]; then
echo "[*] GPS lock OK"
else
echo "[!] GPS lock FAIL"
varFLAG=true
fi
## terminate errant Direwolf
if [ $varFLAG = true ]; then
echo "[!] terminating Direwolf due to erros"
if pgrep direwolf; then
sudo kill -9 `pgrep direwolf`
sudo kill -9 `pgrep rtl_fm`
fi
fi
if ps -A | pgrep direwolf; then
echo "[*] direwolf running OK"
elif [ $varFLAG = false ]; then
echo "[!] direwolf missing FAIL"
touch /var/log/direwolf.log
sudo rtl_fm -f 144.39M - | direwolf -t 0 -c /etc/direwolf.conf -r 24000 -D 1 -dgitm - >> /var/log/direwolf.log &
fi
You may have to adjust the permissions of this location for logging.
sudo touch /var/log/direwolf.log
sudo chown USERNAME:USERNAME /var/log/direwolf.log
Lastly, the line to add via crontab -e
to keep Direwolf running:
*/5 * * * * /home/pi/auto_aprs.sh
@reboot ubxtool -p RESET; sleep 5; ubxtool -e BINARY; ubxtool -d NMEA
Conclusion
This guide should get you 99% to your end solution. After setting everything up, I put everything into a Pelican 1200 case to hold the equipment and batteries. Now the unit is easily transportable and can be setup anywhere. After a quick GPS fix and tethering to a mobile phone, the APRS station provides coverage on the go.
de KD3BUG