Raspberry Pi based APRS Tracker/Digipeater/iGate

I have been working on setting up my new Ford Everest for Amateur Radio and in particular, activating SOTA peaks. One of the things I wanted to be able to do was enable APRS tracking to allow chasers to follow my progress to a summit. Given that SOTA peaks are sometimes remote, I wanted to be able to support a high power digipeater from the car to digipeat my APRS-enabled HT. The simple way to do this would have been to buy a Kenwood TM-D710 which has all this built in. However, I like building things, and I’d read about a great piece of open-source software – Direwolf – which can turn a Raspberry Pi into a very versatile tracker/digipeater/iGate with apparently excellent decoding performance.

The main addition required to the Raspberry Pi to turn it into a fully-fledged APRS TNC is a radio interface. As it turns out, there’s a commercially available HAT for the Rapberry Pi designed for just this purpose: North West Radio UDRC II. This is designed primarily for interfacing the Raspberry Pi to a Yaesu DR-1X repeater to add D-Star to it, but it has all the functions necessary to interface two separate radios. It also has a 12V regulator to enable the Raspberry Pi to be powered from a 12V source.

The ability to support two radios also opens the possibility to run an even more versatile setup – VHF and HF. I have a IC-7100 as the main car radio and I’ve installed a FT-2900R as a dedicated APRS 75W 2m radio (a cheap, but rugged and high power radio).

I’ve based the design on the Raspberry Pi model 3 as the UDRC only works on a Pi 2 or Pi 3. This has the added benefit of built in WiFi and Bluetooth and a powerful enough processor to easily run Direwolf with a high audio sample rate and any other applications that may be useful. The WiFi in particular allows you to use any modern mobile phone to provide the Pi an Internet connection and thus enable it as an iGate too!

In my case, I wanted to be able to run the setup in a number of different modes depending on what I was doing. I also didn’t want to have to connect a screen and keyboard to the Raspberry Pi to re-configure it. Hence, I implemented a 10 position thumbwheel switch on the front panel to enable simple re-configuration of the system.

Below I document how this is all configured to help anyone else who is interested in going down this path – sorry for the voluminous post, but there are a lot of details to cover!

Hardware setup

Raspberry Pi system built into a simple chassis for vehicle mounting

Raspberry Pi system built into a simple chassis for vehicle mounting (in my case it’s mounted to the underside of the driver’s seat)

The hardware components for the full mobile installation are:

  • HF Radio (IC-7100 in my case) with data port
  • VHF Radio (FT-2900R in my case)
  • Raspberry Pi 3
  • UDRC II HAT
  • Case to suit Raspberry Pi with attached HAT (modify one of the many available)
  • 12V DC 100A solid state relay (obtained from eBay – Fotek SSR-100 DD)
  • 10 position, BCD coded thumb switch with complementary outputs (e.g: Hartmann DPS9-141-AK2) plus mounting cheeks
  • Toggle switch for relay control
  • 6-pin mini-DIN to 6-pin mini DIN cable for HF radio (modified so only pins 1-4 are connected)
  • HD-15 plug to VHF radio and 12V power cable (custom build this – see below)
  • USB GPS (many options – I used a cheap eBay model based on ublox-7 chip)
  • Compact USB memory stick (to hold log files)

The main wiring required is to connect the thumb wheel switch, power and radio to the HD15 connector on UDRC. The wiring is as follows:

HD-15 pinFunctionConnected to
1GPIO 6Thumb wheel BCD 1 complement
2PTTVHF radio PTT line
3Unused
4Unused
5GndGnd, thumbwheel common
6Unused
7Audio to radioVHF radio mic input (twisted pair with gnd)
8Unused
9Audio from radioVHF radio speaker
10GndGnd
11GPIO 24Thumb wheel BCD 4 complement
12GPIO 22Thumb wheel BCD 8 complement
13Unused
14GPIO 27 (note DIN 6 pin 6 must be open for this to work)Thumb wheel BCD 2 complement
15+12V+13.8V supply

