"""Проверки оконного cs8-декодера RF-001A без SDR и RF TX.""" from __future__ import annotations from datetime import datetime, timedelta, timezone from pathlib import Path import numpy as np from protocol.video003_modem import build_frame from protocol.video005_iq_waveform import shape_packet from tools.qpsk_rf_common import SEED, payload_for_sequence from tools.qpsk_rf_rx import decode_file from tools.rf001a_streaming import ( MemoryPeakSampler, SampleWindow, count_cs8_clipping, cs8_sample_count, decode_cs8_streaming, read_cs8_window, receive_cs8_windows, required_overlap_samples, rf001_packet_waveform_samples, timestamp_sample_window, ) def _write_cs8(path: Path, iq: np.ndarray) -> None: peak = float(np.max(np.abs(iq))) if iq.size else 1.0 normalized = iq / max(peak, 1e-12) * 0.75 interleaved = np.empty(iq.size * 2, dtype=np.int8) interleaved[0::2] = np.rint(normalized.real * 127).astype(np.int8) interleaved[1::2] = np.rint(normalized.imag * 127).astype(np.int8) interleaved.tofile(path) def test_timestamp_to_sample_window_uses_safe_margins() -> None: capture = datetime(2026, 9, 22, 12, 0, tzinfo=timezone.utc) window = timestamp_sample_window( capture_started_at=capture.isoformat(), tx_started_at=(capture + timedelta(seconds=10)).isoformat(), tx_finished_at=(capture + timedelta(seconds=11.1042285)).isoformat(), total_samples=160_000_000, margin_seconds=2.0, ) assert window.start == 64_000_000 assert window.stop == 104_833_824 def test_chunked_cs8_reading_from_large_sparse_capture(tmp_path: Path) -> None: capture = tmp_path / "large_sparse.cs8" with capture.open("wb") as stream: stream.seek(1_920_000_000 - 1) stream.write(b"\0") raw = np.memmap(capture, dtype=np.int8, mode="r") window = read_cs8_window(raw, 900_000_000, 900_001_000) assert capture.stat().st_size == 1_920_000_000 assert cs8_sample_count(capture) == 960_000_000 assert window.dtype == np.complex64 assert window.size == 1_000 assert window.nbytes == 8_000 with MemoryPeakSampler() as memory: assert count_cs8_clipping(capture) == 0 assert memory.additional_peak_bytes < 512_000_000 def test_packet_across_chunk_boundary_is_decoded_once(tmp_path: Path) -> None: payload = payload_for_sequence(SEED, 7) frame = build_frame(payload, "QPSK", "2/3", sequence_number=7) waveform = shape_packet(frame) chunk_samples = 120_000 packet_start = chunk_samples - waveform.size // 2 iq = np.zeros(chunk_samples * 2, dtype=np.complex64) iq[packet_start:packet_start + waveform.size] = waveform capture = tmp_path / "boundary.cs8" _write_cs8(capture, iq) result = receive_cs8_windows( capture, (SampleWindow(0, iq.size),), decoder_backend="legacy", chunk_samples=chunk_samples, ) assert required_overlap_samples() == rf001_packet_waveform_samples() == 48_865 assert [packet.packet for packet in result.reception.packets] == [frame.packet] assert result.maximum_loaded_samples < iq.size def test_capture_without_rf001a_signal_has_zero_packets(tmp_path: Path) -> None: capture = tmp_path / "noise_only.cs8" np.zeros(400_000, dtype=np.int8).tofile(capture) result = decode_cs8_streaming(capture, decoder_backend="legacy") assert result.window_source == "STREAMING_POWER_LOCATOR" assert result.windows == () assert result.reception.packets == () assert result.maximum_loaded_samples == 0 truth = Path(__file__).resolve().parents[1] / "configs/rf/rf001a_truth_metadata.json" report = decode_file( capture, iq_format="cs8", truth_metadata_path=truth, decoder_backend="legacy", ) assert report["exact_packets"] == 0 assert report["crc_valid_packets"] == 0 assert report["streaming_decoder"] is True assert report["peak_additional_ram_bytes"] < 1_000_000_000