Skip to content

jbirby/jaero-codec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JAERO Codec

A Python encoder and decoder for Inmarsat Classic Aero baseband WAV audio, built in the style of the other codec skills in this workspace (inmarsat-c, acars, sstv, ...). ACARS message goes in, Classic Aero WAV comes out; feed the WAV back through the decoder and you get the ACARS message.

Classic Aero is the Inmarsat aviation satellite data link that carries ACARS traffic to/from aircraft on long-haul oceanic, polar, and trans-continental flights, using A-BPSK and A-QPSK modulation at 600/1200/10500 bps on geostationary L-band satellites. It is also the only open-hobbyist path to seeing satellite ACARS in the wild (most other systems are either proprietary or SBB, which is a different physical layer).

Rate/channel coverage

Classic Aero defines four logical channel types on the satellite link:

Channel Direction Use
P Ground -> Air, continuous TDM Signalling, broadcast ACARS, ATS data link
R Air -> Ground, slotted-Aloha burst Short uplink signals, log-on requests
T Air -> Ground, reservation-TDMA burst Longer uplink messages by priority
C Both ways, SCPC voice Cockpit telephone calls (AMBE-coded)

This skill implements:

Rate Channel Modulation Status
600 bps P, R, T A-BPSK (MSK-equivalent) Encode + decode
1200 bps P, R, T A-BPSK (MSK-equivalent) Encode + decode
10500 bps P, R, T A-QPSK (Offset QPSK) Encode + decode
8400/21000 bps C A-QPSK voice (AMBE) Out of scope (proprietary vocoder)

All nine P/R/T × 600/1200/10500 combinations round-trip cleanly through WAV (see scripts/jaero_test.py). The C-channel voice service uses the proprietary AMBE vocoder, for which no open-source codec is redistributable — it is out of scope permanently.

How it works

P-channel (continuous, forward)

The encoder:

  1. Builds an ACARS/ARINC 618 byte sequence with odd-parity, SOH, STX, ETX, CRC-16, and DEL markers.
  2. Chops it into Signal Units (SUs): one Initial SU (type 0x71) carrying 2 bytes of ACARS, followed by Subsequent SUs (0xC0-bit set) each carrying 8 bytes.
  3. Packs the 6 or 26 SUs into a P-channel frame, scrambles the raw bits with a 15-stage LFSR, applies rate 1/2 convolutional FEC (K=7), interleaves the coded bits in 64 × N blocks (N = 6, 9, or 78), wraps with a 16-bit header and a 32-bit unique word tail, and modulates the channel bit stream as MSK-equivalent A-BPSK (600/1200 bps) or Offset QPSK (10500 bps) onto real audio.
  4. Writes 16-bit PCM mono WAV at 48 kHz.

The decoder does the reverse: reads the WAV, demodulates to bits, hunts for the 32-bit unique word, extracts each frame, deinterleaves, Viterbi-decodes, descrambles, parses the 12-byte SUs, checks each SU's CRC-16, reassembles the ACARS userdata, verifies odd parity and the ACARS CRC, and emits the fields.

R-channel (burst, single SU per burst)

Each R-channel burst carries exactly one extended SU (19 octets = 152 bits, with the last 16 bits being the CRC). The interleaver block is 64 × 5 = 320 channel bits, which decodes through FEC to 160 raw bits (152 SU + 8 tail).

T-channel (burst, multiple SUs per burst)

Each T-channel burst carries a 48-bit T-header (4 payload octets + 16-bit CRC) followed by N × 96-bit standard SUs and a 112-bit flush tail. The interleaver block is 64 × (3N + 5) channel bits. The encoder takes an arbitrary SU count and packs the SUs; the decoder sweeps 1..31 SUs and picks the SU count whose header CRC and per-SU CRCs all validate.

Signal chain (per ICAO Doc 9925 Figure 3-2)

TX:  LINK LAYER -> SCRAMBLER -> FEC ENCODER -> INTERLEAVER -> MODULATOR
RX:  LINK LAYER <- DESCRAMBLER <- FEC DECODER <- DE-INTERLEAVER <- DEMODULATOR

The scramble-before-FEC ordering is Classic Aero's convention, inherited directly from the Inmarsat SDM. It differs from some textbook modems that interleave between FEC and modulation with scrambling applied last — match this order exactly or the receiver will see noise.

Key constants (verified against JAERO source aerol.cpp)

Parameter Value Source
Unique word (MSK) 0xE156AE93 (32 bits) aerol.cpp:947
Convolutional polys G1=0o171, G2=0o133 (K=7, rate 1/2) jconvolutionalcodec.cpp, aerol.cpp:937
Scrambler polynomial x^15 + x^14 + 1 aerol.h:AeroLScrambler
Scrambler init state {1,1,0,1,0,0,1,0,1,0,1,1,0,0,1} aerol.h:AeroLScrambler
Interleaver dims 64 rows × N cols, row perm (i*27) mod 64 aerol.cpp:525-536
P-channel N per rate 6 / 9 / 78 at 600 / 1200 / 10500 bps aerol.cpp:setSettings
R-channel block 64 × 5 cols = 320 coded bits aerol.h:RTChannelDeleaveFECScram
T-channel block 64 × (3N + 5) cols for N SUs aerol.h:RTChannelDeleaveFECScram
CRC-16 poly 0x1021 reflected, init 0xFFFF, final NOT aerol.h:CrcClass
Standard SU 96 bits (12 octets), CRC at [10-11] LE aerol.cpp:ISUData::update
Extended SU (R) 152 bits (19 octets), CRC at [17-18] LE aerol.cpp:RISUData::update
A-QPSK defaults Fs=48000, center=8000, RRC α=1.0 oqpskdemodulator.cpp