The Mini-DIN 6 connector should be wired direct to another Mini-DIN 6 connector to the HF radio, but with only pins 1-4 connected (pins 5 and 6 are shared with pins on the HD-15 that are used for the VHF radio).

The chassis illustrating the placement of the power switch, SSR, thumbwheel, Raspberry Pi and RF chokes on mic and power leads.

The chassis illustrating the placement of the power switch, SSR, thumbwheel, Raspberry Pi and RF chokes on mic and power leads.

The power wiring uses a solid state relay (SSR) to switch the power to both the Raspberry Pi and VHF radio (and optionally the HF radio). This is a 100A unit which will require some heat sinking if a 100W HF radio is connected through it. With only an APRS dedicated VHF radio, simply attaching it to an aluminium panel should be sufficient due to the low duty cycle. The relay is controlled very simply by a toggle switch.

The radio volume setting and mic gain may need adjusting as per guidelines in the NW Digital Radio UDRC Direwolf notes. Note that the signal levels to and from the UDRC should be within the following maximums:

Output to radio maximum p-p voltage: 1.5V

Input maximum p-p voltage: 1.5V

Raspberry Pi software configuration

Overview

The UDRC card requires some specific tweaks to the standard Raspian kernel and so North West Digital Radio have created their own variant called ‘compass’. This is the basis for the Rapsberry Pi load and needs to be loaded before any other changes.

The main other requirements are:

  • Load Direwolf and configure it to support mode changing for the various supported use-cases
  • Load support for the GPS (to supply tracking data to Direwolf and time to NTP)
  • Harden the Raspberry Pi to survive power interruptions without corruption and protect the micro-SD card from early failure

Here are the currently supported operations modes (selected by the thumbwheel):

BCD valueModeNotes
0Off
1Beacon VHF (+Internet)Sends normal Smartbeacon signals on VHF radio and also via the internet if available.
2Beacon VHF+HFSends normal Smartbeacon signals on VHF and HF radio and also via the internet if available.
3Private digipeat VHFDigipeats only my own packets on VHF
4Private digipeat VHF+HFDigipeats only my own packets on VHF and HF radios
5Public digipeat VHFProvides a normal overlay digi function on VHF
6Public iGate VHFProvides a 2-way iGate function
7Receive only (+internet)Only receives packets and forwards them to the Internet if available.
8Spare
9Spare

The following sections document the configuration required to achieve this.

Setting up the OS and Direwolf

First step is to install the ‘compass’ image from North West Radio. Follow the NW Digital Radio instructions here: Loading compass

NW Digital Radio has fairly detailed instructions for loading a standard Direwolf configuration and also enabling the GPS: Installing Direwolf

These instructions are fairly generic and need to be modified for the configuration I describe as detailed below.

Now is a good time to remove any packages that may be less than useful for our application. Obvious ones include libreoffice, games, Mathematica, scratch etc. Also remove the swapfile as we won’t need it for this application:

$sudo systemctl stop dphys-swapfile
$sudo systemctl disable dphys-swapfile
$sudo apt-get purge dphys-swapfile

Before we get too far through the install I need to divert to describe setting up a separate filesystem for the logs and other persistent data that will be a part of hardening the Raspberry Pi (described later). I decided to use a separate small USB stick for keeping logs and other persistent data separate from the main SD card. I chose the btrfs filesystem as it’s supposed to be the best for flash based devices in terms of lowering the number of writes needed. To set this up, insert the blank USB stick into a USB port. You should be able to determine the device assigned to the stick with the ‘df’ command. In my case it identified as /dev/sda. Create the new filesystem as follows:

$sudo apt-get install btrfs-tools
$sudo mkfs.btrfs -m single /dev/sda

Add the new filesystem to the /etc/fstab file mounted on /var/log as follows:

