first attempt at radioradar
This commit is contained in:
@@ -30,6 +30,36 @@ def _load_preprocess_asset(payload: dict[str, Any], target: PreprocessAssetModel
|
||||
target.bundle_path = str(payload.get("bundle_path", target.bundle_path))
|
||||
|
||||
|
||||
def _load_string_list(payload: dict[str, Any], key: str, context: str) -> list[str]:
|
||||
"""Load an optional list of strings with strict shape validation."""
|
||||
raw_value = payload.get(key, [])
|
||||
if raw_value is None:
|
||||
return []
|
||||
if not isinstance(raw_value, list):
|
||||
raise ValueError(f"{context}.{key} must be a JSON array")
|
||||
values: list[str] = []
|
||||
for index, item in enumerate(raw_value):
|
||||
if not isinstance(item, str):
|
||||
raise ValueError(f"{context}.{key}[{index}] must be a JSON string")
|
||||
values.append(item)
|
||||
return values
|
||||
|
||||
|
||||
def _load_string_dict(payload: dict[str, Any], key: str, context: str) -> dict[str, str]:
|
||||
"""Load an optional string-to-string dictionary with strict shape validation."""
|
||||
raw_value = payload.get(key, {})
|
||||
if raw_value is None:
|
||||
return {}
|
||||
if not isinstance(raw_value, dict):
|
||||
raise ValueError(f"{context}.{key} must be a JSON object")
|
||||
values: dict[str, str] = {}
|
||||
for item_key, item_value in raw_value.items():
|
||||
if not isinstance(item_key, str) or not isinstance(item_value, str):
|
||||
raise ValueError(f"{context}.{key} must contain only string keys and values")
|
||||
values[item_key] = item_value
|
||||
return values
|
||||
|
||||
|
||||
def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
"""Decode JSON-like payload into :class:`RunConfigModel`."""
|
||||
# Schema carries only minimal-safe fallbacks; operational defaults live in run_config.json.
|
||||
@@ -54,6 +84,8 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
"run.locator_server",
|
||||
)
|
||||
multi_device_payload = _as_dict(radar_payload.get("multi_device"), "radar.multi_device")
|
||||
kamil_adc_payload = _as_dict(radar_payload.get("kamil_adc"), "radar.kamil_adc")
|
||||
laser_control_payload = _as_dict(radar_payload.get("laser_control"), "radar.laser_control")
|
||||
|
||||
model.radar.model = str(radar_payload.get("model", model.radar.model))
|
||||
model.radar.serial = str(radar_payload.get("serial", model.radar.serial))
|
||||
@@ -93,6 +125,110 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel:
|
||||
model.radar.multi_device.recovery_attempts,
|
||||
)
|
||||
)
|
||||
model.radar.kamil_adc.project_dir = str(
|
||||
kamil_adc_payload.get("project_dir", model.radar.kamil_adc.project_dir)
|
||||
)
|
||||
model.radar.kamil_adc.executable_path = str(
|
||||
kamil_adc_payload.get("executable_path", model.radar.kamil_adc.executable_path)
|
||||
)
|
||||
model.radar.kamil_adc.tty_path = str(
|
||||
kamil_adc_payload.get("tty_path", model.radar.kamil_adc.tty_path)
|
||||
)
|
||||
model.radar.kamil_adc.args = _load_string_list(kamil_adc_payload, "args", "radar.kamil_adc")
|
||||
model.radar.kamil_adc.env = _load_string_dict(kamil_adc_payload, "env", "radar.kamil_adc")
|
||||
model.radar.kamil_adc.startup_timeout_s = float(
|
||||
kamil_adc_payload.get("startup_timeout_s", model.radar.kamil_adc.startup_timeout_s)
|
||||
)
|
||||
model.radar.kamil_adc.sweep_timeout_s = float(
|
||||
kamil_adc_payload.get("sweep_timeout_s", model.radar.kamil_adc.sweep_timeout_s)
|
||||
)
|
||||
model.radar.kamil_adc.stop_timeout_s = float(
|
||||
kamil_adc_payload.get("stop_timeout_s", model.radar.kamil_adc.stop_timeout_s)
|
||||
)
|
||||
|
||||
model.radar.laser_control.enabled = bool(
|
||||
laser_control_payload.get("enabled", model.radar.laser_control.enabled)
|
||||
)
|
||||
model.radar.laser_control.port = str(
|
||||
laser_control_payload.get("port", model.radar.laser_control.port)
|
||||
)
|
||||
model.radar.laser_control.mode = str(
|
||||
laser_control_payload.get("mode", model.radar.laser_control.mode)
|
||||
)
|
||||
model.radar.laser_control.pi_coeff1_p = int(
|
||||
laser_control_payload.get("pi_coeff1_p", model.radar.laser_control.pi_coeff1_p)
|
||||
)
|
||||
model.radar.laser_control.pi_coeff1_i = int(
|
||||
laser_control_payload.get("pi_coeff1_i", model.radar.laser_control.pi_coeff1_i)
|
||||
)
|
||||
model.radar.laser_control.pi_coeff2_p = int(
|
||||
laser_control_payload.get("pi_coeff2_p", model.radar.laser_control.pi_coeff2_p)
|
||||
)
|
||||
model.radar.laser_control.pi_coeff2_i = int(
|
||||
laser_control_payload.get("pi_coeff2_i", model.radar.laser_control.pi_coeff2_i)
|
||||
)
|
||||
laser_manual_payload = _as_dict(laser_control_payload.get("manual"), "radar.laser_control.manual")
|
||||
model.radar.laser_control.manual.temp1 = float(
|
||||
laser_manual_payload.get("temp1", model.radar.laser_control.manual.temp1)
|
||||
)
|
||||
model.radar.laser_control.manual.temp2 = float(
|
||||
laser_manual_payload.get("temp2", model.radar.laser_control.manual.temp2)
|
||||
)
|
||||
model.radar.laser_control.manual.current1 = float(
|
||||
laser_manual_payload.get("current1", model.radar.laser_control.manual.current1)
|
||||
)
|
||||
model.radar.laser_control.manual.current2 = float(
|
||||
laser_manual_payload.get("current2", model.radar.laser_control.manual.current2)
|
||||
)
|
||||
laser_variation_payload = _as_dict(
|
||||
laser_control_payload.get("variation"),
|
||||
"radar.laser_control.variation",
|
||||
)
|
||||
model.radar.laser_control.variation.variation_type = str(
|
||||
laser_variation_payload.get(
|
||||
"variation_type",
|
||||
model.radar.laser_control.variation.variation_type,
|
||||
)
|
||||
)
|
||||
model.radar.laser_control.variation.static_temp1 = float(
|
||||
laser_variation_payload.get(
|
||||
"static_temp1",
|
||||
model.radar.laser_control.variation.static_temp1,
|
||||
)
|
||||
)
|
||||
model.radar.laser_control.variation.static_temp2 = float(
|
||||
laser_variation_payload.get(
|
||||
"static_temp2",
|
||||
model.radar.laser_control.variation.static_temp2,
|
||||
)
|
||||
)
|
||||
model.radar.laser_control.variation.static_current1 = float(
|
||||
laser_variation_payload.get(
|
||||
"static_current1",
|
||||
model.radar.laser_control.variation.static_current1,
|
||||
)
|
||||
)
|
||||
model.radar.laser_control.variation.static_current2 = float(
|
||||
laser_variation_payload.get(
|
||||
"static_current2",
|
||||
model.radar.laser_control.variation.static_current2,
|
||||
)
|
||||
)
|
||||
model.radar.laser_control.variation.min_value = float(
|
||||
laser_variation_payload.get("min_value", model.radar.laser_control.variation.min_value)
|
||||
)
|
||||
model.radar.laser_control.variation.max_value = float(
|
||||
laser_variation_payload.get("max_value", model.radar.laser_control.variation.max_value)
|
||||
)
|
||||
model.radar.laser_control.variation.step = float(
|
||||
laser_variation_payload.get("step", model.radar.laser_control.variation.step)
|
||||
)
|
||||
model.radar.laser_control.variation.time_step = int(
|
||||
laser_variation_payload.get("time_step", model.radar.laser_control.variation.time_step)
|
||||
)
|
||||
model.radar.laser_control.variation.delay_time = int(
|
||||
laser_variation_payload.get("delay_time", model.radar.laser_control.variation.delay_time)
|
||||
)
|
||||
|
||||
load_switch_payload(port1_payload, model.output_switch)
|
||||
load_switch_payload(port2_payload, model.input_switch)
|
||||
@@ -249,6 +385,43 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
|
||||
"force_external_reference": model.radar.multi_device.force_external_reference,
|
||||
"recovery_attempts": model.radar.multi_device.recovery_attempts,
|
||||
},
|
||||
"kamil_adc": {
|
||||
"project_dir": model.radar.kamil_adc.project_dir,
|
||||
"executable_path": model.radar.kamil_adc.executable_path,
|
||||
"tty_path": model.radar.kamil_adc.tty_path,
|
||||
"args": list(model.radar.kamil_adc.args),
|
||||
"env": dict(model.radar.kamil_adc.env),
|
||||
"startup_timeout_s": model.radar.kamil_adc.startup_timeout_s,
|
||||
"sweep_timeout_s": model.radar.kamil_adc.sweep_timeout_s,
|
||||
"stop_timeout_s": model.radar.kamil_adc.stop_timeout_s,
|
||||
},
|
||||
"laser_control": {
|
||||
"enabled": model.radar.laser_control.enabled,
|
||||
"port": model.radar.laser_control.port,
|
||||
"mode": model.radar.laser_control.mode,
|
||||
"pi_coeff1_p": model.radar.laser_control.pi_coeff1_p,
|
||||
"pi_coeff1_i": model.radar.laser_control.pi_coeff1_i,
|
||||
"pi_coeff2_p": model.radar.laser_control.pi_coeff2_p,
|
||||
"pi_coeff2_i": model.radar.laser_control.pi_coeff2_i,
|
||||
"manual": {
|
||||
"temp1": model.radar.laser_control.manual.temp1,
|
||||
"temp2": model.radar.laser_control.manual.temp2,
|
||||
"current1": model.radar.laser_control.manual.current1,
|
||||
"current2": model.radar.laser_control.manual.current2,
|
||||
},
|
||||
"variation": {
|
||||
"variation_type": model.radar.laser_control.variation.variation_type,
|
||||
"static_temp1": model.radar.laser_control.variation.static_temp1,
|
||||
"static_temp2": model.radar.laser_control.variation.static_temp2,
|
||||
"static_current1": model.radar.laser_control.variation.static_current1,
|
||||
"static_current2": model.radar.laser_control.variation.static_current2,
|
||||
"min_value": model.radar.laser_control.variation.min_value,
|
||||
"max_value": model.radar.laser_control.variation.max_value,
|
||||
"step": model.radar.laser_control.variation.step,
|
||||
"time_step": model.radar.laser_control.variation.time_step,
|
||||
"delay_time": model.radar.laser_control.variation.delay_time,
|
||||
},
|
||||
},
|
||||
"sweep": {
|
||||
"start_hz": model.radar.sweep.start_hz,
|
||||
"stop_hz": model.radar.sweep.stop_hz,
|
||||
|
||||
@@ -6,6 +6,10 @@ from python_app.models.run_config_schema import (
|
||||
GprModel,
|
||||
GprRxGeometryModel,
|
||||
GprTxGeometryModel,
|
||||
KamilAdcModel,
|
||||
LaserControlModel,
|
||||
LaserManualModeModel,
|
||||
LaserVariationModeModel,
|
||||
LocatorServerRuntimeModel,
|
||||
PreprocessAssetModel,
|
||||
PreprocessNotchModel,
|
||||
@@ -33,6 +37,10 @@ __all__ = [
|
||||
"GprModel",
|
||||
"GprRxGeometryModel",
|
||||
"GprTxGeometryModel",
|
||||
"KamilAdcModel",
|
||||
"LaserControlModel",
|
||||
"LaserManualModeModel",
|
||||
"LaserVariationModeModel",
|
||||
"LocatorServerRuntimeModel",
|
||||
"PreprocessAssetModel",
|
||||
"PreprocessNotchModel",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -37,6 +38,61 @@ class RadarMultiDeviceModel:
|
||||
recovery_attempts: int = 3
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class KamilAdcModel:
|
||||
"""External Kamil ADC acquisition process settings."""
|
||||
|
||||
project_dir: str = ""
|
||||
executable_path: str = ""
|
||||
tty_path: str = ""
|
||||
args: list[str] = field(default_factory=list)
|
||||
env: dict[str, str] = field(default_factory=dict)
|
||||
startup_timeout_s: float = 5.0
|
||||
sweep_timeout_s: float = 5.0
|
||||
stop_timeout_s: float = 2.0
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LaserManualModeModel:
|
||||
"""Manual laser-control setpoints."""
|
||||
|
||||
temp1: float = 25.0
|
||||
temp2: float = 25.0
|
||||
current1: float = 30.0
|
||||
current2: float = 30.0
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LaserVariationModeModel:
|
||||
"""Laser-control variation task parameters."""
|
||||
|
||||
variation_type: str = "CHANGE_CURRENT_LD1"
|
||||
static_temp1: float = 25.0
|
||||
static_temp2: float = 25.0
|
||||
static_current1: float = 30.0
|
||||
static_current2: float = 30.0
|
||||
min_value: float = 30.0
|
||||
max_value: float = 35.0
|
||||
step: float = 0.1
|
||||
time_step: int = 20
|
||||
delay_time: int = 3
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LaserControlModel:
|
||||
"""Laser-control board settings applied before Kamil ADC acquisition."""
|
||||
|
||||
enabled: bool = False
|
||||
port: str = ""
|
||||
mode: str = "manual"
|
||||
pi_coeff1_p: int = 2560
|
||||
pi_coeff1_i: int = 128
|
||||
pi_coeff2_p: int = 2560
|
||||
pi_coeff2_i: int = 128
|
||||
manual: LaserManualModeModel = field(default_factory=LaserManualModeModel)
|
||||
variation: LaserVariationModeModel = field(default_factory=LaserVariationModeModel)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class RadarModel:
|
||||
"""Radar section of run configuration."""
|
||||
@@ -49,6 +105,8 @@ class RadarModel:
|
||||
mock_signal_hz: float = 1_000_000.0
|
||||
sweep: RadarSweepModel = field(default_factory=RadarSweepModel)
|
||||
multi_device: RadarMultiDeviceModel = field(default_factory=RadarMultiDeviceModel)
|
||||
kamil_adc: KamilAdcModel = field(default_factory=KamilAdcModel)
|
||||
laser_control: LaserControlModel = field(default_factory=LaserControlModel)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -204,6 +262,7 @@ class RunConfigModel:
|
||||
LIBREVNA_MODEL = "librevna"
|
||||
LIBREVNA_MULTI_MODEL = "librevna_multi"
|
||||
COMPACT_M_K209_MODEL = "compact_m_k209"
|
||||
KAMIL_ADC_MODEL = "kamil_adc"
|
||||
MULTI_DEVICE_INPUT_POSITIONS = 4
|
||||
MULTI_DEVICE_OUTPUT_POSITIONS = 2
|
||||
|
||||
@@ -229,6 +288,57 @@ class RunConfigModel:
|
||||
"""Return whether this config targets synchronized multi-device acquisition."""
|
||||
return self.radar.model == self.LIBREVNA_MULTI_MODEL
|
||||
|
||||
@property
|
||||
def is_kamil_adc(self) -> bool:
|
||||
"""Return whether this config targets the external Kamil ADC acquisition path."""
|
||||
return self.radar.model == self.KAMIL_ADC_MODEL
|
||||
|
||||
def radar_key_extra_parts(self) -> list[str]:
|
||||
"""Return model-specific identity parts that affect captured data."""
|
||||
if self.is_multi_device:
|
||||
return list(self.radar.multi_device.slave_serials)
|
||||
if self.is_kamil_adc:
|
||||
payload = {
|
||||
"kamil_adc": {
|
||||
"project_dir": self.radar.kamil_adc.project_dir,
|
||||
"executable_path": self.radar.kamil_adc.executable_path,
|
||||
"tty_path": self.radar.kamil_adc.tty_path,
|
||||
"args": list(self.radar.kamil_adc.args),
|
||||
"env": dict(sorted(self.radar.kamil_adc.env.items())),
|
||||
},
|
||||
"laser_control": {
|
||||
"enabled": self.radar.laser_control.enabled,
|
||||
"port": self.radar.laser_control.port,
|
||||
"mode": self.radar.laser_control.mode,
|
||||
"pi_coeff1_p": self.radar.laser_control.pi_coeff1_p,
|
||||
"pi_coeff1_i": self.radar.laser_control.pi_coeff1_i,
|
||||
"pi_coeff2_p": self.radar.laser_control.pi_coeff2_p,
|
||||
"pi_coeff2_i": self.radar.laser_control.pi_coeff2_i,
|
||||
"manual": {
|
||||
"temp1": self.radar.laser_control.manual.temp1,
|
||||
"temp2": self.radar.laser_control.manual.temp2,
|
||||
"current1": self.radar.laser_control.manual.current1,
|
||||
"current2": self.radar.laser_control.manual.current2,
|
||||
},
|
||||
"variation": {
|
||||
"variation_type": self.radar.laser_control.variation.variation_type,
|
||||
"static_temp1": self.radar.laser_control.variation.static_temp1,
|
||||
"static_temp2": self.radar.laser_control.variation.static_temp2,
|
||||
"static_current1": self.radar.laser_control.variation.static_current1,
|
||||
"static_current2": self.radar.laser_control.variation.static_current2,
|
||||
"min_value": self.radar.laser_control.variation.min_value,
|
||||
"max_value": self.radar.laser_control.variation.max_value,
|
||||
"step": self.radar.laser_control.variation.step,
|
||||
"time_step": self.radar.laser_control.variation.time_step,
|
||||
"delay_time": self.radar.laser_control.variation.delay_time,
|
||||
},
|
||||
},
|
||||
}
|
||||
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
||||
digest = hashlib.sha256(encoded.encode("utf-8")).hexdigest()[:16]
|
||||
return [f"kamil_{digest}"]
|
||||
return []
|
||||
|
||||
def apply_device_model_constraints(self) -> None:
|
||||
"""Apply only required wire-format constraints for the selected device model."""
|
||||
if not self.is_multi_device:
|
||||
|
||||
Reference in New Issue
Block a user