Initial Galileo receiver and SignalSim integration
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
echo "usage: $0 /path/to/SignalSim [build-directory]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
signalsim_source=$(realpath "$1")
|
||||
project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
build_directory=${2:-"${project_root}/build/signalsim"}
|
||||
|
||||
if [[ ! -f "${signalsim_source}/IFdataGen/CMakeLists.txt" ]]; then
|
||||
echo "error: ${signalsim_source} is not a SignalSim tree with IFdataGen/CMakeLists.txt" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
for dependency in cmake c++; do
|
||||
if ! command -v "${dependency}" >/dev/null 2>&1; then
|
||||
echo "error: required build dependency '${dependency}' is missing" >&2
|
||||
exit 4
|
||||
fi
|
||||
done
|
||||
|
||||
generator=()
|
||||
if command -v ninja >/dev/null 2>&1; then
|
||||
generator=(-G Ninja)
|
||||
fi
|
||||
|
||||
echo "SignalSim source: ${signalsim_source}"
|
||||
echo "Build directory: ${build_directory}"
|
||||
echo "CMake: $(cmake --version | head -n 1)"
|
||||
echo "Compiler: $(c++ --version | head -n 1)"
|
||||
if command -v ninja >/dev/null 2>&1; then
|
||||
echo "Ninja: $(ninja --version)"
|
||||
else
|
||||
echo "Ninja: unavailable; using CMake default generator"
|
||||
fi
|
||||
|
||||
cmake -S "${signalsim_source}/IFdataGen" -B "${build_directory}" "${generator[@]}" \
|
||||
-DCMAKE_BUILD_TYPE=Release -DUSE_NATIVE_OPT=OFF
|
||||
cmake --build "${build_directory}" --parallel
|
||||
|
||||
executable="${build_directory}/IFdataGen"
|
||||
if [[ ! -x "${executable}" ]]; then
|
||||
echo "error: build completed without expected executable ${executable}" >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
echo "SIGNALSIM_EXECUTABLE=${executable}"
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/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"]
|
||||
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}},
|
||||
}
|
||||
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"]
|
||||
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": 8_184_000.0,
|
||||
"intermediate_frequency_hz": 2_046_000.0,
|
||||
"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())
|
||||
Reference in New Issue
Block a user