APRS and Node Red
Introduction
Running a large number of digipeaters and IGates, it was time-consuming to verify that everything was running as expected. I previously had been playing with Node-RED and eventually created a script to gather information on my APRS stations and display a simple red/green status for each. This allows for a quick scan to note any broken stations, and start to do additional troubleshooting to identify the problem. Previously my checking was sporadic and inconsistent, and might be weeks before I was aware of a problem with some of my more remote stations. The Node-RED page provides an easy way to do a quick scan for any issues each morning. On some sites, I also monitor other non-ham equipment.

This setup has two parts: a series of Linux scripts and files and then the Node-RED flows.
Part 1 – Linux scripts
I have written the information below assuming that the user is familiar with basic Linux commands, bash scripts, and setting permissions to allow script execution. I am not a Linux expert by any means, and there may be better methods of accomplishing these tasks, but this was what worked for me and was as simple as I could make it.
I’m operating Node-RED on a Raspberry Pi 2B running Raspberry Pi OS (Bullseye). These scripts should work fine on the current OS (Bookworm) with some slight changes to reflect the file configurations/locations.
I pull the data directly from the APRS-IS rather than trying to pull information from user websites such as findu.com or aprs.fi. Those user websites typically have limits on how often one can access their systems, and/or limitations on any APIs. I strongly recommend that you do not add to the costs and demands on those user web interfaces. If you are using an APRS client on a computer you are most likely already connecting to the APRS-IS.
Using a direct connection to the APRS-IS stream has the added advantage that I can create daily logs and refer to them in the future for whatever time frame I elect to store those files. If the file locations are accessible to the Node-RED computer, both parts need not be run on the same computer.
Netcat is a standard Linux command that should be available on most Linux OSs. It allows you to create a TCP connection to the APRS-IS server.
I am also using a command called ts
for time stamps. It is a utility that you will likely have to add to your system and is contained in the moreutils
package.
sudo apt install -y moreutils
I’ve configured the script below to run each day at 23:59 local time. The script stops the network connection, moves the current day’s log into a new file, which includes today's date in the file name, and restarts the network connection for the new day.
A second version of the connect script does not do the file movements, so that a connection can be restarted during the day and not delete existing log files.
All of these scripts should be in the same directory. Two subdirectories: aprsTemp
& aprsLog
are also used.
connect.sh
sudo pkill netcat
NAME=`date '+%F'`;
cat /home/pi/logfile | grep -v logresp > /home/pi/aprsLog/temp
cat /home/pi/aprsLog/temp | grep -v aprsc > /home/pi/aprsLog/$NAME
sleep 1
rm logfile
sleep 2
netcat noam.aprs2.net 14580 < /home/pi/a1 | /bin/ts '[%Y-%m-%d %H:%M:%S]' >> /home/pi/logfile &
echo "connections started"
NOTE: the Netcat line above did wrap due to page formatting. Your script should have the entire command netcat…
through …/logfile &
on a single line. Make sure you make this script executable. i.e.:
chmod +x connect.sh
connect1.sh (for use during the day)
sudo pkill netcat
sleep 3
netcat noam.aprs2.net 14580 < /home/pi/a1 | ts '[%Y-%m-%d %H:%M:%S]' >> /home/pi/logfile &
echo "connections started"
NOTE: beware the line wrap above & make sure it is executable.
chmod +x connect1.sh
pkill netcat
kills any processes that the name includes netcat
, pauses 3 seconds, then calls netcat
with a connection to a North American APRS Tier 2 server, uses the a1
file for the login information, it then adds the timestamp to the beginning of each line in the format Year, Month, Day, etc. and outputs those lines to the file called logfile in the /home/pi
directory.
When you make a connection to the APRS-IS on port 14580 you need to log in and define what information you want. The /home/pi/a1
is a distinct file that contains that info and allows me to only have to change it in one location and not both connect scripts. In order to log in, you will need the passcode for your callsign (an Internet search for ‘APRS passcode’ will quickly find you a web page that explains the system and can create one for you).
My a1
file: note the passcode has been obscured with *****
.
user N0NHJ-14 pass ***** vers raspi 1.0 filter b/ABAJO/ANVIL/BALD/BAXTER/BLUEMT/BUFBOY/CBUTTE/CHAIR/DOLLAR/FLATTP/GOODMN/GRAND/H7ONE/HAYDN/KENDAL
Again, this was wrapped, but it should be a single line in your file
My file is based on the buddy list of specific station names I want to monitor. You can use any of the APRS-IS filters to select the stations you download. For the available APRS-IS filter commands, see http://www.aprs-is.net/javAPRSFilter.aspx.
If you are rotating the log as I do in the connect.sh script, you’ll need a cron job to call connect.sh each day at 23:59.
59 23 * * * /home/pi/connect.sh
Make sure the path matches your setup.
So, if this executes correctly, you will have a log file that looks similar to this:
[2025-01-28 18:49:37] ANVIL>APMI04,TCPIP*,qAC,T2RDU:T#253,164,018,001,083,128,00000000
[2025-01-28 18:49:38] ANVIL>APMI04,qAR,MAGMTN:T#252,164,018,001,083,128,00000000
[2025-01-28 18:49:43] ERIDGE>APOT30,qAR,SUNLGT:!3950.15NS10625.03W# 12.8V 79F Solar Digi n0nhj /A=011089
[2025-01-28 18:49:43] ERIDGE>APOT30,qAR,VAILMT:T#149,128,022,075,041,048,00000000
[2025-01-28 18:49:46] # aprsc 2.1.14-g5e22b37 29 Jan 2025 01:49:46 GMT T2USANW 44.25.16.4:14580
[2025-01-28 18:49:46] # aprsc 2.1.14-g5e22b37 29 Jan 2025 01:49:46 GMT T2USANW 44.25.16.4:14580
[2025-01-28 18:50:03] CHAIR>BEACON,WIDE2-1,qAR,SUNLGT:T#043,0.1,12.2,12.2,12.2,0.1,00000000
[2025-01-28 18:50:06] # aprsc 2.1.14-g5e22b37 29 Jan 2025 01:50:06 GMT T2USANW 44.25.16.4:14580
[2025-01-28 18:50:06] # aprsc 2.1.14-g5e22b37 29 Jan 2025 01:50:06 GMT T2USANW 44.25.16.4:14580
The TS utility adds the time stamp to each line as it is received, you can see the packets from 3 of the sites, and the #aprsc…
is a status/keep alive packet between your system and the APRS-IS server.
I have a script called aprs.sh that runs every 10 minutes to filter the packets from specific sites into dedicated files for each site.
aprs.sh
rm /home/pi/aprsTemp/*
tail /home/pi/logfile -n 4500 > /home/pi/aprs
cat aprs | grep "ABAJO" > /home/pi/aprsTemp/abajo
cat aprs | grep "ANVIL" > /home/pi/aprsTemp/anvil
cat aprs | grep "BALD" > /home/pi/aprsTemp/bald
cat aprs | grep "BAXTER" > /home/pi/aprsTemp/baxter
cat aprs | grep "BLUEMT" > /home/pi/aprsTemp/bluemt
cat aprs | grep "BUFBOY" > /home/pi/aprsTemp/bufboy
cat aprs | grep "CBUTTE" > /home/pi/aprsTemp/cbutte
cat aprs | grep "CHAIR" > /home/pi/aprsTemp/chair
cat aprs | grep "DOLLAR" > /home/pi/aprsTemp/dollar
cat aprs | grep "DTSERO" > /home/pi/aprsTemp/dtsero
cat aprs | grep "DURNGO" > /home/pi/aprsTemp/durngo
cat aprs | grep "FLATTP" > /home/pi/aprsTemp/flattp
cat aprs | grep "GOODMN" > /home/pi/aprsTemp/goodmn
cat aprs | grep "GRAND" > /home/pi/aprsTemp/grand
The script starts by deleting all the site-specific files created during the last cycle.
Then it moves the last 4500 lines of the logfile into a temporary file called aprs
. The number of lines is used to determine how far back you want to look, this number will vary depending on the number of sites, how often they beacon, the traffic that they handle, and how long between packets being seen before you decide the site is down/red. I run the aprs.sh
script every 10 minutes, and shoot for gathering the past hour or two. My sites all beacon every 10 minutes, so I could miss a single packet (collision loss, etc.), and still show the site as operational.
So every 10 minutes the site files are cleared, and should have the past hour or two of beacons from the sites.
Cron job:
*/10 * * * * /home/pi/aprs.sh
These site-specific files are then available for Node-RED to pull from.
An example of the site-specific file for BALD:
[2025-02-06 13:52:10] K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AQ}MT-RTG|#l%!'a|!wp<!|3
[2025-02-06 13:52:17] BAXTER>BEACON,qAR,BALD:T#661,13.6,0.0,0,0,11.2,00000000
[2025-02-06 13:52:40] BALD>APMI04,TCPIP*,qAC,T2SJC:@062052z3831.72NT10919.48W& Bald Igate/Digi U=11.8V. PHG4830 n0nhj/k7qeq
[2025-02-06 13:53:22] SANDFL>BEACON,qAR,BALD:;146.900-u*111111z3834.39N/10933.00WrT88 R20m /A=004026
[2025-02-06 13:53:35] BALD>APMI04,TCPIP*,qAC,T2SJC:T#167,150,026,008,082,000,00000000
[2025-02-06 13:53:35] BALD>APMI04,WIDE2-2,qAR,HORNMT:T#166,150,026,008,082,000,00000000
[2025-02-06 13:54:09] K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AP}|#m%!'a|!wm9!|3
[2025-02-06 13:54:09][2025-02-06 13:54:09] K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AP}|#m%!'a|!wm9!|3
K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AP}|#m%!'a|!wm9!|3
[2025-02-06 13:55:03] HORNMT>APRX29,WIDE2-1,qAR,BALD:!3913.05NI11110.46W#K7YI I-Gate / Digi
[2025-02-06 13:55:41][2025-02-06 13:55:41] K7QEQ-5>S8SSXS,SANDFL*,WIDE2-1,qAR,BALD:`u<{l |>/'"AU}MT-RTG|&c%I'j|!wQn!|3
K7QEQ-5>S8SSXS,SANDFL*,WIDE2-1,qAR,BALD:`u<{l |>/'"AU}MT-RTG|&c%I'j|!wQn!|3
[2025-02-06 13:55:41] K7QEQ-5>S8SSXS,SANDFL*,WIDE2-1,qAR,BALD:`u<{l |>/'"AU}MT-RTG|&c%I'j|!wQn!|3
[2025-02-06 13:55:47] BAXTER>APN391,qAR,BALD:!3935.30NS10856.98W#PHG4820 W2,COn /A=008720 N0NHJ
[2025-02-06 13:56:08] K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AQ}|#n%!'a|!wh-!|3
[2025-02-06 13:56:22] SANDFL>APN382,qAR,BALD:!3834.22N110931.68W#PHG4090/W1,UTn N0NHJ K7QEQ /A=004610
[2025-02-06 14:12:01][2025-02-06 14:12:01] K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AS}|#v${'b|!wc6!|3
K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AS}|#v${'b|!wc6!|3
[2025-02-06 14:12:01] K7QEQ-6>S8SSXT,SANDFL*,WIDE2-1,qAR,BALD:`u<|l O>/'"AS}|#v${'b|!wc6!|3
[2025-02-06 14:12:40] BALD>APMI04,TCPIP*,qAC,T2SJC:@062112z3831.72NT10919.48W& Bald Igate/Digi U=11.8V. PHG4830 n0nhj/k7qeq
[2025-02-06 14:13:34] BALD>APMI04,TCPIP*,qAC,T2SJC:T#170,132,028,008,081,000,00000000
[2025-02-06 14:13:36][2025-02-06 14:13:36] BALD>APMI04,BAXTER*,WIDE2-1,qAR,MAGMTN:T#171,134,028,008,081,000,00000000
BALD>APMI04,BAXTER*,WIDE2-1,qAR,MAGMTN:T#171,134,028,008,081,000,00000000
[2025-02-06 14:13:37] HORNMT>APRX29,WIDE2-1,qAR,BALD:;447.625- *111111z3912.59N/11108.52WrT123.0 WX7Y Horn Mtn
[2025-02-06 14:15:33] K7QEQ-5>S8SSXS,SANDFL*,WIDE2-1,qAR,BALD:`u<{l |>/'"AX}|&m%H'k|!wIt!|3
[2025-02-06 14:15:33] K7QEQ-5>S8SSXS,SANDFL*,WIDE2-1,qAR,BALD:`u<{l |>/'"AX}|&m%H'k|!wIt!|3
[2025-02-06 14:15:43] BAXTER>APN391,WIDE2-1,qAR,BALD:!3935.30NS10856.98W#PHG4820 W2,COn /A=008720 N0NHJ
[2025-02-06 14:15:43] BAXTER>APN391,WIDE2-1,qAR,BALD:!3935.30NS10856.98W#PHG4820 W2,COn /A=008720 N0NHJ
The duplicates are not RF duplicates. They are created due to my filtering process, but since the temporary files only exist for 10 minutes are of little consequence. The file will include not only packets originated by BALD but also those that have been digipeated or IGated at Bald Mesa.
To reduce the odds of network hiccups disconnecting a Netcat session, I restart it hourly with the connect1.sh
script above. This can also create problems, so you might prefer to let it run for the entire day without restarts if that works better for you.
Below is the complete cron job:
# Edit this file to introduce tasks to be run by cron.
#
# m h dom mon dow command
0 */1 * * * /home/pi/connect1.sh
*/10 * * * * /home/pi/aprs.sh
59 23 * * * /home/pi/connect.sh
These scripts gather all of your raw data for Node-RED to then evaluate.
Part 2 – Node-RED
This section presumes that you are familiar with Node-RED and have previously built flows.
Node-RED evaluates the files created in the Linux scripts and looks for specific text to test if those types of packets exist.
For my purposes, I check several actions using the APRS-IS data. For standalone digipeaters, I typically look for regular location packets. For the IGates, I may check if there are packets that indicate a connection to APRS-IS. For those where I have a VPN, I’ll also ping the gateway device.
All of these are part of a page that displays the status for each node.


A red indicator helps me identify sites that need attention, or that I need to start troubleshooting. I’m able to open the Node-RED page if I’m at the computer and check it a couple of times a day, or if I need to can check it on my phone via a VPN.
Location packets are verified by looking for them in the site-specific files created by the bash scripts described in part 1.
Example of a line we are searching for:
[2025-02-07 14:33:09]
ANVIL>APMI04,WIDE2,qAR,MAGMTN:@072132z3932.07NT10757.44W&WX3in1Mini U 14.1V Anvil Pts Igate n0nhj
The middle of this flow is used to check for location packets. All of these are basic nodes included in the initial Node-RED setup. If you are reading from your local files, you do not need to install any of the custom APRS-related nodes.

The timestamp checks the file every 5 minutes and calls the read file node.

The read file reads the file for your site that was created in the Linux scripts from Part 1.

The Switch node ANVIL Location looks for the location packets that should be sent every 10 minutes.
They will be formatted with the digi name and the tocall
, so for this example I test for ‘ANVIL>A’. You can make this test as specific as you like. Since the device I’m monitoring is a Microsat WX3in1 Mini, both the location packets and the telemetry packets will start ANVIL>APMI04…
and will pass this test, so in this case ‘location’ is a bit of a misnomer. I could get more specific and validate the latitude or longitude which should be unique to this site & location packet, but I did not take it that far.
If the test is passed, and the string is found, it outputs connection 1, if not connection 2.


The change node (set msg.payload
) sets it to Boolean true for connection 1 (string found – site is working), or false for connection 2 – not found so the site is not working.




The Boolean output is read by an LED node and is used for the red or green indication.


Which creates an output on the display page:

To determine if the packets are sent to APRS-IS via an IP connection, the flow is virtually identical with the test being ANVIL>APMI04,TCPIP*
similar to this packet from SUNLGT:
[2025-01-28 21:39:29] SUNLGT>APMI04,TCPIP*,qAC,T2CAEAST:@290439z3925.36NT10722.53W&Sunlight Igate/Digi U=13.4V. PHG4830 n0nhj
For my Digi’d or IGate check, I test for ,ANVIL
which will exclude packets originated by Anvil, but include packets where ANVIL is in the used path. Note the comma prior to the name.
Example of the gated packet:
[2025-02-01 23:54:30] VAILMT>APMI04,WIDE2,qAR,ANVIL:;146.610CO*111111z3936.33N/10621.56WrT107 -060 Ski Country ARC
By looking at the raw packets from a station, you should be able to determine what the appropriate text needs to be. By keeping it short, such as the NAME>A it doesn’t matter if I happen to change out equipment since all of the APRS tocall
normally start with A. If you get more specific you could run into issues if the tocall
changes due to equipment changes.
Checking the network connection is very similar. Node-RED has a ping function node. The ping provides a variable/false that is passed to another LED indicator.




The ping checks are also useful for other equipment at the site such as routers, remote power switches, etc. I’m only able to do ping checks at sites where I have a VPN available.
I also use Node-RED at several sites to control remote power switches, and other interfaces, which is beyond the scope of this article and well-documented in other places. I only put a power cycle button on my Node-RED displays, to avoid the possibility of turning off something like a router and then losing contact with the remote site and being unable to turn it back on.
Much of my development in Node-RED monitoring involved trying things and adapting based on what worked and what did not. Several additional methods may provide similar data. The nice thing about Node-RED is how easy it is to try new things. Remember to use the debug nodes to follow your flow if you need to troubleshoot.