/dev/sda /var/log        btrfs   defaults 0    1

Supporting Direwolf mode changes from the mode switch

The NW Digital Radio instructions work fine if you only want to run a tracker, a digipeater or iGate. If you want the ability to change them on the fly, you need to be able to shutdown and restart Direwolf with a different configuration file. In my case, I wanted to be able to make this happen in response to the position of the thumb wheel mode switch.

The latest versions of Raspian (including compass) use the SystemD init system and so Direwolf and the mode control software needs to be configured to use this system. SystemD uses “.service” files to specify the characteristics of each service and the actions associated with it.

The first step is to set up Direwolf to use SystemD. This is done by creating a direwolf.service file in /etc/systemd/system. It’s content is:

[Unit]
Description=Direwolf Daemon

[Service]
ExecStart=/usr/bin/direwolf -a 100 -t 0 -c /tmp/direwolf.conf -l /var/log/direwolf
ExecReload=/bin/kill -KILL $MAINPID ; /usr/bin/direwolf -a 100 -t 0 -c /tmp/direwolf.conf -l /var/log/direwolf

[Install]
DefaultInstance=1
 We also need to create a log file directory as follows:
 $sudo mkdir /var/log/direwolf

You will note that the direwolf config file is located in /tmp as it is a dynamically created file based on the current mode. Note also that this .service file does not automatically start Direwolf as this will be controlled by the mode control script.

Direwolf produces much output to stdout when it is running. As a service, this will end up in syslog unless we do something about it. This can be changed through the rsyslog config file. Create a new file /etc/rsyslog.d/01-direwolf.conf and put the following 2 lines in it:

if $programname == 'direwolf' then /var/log/direwolf/direwolf.log

if $programname == 'direwolf' then ~     # Discards the message after logging in direwolf.log

Activate this with:

$sudo systemctl restart rsyslog

We also need to apply logrotate to this logfile as it will grow rapidly. Create the file /etc/logrotate.d/direwolf with the following content:

 /var/log/direwolf/direwolf.log {
         rotate 7
         daily
         missingok
         notifempty
         delaycompress
         compress
         postrotate
                 invoke-rc.d rsyslog rotate > /dev/null
         endscript
 }

One other utility is needed to enable access to the thumbwheel switch inputs (which are connected to 4 GPIOs that are brought out on the HD15 connector) – WiringPi. This is a package that allows simple control and access to the GPIO pins. In this case, the only component we need is the ‘gpio’ command. WiringPi is pre-loaded in Raspian (and compass), so no extra loading is needed.

The mode control is managed by a separate service: direwolf-mode.service which controls a single script direwolf-mode.sh. The following file controls the service: /etc/systemd/system/direwolf-mode.service

[Unit]
Description=Direwolf mode control Daemon
After=sound.target

[Service]
ExecStart=/usr/local/bin/direwolf-mode.sh
ExecReload=/bin/kill -KILL $MAINPID ; /usr/local/bin/direwolf-mode.sh
Restart=always

[Install]
WantedBy=multi-user.target
DefaultInstance=1
This service automatically starts after the sound system (needed for direwolf) is loaded and manages the starting and stopping of direwolf.
The script is located in /usr/local/bin and is as follows:
/usr/local/bin/direwolf-mode.sh
#!/bin/bash
#
# Script to control Direwolf state based on physical switch status
#
# Control switch input is a BCD thumb wheel connected as follows:
# - IO22 - WiringPi: 3 - BCD 8 complement - HD15 pin 12
# - IO24 - WiringPi: 5 - BCD 4 complement - HD15 pin 11
# - IO27 - WiringPi: 2 - BCD 2 complement - HD15 pin 14
# - IO6 - WiringPi: 22 - BCD 1 complement - HD15 pin 1
#
# Modes are as follows:
# 0 - Off
# 1 - Beacon VHF+iGate
# 2 - Beacon VHF+HF+iGate
# 3 - Private digipeat VHF (+iGate)
# 4 - Private digipeat VHF+HF
# 5 - Public digipeat VHF
# 6 - Public iGate VHF
# 7 - Receive only (+iGate)
# 8 - Spare (off)
# 9 - Spare (off)
#
#
# First make sure that the PTT lines are set as output and set to 0 (off)
# Seems to be a problem with a reboot leaving them configured as inputs and drifting high...

