"""RF-001: построение 100 QPSK-пакетов и IQ только в режиме dry-run.""" from __future__ import annotations import argparse from datetime import datetime, timezone import hashlib import json from pathlib import Path import sys import numpy as np if __package__ in (None, ""): sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from protocol.video003_modem import SAMPLE_RATE_HZ, build_frame from protocol.video005_iq_waveform import GUARD_SAMPLES, shape_packet from tools.qpsk_rf_common import ( GAP_MS, GAP_SAMPLES, IQ_FORMAT, PACKET_COUNT, PAYLOAD_BYTES, SEED, TEST_ID, payload_for_sequence, radio_mode, sha256_file, ) ROOT = Path(__file__).resolve().parents[1] DEFAULT_OUTPUT = ROOT / "data/raw/rf001/offline_qpsk_100_packets" def build_dry_run() -> tuple[np.ndarray, dict]: """Собирает ровно утверждённый режим; границы остаются только в TX-ведомости.""" separator = GAP_SAMPLES - 2 * GUARD_SAMPLES if separator < 0: raise AssertionError("пауза короче двух защитных интервалов RRC") waveforms = [] expected = [] starts = [] position = 0 for sequence_number in range(PACKET_COUNT): payload = payload_for_sequence(SEED, sequence_number) frame = build_frame(payload, "QPSK", "2/3", sequence_number=sequence_number) waveform = shape_packet(frame) starts.append(position) waveforms.append(waveform) expected.append({ "sequence_number": sequence_number, "payload_sha256": hashlib.sha256(payload).hexdigest(), "packet_sha256": hashlib.sha256(frame.packet).hexdigest(), }) position += waveform.size + (separator if sequence_number < PACKET_COUNT - 1 else 0) iq = np.zeros(position, dtype=" tuple[Path, Path]: """Создаёт новый каталог; существующие IQ и метаданные не перезаписывает.""" output_directory.mkdir(parents=True, exist_ok=False) iq, metadata = build_dry_run() iq_path = output_directory / "tx_waveform.c64le" metadata_path = output_directory / "tx_metadata.json" iq.tofile(iq_path) metadata["iq_sha256"] = sha256_file(iq_path) metadata_path.write_text(json.dumps(metadata, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") return iq_path, metadata_path def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT) arguments = parser.parse_args() iq_path, metadata_path = write_dry_run(arguments.output_dir) print(f"RF-001 DRY-RUN: {iq_path}; metadata={metadata_path}; RF TX=0") if __name__ == "__main__": main()