Wi-Fi and Zigbee Emitter Algorithms

IEEE 802.11 and 802.15.4 waveform synthesis paths.

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.

5. Zigbee / 802.15.4

Family. iot.zigbee. 2.4 GHz O-QPSK with half-sine pulse shaping and 32-chip DSSS per 4-bit symbol.

def generate_zigbee(*, class_label, sample_rate, duration_s, params, rng):
    """
    params:
        channel: int             # 11..26 (2405..2480 MHz, 5 MHz spacing)
        pdu_bytes: bytes
    """
    chip_rate = 2e6  # 2 Mchip/s
    samples_per_chip = sample_rate / chip_rate
    bw = 2e6  # 99% occupancy

    # Build bitstream: preamble + SFD + PHR + PSDU
    bits = _zigbee_build_bitstream(params["pdu_bytes"])

    # 4-bit symbol → 32-chip PN sequence (per IEEE 802.15.4 Table 73)
    chips = []
    for i in range(0, len(bits), 4):
        symbol = sum(bits[i+j] << j for j in range(4))
        chips.extend(IEEE_802_15_4_CHIP_TABLE[symbol])

    chip_arr = torch.tensor(chips, dtype=torch.float32) * 2 - 1  # ±1

    # O-QPSK: I from even chips, Q from odd chips, Q delayed by Tc/2
    even = chip_arr[0::2]
    odd = chip_arr[1::2]

    # Half-sine pulse shaping per 802.15.4 §10.2.6
    pulse = torch.sin(torch.pi * torch.arange(int(round(samples_per_chip * 2))) /
                      (2 * samples_per_chip))

    i_pulses = torch.zeros(len(even) * int(round(samples_per_chip * 2)))
    for k, c in enumerate(even):
        start = k * int(round(samples_per_chip * 2))
        i_pulses[start:start + len(pulse)] = c * pulse

    q_pulses = torch.zeros_like(i_pulses)
    delay_samples = int(round(samples_per_chip))     # Tc/2 offset
    for k, c in enumerate(odd):
        start = k * int(round(samples_per_chip * 2)) + delay_samples
        if start + len(pulse) > len(q_pulses):
            break
        q_pulses[start:start + len(pulse)] = c * pulse

    iq = i_pulses + 1j * q_pulses
    iq = _resample_to(iq, target_len=int(round(sample_rate * duration_s)))
    return Signal(iq=iq, metadata=SignalMetadata(bandwidth_hz=bw, ...))

Reference. IEEE 802.15.4 §10 (the public spec). gr-ieee802-15-4 (GPL) is the reference implementation we cross-check against.


6. Wi-Fi 802.11 a/g/p

Family. comms.wifi. Out of scope (for v1): 802.11n/ac/ax/be (MIMO, 40+ MHz). In scope: 802.11a/g (20 MHz OFDM), 802.11p (10 MHz V2X variant).

Library tier: Independent implementation (fallback). Concepts / Emitters / Library Landscape designates Wi-Fi as runtime-isolated with the MATLAB WLAN Toolbox (wlanWaveformGenerator) as the preferred backend. The NumPy/PyTorch implementation below is the Tier 3 fallback for environments where MATLAB is unavailable. Before implementing this path, check whether the MATLAB engine (via matlab.engine) or the open-source gr-ieee802-11 block (GPL; subprocess boundary required) is more appropriate for your deployment. This fallback is appropriate for rapid prototyping or CI environments without MATLAB licenses.

Custom logic: verify as follows. The 802.11a/g OFDM PHY (scrambler, convolutional encoder, puncturer, interleaver, constellation mapper, IFFT, cyclic prefix, STF/LTF preamble) is a self-contained NumPy/PyTorch re-implementation per IEEE 802.11-2020 §17. To verify: pass a known test frame through this encoder and through gr-ieee802-11’s decoder; both must decode the payload bit-exact. MathWorks’ WLAN Toolbox is the validation oracle for spectral mask compliance.

Modulation. 64-point IFFT OFDM PHY. 8 data rates from 6 to 54 Mbps (BPSK/QPSK/16-QAM/64-QAM at three coding rates).

def generate_wifi(*, class_label, sample_rate, duration_s, params, rng):
    """
    class_label: "comms.wifi.{11a,11g,11p}.ofdm-{20,10}mhz.r-{6,9,12,18,24,36,48,54}mbps"
    params:
        psdu_bytes: bytes
        data_rate_mbps: int
        channel: int                       # 11g: 1-13; 11a: 36-165
    """
    bw = 20e6 if "20mhz" in class_label else 10e6
    nfft = 64
    cp_length = 16  # 1/4 cyclic prefix per 802.11a/g
    n_data_subcarriers = 48
    n_pilot = 4

    # Map data rate → modulation + coding
    rate_table = {
        6: ("BPSK", "1/2"),  9: ("BPSK", "3/4"),
        12: ("QPSK", "1/2"), 18: ("QPSK", "3/4"),
        24: ("16-QAM", "1/2"), 36: ("16-QAM", "3/4"),
        48: ("64-QAM", "2/3"), 54: ("64-QAM", "3/4"),
    }
    mod, coding = rate_table[params["data_rate_mbps"]]

    # 1. Scramble + convolutional code + puncture (per 802.11 §17.3)
    bits = _wifi_scramble(_wifi_pdu_bits(params["psdu_bytes"]), seed=0x5d)
    coded = _convolutional_encode_K7_g133_g171(bits)
    coded = _puncture(coded, coding)

    # 2. Interleave + map to constellation
    interleaved = _wifi_interleave(coded, n_data_subcarriers, mod)
    symbols = _constellation_map(interleaved, mod)

    # 3. Build OFDM symbols (insert pilots, IFFT, CP)
    ofdm_syms = []
    for chunk in _chunks(symbols, n_data_subcarriers):
        freq_grid = torch.zeros(nfft, dtype=torch.complex64)
        # Place data + pilots per 802.11a subcarrier mapping
        freq_grid = _place_subcarriers(chunk, _pilots(rng), freq_grid)
        time_sym = torch.fft.ifft(torch.fft.ifftshift(freq_grid)) * nfft
        # Add cyclic prefix
        full = torch.cat([time_sym[-cp_length:], time_sym])
        ofdm_syms.append(full)

    # 4. Prepend STF + LTF + SIGNAL field
    preamble = _wifi_preamble()  # short + long training, SIGNAL field
    iq_native = torch.cat([preamble] + ofdm_syms)

    # 5. Resample 20 MSps → sample_rate
    native_rate = bw  # 20 MHz or 10 MHz
    iq = _resample(iq_native, native_rate=native_rate, target_rate=sample_rate)
    iq = _trim_or_pad(iq, target_len=int(round(sample_rate * duration_s)))

    return Signal(iq=iq, metadata=SignalMetadata(bandwidth_hz=16.6e6 if bw == 20e6 else 8.3e6, ...))

Reference. IEEE 802.11-2020 §17 (OFDM PHY). gr-ieee802-11 (GPL) cross-check.


See Also

References

Wi-Fi 802.11a/g/p and Zigbee 802.15.4 PHY chains are public specs; the framework re-implements each from the canonical IEEE document and cross-checks the result against the gr-ieee802-* GNU Radio reference implementations.

  1. IEEE Std 802.11-2020, IEEE Standard for Information Technology - Telecommunications and Information Exchange between Systems Local and Metropolitan Area Networks - Specific Requirements - Part 11: Wireless LAN Medium Access Control (MAC) and Physical Layer (PHY) Specifications. IEEE, 2020. (§17 OFDM PHY: 64-point IFFT, 48 data + 4 pilot subcarriers, 1/4 cyclic prefix, 8 data rates; scrambler seed 0x5d, K=7 g133 / g171 convolutional code, puncturing, interleaver)

  2. IEEE Std 802.11p-2010, Amendment 6: Wireless Access in Vehicular Environments. IEEE, 2010. (10 MHz V2X variant; identical OFDM structure with halved subcarrier spacing)

  3. IEEE Std 802.15.4-2020, IEEE Standard for Low-Rate Wireless Networks. IEEE, 2020. (§10.2 OQPSK PHY at 2.4 GHz: 2 Mchip/s, 32-chip PN per 4-bit symbol per Table 73, half-sine pulse shaping per §10.2.6, channels 11…26 at 5 MHz spacing)

  4. Bloessl, B. et al. gr-ieee802-11: An IEEE 802.11 a/g/p OFDM Receiver for GNU Radio, ACM SRIF, 2013. https://github.com/bastibl/gr-ieee802-11. (Reference encoder / decoder used for round-trip cross-checks)

  5. Bloessl, B. et al. gr-ieee802-15-4: An IEEE 802.15.4 transceiver for GNU Radio. https://github.com/bastibl/gr-ieee802-15-4. (Reference encoder / decoder for the Zigbee cross-check path)