ADS-B Emitter Algorithm

Mode-S pulse-position modulation synthesis for ADS-B captures and synthetic scenes.

Note

Layer 3 shipped, Pass-1. The Layer 3 implementation (emitters, device-fingerprint, tx-impairments, propagation, rx-frontend) landed on branch rfgen-impl-2026-06-25-105955 (PR #94). The class names, Pydantic schemas, and Transformation enum members referenced below match the shipped surface; Pass-1 stubs (GNU Radio OOT emitters, cellular emitters, Sionna propagation backends) construct cleanly and raise an EmitterError or ChannelError tagged with stage="pass1_stub" until backend wiring lands. See Reference / rfgen.emitters and Reference / rfgen.channels for the shipped class roster.

2. ADS-B (Mode-S PPM)

Family. aviation.adsb. Carrier 1090 MHz; we synthesize at scene baseband and let the scene composer mix to scene-relative offset.

Modulation. Binary PPM at 1 Mbit/s. Each bit is 1 µs, split into two 0.5 µs half-bits:

  • 1 → first half-bit high, second low

  • 0 → first half-bit low, second high

Frame structure.

Field

Bits

Notes

Preamble

8 µs (4 short pulses)

Fixed pattern at samples 0, 1, 3.5, 4.5 µs

Data

56 (DF11) or 112 (DF17/DF18) bits

Per format

def generate_adsb(*, class_label, sample_rate, duration_s, params, rng):
    """
    params:
        df: int                # downlink format: 11, 17, 18
        message_bytes: bytes   # if None, draw a plausible payload
        cadence_hz: float = 1.0  # 1 Hz nominal
        cadence_jitter_pct: float = 0.10
    """
    bit_rate = 1e6  # 1 Mbit/s
    samples_per_bit = sample_rate / bit_rate
    samples_per_half = samples_per_bit / 2

    # Build one frame
    frame_bits = _build_mode_s_frame(params["df"], params.get("message_bytes"), rng)

    # Preamble: pulses at 0, 1, 3.5, 4.5 µs (each 0.5 µs wide)
    preamble = torch.zeros(int(round(8 * samples_per_bit)), dtype=torch.complex64)
    for offset_us in [0, 1, 3.5, 4.5]:
        start = int(round(offset_us * samples_per_bit))
        end = start + int(round(samples_per_half))
        preamble[start:end] = 1.0

    # PPM data
    n_data = len(frame_bits) * int(round(samples_per_bit))
    data = torch.zeros(n_data, dtype=torch.complex64)
    for i, bit in enumerate(frame_bits):
        slot = int(round(i * samples_per_bit))
        h = int(round(samples_per_half))
        if bit == 1:
            data[slot:slot + h] = 1.0
        else:
            data[slot + h:slot + 2*h] = 1.0

    one_frame = torch.cat([preamble, data])

    # Place frames at ~1 Hz cadence with jitter
    iq = torch.zeros(int(round(sample_rate * duration_s)), dtype=torch.complex64)
    cadence_s = 1.0 / params["cadence_hz"]
    nominal_n = int(round(cadence_s * sample_rate))
    pos = 0
    while pos + len(one_frame) < len(iq):
        iq[pos:pos + len(one_frame)] = one_frame
        jitter = (torch.rand(1, generator=rng) - 0.5) * 2 * params["cadence_jitter_pct"]
        pos += int(round(nominal_n * (1 + jitter)))

    return Signal(iq=iq, metadata=SignalMetadata(bandwidth_hz=2.5e6, ...))


def _build_mode_s_frame(df: int, payload: bytes | None, rng) -> list[int]:
    """Construct DF11 (56-bit) or DF17/DF18 (112-bit) frame."""
    if df == 11:
        # DF11 (5-bit DF + 51-bit data + 24-bit CRC)
        ...
    elif df == 17:
        # DF17 ADS-B extended squitter
        # 5-bit DF=17 + 3-bit CA + 24-bit ICAO + 56-bit ME + 24-bit CRC
        ...
    # Use pyModeS for canonical bit-level encoding

Bandwidth. ~2.5 MHz occupied (RC-filtered PPM with rise time ~50 ns).

Reference dataset. Daytona ADS-B. Use pyModeS (GPL, lazy-imported) for canonical bit-level ME field encoding (airborne position, velocity, identification).

Cadence. 1 Hz per aircraft per message-type. Realistic distribution per aircraft: ~50% airborne_position, ~25% airborne_velocity, ~15% identification, ~10% surface_position.


See Also

References

The Mode-S frame layout, downlink-format codes, and PPM bit-encoding above follow the canonical aviation surveillance specs. The framework re-implements the synthesis path; payload encoding is delegated to pyModeS rather than re-derived.

  1. RTCA DO-260B, Minimum Operational Performance Standards for 1090 MHz Extended Squitter Automatic Dependent Surveillance Broadcast (ADS-B) and Traffic Information Services Broadcast (TIS-B), RTCA Inc., 2009. (DF17 / DF18 extended-squitter ME field, 112-bit frame layout, 24-bit CRC)

  2. ICAO Annex 10 Volume IV, Aeronautical Telecommunications: Surveillance and Collision Avoidance Systems, International Civil Aviation Organization, 5th ed., 2014. (Mode-S all-call DF11, 56-bit short squitter, 1090 MHz reply channel, 1 Mbit/s PPM bit encoding)

  3. Junzi Sun. The 1090 Megahertz Riddle: A Guide to Decoding Mode S and ADS-B Signals, 2nd ed., 2021. https://mode-s.org/decode/. (Reference for the pyModeS decoder used for payload encoding cross-checks)