"""Формат файлов и воспроизводимые пакеты для подготовки RF-001 без SDR.""" from __future__ import annotations import hashlib from pathlib import Path import numpy as np from protocol.video003_modem import SAMPLE_RATE_HZ from protocol.video005_iq_waveform import NOMINAL_CARRIER_HZ, SYMBOL_RATE TEST_ID = "RF001_QPSK_500K_R23_256B_100P_GAP5MS" SEED = 20260917 PAYLOAD_BYTES = 256 PACKET_COUNT = 100 GAP_MS = 5 GAP_SAMPLES = SAMPLE_RATE_HZ * GAP_MS // 1000 IQ_FORMAT = "c64le" def payload_for_sequence(seed: int, sequence_number: int) -> bytes: """Создаёт 256 байт через SHA-256, не завися от версии генератора NumPy.""" if not 0 <= seed < 2**64 or not 0 <= sequence_number <= 0xFFFF: raise ValueError("seed или номер пакета вне допустимого диапазона") prefix = seed.to_bytes(8, "big") + sequence_number.to_bytes(2, "big") return b"".join( hashlib.sha256(prefix + block.to_bytes(2, "big")).digest() for block in range(PAYLOAD_BYTES // hashlib.sha256().digest_size) ) def sha256_file(path: Path) -> str: """Хеширует файл потоково, не копируя весь IQ в память.""" digest = hashlib.sha256() with path.open("rb") as stream: while chunk := stream.read(1 << 20): digest.update(chunk) return digest.hexdigest() def read_iq_file(path: Path, iq_format: str = IQ_FORMAT) -> tuple[np.ndarray, int | None]: """Читает файловый IQ; `cs8` сохраняет исходный файл без изменений.""" size = path.stat().st_size if iq_format == "c64le": if size == 0 or size % 8: raise ValueError("c64le должен содержать целое ненулевое число отсчётов") return np.memmap(path, dtype=" dict[str, int | float | str]: """Параметры номинального режима, которые разрешено знать приёмнику.""" return { "modulation": "QPSK", "symbol_rate": SYMBOL_RATE, "sample_rate": SAMPLE_RATE_HZ, "fec_rate": "2/3", "digital_carrier_offset_hz": NOMINAL_CARRIER_HZ, }