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 json
import logging import logging
from pathlib import Path from pathlib import Path
import shutil
import statistics import statistics
import subprocess
import time import time
import numpy as np import numpy as np
@@ -65,6 +67,60 @@ DO8_FREQ_REF_ARGS = [
] ]
COLLECTOR_PATH = "build/bin/kamil_adc_collector" 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: 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. """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("--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("--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-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( parser.add_argument(
"--diagnose", "--diagnose",
action="store_true", action="store_true",
@@ -541,6 +602,11 @@ def main() -> int:
config.radar.kamil_adc.executable_path = COLLECTOR_PATH config.radar.kamil_adc.executable_path = COLLECTOR_PATH
config.radar.kamil_adc.args = list(DO8_FREQ_REF_ARGS) 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: if not args.no_laser:
logger.info("Applying laser control...") logger.info("Applying laser control...")
apply_kamil_adc_laser_control(config) apply_kamil_adc_laser_control(config)