/usr/bin/gpio -g mode 12 out
/usr/bin/gpio -g mode 23 out
/usr/bin/gpio -g write 12 0
/usr/bin/gpio -g write 23 0

# Variable 'mode' contains the current state of direwolf
#
# Config files are of the form: /usr/local/bin/direwolf-modeX-config

# First set dummy config file
/bin/echo "# Waiting for GPSD" > /tmp/direwolf.conf

declare -i mode=0
declare -i new_mode

/usr/bin/gpio mode 3 in # BCD 8 complement
/usr/bin/gpio mode 3 up # Enable weak pull-up
/usr/bin/gpio mode 5 in # BCD 4 complement
/usr/bin/gpio mode 5 up # Enable weak pull-up
/usr/bin/gpio mode 2 in # BCD 2 complement 
/usr/bin/gpio mode 2 up # Enable weak pull-up
/usr/bin/gpio mode 22 in # BCD 1 complement 
/usr/bin/gpio mode 22 up # Enable weak pull-up

# Wait here until gpsd is running as Direwolf won't work properly for tracking unless gpsd is running

while [ ! -e /run/gpsd/gpsd.pid ]; do
 /bin/sleep 5
done

# gpsd started OK, now set a dummy config file

/bin/echo "# Direwolf not started" > /tmp/direwolf.conf

while true; do 
 /bin/sleep 5 

 # Get the current thumb wheel switch value 
 let "new_mode = $(/usr/bin/gpio read 3)" 
 let "new_mode = new_mode << 1" 
 let "new_mode += $(/usr/bin/gpio read 5)" 
 let "new_mode = new_mode << 1" 
 let "new_mode += $(/usr/bin/gpio read 2)" 
 let "new_mode = new_mode << 1" 
 let "new_mode += $(/usr/bin/gpio read 22)" 

 if [ $new_mode = $mode ] 
 then 
 # No change to mode, so just loop 
     continue
 fi 
 case $new_mode in 

 0|8|9|10|11|12|13|14|15) 
     # New mode is off or undefined, so change to off mode 
     if [ $mode != 0 ]
     then
         systemctl stop direwolf.service 
         /bin/echo "# Direwolf stopped" > /tmp/direwolf.conf
         mode=0 
     fi
     ;; 

 *) 
     # Change to new mode by replacing the configuration file and 
     # restarting Direwolf 
     cp /usr/local/bin/direwolf-mode${new_mode}-config /tmp/direwolf.conf 
     systemctl restart direwolf.service 
     mode=$new_mode 
     ;; 

 esac 

done
This script simply tests the mode switch every 5 seconds and restarts direwolf with a new configuration if it has changed.
The Direwolf config script is different for each case. Here’s my samples for reference (you’ll need to replace YOURCALL with your actual call-ssid and of course your password for the Internet Server)

direwolf-mode1-config

Finally to get the daemon enabled to automatically start, type:
$sudo systemctl enable direwolf-mode.service

As noted in the script above, there seems to be a problem with the PTT lines defaulting to transmit on power up. The script resets them as soon as it starts, but there is still a period while the OS boots where the PTT is active that can result in a carrier being transmitted for a few seconds. Haven’t found a work-around as yet.

Setting up the GPS and providing time to the Raspberry Pi

One of the challenges of running a RPi in a mobile setting where it doesn’t have access to the Internet is maintaining the system time. The Rapberry Pi doesn’t have a battery backup on the clock and relies on syncing to Internet time servers on power up. If there is no Internet connection, the time continues on from  a saved value (by fake-hwclock) at the last power down. We have, however, a very accurate time source in the form of GPS and it is not hard to make the Raspberry Pi use this for syncing the system time.

The default version of gpsd in the Raspian distribution (and Compass) seems to be a bit unreliable in updating the shared memory segments. I found I needed to manually install the most recent version of gpsd for reliability. In my case this is v3.16 and it can be downloaded from here: http://download-mirror.savannah.gnu.org/releases/gpsd/

Once gpsd is installed, you can configure NTP to use it as a time source by changing the /etc/ntp.conf file to the following:

 pool au.pool.ntp.org minpoll 8 iburst    # change the pool for your appropriate region

 driftfile /var/log/persistent/ntp.drift
 logfile /var/log/ntp.log

 restrict default kod nomodify notrap nopeer noquery
 restrict 127.0.0.1 mask 255.255.255.0

 # GPS Serial data reference - access via shared memory inter-process communication
 server 127.127.28.0 prefer true
 fudge 127.127.28.0 time1 +0.075 refid GPSD stratum 1

 # time1 +0.075 adjusts for delays through the USB interface (needs to 
 # be adjusted for the specific hardware for best accuracy, but not 
 # essential for this application)

A complication is that gpsd is by default configured to start only when a client application connects to it and so won’t provide timing information to NTP through the shared memory interface until something like Direwolf connects to it. This can be fixed through the initialisation scripts as follows:

$sudo cp /lib/systemd/system/gpsd.service /etc/systemd/system

Add the following line to /etc/systemd/system/gps.service [Unit] section:

Before=direwolf-mode.service

This makes sure gpsd is started before direwolf-mode service.

Add the following line to /etc/systemd/system/gps.service [Install] section:

WantedBy=multi-user.target

This tells SystemD to start gpsd as part of bringing up the multi-user target (which represent the system up and running in multi-user mode).

Finally tell SystemD that something has changed (rebooting also works) and enable gpsd for automatic start:

$sudo systemctl daemon-reload
$sudo systemctl enable gpsd

You can check that NTP is getting the time from the gps with the following command:

$ntpq -p

The output should look something like this:

  remote refid st t when poll reach delay offset jitter
==============================================================================
+ns2.unico.com.a 27.124.125.252 3 u 81 256 1 0.471 76.026 0.943
+a.pool.ntp.uq.e 27.124.125.252 3 u 80 256 1 0.209 73.916 1.696
+ntp.2000cn.com. 27.124.125.252 3 u 79 256 1 0.466 73.684 1.806
+ntp3.syrahost.c 27.124.125.252 3 u 78 256 1 0.269 73.917 1.426
*SHM(0) .GPSD. 1 l 23 64 3 0.000 69.758 8.381

The asterisk next to the GPS shows that it is the primary source of time. Other lines will only appear if the Raspberry Pi is connected to the Internet.

The final tweak for gpsd is to pass it the -n option which forces the daemon to start querying the gps even before a client connects. This is needed to make sure it is populating the shared memory for ntp when nothing else is using it. This is a simple change to /etc/default/gpsd – just add the -n to the GPSD_OPTIONS variable:

GPSD_OPTIONS="-n"

Hardening the Raspberry Pi

One of the limitations of the Raspberry Pi for embedded applications is that it uses a micro-SD card as its primary storage. The issue is that SD cards have a finite service life based on the number of writes to them. In an application like Linux, there are many writes made to the card as various parameters are updated and this can lead to a relatively short life with random errors and failures appearing over time. A second issue for embedded systems is that Linux is not designed to be arbitrarily stopped with a power switch. This can lead to file corruption and consequent instability.

Overlay filesystem

