Finding APRS on 144.800 MHz with bladeRF, SDR++ WAV captures, and Dire Wolf
Objective
The goal of this experiment was to investigate a set of short burst-like signals observed in an SDR++ baseband WAV recording around the VHF amateur radio band. The signal initially appeared as an unknown narrowband burst. The research objective was to identify the signal, extract it from the wider IQ recording, decode it if possible, and then build a small realtime receiver pipeline for longer protocol observation.
This was a receive-only experiment. No transmission, interference, authentication bypass, or protected-content recovery was attempted.
Setup
Hardware
- SDR: Nuand bladeRF 2.0 micro xA5
- Antenna: Diamond D3000N discone
- Location: fixed antenna, apartment, high-floor urban RF environment
- Host OS: Kali Linux
Software
- SDR++ for initial recording
- Python with
numpy,scipy,matplotlib iq_quicklook.pyfor WAV triageiq_extract.pyfor signal extraction and FM audio generationmultimon-ngfor initial APRS/AFSK1200 decoding- Dire Wolf for AX.25/APRS validation and richer protocol interpretation
- GNU Radio / osmosdr for realtime bladeRF receive pipeline
Initial Capture
The signal was found inside an SDR++ baseband WAV file:
file: baseband_144676000Hz_16-20-19_14-06-2026.wav
center frequency: 144.676000 MHz
sample rate: 520834 Hz
channels: 2
format: 16-bit interleaved IQ WAV
duration: 708.00 seconds
The first triage command was:
python3 iq_quicklook.py baseband_144676000Hz_16-20-19_14-06-2026.wav
The file was large, and the script was eventually killed while trying to produce the full spectrogram. However, burst detection completed before the process was terminated.
Detected bursts:
# start(s) end(s) dur(ms) f_off(kHz) peak(dB)
0 427.868 428.778 910.0 121.97 29.9
1 576.198 577.040 842.6 121.97 29.7
2 695.957 696.800 842.6 121.97 29.6
The repeated offset was the key observation.
The estimated RF frequency was:
144.676000 MHz + 0.121970 MHz = 144.797970 MHz
This is effectively the European APRS frequency around 144.800 MHz.
At this stage, the strongest hypothesis became:
Mode: APRS / AX.25 packet radio
RF modulation: narrow FM
Data modem: Bell 202 AFSK1200
Quicklook Windows
To avoid plotting the entire 708-second WAV, smaller windows were generated around each event:
python3 iq_quicklook.py baseband_144676000Hz_16-20-19_14-06-2026.wav \
--start 425 \
--max-seconds 6
mv baseband_144676000Hz_16-20-19_14-06-2026_quicklook.png event0_quicklook.png
python3 iq_quicklook.py baseband_144676000Hz_16-20-19_14-06-2026.wav \
--start 574 \
--max-seconds 6
mv baseband_144676000Hz_16-20-19_14-06-2026_quicklook.png event1_quicklook.png
python3 iq_quicklook.py baseband_144676000Hz_16-20-19_14-06-2026.wav \
--start 694 \
--max-seconds 5
mv baseband_144676000Hz_16-20-19_14-06-2026_quicklook.png event2_quicklook.png
The quicklook images showed a strong narrowband signal at approximately +122 kHz from the WAV center.
Signal Extraction
Each burst was extracted with a small amount of padding and shifted to baseband using iq_extract.py.
mkdir -p extracted_144800
Event 0
python3 iq_extract.py baseband_144676000Hz_16-20-19_14-06-2026.wav \
--start 427.70 \
--end 430.20 \
--offset 121970 \
--bw 25000 \
--center 144676000
mv baseband_144676000Hz_16-20-19_14-06-2026_bb.wav extracted_144800/event0_144800_bb.wav
mv baseband_144676000Hz_16-20-19_14-06-2026_audio.wav extracted_144800/event0_144800_audio.wav
Event 1
python3 iq_extract.py baseband_144676000Hz_16-20-19_14-06-2026.wav \
--start 574.80 \
--end 577.40 \
--offset 121970 \
--bw 25000 \
--center 144676000
mv baseband_144676000Hz_16-20-19_14-06-2026_bb.wav extracted_144800/event1_144800_bb.wav
mv baseband_144676000Hz_16-20-19_14-06-2026_audio.wav extracted_144800/event1_144800_audio.wav
Event 2
python3 iq_extract.py baseband_144676000Hz_16-20-19_14-06-2026.wav \
--start 694.60 \
--end 697.10 \
--offset 121970 \
--bw 25000 \
--center 144676000
mv baseband_144676000Hz_16-20-19_14-06-2026_bb.wav extracted_144800/event2_144800_bb.wav
mv baseband_144676000Hz_16-20-19_14-06-2026_audio.wav extracted_144800/event2_144800_audio.wav
The extraction produced two useful files per event:
*_bb.wav extracted narrowband IQ
*_audio.wav FM-demodulated mono audio preview
The audio file does not mean voice content. In this case it contains AFSK tones, which carry digital packet data.
First Decode with multimon-ng
The extracted FM-demodulated audio was tested with multimon-ng using the AFSK1200 demodulator:
multimon-ng -t wav -a AFSK1200 extracted_144800/event0_144800_audio.wav
The result confirmed APRS:
AFSK1200: fm YO8M-2 to APMI03-0 via WIDE2-1 UI pid=F0
@141154#4706.01N/02739.92E# DigiPeater Paun U=12.8V T=23.5C
This established the protocol stack:
RF channel: ~144.800 MHz
RF modulation: narrow FM
Audio modem: Bell 202 AFSK
Baud rate: 1200 baud
Link layer: AX.25 UI frame
Application: APRS
Cross-validation with Dire Wolf
Dire Wolf was then used as a second decoder. A minimal receive-only configuration was created:
cat > direwolf_rx_only.conf <<'EOF'
# Minimal receive-only Dire Wolf config for offline APRS WAV decoding
ACHANNELS 1
CHANNEL 0
MYCALL N0CALL
MODEM 1200
EOF
Dire Wolf expects either an audio device or stdin. To decode the WAV files offline, sox was used to pipe raw 48 kHz signed 16-bit mono audio into Dire Wolf:
for f in extracted_144800/*_audio.wav; do
echo
echo "===== $f ====="
sox -q "$f" \
-t raw -r 48000 -e signed-integer -b 16 -c 1 - | \
direwolf -c ./direwolf_rx_only.conf \
-r 48000 \
-n 1 \
-b 16 \
-t 0 \
-
done | tee aprs_offline_analysis/direwolf_samples.log
Dire Wolf successfully decoded the packets and provided richer interpretation.
Decoded Results
Event 0: YO8M-2 digipeater beacon
YO8M-2>APMI03,WIDE2-1:@141154#4706.01N/02739.92E# DigiPeater Paun U=12.8V T=23.5C
Decoded interpretation:
Source: YO8M-2
Destination: APMI03
Path: WIDE2-1
Frame type: AX.25 UI
Application: APRS
Position: 47.100167 N, 27.665333 E
Comment: DigiPeater Paun
Voltage: 12.8 V
Temperature: 23.5 C
Dire Wolf identified APMI03 as a Microsat PLXDigi software/device identifier.
It also reported:
Timestamp must be 6 digits followed by z, h, or /.
This refers to the payload timestamp format:
@141154#
The position still decoded successfully, so this warning was interpreted as an APRS-format issue rather than a demodulation failure.
Event 1: YO8SDE-10 direct packet and repeated copy
Dire Wolf decoded a direct packet:
YO8SDE-10>APDW18,WIDE1-1,WIDE2-1:!4709.54NT02734.22E&PHG2140 Test YO
And then a digipeated copy:
YO8SDE-10>APDW18,YO8M-2,WIDE1*,WIDE2-1:!4709.54NT02734.22E&PHG2140 Test YO
Interpretation:
Source: YO8SDE-10
Destination: APDW18
Software: Dire Wolf
Position: 47.159000 N, 27.570333 E
Comment: Test YO
PHG: PHG2140
The important path element was:
YO8M-2,WIDE1*
The asterisk indicates that the WIDE1 hop was used. This means YO8M-2 repeated the packet. The receiver therefore heard both the original station transmission and the copy repeated by the digipeater.
Event 2: YO8SDE-10 repeated observation
A later burst again decoded as YO8SDE-10, including both a direct and digipeated copy:
YO8SDE-10>APDW18,WIDE1-1,WIDE2-1:!4709.54NS02734.22E#PHG2140 Test YO
YO8SDE-10>APDW18,YO8M-2,WIDE1*,WIDE2-1:!4709.54NS02734.22E#PHG2140 Test YO
This reinforced that the local APRS channel was active and that YO8M-2 was participating as a digipeater.
About “Audio Level”
Dire Wolf reported lines like:
YO8SDE-10 audio level = 69(6/5) ____||___
This does not mean voice was present. It is Dire Wolf’s report of the received AFSK tone level and decoder quality. APRS in this case is digital packet data carried as audio-frequency tones over an FM radio channel.
The signal chain is:
144.800 MHz RF
→ narrow FM
→ Bell 202 AFSK tones at 1200 / 2200 Hz
→ 1200 baud data stream
→ AX.25 UI frame
→ APRS payload
No spoken audio was identified in the decoded APRS packets.
Realtime Receive Pipeline
After offline decoding succeeded, a realtime receive chain was built:
bladeRF
→ GNU Radio / osmosdr source
→ frequency translation to 144.800 MHz
→ narrowband FM demod
→ raw 48 kHz signed 16-bit mono audio FIFO
→ Dire Wolf
→ text log only
The realtime goal was to avoid storing large IQ or WAV files. Instead, only decoded text logs were saved.
A short realtime test eventually produced a live decode:
2026-06-14T17:17:30+0200 YO8M-2 audio level = 89(22/23) _|||||___
2026-06-14T17:17:30+0200 [0.3] YO8M-2>APMI03,WIDE2-1:@141244#4706.01N/02739.92E# DigiPeater Paun U=12.8V T=23.0C
2026-06-14T17:17:30+0200 Timestamp must be 6 digits followed by z, h, or /.
2026-06-14T17:17:30+0200 Position with time, Generic digipeater, Microsat PLXDigi
2026-06-14T17:17:30+0200 N 47 06.0100, E 027 39.9200
2026-06-14T17:17:30+0200 DigiPeater Paun U=12.8V T=23.0C
This confirmed that the live bladeRF pipeline was working.
Realtime Log-only Command
The final realtime direction was to run a longer observation window while saving only text logs:
cd /home/kali/sdr/aprs
SESSION=$(date +%Y%m%d_%H%M%S)
OUTDIR="aprs_live_$SESSION"
FIFO="/tmp/aprs_144800_$SESSION.s16"
mkdir -p "$OUTDIR"
rm -f "$FIFO"
mkfifo "$FIFO"
LOG="$OUTDIR/direwolf_live.log"
GRLOG="$OUTDIR/gnuradio.log"
cat "$FIFO" | \
stdbuf -oL direwolf \
-c ./direwolf_rx_only.conf \
-r 48000 \
-n 1 \
-b 16 \
-t 0 \
- | \
awk '{ print strftime("%Y-%m-%dT%H:%M:%S%z"), $0; fflush(); }' | \
tee "$LOG" &
DEC_PID=$!
python3 -u ./bladerf_aprs_live.py \
--fs 625000 \
--fc 144676000 \
--target 144800000 \
--gain 35 \
--bandwidth 300000 \
--channel-bw 50000 \
--seconds 1200 \
--out "$FIFO" 2>&1 | tee "$GRLOG"
wait "$DEC_PID"
rm -f "$FIFO"
echo
echo "Saved:"
echo " $LOG"
echo " $GRLOG"
du -h "$OUTDIR"
This approach avoids storing:
large IQ recordings
raw demodulated audio
WAV files
Only decoded protocol logs are kept.
Findings
The unknown VHF bursts were successfully identified as APRS packet radio traffic.
Confirmed findings:
Frequency: approximately 144.800 MHz
Mode: narrow FM
Data modem: Bell 202 AFSK1200
Protocol: AX.25 UI frames
Application: APRS
Confirmed tools: multimon-ng and Dire Wolf
Observed stations:
YO8M-2
Role: likely digipeater beacon
Position: 47.100167 N, 27.665333 E
Comment: DigiPeater Paun
Telemetry: voltage and temperature reported
YO8SDE-10
Role: APRS station / test station
Position: 47.159000 N, 27.570333 E
Comment: Test YO
Path: observed both directly and via YO8M-2
The channel contained both:
1. digipeater self-beacons
2. third-party APRS packets repeated by the digipeater
This gives a useful view into the local APRS topology.
Limitations
This was a small observation window and should not be treated as a complete APRS coverage study.
Limitations:
- Only a few offline bursts were analyzed initially.
- The receiving antenna was fixed and not calibrated.
- Signal strength was not converted into absolute field strength.
- Station reachability is receiver-location dependent.
- Some APRS formatting warnings were present, but they did not prevent position decoding.
- The analysis was receive-only and did not validate station ownership or intent.
The decoded data was treated as open over-the-air amateur radio packet traffic. No private or protected content was targeted.
Reproducibility Notes
The minimum useful workflow was:
python3 iq_quicklook.py capture.wav --start <seconds> --max-seconds <window>
Then extract the signal:
python3 iq_extract.py capture.wav \
--start <event_start> \
--end <event_end> \
--offset <offset_hz> \
--bw 25000 \
--center <capture_center_hz>
Then decode:
multimon-ng -t wav -a AFSK1200 extracted_audio.wav
Cross-check with Dire Wolf:
sox -q extracted_audio.wav \
-t raw -r 48000 -e signed-integer -b 16 -c 1 - | \
direwolf -c ./direwolf_rx_only.conf \
-r 48000 \
-n 1 \
-b 16 \
-t 0 \
-
The decisive observation was that all detected bursts were at the same offset:
+121.97 kHz from 144.676 MHz center
which placed the signal at:
144.797970 MHz ≈ 144.800 MHz
This matched the APRS hypothesis and led directly to successful AFSK1200 decoding.
Next Steps
The next useful step is a longer realtime log-only run, around 20–25 minutes, followed by protocol-level analysis.
Recommended analysis fields:
receive timestamp
source callsign
destination/software identifier
digipeater path
direct vs repeated packets
payload type
position
telemetry
comments
duplicates
beacon interval
station timing
Useful questions for the longer run:
- How often does YO8M-2 beacon?
- Which stations are heard directly?
- Which packets are repeated by YO8M-2?
- Are there weather, telemetry, object, item, or message packets?
- What digipeater paths are most common?
- Are repeated packets delayed by a measurable interval?
- Is the channel mostly local traffic, or does the antenna hear wider regional APRS activity?
Conclusion
The unknown narrowband bursts in the VHF recording were identified and confirmed as APRS packet radio on approximately 144.800 MHz.
The experiment demonstrated a complete workflow:
wide IQ recording
→ burst detection
→ frequency estimation
→ narrowband extraction
→ FM demodulation
→ AFSK1200 decoding
→ AX.25/APRS interpretation
→ realtime receive pipeline
The strongest evidence came from independent decoder agreement: both multimon-ng and Dire Wolf decoded valid AX.25/APRS frames from the extracted audio. A live bladeRF pipeline then decoded the same digipeater beacon in realtime, confirming that the method works beyond offline replay.
This was a successful example of turning an unknown SDR waterfall observation into a validated protocol finding.