Installation

Requires Python 3.9+ and NumPy:

pip install numpy

scipy is optional. The pipeline is pure-NumPy.

Usage

Encode an ACARS position report on the 1200 bps P-channel

python3 scripts/jaero_encode.py out.wav --rate 1200 --channel P \
    --tail N12345 --label H1 --block-id A \
    --text "POS N51.5 W010.2 FL360 M0.82"

Encode the same on R-channel (log-on-style single SU)

python3 scripts/jaero_encode.py out.wav --rate 1200 --channel R \
    --r-msg-type 0x22 --r-user-data "POS_REQ"

Encode on T-channel (multi-SU burst)

python3 scripts/jaero_encode.py out.wav --rate 1200 --channel T \
    --tail N54321 --label H1 --text "TDMA BURST TEST"

Encode at 10500 bps A-QPSK (any channel)

python3 scripts/jaero_encode.py out.wav --rate 10500 --channel P \
    --tail N88888 --label H1 --text "FAST LINK 10500"

Decode any of the above

python3 scripts/jaero_decode.py out.wav --rate 1200 --channel P
python3 scripts/jaero_decode.py out.wav --rate 1200 --channel R
python3 scripts/jaero_decode.py out.wav --rate 1200 --channel T
python3 scripts/jaero_decode.py out.wav --rate 10500 --channel P

Add --json for structured output and --verbose for per-SU detail.

Verify the codec against itself

python3 scripts/jaero_test.py
# Results: 56/56 PASSED

Tests cover: CRC, Viterbi (clean and under bit errors), scrambler self- inverse with frozen vector, interleaver roundtrip at N ∈ {6, 9, 78}, UW correlator, RRC filter properties, A-BPSK mod/demod roundtrip at 600/1200, A-QPSK mod/demod roundtrip at 10500, SU build/parse, ACARS userdata chop and reassembly, full WAV roundtrip at all three P-channel rates, AWGN robustness at 6 dB and 10 dB SNR, R-channel bit and WAV roundtrip at all three rates, T-channel bit and WAV roundtrip with CRC-verified header and per-SU checks at all three rates.

Reference files

  • ICAO Doc 9925 "Manual on the Aeronautical Mobile Satellite (Route) Service" — authoritative source for channel rates, modulation, frame sizes, SU sizes. Part III Chapter 3 is the operative section. Available for purchase from ICAO at https://store.icao.int. Not redistributed in this repo (copyrighted).
  • github.com/jontio/JAERO — the reference C++ implementation used to verify scrambler, interleaver, UW, FEC, CRC, and R/T burst layout.
  • samples/ — reference WAVs (one per rate × channel pair; nine files).
  • scripts/jaero_common.py — all DSP primitives, FEC/scrambler/interleaver, SU framing, ACARS layer, and both A-BPSK (MSK) and A-QPSK (OQPSK) modulators/demodulators; P / R / T encode + decode.
  • scripts/jaero_encode.py, scripts/jaero_decode.py — thin CLI glue over the common module.
  • scripts/jaero_test.py — self-contained test suite (56 tests).

Limitations and known gaps

  • Header fields in the T-channel 6-octet header are placeholder content (AES ID + SU count + flags). The exact bit layout of the real Inmarsat T-header is in the Inmarsat SDM rather than Doc 9925, so JAERO itself interprets only the SU-count-plus-CRC portion. Our header round-trips through our own decoder but may not match a real Aero downlink byte-for- byte.
  • P-channel messages are limited to one frame per message for now. The ACARS userdata must fit in 6 SUs at 600/1200 bps (2 + 5*8 = 42 bytes of ACARS) or 26 SUs at 10500 bps. Multi-frame span (ISU in one frame, SSUs in subsequent frames) is tracked as a future enhancement.
  • JAERO interop has not been exercised end-to-end yet. The bit-level constants (scrambler, FEC, interleaver, UW, CRC, SU layout) match JAERO's source exactly, so demodulation in JAERO is expected to work; manual verification is the last outstanding acceptance gate.
  • C-channel voice is out of scope, permanently. AMBE vocoder is not redistributable.
  • Phase-ambiguity resolution on the OQPSK decoder is primitive. The internal round-trip works because transmit and receive agree on phase, but a JAERO-produced OQPSK WAV may need the 4-way phase rotation search that JAERO's decoder performs — easy to add if interop requires it.

About

Python encoder/decoder for Inmarsat Classic Aero satellite ACARS (JAERO port, P/R/T channels, 600/1200/10500 bps)

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages