How-To: Host two ADS-B receivers on one Raspberry Pi
Using one Raspberry Pi to run two instances of dump1090-fa listening to two FlightAware FlightStick Pro Plus with two antennas
Firstly, this how-to assumes there’s already a pi running dump1090-fa with a FlightStick and antenna, and that it is all already working – like this.
Secondly, FlightSticks tend to all have the same serial number (00001000) and so to run two on one system requires that at least one of them be updated with a new serial number.
Amending the serial numbers
To amend the serial number, install RTL-SDR tools on the Pi with the FlightStick inserted and execute a command with the new serial number.
Install RTL-SDR tools on the Pi:
sudo apt install rtl-sdr
Stop the dump1090-fa service so that the FlightStick can be accessed by RTL-SDR tools:
sudo systemctl stop dump1090-fa
Then execute the following command to update the serial number to 00000101 (I numbered mine 00000101 & 00000102 for the two sticks on one Pi and 00000201 for the stick on my second Pi):
rtl_eeprom -s 00000101
I’d suggest then labelling the FlightStick that has this serial number. Then restart dump1090-fa and ensure it is still working as expected.
sudo systemctl start dump1090-fa
Then repeat the above with the second stick, using a unique serial number.
Installing and configuring a second instance of dump1090-fa to use the second stick
Firstly, create a blank script to start the new instance, and make it executable
sudo touch /etc/init.d/dump1090-fa-2
sudo chmod +x /etc/init.d/dump1090-fa-2
Next, open the new file to edit it
sudo nano /etc/init.d/dump1090-fa-2
and copy the following into it
#!/bin/sh
### BEGIN INIT INFO
# Provides: dump1090-mutability
# Required-Start: $remote_fs $network
# Required-Stop: $remote_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: dump1090 daemon (mutability variant)
# Description: Receives ADS-B messages from a RTLSDR dongle
# and makes them available to other applications via
# a variety of network protocols.
### END INIT INFO
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="dump1090-fa-2 daemon"
NAME=dump1090-fa-2
DAEMON=/usr/bin/dump1090-fa
ARGS="--measure-noise "
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# work out daemon args
# sanitize missing settings
[ -z "$START_DUMP1090" ] && START_DUMP1090=no
[ -z "$DUMP1090_USER" ] && DUMP1090_USER="missing-DUMP1090_USER-setting-in-config"
[ -z "$RAW_INPUT_PORT" ] && RAW_INPUT_PORT=0
[ -z "$RAW_OUTPUT_PORT" ] && RAW_OUTPUT_PORT=0
[ -z "$SBS_OUTPUT_PORT" ] && SBS_OUTPUT_PORT=0
[ -z "$BEAST_INPUT_PORT" ] && BEAST_INPUT_PORT=0
[ -z "$BEAST_OUTPUT_PORT" ] && BEAST_OUTPUT_PORT=0
[ -z "$NET_BUFFER" ] && NET_BUFFER=0
[ -z "$JSON_INTERVAL" ] && JSON_INTERVAL=0
[ -z "$MAX_RANGE" ] && MAX_RANGE=300
# receiver:
NICELEVEL="--nicelevel -5"
case "x$DEVICE" in
x|x0) ARGS="$ARGS --net" ;;
xnone) ARGS="$ARGS --net-only"; NICELEVEL="" ;;
*) ARGS="$ARGS --net --device-index $DEVICE" ;;
esac
case "x$GAIN" in
x|xmax) ;;
xagc) ARGS="$ARGS --gain -10" ;;
*) ARGS="$ARGS --gain $GAIN" ;;
esac
if [ -n "$PPM" ]; then ARGS="$ARGS --ppm $PPM"; fi
# decoder:
if [ "x$FIX_CRC" = "xyes" ]; then ARGS="$ARGS --fix"; fi
if [ -n "$LAT" ]; then ARGS="$ARGS --lat $LAT"; fi
if [ -n "$LON" ]; then ARGS="$ARGS --lon $LON"; fi
ARGS="$ARGS --max-range $MAX_RANGE"
# net:
ARGS="$ARGS \
--net-ri-port $RAW_INPUT_PORT --net-ro-port $RAW_OUTPUT_PORT \
--net-bi-port $BEAST_INPUT_PORT --net-bo-port $BEAST_OUTPUT_PORT \
--net-sbs-port $SBS_OUTPUT_PORT"
if [ -n "$NET_HEARTBEAT" ]; then ARGS="$ARGS --net-heartbeat $NET_HEARTBEAT"; fi
if [ -n "$NET_OUTPUT_SIZE" ]; then ARGS="$ARGS --net-ro-size $NET_OUTPUT_SIZE"; fi
if [ -n "$NET_OUTPUT_INTERVAL" ]; then ARGS="$ARGS --net-ro-interval $NET_OUTPUT_INTERVAL"; fi
if [ "$NET_BUFFER" -le "65536" ]; then ARGS="$ARGS --net-buffer 0"
elif [ "$NET_BUFFER" -le "131072" ]; then ARGS="$ARGS --net-buffer 1"
elif [ "$NET_BUFFER" -le "262144" ]; then ARGS="$ARGS --net-buffer 2"
else ARGS="$ARGS --net-buffer 3"; fi
if [ -n "$NET_BIND_ADDRESS" ]; then ARGS="$ARGS --net-bind-address $NET_BIND_ADDRESS"; fi
# misc:
if [ -n "$STATS_INTERVAL" ]; then ARGS="$ARGS --stats-every $STATS_INTERVAL"; fi
if [ -n "$JSON_DIR" ]; then ARGS="$ARGS --write-json $JSON_DIR"; fi
if [ -n "$JSON_INTERVAL" ]; then ARGS="$ARGS --write-json-every $JSON_INTERVAL"; fi
case "x$JSON_LOCATION_ACCURACY" in
xexact) ARGS="$ARGS --json-location-accuracy 2" ;;
xapproximate) ARGS="$ARGS --json-location-accuracy 1" ;;
*) ARGS="$ARGS --json-location-accuracy 0" ;;
esac
if [ "x$LOG_DECODED_MESSAGES" != "xyes" ]; then ARGS="$ARGS --quiet"; fi
if [ -n "$EXTRA_ARGS" ]; then ARGS="$ARGS $EXTRA_ARGS"; fi
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
if [ "x$START_DUMP1090" != "xyes" ]; then
log_warning_msg "Not starting $NAME daemon, disabled via /etc/default/$NAME"
return 2
fi
start-stop-daemon --start --quiet --pidfile $PIDFILE --user "$DUMP1090_USER" --exec $DAEMON --test > /dev/null \
|| return 1
# create JSON_DIR with the appropriate permissions
# (it is on /run by default, so will be lost on reboot)
if [ "x$JSON_DIR" != "x" ]; then
if [ ! -e $JSON_DIR ]; then
(mkdir $JSON_DIR && chmod 0755 $JSON_DIR && chown $DUMP1090_USER $JSON_DIR) || log_warning_msg "Failed to create $JSON_DIR"
fi
fi
# create logfile with the appropriate permissions if not already there
touch $LOGFILE
chown "$DUMP1090_USER":root $LOGFILE
start-stop-daemon --start $NICELEVEL --quiet --pidfile $PIDFILE --user "$DUMP1090_USER" --chuid "$DUMP1090_USER" --make-pidfile --background --no-close --exec $DAEMON -- \
$ARGS >>$LOGFILE 2>&1 \
|| return 2
sleep 1
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --retry=TERM/30/KILL/5 --pidfile $PIDFILE --user "$DUMP1090_USER" --exec $DAEMON
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
sleep 1
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
:
Close and save the above file by pressing Ctrl+X, answering “yes” and pressing ENTER to keep the same filename
Next, enable autostart, and reload the newly installed daemon
sudo update-rc.d dump1090-fa-2 defaults
sudo systemctl daemon-reload
Create a new blank file to hold the configuration
sudo nano /etc/default/dump1090-fa-2
And copy the following into it
# dump1090-fa-2 configuration file
# This is a POSIX shell fragment.
# You can edit this file directly.
# Set to "yes" to start dump1090 on boot.
START_DUMP1090="yes"
# User to run dump1090 as.
DUMP1090_USER="dump1090"
# Logfile to log to
LOGFILE="/var/log/dump1090-fa-2.log"
#
# Receiver options
#
# RTLSDR device index or serial number to use
# If set to "none", dump1090 will be started in --net-only mode
DEVICE="1"
# RTLSDR gain in dB.
# If set to "max" the maximum supported gain is used.
# If set to "-10" (the default) the tuner AGC is used to set the gain.
GAIN="-10"
# RTLSDR frequency correction in PPM
PPM="0"
#
# Decoding options
#
# If yes, fixes messages with correctable CRC errors.
FIX_CRC="yes"
# If set, supplies a reference location for local position decoding.
LAT=""
LON=""
# If set, provides the absolute maximum receiver range used to
# filter bad position reports, and to determine when local position
# decoding is safe to use. Specify this in nautical miles (NM).
MAX_RANGE="300"
#
# Networking options
#
# Port to listen on for raw (AVR-format) input connections. 0 disables.
RAW_INPUT_PORT="31001"
# Port to listen on for raw (AVR-format) output connections. 0 disables.
RAW_OUTPUT_PORT="31002"
# Port to listen on for SBS-format output connections. 0 disables.
SBS_OUTPUT_PORT="31003"
# Port to listen on for Beast-format input connections. 0 disables.
BEAST_INPUT_PORT="31004,31104"
# Port to listen on for Beast-format output connections. 0 disables.
BEAST_OUTPUT_PORT="31005"
# TCP heartbeat interval in seconds. 0 disables.
NET_HEARTBEAT="60"
# Minimum output buffer size per write, in bytes.
NET_OUTPUT_SIZE="500"
# Maximum buffering time before writing, in seconds.
NET_OUTPUT_INTERVAL="1"
# TCP buffer size, in bytes
NET_BUFFER="262144"
# Bind ports on a particular address. If unset, binds to all interfaces.
# This defaults to binding to localhost. If you need to allow remote
# connections, change this.
#NET_BIND_ADDRESS="127.0.0.1"
#
# Misc options
#
# Interval (in seconds) between logging stats to the logfile. 0 disables.
STATS_INTERVAL="3600"
# Path to write json state to (for use with an external webserver). Blank disables.
JSON_DIR="/run/dump1090-fa-2"
# Interval between writing json state (in seconds). 0 disables.
JSON_INTERVAL="1"
# Accuracy of receiver location to write to json state, one of "exact" / "approximate" / "none"
JSON_LOCATION_ACCURACY="approximate"
# Set to yes to log all decoded messages
# This can get large fast!
LOG_DECODED_MESSAGES="no"
# Additional options that are passed to the Daemon.
EXTRA_ARGS=""
NET_BIND_ADDRESS=""
Edit the following lines within the above to reflect the location of the receiver
# If set, supplies a reference location for local position decoding._
LAT=""
LON=""
Enabling a map to show the new receiver’s aircraft separately
Make a copy of the existing dump1090-fa folder
cd /usr/share
sudo cp -r dump1090-fa dump1090-fa-2
Create a new file for the config
sudo nano /etc/lighttpd/conf-available/89-dump1090-fa-2.conf
and copy the following into it
# Allows access to the static files that provide the dump1090 map view,
# and also to the dynamically-generated json parts that contain aircraft
# data and are periodically written by the dump1090 daemon.
alias.url += (
"/dump1090-fa-2/data/" => "/run/dump1090-fa-2/",
"/dump1090-fa-2/" => "/usr/share/dump1090-fa-2/html/"
)
# redirect the slash-less URL
url.redirect += (
"^/dump1090-fa-2$" => "/dump1090-fa-2/"
)
Finally, enable the lighttpd module for the second dump1090-fa instance and reboot the Pi
sudo lighty-enable-mod dump1090-fa-2
sudo /etc/init.d/lighttpd force-reload
sudo shutdown -r now
The new receiver’s map is available at
http://<ip-address>/dump1090-fa-2
The map & table can be customised by editing the config.js file:
sudo nano /usr/local/share/tar1090/html-combine1090/config.js
Adding the plane count into the page title allows easy reference in a browser tab:
PlaneCountInTitle = true;
The columns that show in the table by default when opening the page can be amended by uncommenting relevant rows in this block
// Columns that have a // in front of them are shown.
// add slash-star at start of line to default to not modify columns (and the one at the end)
HideCols = [
"#icao",
// "#flag",
// "#flight",
"#registration",
// "#aircraft_type",
"#squawk",
// "#altitude",
"#speed",
"#vert_rate",
// "#distance",
"#track",
"#msgs",
// "#seen",
"#rssi",
"#lat",
"#lon",
"#data_source",
]
// add star-slash at start of line to default to not modify columns (and the one at the start)
The page title itself can be changed from “tar1090” should you wish
// Controls page title, righthand pane when nothing is selected
PageName = "My ADS-B";
Range rings can be edited
// Range rings
// Also called range rings :)
SiteCircles = true; // true to show circles (only shown if the center marker is shown)
// In miles, nautical miles, or km (depending settings value 'DisplayUnits')
SiteCirclesDistances = new Array(100,150,200,250);
// When more circles defined than cirle colors last color will be used or black by default
//SiteCirclesColors = ['#FF0000', '#0000FF', '#00FF00'];
// Show circles using dashed line (CAUTION, can be slow, especially when zooming in a lot)
//SiteCirclesLineDash = [5, 5]; // null - solid line, [5, 5] - dashed line with 5 pixel lines and spaces in between
Once any changes are made to the config file, don’t forget to restart combine1090
sudo systemctl restart combine1090
Useful links
https://discussions.flightaware.com/t/one-pi-two-dongles-two-maps-two-receivers/33978/14