This commit is contained in:
Ayzen
2026-06-22 17:33:15 +03:00
parent d7ddca6e76
commit 716fd0b07a
+66
View File
@@ -31,7 +31,9 @@ from contextlib import suppress
import json
import logging
from pathlib import Path
import shutil
import statistics
import subprocess
import time
import numpy as np
@@ -65,6 +67,60 @@ DO8_FREQ_REF_ARGS = [
]
COLLECTOR_PATH = "build/bin/kamil_adc_collector"
# Hardware the daemon / a prior collector may be holding, and how to free it.
# Mirrors what start.sh does before an interactive launch (stop the daemon, kill
# an orphaned collector) so this tool can grab the L-Card E-502 + lasers too.
RADAR_SERVICE_NAME = "radar.service"
COLLECTOR_PROCESS_PATTERN = "kamil_adc_collector"
# The E-502 is not reacquirable the instant it is released; wait before opening.
_DEVICE_SETTLE_SECONDS = 8.0
def _release_radar_hardware(*, settle_seconds: float = _DEVICE_SETTLE_SECONDS) -> None:
"""Free the L-Card E-502 + lasers before we open our own collector.
Mirrors ``start.sh`` on an interactive launch: stop the headless
``radar.service`` daemon (if active) so it releases the hardware, then kill any
orphaned ``kamil_adc_collector`` a prior (e.g. SSH-killed) run left holding the
device. Both steps are best-effort and non-fatal — the collector's own
open-retry covers the residual settle time — this just removes the usual reason
it never frees up. Passwordless ``sudo systemctl stop radar.service`` is
provisioned in ``/etc/sudoers.d/radar``.
"""
freed = False
systemctl = shutil.which("systemctl")
if systemctl is not None:
is_active = subprocess.run(
[systemctl, "is-active", "--quiet", RADAR_SERVICE_NAME],
check=False,
).returncode == 0
if is_active:
logger.info("Stopping %s so it releases the radar hardware...", RADAR_SERVICE_NAME)
stopped = subprocess.run(
["sudo", systemctl, "stop", RADAR_SERVICE_NAME], check=False
).returncode == 0
if stopped:
freed = True
else:
logger.warning(
"Could not stop %s (need passwordless sudo?); continuing anyway",
RADAR_SERVICE_NAME,
)
pkill = shutil.which("pkill")
if pkill is not None:
# pkill returns 0 when it matched & signalled at least one process.
if subprocess.run(
[pkill, "-9", "-f", COLLECTOR_PROCESS_PATTERN], check=False
).returncode == 0:
logger.info("Killed orphaned %s process(es) holding the device", COLLECTOR_PROCESS_PATTERN)
freed = True
if freed:
logger.info("Waiting %.0fs for the L-Card E-502 to settle...", settle_seconds)
time.sleep(settle_seconds)
def _open_with_retry(service: KamilAdcService, *, attempts: int = 4, delay_s: float = 8.0) -> None:
"""Open the collector, retrying the transient E-502 device-busy after a close.
@@ -519,6 +575,11 @@ def main() -> int:
parser.add_argument("--warmup", type=int, default=10, help="Sweeps to discard first (default 10)")
parser.add_argument("--apply", action="store_true", help="Write the calibration back to --config")
parser.add_argument("--no-laser", action="store_true", help="Skip laser setup (already running)")
parser.add_argument(
"--no-release",
action="store_true",
help="Do not stop radar.service / kill orphaned collectors before opening",
)
parser.add_argument(
"--diagnose",
action="store_true",
@@ -541,6 +602,11 @@ def main() -> int:
config.radar.kamil_adc.executable_path = COLLECTOR_PATH
config.radar.kamil_adc.args = list(DO8_FREQ_REF_ARGS)
# Free the device the headless daemon / an orphaned collector may be holding,
# so opening our own collector does not fail with E-502 device-busy.
if not args.no_release:
_release_radar_hardware()
if not args.no_laser:
logger.info("Applying laser control...")
apply_kamil_adc_laser_control(config)