Pi-StAPRS: “External” APRS-IS Beacons for Pi-Star

Pi-StAPRS is a shell script that will allow you to beacon your hotspot to APRS-IS with real-time GPS information as well as displaying your reflectors/talkgroups as a comment. It attempts to prevent beaconing when GPS information is invalid; and also features a very basic WebUI to allow you to enable or disable beaconing. It is limited, however, in that it requires a source of GPS data over the network since the MMDVM hat takes up our required pins. The project was originally an off-shoot of creating a stratum-1 sourced ntpd using GPS.

This page is a work in progress and will be finished by the time I release a tarball.

How It Works

The script starts by polling for GPS Data and gathering the required NEMA strings. Once the string is found it is processed through awk to extract latitude and longitude information and store each in a variable. We also use awk to extract the heading and speed information. Altitude is extracted using awk as well, but is processed using bc to convert meters to feet.

The script then scrapes the Pi-Star dashboard for reflector information. D-Star reflector information is scraped directly from the dashboard using curl to pull a page with the information and sed to extract and format what we need. For BrandMeister, we use a modified PHP file from Pi-Star itself that gives us a list of static and dynamic talkgroups, which uses egrep and sed to filter and format the information. YSF information is pulled similarly to D-Star. BrandMeister group has an additional check for no active talkgroups connections.

Once all the information has been written to variables, we hand parse a packet to send to APRS-IS. It essentially amounts to just assembling all the data the way we need it, including some configuration data from the script. With the packet formed, we either push it to the terminal output for testing; or use ncat to push it to APRS.

The script is activated by a simple PHP page that modifies a file stored in a temporary rw directory. If the file is zero-length; the script won’t run. A PHP file merely zeros out the file to turn it off, or writes to the file to turn it on. A cronjob continuously runs the script however many minutes you configure it for.


On the hotspot itself; you will need to install the following packages:

  • bc
  • libgps21
  • nmap

In addition you will need gpspipe. This is normally provided by the gpsd package; but I’ve had problems with it installing on some versions of PiStar. I have provided a binary in the tarball with installation instructions (actually I have a whole script). The installation tarball is not yet available (I have to recover everything and figure out what the last good versions were.)

For GPS data you will need the following:

  • something providing gpsd
  • a “GPS Tether” app for a smartphone

This script was designed around having a RaspberryPi with a gps module pumping data over gpsd; with the gpsd socket configured for LAN access. However, it has been tested with a few “GPS Tether” Android apps that just pump NEMA data over a UDP port via WiFi or Bluetooth. With a rooted Android phone you could probably make it’s internal gpsd available over wifi. The script doesn’t care where this GPS data comes from, as long as it has a way of getting the generated sentences. Brandmeister data only works if your hotspot has an API Key, which it already should.

You will also need to configure several things in the shell script itself; such as where and what format the gps data is, your APRS-IS login information, icon information, and by default the script comes deactivated and improperly configured.

All script configuration is at the very top of the script. You will need to pick which GPS method you’re using, the hostname/IP and port of your GPS data, your APRS-IS login/call, your APRS-IS passcode, your APRS icon, and which mode you’re using/want to display.

At this point, do not set the activation option in the script to “1”. You should run the script manually and examine the output it would send to make sure the data is valid and not malformed. I will give examples later when I make an official release of what a valid APRS string looks like. Once you are satisfied the script’s output is correct and sane; you can set activation to 1; this turns on ncat output.

A “master activation” is done through a simple webUI that’s available at pi-star.local/pistaprs. It consists of an off button, an on button, and a current status. By default the script is off; that is because the activation file used is stored in a temporary file system, so the file doesn’t exist on first boot. The simple act of loading the WebUI creates the file. This was by design, as I wanted the absolute default state of this script to be off…and also by limitation since most of PiStar’s file-system is read-only.


# pistaprs by nq4t - https://nq4t.com/pistaprs
# script activation/webui control
[ ! -s "/tmp/aprs" ] &&  exit

#       Script Configuration

# Make sure you've really tested the script before you activate!!!!!
# GPS Server IP/Hostname and port
# Use GPSD? (0 = No, 1 = Yes)
# Android "gps tethering" app (or NEMA over IP)?
# Is 'GPS Tether' UDP?
# APRS-IS user id
# APRS-IS passcode
# APRS Icon Selection (refer to pdf)
# Standard table
# Alternate Table - this MUST be double backslash or it won't work
# Symbol - If your symbol is a backslash, you must double backslash.
# What system are you running? (And if you say YSF then good luck pal.)
# YSF is completely untested.

