new kamil adc

This commit is contained in:
Ayzen
2026-06-11 13:02:02 +03:00
parent 21f76d7cd2
commit 9661504e51
46 changed files with 12097 additions and 1063 deletions
+91 -32
View File
@@ -18,18 +18,21 @@ LOCK_FILE="/tmp/radar_system.lock"
SKIP_BUILD=0
BUILD_ONLY=0
CLEAN_SHM=0
KAMIL_ADC_MODE=0
AUTO_START=0
PRODUCER_ONLY=0
HEADLESS=0
# Acquisition device, detected from the active run config's radar.model. Drives
# device-specific provisioning, dependencies, and which collector binary to build
# — so every device launches the same way (no per-device flags).
RADAR_MODEL=""
print_usage() {
cat <<'EOF'
Usage: ./start.sh [options]
Options:
--kamil-adc Use the Raspberry Pi Kamil ADC profile
--profile PATH Use a specific GUI/run config profile
--profile PATH Use a specific GUI/run config profile (device is auto-detected
from its radar.model)
--auto-start Start the GUI pipeline automatically after launch
--headless Run without a display (Qt offscreen platform) and apply
the active radar config, then start the pipeline. Suitable
@@ -55,9 +58,6 @@ parse_args() {
--clean-shm)
CLEAN_SHM=1
;;
--kamil-adc)
KAMIL_ADC_MODE=1
;;
--profile)
if (($# < 2)); then
echo "--profile requires a path argument." >&2
@@ -100,16 +100,6 @@ absolute_path() {
}
resolve_profile_path() {
if ((KAMIL_ADC_MODE == 1)); then
if [[ -z "${PROFILE_PATH}" ]]; then
if [[ -f "${PROJECT_ROOT}/run_config_kamil_adc.pi.json" ]]; then
PROFILE_PATH="${PROJECT_ROOT}/run_config_kamil_adc.pi.json"
else
PROFILE_PATH="${PROJECT_ROOT}/run_config_kamil_adc.example.json"
fi
fi
fi
if [[ -n "${PROFILE_PATH}" ]]; then
PROFILE_PATH="$(absolute_path "${PROFILE_PATH}")"
if [[ ! -f "${PROFILE_PATH}" ]]; then
@@ -119,6 +109,44 @@ resolve_profile_path() {
fi
}
# Resolve the config that will actually be used (explicit --profile, else the
# active run_config.json) and read its radar.model. Best-effort: any failure
# falls back to 'librevna' (the full-provisioning superset), so detection can
# never make a launch less safe. Uses system python3 (the venv may not exist yet).
detect_radar_model() {
local config_path="${PROFILE_PATH:-${PROJECT_ROOT}/run_config.json}"
RADAR_MODEL="librevna"
[[ -f "${config_path}" ]] || return
local detected
detected="$(python3 -c '
import json, sys
try:
with open(sys.argv[1]) as handle:
data = json.load(handle)
radar = data.get("radar") if isinstance(data, dict) else {}
model = radar.get("model") if isinstance(radar, dict) else None
print(model or "librevna")
except Exception:
print("librevna")
' "${config_path}" 2>/dev/null)" || detected=""
[[ -n "${detected}" ]] && RADAR_MODEL="${detected}"
echo "[start.sh] Detected radar model: ${RADAR_MODEL} (config: ${config_path})"
}
# Acquisition producer for the active model, mirroring the process supervisor's
# selection so --producer-only behaves identically to a full pipeline launch.
producer_command() {
local config_path="$1"
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
@@ -138,7 +166,8 @@ check_environment() {
ensure_python_dependencies() {
local dependency_check
if ((KAMIL_ADC_MODE == 1)); then
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'
@@ -260,14 +289,20 @@ EOF
}
build_cpp_binaries() {
if make -C "${PROJECT_ROOT}" -q all >/dev/null 2>&1; then
local jobs
jobs="${BUILD_JOBS:-$(nproc)}"
# The Kamil ADC collector is an extra, device-specific binary built only for
# that model; all models share the core pipeline binaries (`all`).
local targets="all"
if [[ "${RADAR_MODEL}" == "kamil_adc" ]]; then
targets="all kamil_adc_collector"
fi
if make -C "${PROJECT_ROOT}" -q ${targets} >/dev/null 2>&1; then
echo "[start.sh] C++ binaries are up to date; skipping build."
return
fi
local jobs
jobs="${BUILD_JOBS:-$(nproc)}"
echo "[start.sh] Building C++ binaries (jobs=${jobs})..."
make -C "${PROJECT_ROOT}" -j"${jobs}" all
echo "[start.sh] Building C++ binaries (jobs=${jobs}, targets: ${targets})..."
make -C "${PROJECT_ROOT}" -j"${jobs}" ${targets}
}
cleanup_known_shm() {
@@ -286,6 +321,17 @@ cleanup_known_shm() {
|| true
}
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
}
run_gui() {
if [[ -n "${PROFILE_PATH}" ]]; then
export RADAR_SYSTEM_PROFILE="${PROFILE_PATH}"
@@ -311,18 +357,20 @@ run_gui() {
}
run_producer_only() {
if ((KAMIL_ADC_MODE != 1)); then
echo "--producer-only currently requires --kamil-adc." >&2
exit 1
fi
if [[ -z "${PROFILE_PATH}" ]]; then
echo "--producer-only requires a resolved config profile." >&2
local config_path="${PROFILE_PATH:-${PROJECT_ROOT}/run_config.json}"
if [[ ! -f "${config_path}" ]]; then
echo "--producer-only needs a config (pass --profile, or create run_config.json)." >&2
exit 1
fi
export PYTHONPATH="${PROJECT_ROOT}${PYTHONPATH:+:${PYTHONPATH}}"
echo "[start.sh] Starting Kamil ADC raw producer with profile: ${PROFILE_PATH}"
exec "${PYTHON_CMD}" -m python_app.scripts.kamil_adc_raw_producer --config "${PROFILE_PATH}"
# 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[@]}"
}
stop_headless_service() {
@@ -345,7 +393,13 @@ verify_cpp_binaries() {
# Guard the daemon's --skip-build path: refuse to run against a tree whose C++
# binaries were never built, instead of failing obscurely at spawn time.
local missing=0 bin
for bin in data_processor data_preprocessor; do
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
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
missing=1
@@ -373,10 +427,13 @@ main() {
parse_args "$@"
resolve_profile_path
check_environment
detect_radar_model
# 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.
if ((KAMIL_ADC_MODE == 0 && HEADLESS == 0)); then
# 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
ensure_system_dependencies
ensure_usb_access_rules
fi
@@ -404,6 +461,8 @@ main() {
stop_headless_service
fi
acquire_single_instance_lock
# Clear any ADC collector wedged by a previous run before we launch a new one.
kill_stale_adc_collector
if ((PRODUCER_ONLY == 1)); then
run_producer_only