"""Расчётная длительность пакета VIDEO-002; PHY и радиоприёмник не меняются.""" from __future__ import annotations from dataclasses import asdict, dataclass import math from protocol.video_stream_transport import HEADER_SIZE PROTOCOL_OVERHEAD_BYTES = 12 # Заголовок protocol.packet и CRC-32. PREAMBLE_SYMBOLS = 1280 PHY_HEADER_SYMBOLS = 144 TRANSIENT_SYMBOLS = 42 # Два защитных интервала и хвост RRC. FEC_TAIL_BITS = 6 RRC_ROLLOFF = 0.35 CURRENT_MAX_PACKET_BYTES = 1024 @dataclass(frozen=True) class RadioEstimate: modulation: str symbols_per_second: int phy_payload_bytes: int gap_ms: float video_bytes_per_packet: int packet_bytes_before_fec: int coded_body_bits: int airtime_ms: float useful_video_bps: float overhead_fraction: float raw_bps: int occupied_bandwidth_hz: float symbol_duration_us: float within_current_packet_limit: bool implemented_in_current_phy: bool def as_dict(self) -> dict: return asdict(self) def estimate(modulation: str, symbols_per_second: int, phy_payload_bytes: int, gap_ms: float) -> RadioEstimate: """QPSK: преамбула/PHY-заголовок BPSK, тело FEC по два бита на символ.""" if modulation not in ("BPSK", "QPSK") or symbols_per_second <= 0: raise ValueError("Неизвестная модуляция или символьная скорость") if phy_payload_bytes <= HEADER_SIZE or gap_ms < 0: raise ValueError("Недопустимые нагрузка или пауза") packet_bytes = phy_payload_bytes + PROTOCOL_OVERHEAD_BYTES coded_bits = 2 * (packet_bytes * 8 + FEC_TAIL_BITS) bits_per_symbol = 1 if modulation == "BPSK" else 2 body_symbols = math.ceil(coded_bits / bits_per_symbol) symbols = PREAMBLE_SYMBOLS + PHY_HEADER_SYMBOLS + body_symbols + TRANSIENT_SYMBOLS airtime_ms = 1000 * symbols / symbols_per_second video_bytes = phy_payload_bytes - HEADER_SIZE useful_bps = video_bytes * 8000 / (airtime_ms + gap_ms) # Вся ёмкость интервала, кроме байтов изображения, относится к накладным расходам. overhead = 1 - useful_bps / (symbols_per_second * bits_per_symbol) return RadioEstimate( modulation, symbols_per_second, phy_payload_bytes, gap_ms, video_bytes, packet_bytes, coded_bits, airtime_ms, useful_bps, overhead, symbols_per_second * bits_per_symbol, symbols_per_second * (1 + RRC_ROLLOFF), 1_000_000 / symbols_per_second, packet_bytes <= CURRENT_MAX_PACKET_BYTES, modulation == "BPSK" and symbols_per_second == 500_000 and phy_payload_bytes == 256 and gap_ms == 5, ) def matrix() -> list[RadioEstimate]: return [estimate(modulation, speed, payload, gap) for modulation, speed in (("BPSK", 500_000), ("QPSK", 250_000), ("QPSK", 500_000)) for payload in (256, 512, 1024) for gap in (5, 2, 1, 0)] def margin_fits(rate_bps: float, peak_bps: float, margin: float) -> bool: """Запас 20%: предельная скорость >= пик * 1.2.""" if not 0 <= margin <= 1: raise ValueError("Запас вне диапазона 0–1") return rate_bps >= peak_bps * (1 + margin)