111 lines
5.8 KiB
Python
Executable File
111 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Regenerate deterministic SignalSim vectors without modifying SignalSim."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
from copy import deepcopy
|
|
|
|
import yaml
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
|
digest.update(block)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--signalsim-path", required=True, type=Path)
|
|
parser.add_argument("--executable", type=Path)
|
|
vector_names = [
|
|
"high_cn0_single_prn",
|
|
"nominal_cn0_single_prn",
|
|
"negative_doppler",
|
|
"multiple_prns",
|
|
"navigation_single_prn",
|
|
"navigation_multisat",
|
|
]
|
|
parser.add_argument("--vector", choices=[*vector_names, "all"], default="high_cn0_single_prn")
|
|
parser.add_argument("--project-root", type=Path, default=Path(__file__).resolve().parents[1])
|
|
args = parser.parse_args()
|
|
source = args.signalsim_path.resolve()
|
|
root = args.project_root.resolve()
|
|
executable = args.executable.resolve() if args.executable else root / "build" / "signalsim" / "IFdataGen"
|
|
if not executable.is_file():
|
|
subprocess.run([str(root / "tools" / "build_signalsim.sh"), str(source)], check=True)
|
|
template_path = root / "verification" / "vectors" / "signalsim" / "high_cn0_single_prn" / "config.json"
|
|
template = json.loads(template_path.read_text(encoding="utf-8"))
|
|
rinex = source / "EphData" / "BRDC00IGS_R_20211700000_01D_MN.rnx"
|
|
if not rinex.is_file():
|
|
raise FileNotFoundError(f"bundled RINEX input is missing: {rinex}")
|
|
commit = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
|
|
variants = {
|
|
"high_cn0_single_prn": {"duration_s": 2.2, "cn0_db_hz": 55.0, "visible_prns": [1], "expected_doppler_hz": {1: 1828.0}},
|
|
"nominal_cn0_single_prn": {"duration_s": 0.6, "cn0_db_hz": 50.0, "visible_prns": [1], "expected_doppler_hz": {1: 1828.0}},
|
|
"negative_doppler": {"duration_s": 2.2, "cn0_db_hz": 55.0, "visible_prns": [27], "expected_doppler_hz": {27: -2637.0}},
|
|
"multiple_prns": {"duration_s": 0.12, "cn0_db_hz": 55.0, "visible_prns": [1, 4, 19, 21, 27], "expected_doppler_hz": {1: 1828.0, 4: 2171.0, 19: -367.0, 21: -712.0, 27: -2637.0}},
|
|
"navigation_single_prn": {"duration_s": 4.2, "cn0_db_hz": 55.0, "visible_prns": [1], "expected_doppler_hz": {1: 1828.0}, "sample_frequency_mhz": 4.092, "center_frequency_mhz": 1574.397},
|
|
"navigation_multisat": {"duration_s": 32.2, "cn0_db_hz": 55.0, "visible_prns": [1, 4, 19, 21, 27], "expected_doppler_hz": {1: 1828.0, 4: 2171.0, 19: -367.0, 21: -712.0, 27: -2637.0}, "sample_frequency_mhz": 4.092, "center_frequency_mhz": 1574.397},
|
|
}
|
|
selected = vector_names if args.vector == "all" else [args.vector]
|
|
for vector_name in selected:
|
|
parameters = variants[vector_name]
|
|
vector_dir = root / "verification" / "vectors" / "signalsim" / vector_name
|
|
vector_dir.mkdir(parents=True, exist_ok=True)
|
|
config = deepcopy(template)
|
|
config["description"] = f"SignalSim validation vector: {vector_name}"
|
|
config["ephemeris"]["name"] = str(rinex)
|
|
config["trajectory"]["trajectoryList"][0]["time"] = parameters["duration_s"]
|
|
config["power"]["initPower"]["value"] = parameters["cn0_db_hz"]
|
|
config["output"]["sampleFreq"] = parameters.get("sample_frequency_mhz", 8.184)
|
|
config["output"]["centerFreq"] = parameters.get("center_frequency_mhz", 1573.374)
|
|
visible = set(parameters["visible_prns"])
|
|
config["output"]["config"]["maskOut"] = [{"system": "Galileo", "svid": [prn for prn in range(1, 37) if prn not in visible]}]
|
|
output_name = f"{vector_name}_iq8.bin"
|
|
config["output"]["name"] = output_name
|
|
generated_config = vector_dir / "generated_config.json"
|
|
generated_config.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
|
|
output = vector_dir / output_name
|
|
command = [str(executable), "-c", str(generated_config), "-o", str(output), "-t", "-st"]
|
|
completed = subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
(vector_dir / "generation.log").write_text(completed.stdout, encoding="utf-8")
|
|
metadata = {
|
|
"name": vector_name,
|
|
"signalsim_path": str(source),
|
|
"signalsim_commit": commit,
|
|
"command": command,
|
|
"output": str(output.relative_to(root)),
|
|
"size_bytes": output.stat().st_size,
|
|
"sha256": sha256(output),
|
|
"complex_sample_count": output.stat().st_size // 2,
|
|
"sampling_frequency_hz": round(float(config["output"]["sampleFreq"]) * 1e6, 6),
|
|
"intermediate_frequency_hz": round(
|
|
(1575.42 - float(config["output"]["centerFreq"])) * 1e6, 6
|
|
),
|
|
"sample_format": "IQ8_interleaved_signed_I_then_Q",
|
|
"duration_s": parameters["duration_s"],
|
|
"prns": parameters["visible_prns"],
|
|
"components": ["E1B", "E1C"],
|
|
"expected_doppler_hz": parameters["expected_doppler_hz"],
|
|
"expected_code_delay": None,
|
|
"navigation_truth": "INavBit generated from bundled RINEX; no sample-side truth file",
|
|
"secondary_truth": "PilotBit.cpp SecondaryCodeE1, phase must be synchronized",
|
|
}
|
|
(vector_dir / "metadata.yaml").write_text(yaml.safe_dump(metadata, sort_keys=False), encoding="utf-8")
|
|
print(completed.stdout, end="")
|
|
print(yaml.safe_dump(metadata, sort_keys=False), end="")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|