Skip to content
moricefPublic

About

dec406 — Open-source COSPAS-SARSAT 406 MHz FGB/1G and SGB/2G SDR receiver and decoder. DSSS/OQPSK, real IQ recordings, MEOSAR downlink validation.

Topics

Resources

Stars

4 stars

Watchers

2 watching

Forks

Repository files navigation

dec406 — COSPAS-SARSAT 406 MHz Beacon Decoder

Decoder for 1st (FGB) and 2nd (SGB) generation COSPAS-SARSAT emergency beacons at 406 MHz.

Supports COSPAS-SARSAT 406 MHz FGB/1G and SGB/2G beacon decoding from IQ recordings and SDR receivers.

Validated on real signals, not only on synthetic test vectors. The decoder has been exercised on direct 406 MHz reception, CNES system/calibration beacons, and the 1544 MHz MEOSAR downlink. An independent validation at the F4KLO La Villette radiotelescope decoded real SGB traffic received with an Airspy at 2.5 Msps and located the Larnaca calibration beacon within a few metres. Across two F4KLO captures, 23 SGB messages from seven system or calibration beacons in four countries on four continents were decoded with a clean BCH result.

F4KLO field report · Real SGB IQ recordings and SigMF metadata · Validation details · French README

Real over-the-air SGB decoded by dec406

Reproduce the complete receive chain on the published real IQ recording:

make demo-real-sgb

This downloads the sample, verifies its SHA-256, then performs acquisition, despreading, BCH validation and field decoding. See the real-signal demonstration notes.

The orbitography implementation also decodes the unprotected UTC calibration time defined by C/S T.006 instead of incorrectly treating those national-use bits as BCH-2.

Branch: main

Repository renamed to dec406; links using the former Decode_sarsat_406_v1g_v2g name continue to redirect here.

License: MIT Language: C Platform: Linux GitHub last commit GitHub stars


What works

Decoders (bit-perfect)

  • 1G (FGB): T.001 biphase-L PSK ±1.1 rad, 400 bps, all Location/User protocols — dec406_v1g.c
  • 2G (SGB): BCH(250,202) with full Berlekamp-Massey + Chien error correction, all T.018 fields — dec406_v2g.c

Demodulators

  • DSSS OQPSK (2G) — dsss_demod.c direct processing chain (no sample-rate tracking loop): DC blocker → FFT-correlation frequency acquisition → NCO wipeoff → OQPSK delay → multi-offset boxcar decimation → despread with preamble linear-fit frequency estimation + per-bit Costas PLL → BCH. Oracle tries 4 boxcar offsets × 4 Costas phases (16 combos) before giving up. The acquisition lag search is capped to the burst pre-roll to avoid late noise/data peaks beating the true preamble. The FFT-correlation frequency search tries ±8 kHz first, then widens to ±16 kHz only when needed to cover larger frequency offsets observed on real CNES SGB bursts.
  • FGB IQ-direct (1G) — fgb_iq_demod.c: complex baseband BPSK biphase-L decoder without FM-demod → audio detour. Dual-grid CW end detection, multi-phase Costas search (4 initial phases × 13 offsets), Manchester slicer, BCH-1 correction (t=3), and BCH-2 correction for long non-orbitography frames (t=2).

Real-time scanner

dec406_scan is a unified real-time scanner with automatic SDR backend selection for interactive use (Airspy Mini, RTL-SDR, PlutoSDR, HackRF). The RTL-SDR backend uses rtlsdr_read_async() with large buffers. The scanner runs a spectral burst detector over the 100 kHz band, classifies each burst as FGB or SGB by bandwidth, and queues decoding in a dedicated worker so capture and spectral detection continue during expensive decodes. Heartbeats report SDR ring overruns and decode queue drops separately. For services, the backend can be forced explicitly with DEC406_BACKEND=rtl|airspy|pluto|hackrf so there is no startup probing.

Current validation status

Rates depend strongly on propagation, time of day, local noise, and which CNES system beacons are active. Track three SGB buckets separately:

Metric Definition
SGB acquisition/sync (BCH OK + FRAME REJECTED) / detected SGB bursts
SGB decoder purity BCH OK / (BCH OK + FRAME REJECTED)
SGB end-to-end BCH OK / detected SGB bursts

Recent field validation shows that SGB bursts reaching synchronization validate BCH cleanly. Firmin relay checks and local RTL/Yagi tests remain in a high-success range, with FGB staying in its usual class. Treat these as run-specific field checks, not fixed global rates.


Build

make build/dec406_iq        # SGB decoder from IQ file
make build/dec406_scan      # Real-time scanner (needs librtlsdr-dev)
make                        # all targets

Binaries produced in build/:

Binary Purpose
dec406_iq SGB DSSS/OQPSK demodulator from IQ file
dec406_fgb_iq FGB demodulator from IQ file, the 1G counterpart of dec406_iq
dec406_hex 1G/2G decoder from hex string
dec406_audio 1G decoder from WAV file (legacy FM-demod pipeline)
dec406_scan Real-time FGB+SGB band scanner (Airspy/RTL-SDR/PlutoSDR/HackRF)
dec406_ring Triggered IQ recorder — detects bursts, writes SigMF, no decoding
dec406_scan_rtlsdr Legacy standalone scanner, RTL-SDR only
dec406_scan_airspy Legacy standalone scanner, Airspy only
dec406_dsss_test DSSS demodulator unit test driver
generate_2g_hex 2G test frame generator
reset_usb USB device reset utility

Usage

Offline IQ files

# SGB from IQ file (float32 complex, default)
./build/dec406_iq signal.iq -s 2457600

# SDRangel ci32_le recording (int32 complex little-endian)
./build/dec406_iq recording.sigmf-data -s 2457600 -I

# RTL-SDR uint8 recording
./build/dec406_iq recording.iq -s 2457600 -u

Input formats: float32 complex (default), -u RTL-SDR uint8, -i int16 interleaved, -I int32 SDRangel ci32_le.

Hex frames

./build/dec406_hex 09C4745638D95999A02B33326C3EC4400003FFF00C02832000002B774C24FE4
./build/dec406_hex 8E3301E2402B002BBA863609670908

Real-time scanner

./build/dec406_scan 406.0M 406.1M             # AGC, ppm=0
./build/dec406_scan 406.0M 406.1M 30 0        # fixed gain 30 dB, ppm=0
./build/dec406_scan 406.0M 406.1M -1 30       # AGC, ppm=30

Arguments are positional: <freq_start> <freq_end> [gain] [extra]. A negative gain selects the automatic mode, so pass -1 explicitly when you need to reach the fourth argument. extra means ppm correction on RTL-SDR, bias-tee on Airspy, and amplifier on HackRF; the startup banner labels it accordingly.

The scanner reads samples at 2.4576 Msps, detects bursts on a power spectrogram, classifies them by bandwidth (BW_SPLIT_HZ = 20 kHz; wider bursts are SGB), and runs the appropriate decoder. On RTL-SDR it resets the USB device at startup, then captures continuously through the asynchronous librtlsdr API.

Set RTL_DIAG=1 to log effective RTL throughput every few seconds:

RTL_DIAG=1 ./build/dec406_scan 406.0M 406.1M

Useful SGB diagnostics:

DSSS_DIAG=1 ./build/dec406_scan 406.0M 406.1M
DUMP_FAIL=1 ./build/dec406_scan 406.0M 406.1M
make build/sgb_epl_diag

DUMP_FAIL=1 writes failed SGB burst windows as burst_sgb_*.cf32 for offline replay. build/sgb_epl_diag probes those dumps with EPL correlators at wider residual-frequency ranges. ACQ_BANDPASS_HZ is kept as a diagnostic acquisition-only filter; default 0 leaves it disabled.

1544 MHz downlink service

systemd/scan1544.service is the dedicated downlink unit. It keeps the backend explicit and switches the alert path to geographic filtering instead of the 406 MHz distress-channel whitelist:

Environment=DEC406_BACKEND=hackrf
Environment=DEC406_ALERT_MODE=downlink
Environment=DEC406_ALERT_CENTER=43.0,1.5
Environment=DEC406_ALERT_RADIUS_KM=80

If Pluto is used instead of HackRF on a given station, only DEC406_BACKEND changes. In downlink mode, an email is sent only when the decoded beacon position is valid and inside the configured radius.

As a systemd service

[Unit]
Description=Scan406 Service
After=network.target

[Service]
Type=simple
SyslogIdentifier=scan406
WorkingDirectory=/home/firmin/balise_406MHz/dec406_scan/
ExecStart=/home/firmin/balise_406MHz/dec406_scan/build/dec406_scan 406.0M 406.1M
Restart=always
RestartSec=25
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Stderr is tagged with systemd journal priority prefixes:

journalctl -u scan406 -p info      # clean trace (decoded frames only)
journalctl -u scan406 -p debug     # full diagnostic stream

Email alerts on T.012 distress channels

If a data/config_mail.txt is present, the scanner emails the decoded frame whenever a beacon decodes on a T.012 Table H.2 distress channel (B/C/D/F/G/J/K/N/O/R/S, ±2 kHz). The sendemail binary must be installed — it is a Perl SMTP client, unrelated to the system sendmail.

A genuine alert also requires the frame to be a real distress message and the beacon to be heard twice. Test beacons, orbitography, bench tests and one-shot transmissions never raise one. To exercise the whole alert path anyway:

DEC406_ALERT_TEST=1 ./build/dec406_scan 406.0M 406.1M   # all beacons
DEC406_ALERT_TEST=2 ./build/dec406_scan 406.0M 406.1M   # SGB only

With value 1 every decoded beacon mails; value 2 restricts to SGB only. All alerts raised this way carry a [TEST] subject prefix and a banner in the body, so they cannot be mistaken for a real one. Leave the variable unset in production.

At startup the banner prints the mail settings that matter for operation:

alerts  : enabled
mail smtp : smtp.gmail.com:587
mail user : xxxx@gmail.com
mail to   : a@b.fr,c@d.fr

To enable alerts, copy the template and fill in the four values:

cp data/config_mail.txt.example data/config_mail.txt
$EDITOR data/config_mail.txt          # set your SMTP creds + recipients
chmod 600 data/config_mail.txt        # contains an app password

Format (key=value, one per line):

smtp_serveur=smtp.gmail.com:587
utilisateur=user@example.org
password=app_password
destinataires=a@b.com,c@d.fr

Without that file the scanner runs normally; the banner prints alerts : disabled and no email is sent.

Channel A (406.022 — orbitography/calibration) is excluded by design. Additional silencing filters layered on the channel whitelist:

  • FGB beacons identified as Orbitography or with ID-NOT-AVAIL (factory-fresh / bench tests) never trigger.
  • SGB test transmissions (T.018 §3 bit 43 = 1) never trigger.
  • A second sighting of the same Hex ID within 3 min is required before mailing (filters one-shot bench-test bursts; a real distress beacon repeats every ~50 s and is confirmed at the second burst).

2G Demodulation Chain

IQ @ 2.4576 MHz
  → DC blocker (IIR α=0.001)
  → boxcar decimation to chip rate (acquisition only)
  → freq_acq_fft_corr (chip-rate FFT-correlation, ±8 kHz then ±16 kHz fallback, ~1 Hz precision)
  → NCO wipeoff at sample rate
  → OQPSK delay (Q advanced by SPS/2)
  → multi-offset boxcar decimation (4 sub-chip offsets)
  → despread (preamble linear-fit freq/phase estimation,
       per-bit BPSK Costas PLL over 256 chips)
  → 250 bits → bch_decode_250_202 (nerr) → decode_beacon

For each acquired burst the chain tries 4 boxcar offsets × 4 Costas phases (16 combos) against BCH, keeping the first that decodes cleanly. Preamble linear regression initialises carrier frequency/phase, replacing slow PLL convergence. A frame whose codeword BCH cannot correct is rejected.


1G Demodulation Chain (IQ-direct)

IQ
  → multi-stage moving-average decimation to ~9.6 kHz
  → CW preamble carrier-frequency estimate (sample-level phase diffs)
  → frequency wipeoff
  → dual-grid CW end detection (two grids offset half/2)
  → multi-phase Costas search (4 phases × 13 offsets)
  → Manchester slicer (biphase-L ±1.1 rad)
  → BCH1 brute-force correction (t=3, bits 24..105)
  → 144 bits → BCH-1/BCH-2 validation + polarity fallback

No FM-demod, no audio detour, no biphase-L codec dependency.


Project Structure

src/         C sources (dsss_demod, despread, freq_acq, fgb_iq_demod,
              dec406_v1g, dec406_v2g, main_iq, main_scan, scan_alert, ...)
include/     Headers
build/       Compiled binaries
tests/       SGB codec unit tests, BCH reject tests
utils/       Offline diagnostic tools
scripts/     Analysis / debug scripts
docs/        Specifications, deployment notes, SGB status notes
data/        Runtime data (config_mail.txt, etc.)

Documentation

  • CHANGELOG.md — Version history
  • CLAUDE.md — Project conventions, debug methodology
  • docs/ARCHITECTURE_dec406.md — Detailed architecture (French)
  • docs/TESTS_VALIDATION.md — Validation procedures
  • scripts/scan406.pl — Legacy Perl scanner (superseded by dec406_scan)

License

This project is licensed under the MIT License. See LICENSE.


References

  • C/S T.001 — 1st Generation Beacon Specification (FGB)
  • C/S T.012 — Beacon Type Approval (channel list, Table H.2)
  • C/S T.018 — 2nd Generation Beacon Specification (SGB)
  • gr-cospas / gr-satellites — GNU Radio receivers
  • sgb-codec (jbirby) — Python reference SGB codec
  • sarsat_sgb (ADALM-PLUTO) — SGB modulator for PlutoSDR
  • scan406.pl (F4EHY 2020) — Perl scanner ancestor, ported to C in dec406_scan + scan_alert

About

dec406 — Open-source COSPAS-SARSAT 406 MHz FGB/1G and SGB/2G SDR receiver and decoder. DSSS/OQPSK, real IQ recordings, MEOSAR downlink validation.

Topics

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages