import numpy as np
import pytest
import orion_sdr as sdr
def _sample_payload(n: int) -> np.ndarray:
return np.frombuffer(bytes((i * 37 + 11) & 0xFF for i in range(n)), dtype=np.uint8)
def _place_with_offset(iq: np.ndarray, sps: int, lead: int = 200) -> np.ndarray:
return np.concatenate(
[
np.zeros(lead, dtype=np.complex64),
iq,
np.zeros(sps, dtype=np.complex64),
]
)
def test_params_roundtrip_getters():
p = sdr.DvbTFrameParams("1/8", "qpsk", "1/2", frame_number=2, cell_id=0x5A)
assert p.guard == "1/8"
assert p.constellation == "qpsk"
assert p.code_rate == "1/2"
assert p.frame_number == 2
assert p.cell_id == 0x5A
def test_params_defaults():
p = sdr.DvbTFrameParams("1/32", "qam16", "3/4")
assert p.frame_number == 0
assert p.cell_id == 0
@pytest.mark.parametrize(
"guard,const,rate",
[
("bad", "qpsk", "1/2"),
("1/8", "qam256", "1/2"), ("1/8", "qpsk", "9/10"),
],
)
def test_params_reject_bad_strings(guard, const, rate):
with pytest.raises(ValueError):
sdr.DvbTFrameParams(guard, const, rate)
def test_frame_shape():
p = sdr.DvbTFrameParams("1/8", "qpsk", "1/2")
frame = sdr.DvbTFrameMod(p).modulate(_sample_payload(184))
assert frame.n_symbols >= 68
assert frame.samples_per_symbol == 2048 + 256 assert frame.iq.shape == (frame.n_symbols * frame.samples_per_symbol,)
assert frame.iq.dtype == np.complex64
@pytest.mark.parametrize(
"guard,const,rate",
[
("1/8", "qpsk", "1/2"),
("1/32", "qpsk", "2/3"),
("1/8", "qam16", "3/4"),
("1/4", "qam16", "1/2"),
],
)
def test_roundtrip_recovers_payload_and_tps(guard, const, rate):
p = sdr.DvbTFrameParams(guard, const, rate, frame_number=1, cell_id=0x33)
payload = _sample_payload(184)
frame = sdr.DvbTFrameMod(p).modulate(payload)
buf = _place_with_offset(frame.iq, frame.samples_per_symbol)
rx = sdr.DvbTFrameDemod(p).decode(buf, frame.n_symbols, len(payload))
assert rx.payload == payload.tobytes()
assert rx.tps.frame_number == 1
assert rx.tps.constellation == const
assert rx.tps.code_rate == rate
assert rx.tps.guard == guard
assert rx.tps.cell_id == 0x33
def test_payload_returned_as_bytes():
p = sdr.DvbTFrameParams("1/8", "qpsk", "1/2")
payload = _sample_payload(184)
frame = sdr.DvbTFrameMod(p).modulate(payload)
buf = _place_with_offset(frame.iq, frame.samples_per_symbol)
rx = sdr.DvbTFrameDemod(p).decode(buf, frame.n_symbols, len(payload))
assert isinstance(rx.payload, bytes)
assert len(rx.payload) == len(payload)
def test_demodulate_too_short_raises():
p = sdr.DvbTFrameParams("1/8", "qpsk", "1/2")
frame = sdr.DvbTFrameMod(p).modulate(_sample_payload(184))
with pytest.raises(ValueError):
sdr.DvbTFrameDemod(p).decode(
np.zeros(5000, dtype=np.complex64), frame.n_symbols, 184
)
def test_integer_cfo_correction_builder():
p = sdr.DvbTFrameParams("1/8", "qpsk", "1/2")
payload = _sample_payload(184)
frame = sdr.DvbTFrameMod(p).modulate(payload)
n_fft = 2048
k = 5
n = np.arange(frame.iq.shape[0])
shifted = (frame.iq * np.exp(2j * np.pi * k * n / n_fft)).astype(np.complex64)
buf = _place_with_offset(shifted, frame.samples_per_symbol)
with pytest.raises(ValueError):
sdr.DvbTFrameDemod(p).decode(buf, frame.n_symbols, len(payload))
demod = sdr.DvbTFrameDemod(p).with_integer_cfo_correction(True)
assert demod.integer_cfo_correction is True
rx = demod.decode(buf, frame.n_symbols, len(payload))
assert rx.payload == payload.tobytes()
@pytest.mark.parametrize(
"mode,occupied",
[("333khz", 333_000.0), ("1mhz", 1_000_000.0), ("2mhz", 2_000_000.0)],
)
def test_nb_bandwidth(mode, occupied):
assert sdr.nb_bandwidth_occupied_hz(mode) == pytest.approx(occupied)
assert sdr.nb_bandwidth_fs(mode) == pytest.approx(occupied * 2048.0 / 1705.0, rel=1e-4)
def test_nb_bandwidth_rejects_unknown():
with pytest.raises(ValueError):
sdr.nb_bandwidth_fs("5mhz")