Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
+40 -74
View File
@@ -133,37 +133,6 @@ except Exception:
echo "[start.sh] Detected radar model: ${RADAR_MODEL} (config: ${config_path})"
}
# radar.replay.dataset_dir of a config, empty unless it replays a recording.
replay_dataset_dir() {
python3 -c '
import json, sys
try:
radar = json.load(open(sys.argv[1])).get("radar", {})
print(str(radar.get("replay", {}).get("dataset_dir", "")).strip())
except Exception:
print("")
' "$1" 2>/dev/null
}
# Acquisition producer for the active model, mirroring the process supervisor's
# selection so --producer-only behaves identically to a full pipeline launch: a
# replay dataset takes precedence over the model's producer.
producer_command() {
local config_path="$1"
if [[ -n "$(replay_dataset_dir "${config_path}")" ]]; then
printf '%s\0' "${PYTHON_CMD}" -m python_app.scripts.replay_raw_producer --config "${config_path}"
return
fi
case "${RADAR_MODEL}" in
kamil_adc)
printf '%s\0' "${PYTHON_CMD}" -m python_app.scripts.kamil_adc_raw_producer --config "${config_path}" ;;
librevna_multi|sn9000)
printf '%s\0' "${PYTHON_CMD}" -m python_app.scripts.matrix_raw_producer --config "${config_path}" ;;
*)
printf '%s\0' "${PROJECT_ROOT}/build/bin/sweep_orchestrator" --config "${config_path}" ;;
esac
}
check_environment() {
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 is not installed or not found in PATH." >&2
@@ -182,13 +151,7 @@ check_environment() {
}
ensure_python_dependencies() {
local dependency_check
if [[ "${RADAR_MODEL}" == "kamil_adc" ]]; then
# Kamil ADC has no VISA dependency (it talks to its own L-Card collector).
dependency_check='import numpy, serial, PyQt6, pyqtgraph, usb1'
else
dependency_check='import numpy, serial, PyQt6, pyqtgraph, usb1, pyvisa'
fi
local dependency_check='import numpy, serial, PyQt6, pyqtgraph, usb1'
if [[ ! -x "${VENV_PYTHON}" ]]; then
echo "[start.sh] Creating virtual environment..."
@@ -257,24 +220,25 @@ ensure_system_dependencies() {
fi
if command -v pkg-config >/dev/null 2>&1 && pkg-config --exists libusb-1.0 \
&& ((qt_gui_packages_missing == 0)); then
&& ((qt_gui_packages_missing == 0)) \
&& python3 -c 'import ctypes; ctypes.CDLL("libatomic.so.1")' >/dev/null 2>&1; then
return
fi
echo "[start.sh] Installing system dependencies: libusb-1.0 dev headers (and Qt xcb libraries on apt)..."
echo "[start.sh] Installing system dependencies: libusb headers, libatomic and Qt xcb libraries..."
if command -v apt-get >/dev/null 2>&1; then
run_privileged apt-get update
run_privileged apt-get install -y pkg-config libusb-1.0-0-dev libxcb-cursor0 libxkbcommon-x11-0
run_privileged apt-get install -y pkg-config libusb-1.0-0-dev libatomic1 libxcb-cursor0 libxkbcommon-x11-0
elif command -v dnf >/dev/null 2>&1; then
run_privileged dnf install -y pkgconf-pkg-config libusb1-devel
run_privileged dnf install -y pkgconf-pkg-config libusb1-devel libatomic
elif command -v yum >/dev/null 2>&1; then
run_privileged yum install -y pkgconfig libusb1-devel
run_privileged yum install -y pkgconfig libusb1-devel libatomic
elif command -v pacman >/dev/null 2>&1; then
run_privileged pacman -Sy --needed pkgconf libusb
run_privileged pacman -Sy --needed pkgconf libusb gcc-libs
elif command -v zypper >/dev/null 2>&1; then
run_privileged zypper --non-interactive install pkg-config libusb-1_0-devel
run_privileged zypper --non-interactive install pkg-config libusb-1_0-devel libatomic1
elif command -v brew >/dev/null 2>&1; then
brew install pkg-config libusb
brew install pkg-config libusb gcc
else
echo "Could not detect supported package manager." >&2
echo "Install manually: pkg-config and libusb development package." >&2
@@ -354,14 +318,11 @@ cleanup_known_shm() {
}
kill_stale_adc_collector() {
# The L-Card ADC collector runs in its own session and can outlive a crashed
# or force-killed run, holding the E-502 device and hanging the next start.
# Kill any leftover hard before launching so acquisition always opens cleanly.
if command -v pkill >/dev/null 2>&1; then
if pkill -9 -f kamil_adc_collector 2>/dev/null; then
echo "[start.sh] Killed leftover ADC collector process(es)."
fi
fi
# Exact executable matching: an argument mentioning the collector is not a process to kill.
PYTHONPATH="${PROJECT_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" "${PYTHON_CMD}" -c '
from python_app.orchestration.process_supervisor import ProcessSupervisor
ProcessSupervisor._kill_kamil_collectors()
'
}
run_gui() {
@@ -396,13 +357,14 @@ run_producer_only() {
fi
export PYTHONPATH="${PROJECT_ROOT}${PYTHONPATH:+:${PYTHONPATH}}"
# Run the acquisition producer the supervisor would pick for this model.
local -a command=()
while IFS= read -r -d '' token; do
command+=("${token}")
done < <(producer_command "${config_path}")
echo "[start.sh] Starting ${RADAR_MODEL} producer: ${command[*]}"
exec "${command[@]}"
exec "${PYTHON_CMD}" -c '
import os, sys
from pathlib import Path
from python_app.orchestration.producer_selection import acquisition_command
command = acquisition_command(Path(sys.argv[1]), Path(sys.argv[2]))
print("[start.sh] Starting producer:", command, flush=True)
os.execv(command[0], command)
' "${PROJECT_ROOT}" "${config_path}"
}
stop_headless_service() {
@@ -426,11 +388,16 @@ verify_cpp_binaries() {
# binaries were never built, instead of failing obscurely at spawn time.
local missing=0 bin
local required_bins="data_processor data_preprocessor"
# Kamil ADC also needs its acquisition collector; other models use the C++
# sweep_orchestrator, which `all` always builds.
if [[ "${RADAR_MODEL}" == "kamil_adc" ]]; then
required_bins="${required_bins} kamil_adc_collector"
fi
local acquisition_binary
acquisition_binary="$(PYTHONPATH="${PROJECT_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" "${PYTHON_CMD}" -c '
import sys
from pathlib import Path
from python_app.orchestration.producer_selection import acquisition_command
command = acquisition_command(Path(sys.argv[1]), Path(sys.argv[2]))
print("kamil_adc_collector" if "python_app.scripts.kamil_adc_raw_producer" in command
else "sweep_orchestrator" if len(command) == 3 else "")
' "${PROJECT_ROOT}" "${PROFILE_PATH:-${PROJECT_ROOT}/run_config.json}")"
required_bins="${required_bins} ${acquisition_binary}"
for bin in ${required_bins}; do
if [[ ! -x "${PROJECT_ROOT}/build/bin/${bin}" ]]; then
echo "Required binary missing or not executable: build/bin/${bin}" >&2
@@ -463,18 +430,14 @@ main() {
# Skip first-time provisioning (build headers, USB udev rule) in headless
# mode: the daemon runs unattended at boot as a non-root user and must not
# block on sudo. A fresh machine is provisioned by one interactive launch.
# Also skip it for non-LibreVNA devices (e.g. Kamil ADC), which neither use
# libusb directly nor need the LibreVNA USB access rule.
if [[ "${RADAR_MODEL}" != "kamil_adc" ]] && ((HEADLESS == 0)); then
# `all` builds the libusb orchestrator for every model; only the USB access
# rule is device-specific. Python/C++ SHM also needs libatomic.
if ((HEADLESS == 0)); then
ensure_system_dependencies
ensure_usb_access_rules
if [[ "${RADAR_MODEL}" != "kamil_adc" ]]; then ensure_usb_access_rules; fi
fi
ensure_python_dependencies
if ((CLEAN_SHM == 1)); then
cleanup_known_shm
fi
if ((SKIP_BUILD == 0)); then
build_cpp_binaries
fi
@@ -493,6 +456,9 @@ main() {
stop_headless_service
fi
acquire_single_instance_lock
if ((CLEAN_SHM == 1)); then
cleanup_known_shm
fi
# Clear any ADC collector wedged by a previous run before we launch a new one.
kill_stale_adc_collector