Harden RF handoff and stream OTA decode

This commit is contained in:
LittleSam129
2026-09-22 17:43:58 +03:00
parent 168e03d9e7
commit a94f2db4ee
17 changed files with 1548 additions and 497 deletions

View File

@@ -31,6 +31,12 @@ from tools.qpsk_rf_common import (
GAP_MS, PACKET_COUNT, PAYLOAD_BYTES, SEED, TEST_ID,
payload_for_sequence, read_iq_file, sha256_file,
)
from tools.rf001a_streaming import (
DEFAULT_CHUNK_SAMPLES,
MemoryPeakSampler,
cs8_sample_count,
decode_cs8_streaming,
)
def _bit_errors(observed: bytes, expected: bytes) -> tuple[int, int]:
@@ -124,7 +130,7 @@ def summarize_result(
unique_sequences = set(sequences)
cfo = [row["cfo_hz"] for row in rows]
report: dict[str, Any] = {
"rf001_status": "PREPARED_NOT_RUN",
"rf001_status": "OFFLINE_ANALYZED",
"test_id": truth.get("test_id") if truth is not None else None,
"modulation": "QPSK",
"symbol_rate": 500_000,
@@ -242,10 +248,32 @@ def decode_file(
iq_format: str = "c64le",
truth_metadata_path: Path | None = None,
decoder_backend: str = "numba",
capture_started_at: str | None = None,
tx_started_at: str | None = None,
tx_finished_at: str | None = None,
chunk_samples: int = DEFAULT_CHUNK_SAMPLES,
) -> dict[str, Any]:
"""Открывает только файл; truth читается после вызова рабочего приёмника."""
iq, clipping_codes = read_iq_file(iq_path, iq_format)
reception = receive_iq_stream(iq, decoder_backend=decoder_backend)
"""Декодирует c64le целиком, а большие cs8 — ограниченными окнами."""
streaming = None
with MemoryPeakSampler() as memory:
if iq_format == "cs8":
streaming = decode_cs8_streaming(
iq_path,
capture_started_at=capture_started_at,
tx_started_at=tx_started_at,
tx_finished_at=tx_finished_at,
decoder_backend=decoder_backend,
chunk_samples=chunk_samples,
)
reception = streaming.reception
iq_sample_count = cs8_sample_count(iq_path)
clipping_codes = streaming.clipping_codes
iq = None
else:
iq, clipping_codes = read_iq_file(iq_path, iq_format)
reception = receive_iq_stream(iq, decoder_backend=decoder_backend)
iq_sample_count = int(iq.size)
truth = (json.loads(truth_metadata_path.read_text(encoding="utf-8"))
if truth_metadata_path is not None else None)
if truth is not None and (
@@ -262,17 +290,38 @@ def decode_file(
or truth.get("seed") != SEED
):
raise ValueError("TX-ведомость не соответствует режиму RF-001")
raw_bits = (_raw_ber_after_decode(iq, reception, int(truth["seed"]), decoder_backend)
if truth is not None else None)
return summarize_result(
raw_bits = (
_raw_ber_after_decode(iq, reception, int(truth["seed"]), decoder_backend)
if truth is not None and iq is not None
else None
)
report = summarize_result(
reception,
iq_sample_count=int(iq.size),
iq_sample_count=iq_sample_count,
iq_sha256=sha256_file(iq_path),
iq_format=iq_format,
clipping_codes=clipping_codes,
truth=truth,
raw_bits=raw_bits,
)
report["peak_analysis_memory_bytes"] = memory.peak_bytes
report["peak_additional_ram_bytes"] = memory.additional_peak_bytes
if streaming is not None:
report.update({
"streaming_decoder": True,
"window_source": streaming.window_source,
"analysis_windows": [
{"start_sample": window.start, "stop_sample": window.stop}
for window in streaming.windows
],
"overlap_samples": streaming.overlap_samples,
"maximum_loaded_complex_samples": streaming.maximum_loaded_samples,
"maximum_loaded_iq_bytes": streaming.maximum_loaded_samples * 8,
"raw_ber_scope": "NOT_COMPUTED_FOR_STREAMING_WINDOWS",
})
else:
report["streaming_decoder"] = False
return report
def main() -> None:
@@ -282,6 +331,10 @@ def main() -> None:
parser.add_argument("--truth-metadata", type=Path)
parser.add_argument("--report", required=True, type=Path)
parser.add_argument("--numba-packages", type=Path)
parser.add_argument("--capture-started-at")
parser.add_argument("--tx-started-at")
parser.add_argument("--tx-finished-at")
parser.add_argument("--chunk-samples", type=int, default=DEFAULT_CHUNK_SAMPLES)
arguments = parser.parse_args()
if arguments.report.exists():
parser.error("отчёт уже существует; перезапись запрещена")
@@ -290,7 +343,11 @@ def main() -> None:
parser.error("каталог пакетов Numba отсутствует")
sys.path.insert(0, str(arguments.numba_packages.resolve()))
result = decode_file(arguments.iq_file, iq_format=arguments.iq_format,
truth_metadata_path=arguments.truth_metadata)
truth_metadata_path=arguments.truth_metadata,
capture_started_at=arguments.capture_started_at,
tx_started_at=arguments.tx_started_at,
tx_finished_at=arguments.tx_finished_at,
chunk_samples=arguments.chunk_samples)
arguments.report.parent.mkdir(parents=True, exist_ok=True)
arguments.report.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8")