# Why the hell did I have to add more GPS support?
[ "$gpsd" = "$gpstether" ] && echo "Invalid GPS Configuration" && exit
[ "$gpsd" -eq 1 ] && nema=$(gpspipe -n 9 -r "$ip:$port")
[ "$gpstether" -eq 1 ] && [ "$gpsudp" -eq 1 ] && nema=$(timeout 3 ncat -u $ip $port)
[ "$gpstether" -eq 1 ] && [ "$gpsudp" -eq 0 ] && nema=$(timeout 3 ncat $ip $port)
# New filtering method for altitude and non-standard sentence names.
rmc=$(printf "$nema" | sed -n '/$G.RMC/{p;q}')
#echo "$rmc"
[ -z "$rmc" ] && echo "Better luck next time. (Or modify script for more gpspipe data.)" && exit
gga=$(printf "$nema" | sed -n '/$G.GGA/{p;q}')
#echo "$gga"
# GPS Lock Check
gpss=$(printf "$rmc" | cut -d ',' -f3)
[ "$gpss" = "V" ] && echo "No GPS Lock" && exit
# GPS Coordinate Set
lat=$(printf "$rmc" | awk -F, '{printf "%07.2f%c", $4, $5;}')
[ -z "$lat" ] && echo "Latitude error?" && exit
lon=$(printf "$rmc" | awk -F, '{printf "%08.2f%c", $6, $7;}')
[ -z "$lon" ] && echo "Longitude error?" && exit
# Set heading & speed
hsp=$(printf "$rmc" | awk -F, '{printf "%03.0f%c%03.0f", $9, "/", $8}')
# Altitude (aka literally the only reason we pull a $gpgga)
altm=$(echo "$gga" | cut -d ',' -f10)
ft=$(echo "/A=$(echo "$altm*3.280839895" | bc | xargs printf "%06.0f")")
# DMR - Scrape for TGs and set comment.
[ "$mode" = "dmr" ] && tg=$(curl -s| sed 's/<[^>]\+>//g' | sed 's/None//g' | sed ':a;N;$!ba;s/\n/ /g' | sed 's/TG/#/g')
comment="Brandmeister TGs: $tg"
[ -z "$tg" ] && comment="Brandmeister TGs: None"
# DSTAR - Scrape for reflector and set comment.
[ "$mode" = "dstar" ] &&  comment="DStar "$(curl -s | egrep "Linked|linked" | sed 's/<[^>]\+>//g' | sed 's/L/l/')
# Dear god why the hell are you using an entirely untested mode?
[ "$mode" = "ysf" ] &&  comment="YSF "$(curl -s | egrep "Linked|linked" | sed 's/<[^>]\+>//g' | sed 's/L/l/')
# Here's how we hand-craft an APRS packet. (Who needs a client anyway?)
data="$senduser>APRS,TCPIP*:!$lat$table$lon$symbol$hsp $comment $ft"
# Send data to the terminal for testing
[ "$activate" -eq 0 ] && printf "%s\n" "user $user pass $password" "$data"
#echo "$rmc"
#echo "$gga"
# Make sure output is sane before actually commiting to server.
[ "$activate" -eq 1 ] && printf "%s\n" "user $user pass $password" "$data" | ncat rotate.aprs2.net 14580


if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root"
   exit 1
echo "Installing required packages..."
apt-get install -y bc libgps21 nmap
echo "Installing gpspipe..."
mv gpspipe /usr/bin/gpspipe
chown root /usr/bin/gpspipe
chmod 755 /usr/bin/gpspipe
echo "Installing WebUI..."
# mkdir /var/www/dashboard/pistaprs
mv pistaprs/ /var/www/dashboard/pistaprs/
chown www-data /var/www/dashboard/pistaprs/
chmod -R 755 /var/www/dashboard/pistaprs/*.php
echo "Finishing install..."
chmod 755 pistaprs.sh
chown pi-star pistaprs.sh
echo "Done!" && echo ""
echo "Please edit the script (pistaprs.sh) to complete configuration"
echo "and visit http://$HOSTNAME.local/pistaprs/ for the WebUI."

Example crontab

*/5 * * * * /home/pi-star/aprsfinal.sh >/dev/null 2>&1

This causes the script to run every five minutes. Your entry should match the actual filename of the script and not my development name.