KB9IQX AIS Receiver & APRS.fi Reporter using RTL-SDR

I saw that APRS.fi had ship locations on the map! So I wanted to add AIS receive function to my RX-only iGate station. Both AIS and APRS are using an SDR, one for each.

My personal preference is to start with the Pi Zero, so as to have the lowest power Pi thrown at the problem. No sense in using the 3B+ or 4 if you don't have to. The Zero and the 3A+ have much better power management anyway. They are cheaper too. Start there.



AIS uses 9600 bps GMSK on 161.975 and 162.025 MHz [source].

Other sites with installation instructions that I referenced before wandering off on my own.

This was the key to bridging the decoder part to the send-to-APRS.fi part [source] :

After fiddling around for some days, I found rtl-ais which is a modified version of rtl-sdr, with direct support for AIS decoding. It worked! Just specifying the tuner error, and it was outputting AIS messages.

However, the decoder was not that elegant, and GNUAIS had interesting features, like the ability to feed data directly to mysql.

AIS is transmitted at 161.975 and 162.025MHz, which is within the 2MHz bandwidth of the RTL-SDR sticks, so both channels can be received at once. The rtl_ais software is a modified version of rtl_fm, which can do exactly this. Feeding the output audio to a named pipe, and setting gnuais up to read from that named pipe, instead of audio card, worked just perfectly!


TL;DR download and run my FORTHCOMING:

sudo ./setup-ais

More detailed, manual version:

1. Install an image. I used a general Raspbian Stretch (9) because that's what I had lying around on my desktop.

2. Log into or SSH into your machine.

3. Install and build Osmocom RTL-SDR At this other page you will find great,detailed instructions... don't worry that it's ADS-B not AIS, the basic drivers are the same.

4. Install and build DGiardini's RTL-AIS. Whether you installed the libsdr libraries via apt repository (apt-get install librtlsdr-dev librtlsdr0) or you installed the OsmoCom RTL-SDR libraries, the default paths in the Makefile are incorrect. There is some futzing you'll have to do with the Makefile to point libraries to the correct paths changes in red bold. Here is what worked for me:

CFLAGS?=-O2 -g -Wall -W 
CFLAGS+= -I./aisdecoder -I ./aisdecoder/lib -I./tcp_listener
# from: https://github.com/dgiardini/rtl-ais/pull/15
# add the line should look like this if you apt-get librtlsdr
#LDFLAGS+=-lpthread -lm -lrtlsdr -L /usr/lib/arm-linux-gnueabihf/
# add the line should look like this if you have OsmoCom rtl-sdr
LDFLAGS+=-lpthread -lm -lrtlsdr -L /usr/local/lib/

ifeq ($(PREFIX),)
    PREFIX := /usr/local
UNAME := $(shell uname)
ifeq ($(UNAME),Linux)
#Conditional for Linux
CFLAGS+= $(shell pkg-config --cflags librtlsdr)
LDFLAGS+=$(shell pkg-config --libs librtlsdr)


#    CMAKE will be much better or create a conditional pkg-config

# This is the default, which doesn't make sense
# For apt-get librtl-sdr package:
# This is for OsmoCom rtl-sdr, which puts things in /usr/local

# Make sure you apt-get install libusb-1.0-0-dev
# and adjust these variables to wit:

ifeq ($(UNAME),Darwin)
#Conditional for OSX
CFLAGS+= -I/usr/local/include/ -I$(LIBUSB_INCLUDE) -I$(RTLSDR_INCLUDE)
LDFLAGS+= -L/usr/local/lib -L$(LIBUSB_LIB) -L$(RTLSDR_LIB) -lrtlsdr -lusb-1.0 
#Conditional for Windows
LDFLAGS+=-L$(LIBUSB_INCLUDE) -L$(RTLSDR_LIB) -L/usr/lib -lusb-1.0 -lrtlsdr -lWs2_32


	main.c rtl_ais.c convenience.c \
	./aisdecoder/aisdecoder.c \
	./aisdecoder/sounddecoder.c \
	./aisdecoder/lib/receiver.c \
	./aisdecoder/lib/protodec.c \
	./aisdecoder/lib/hmalloc.c \
	./aisdecoder/lib/filter.c \


	$(CC) $(OBJECTS) -o $@ $(LDFLAGS)

	$(CC) -c $< -o $@ $(CFLAGS)


	install -d $(DESTDIR)$(PREFIX)/bin
	install -m 755 $(DESTDIR)$(EXECUTABLE) "$(PREFIX)/bin/"

When this is configured, compile rtl_ais and you'll see messages from the compilation. Type:


When it compiles, install the compiled binary. (typing sudo make -n install will tell you what commands it'll run without actually doing them) To actually install, type:

sudo make install
TESTING rtl_ais

You can test whether your installation is receiving AIS data thsu far. Make sure your SDR is plugged into the USB, attach an antenna, and type:

rtl_ais -l 161.975M -r 162.025M -n

This will run the receiver application on the SDR, tuned to both AIS frequencies. Let it sit for a while and you should received actual AIS messages that look like the following output. The lines begining with AIVDM indicate an actual received packet from an actual boat.

/usr/local/bin/rtl-ais> rtl_ais -l 161.975M -r 162.025M -n
Edge tuning disabled.
DC filter enabled.
RTL AGC disabled.
Internal AIS decoder enabled.
Buffer size: 163.84 mS
Downsample factor: 64
Low pass: 25000 Hz
Output: 48000 Hz
Found 1 device(s):
  0:  Realtek, RTL2838UHIDIR, SN: AIS0001

Using device 0: Generic RTL2832U OEM
Found Rafael Micro R820T tuner
Log NMEA sentences to console ON
AIS data will be sent to port 10110
Tuner gain set to automatic.
Tuned to 162000000 Hz.
Sampling at 1600000 S/s.

5. Create a named pipe.

More info about named pipes is here. It seems that gnuais can read a file for input but not take raw input from stdin/command line. It is designed to take raw audio input from a soundcard, back when the only way to receive AIS was to tap into the discriminator audio output of a radio. This does not work for SDR.


There is a test feature which allows gnuais to read sound input from a test file. In this case, our "test file" is the actual audio that is being dumped into a file by rtl_ais. But audio can eat up a lot of room when dumped into a file...


we will tell rtl_ais to dump received SDR audio into the named pipe -- basically a zero-length "file" that resides only in memory and which to applications looks like any other file. Finally we will tell gnuais to read the audio from that same file/named pipe.

In short, the named pipe acts as a regular file... but it does not grow in size -- it only grows in memory -- and clog up your file system.

To create the named pipe, choose a path (location) in your system that works for you where you want to put the file. I chose /var/local/dev/aisdata, for example. Type:

sudo mkfifo /path/to/filename_of_named_pipe

sudo chmod 777 /path/to/filename_of_named_pipe

The first command creates the named pipe. These second changes the permissions so that any user can write data to it. If you do not change the permissions, then only the superuser will be able to write to the file; in which case you will have to run the rtl_ais command as the superuser. This is strictly your choice.

6. Install and build GNU AIS Make sure you have the curl development libraries (raspberryPiOS: sudo apt-get install libcurl4-openssl-dev); it will build without them but they are needed for gnuais to send AIS position reports to APRS.fi. If you want to log data to a mySQL database, make sure you have the requisites.

sudo apt-get install libcurl4-openssl-dev
sudo apt-get install gnuais

7. Configure gnuais.conf

The example configuration file lives in /usr/share/doc/gnuais/examples/gnuais.conf-example. Copy this file to a place that makes sense, e.g. /etc or /usr/local/etc or your user home directory if you are a barbarian. Edit it it as follows:

# Configuration for gnuais

# My callsign or nickname, used when exporting AIS data
# My email address

# Directory, where logs will be written. Defaults to current directory.
#LogDir logs

# ALSA sound device name or "pulse" for pulseaudio,
# If alsa is used: either the name of the card (default for the
# first / default card), or a device name, for example: hw:2,0 for the first
# input of the third card.
SoundDevice default

# Which sound channels to decode:
#   both
#       - ask the sound card for a stereo signal and decode both channels
#       - a good default, if you're not sure which channel your receiver is in
#   mono
#       - ask the sound card for a single mono channel
#       - needed for a true mono card, like some USB audio sticks, which only
#         have a single audio input channel
#   left
#       - ask the sound card for a stereo signal and only decode the
#         left channel
#   right
#       - ask the sound card for a stereo signal and decode the right channel
SoundChannels both

# Print sound peak level information in the log every N seconds.
# The input level should be around 70-90% - if it's 100% the signal is too loud
# and distorted. Tune your mixer settings. Comment the line out, or set to 0,
# to disable level logging below distortion levels.
#SoundLevelLog 1
# There is no need for this every second... once a minute is less annoying.
SoundLevelLog 60 

# Print receiver range statistics in the log every N seconds.
# StatsInterval defines the logging interval. Latitude and Longitude of the
# receiver are given in decimal degrees (not degrees and minutes, or seconds).
# Positive values for northern latitude and eastern longitude, negative
# values for southern latitude and western longitude.
StatsInterval 60
Latitude YOUR LAT
Longitude YOUR LONG

# AIS data uplink configuration - received APRS data will be posted here
# periodically. Can be used for exporting data to aprs.fi. You need to get a
# key for your receiving site to submit data, please read the instructions
# at http://aprs.fi/page/ais_receiver !
# Uplink   
Uplink aprs.fi json http://aprs.fi/jsonais/post/YOUR sEcREt kEY

# MySQL database configuration - received AIS data will be stored in the
# specified database. See create_table.txt for instructions.
#mysql_host localhost
#mysql_db yourmysqldatabase
#mysql_user yourmysqlusername 
#mysql_password xxxx
#mysql_keepsmall yes
#mysql_oldlimit 800

# Serial port for AIS export - NMEA sentences will be written here after
# decoding. Currently it is not possible to read AIS data from an
# AIS receiver connected to a serial port.
#serial_port /dev/ttyS0

# Use test file as input instead of the audio device input
#SoundInFile ../testmessages/gnuais-stereo-2rx.raw
# the name of the named pipe
SoundInFile /var/local/dev/aistraffic

8. Calibrate your SDR to find the PPM offset. This is a good idea. For the life of me I could not get kalibrate-rtl to compile. So I am just running on luck.

Command Line Operation

rtl_ais -> named pipe -> gnuais

The exciting and magic sauce here is the name pipe sitting between rtl_ais receiving from the SDR and gnuais first decoding the audio and subsequently sending it to aprs.fi. The named pipe looks zero-length but accumulates data in system memory from rtl_ais going in on one side and let's another application take data out of it


In one terminal, run rtl_ais:

rtl_ais -l 161.975M -r 162.025M -A /path/to/filename_of_named_pipe

This command tells rtl_ais to listen to both "left" and "right" AIS frequencies (tho the official spec calls them A and B freqs). The -A turns off the decoding function, instead sending out the undecoded audio from the SDR.

If successful, you will see the output seem to hang forever. But it is actually sending the data to the named pipe instead of the screen. So as long as it is "hanging" it is working.

prompt:~ $ /path/to/rtl_ais -l 161.975M -r 162.025M -A /path/to/filename_of_named_pipe
Edge tuning disabled.
DC filter enabled.
RTL AGC disabled.
Internal AIS decoder disabled.
Buffer size: 163.84 mS
Downsample factor: 64
Low pass: 25000 Hz
Output: 48000 Hz
Found 1 device(s):
  0:  Realtek, RTL2838UHIDIR, SN: AIS0001

Using device 0: Generic RTL2832U OEM
Found Rafael Micro R820T tuner 

In another terminal, run rtl_ais:

gnuais -c /path/to/gnuais.conf -l /path/to/filename_of_named_pipe

Here you will see decoded output, including AIVDM NMEA sentences, sailing by.

prompt:~ $ gnuais -c /path/to/gnuais.conf -l /path/to/filename_of_named_pipe
2021/05/03 22:01:56.302307 gnuais[792:1973432320] NOTICE: Starting up...
2021/05/03 22:01:56.325308 gnuais[792:1965036336] NOTICE: Listening for connections from gnuais GUI
2021/05/03 22:01:56.325587 gnuais[792:1973432320] NOTICE: Reading audio from file: /var/local/dev/aistraffic
2021/05/03 22:01:56.325641 gnuais[792:1973432320] NOTICE: Started
2021/05/03 22:01:56.608064 gnuais[792:1973432320] INFO: Level on ch A: 47 %
2021/05/03 22:01:56.608630 gnuais[792:1973432320] INFO: Level on ch B: 48 %
ch A type 1 mmsi 366757740: lat 47.556227 lon -122.341707 course 268 speed 0.0 rateofturn 192 navstat 3 heading 281 (!AIVDM,1,1,,A,15Mi7K?000o?ue2K=Sr:Mpkj0D1h,0*7B)
ch A type 1 mmsi 367705050: lat 47.588960 lon -122.344640 course 163 speed 6.2 rateofturn 192 navstat 3 heading 160 (!AIVDM,1,1,,A,15Nbsng00vo?tn0K>hWnGU1l0L3h,0*34)
ch B type 4 mmsi 003669707: date 2021-5-3 time 22:02:01 lat 47.588684 lon -122.341164 (!AIVDM,1,1,,A,403OvjivEAn21o?uoBK>gvg020S:,0*64)
ch A type 20 mmsi 003669707: reserve 1 (ofs 34 slots 5 timeout 7 incr 225) reserve 2 (ofs 2232 slots 2 timeout 7 incr 375) reserve 3 (ofs 0 slots 1 timeout 7 incr 750) (!AIVDM,1,1,,A,D03Ovjh29N>6;PfGL00Nfp0,0*79)
ch B type 20 mmsi 003669707: reserve 1 (ofs 258 slots 5 timeout 7 incr 225) reserve 2 (ofs 2228 slots 2 timeout 7 incr 375) reserve 3 (ofs 0 slots 1 timeout 7 incr 750) (!AIVDM,1,1,,A,D03Ovjh@9N>6;@fGL00Nfp0,0*1B)
ch B type 1 mmsi 366757740: lat 47.556200 lon -122.341707 course 268 speed 0.0 rateofturn 192 navstat 3 heading 281 (!AIVDM,1,1,,A,15Mi7K?000o?ue4K=Sn:Mpj>0@3h,0*32)
ch A type 1 mmsi 367712660: lat 47.600653 lon -122.397827 course 244 speed 15.9 rateofturn 0 navstat 0 heading 242 (!AIVDM,1,1,,A,15NcIU002OG?e@hK?<29QoT@0@4L,0*3A)
ch B type 21 mmsi 993692021: (!AIVDM,1,1,,A,ENkb9MNb0h@@@@@@@@@@@@@@@@@;Wm`w=Tjw000003vP000,0*13)
ch B type 4 mmsi 003669154: date 2021-5-3 time 22:02:09 lat 47.615429 lon -122.309357 (!AIVDM,1,1,,A,403Ot`QvEAn29o@7;LK?fbg02L90,0*33)

gnuais is also sending the data to aprs.fi

Pop up the map at https://aprs.fi and click on nearby boats to see if they are reporting via your iGate. Admitedly, the aprs.fi interface is not the best at showing that.

Outstanding Issues