One of the best ways to mitigate both these issues is to change the root file system to read-only. This would be fine except for all those files that actually need to be written and modified during the normal execution! There is however a neat solution to this problem – Overlay Filesystem – which is now built into the Linux kernel. This is a slightly complex, but very useful capability which creates essentially a merged filesystem with an “upper” and “lower” layer. In our case, we can set the “lower” layer to be the read-only root filesystem and the “upper” layer to be a RAM-based temporary filesystem to store those files that are changed during operation. When the power is cycled, the system will come up in the state of the “lower” file system which is in a clean state.

The final component needed is somewhere to store persistent data that needs to remain across reboots (such as log files and a small number of other variables).

Fortunately, there are a number of scripts around that simplify the creation of such an environment. For this project, I’ve used one created by Pavel Pisa from Czech Technical University, Prague. Here’s a presentation describing it: init-overlay. Setting this up is fairly straight forward:

$cd /sbin
$sudo wget https://github.com/ppisa/rpi-utils/raw/master/init-overlay/sbin/init-overlay
$sudo wget https://github.com/ppisa/rpi-utils/raw/master/init-overlay/sbin/overlayctl
$sudo chmod +x init-overlay overlayctl
$sudo mkdir /overlay 
$sudo overlayctl install 
$sudo reboot

This is all that’s needed! Read the contents of the two scripts to understand what they are doing if you are interested.

Once the overlay filesystem is active, you can’t update the underlying base system (as it’s read-only). So when you need to make changes, you need to deactivate the overlay filesystem. This is simply accomplished using the ‘overlayctl’ script:

$ sudo overlayctl disable
$ sudo reboot

Similarly, it can be re-enabled:

$ sudo overlayctl enable
$ sudo reboot

Persistent storage

Finally, we need persistent storage for log files etc. Create a directory for other persistent data:

$sudo mkdir /var/log/persistent
$sudo chmod go+wt /var/log/persistent

Key data that needs to be persistent is:

  • Saved clock time (fake-hwclock)
  • NTP drift file

Fix up fake-hwclock by adding the following to /etc/default/fake-hwclock:

FILE=/var/log/persistent/fake-hwclock.data

We also need to make sure fake-hwclock doesn’t start until the filesystems are mounted. This is achieved by adding to the fake-hwclock.service scripts as follows:

$cd /etc/systemd/system
$sudo cp /lib/systemd/system/fake-hwclock.service .

We then edit the .service file and add the following line to the [Unit] section and comment out the Before= line:

After=local-fs.target
#Before=sysinit.target

The NTP drift file is solved using the line in the ntp.conf file earlier in this article.

Watchdog

Finally we need to harden the pi against random lockups and problems using the hardware watchdog built into the Raspberry Pi. This is a piece of hardware built into the processor that continually counts down a counter – if it ever reaches zero, a hard reset is generated. There is a watchdog daemon that “feeds” the watchdog by reseting the counter periodically. The following commands will enable the watchdog:

$sudo apt-get install watchdog

Modify /etc/watchdog to set the device line as follows:

watchdog-device = /dev/watchdog

Edit the /etc/systemd/system/watchdog.service file to include autostart by copying as follows:

$sudo cp /lib/systemd/system/watchdog.service .

Then edit the file to add this line:

[Install]
WantedBy=multi-user.target
Edit /etc/sysctl.conf to add the line:
 kernel.panic = 10
Which forces a reboot 10 seconds after a kernel panic.

At this point it would be prudent to take a backup of your SD card in case anything goes wrong in the future. I use the built-in SD Copier command on the accessories menu of the latest release of Raspbian.

That should be all that you need for a high reliability tracker/digipeater/iGate!

Here’s an image from aprs.fi of a recent SOTA expedition to Mt Howitt in the Victorian high country. You will see tracking of the car (orange line) and tracking of my HT (blue line) which was digipeated from the car. Looking at the raw packets, the digipeating of the HT used at least 2 VHF digis and a few HF gateways to provide fairly continuous coverage of the hike.

aprs.fi image of trip to Mt Howitt. Orange is car tracker, blue is HT tracker digipeated from car.