599 lines
27 KiB
Python
599 lines
27 KiB
Python
"""Encoding and decoding logic for :mod:`python_app.models.run_config_schema`."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from python_app.models.run_config_schema import (
|
|
ComboModel,
|
|
GprRxGeometryModel,
|
|
GprTxGeometryModel,
|
|
PreprocessAssetModel,
|
|
PreprocessNotchModel,
|
|
RunConfigModel,
|
|
)
|
|
from python_app.models.run_config_validation import (
|
|
load_control_button_payload,
|
|
load_ring_payload,
|
|
load_switch_payload,
|
|
validate_gpr_model,
|
|
)
|
|
|
|
|
|
def _as_dict(value: Any, context: str) -> dict[str, Any]:
|
|
"""Validate payload node is object-like, treating missing values as empty object."""
|
|
if value is None:
|
|
return {}
|
|
if not isinstance(value, dict):
|
|
raise ValueError(f"{context} must be a JSON object")
|
|
return value
|
|
|
|
|
|
def _read_str(payload: dict[str, Any], key: str, default: str) -> str:
|
|
"""Return payload string, treating an explicit JSON `null` as missing.
|
|
|
|
`payload.get(key, default)` returns `None` when the key exists with value
|
|
`null`, which is then coerced into the literal string `"None"` by `str()`.
|
|
"""
|
|
value = payload.get(key, default)
|
|
if value is None:
|
|
return default
|
|
return str(value)
|
|
|
|
|
|
def _load_preprocess_asset(payload: dict[str, Any], target: PreprocessAssetModel) -> None:
|
|
"""Load preprocess asset fields into target model."""
|
|
target.set_name = _read_str(payload, "set_name", target.set_name)
|
|
target.bundle_path = _read_str(payload, "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.
|
|
model = RunConfigModel()
|
|
|
|
radar_payload = _as_dict(payload.get("radar"), "radar")
|
|
sweep_payload = _as_dict(radar_payload.get("sweep"), "radar.sweep")
|
|
switches_payload = _as_dict(payload.get("switches"), "switches")
|
|
port1_payload = _as_dict(switches_payload.get("port1"), "switches.port1")
|
|
port2_payload = _as_dict(switches_payload.get("port2"), "switches.port2")
|
|
control_button_payload = _as_dict(payload.get("control_button"), "control_button")
|
|
run_payload = _as_dict(payload.get("run"), "run")
|
|
preprocess_payload = _as_dict(payload.get("preprocess"), "preprocess")
|
|
gpr_payload = _as_dict(payload.get("gpr"), "gpr")
|
|
rings_payload = _as_dict(payload.get("rings"), "rings")
|
|
raw_ring_payload = _as_dict(rings_payload.get("raw"), "rings.raw")
|
|
raw_tap_ring_payload = _as_dict(rings_payload.get("raw_tap"), "rings.raw_tap")
|
|
pre_ring_payload = _as_dict(rings_payload.get("preprocessed"), "rings.preprocessed")
|
|
pre_tap_ring_payload = _as_dict(rings_payload.get("preprocessed_tap"), "rings.preprocessed_tap")
|
|
result_ring_payload = _as_dict(rings_payload.get("results"), "rings.results")
|
|
locator_server_payload = _as_dict(
|
|
run_payload.get("locator_server", run_payload.get("locator")),
|
|
"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))
|
|
model.radar.remote_host = str(radar_payload.get("remote_host", model.radar.remote_host))
|
|
model.radar.remote_port = int(radar_payload.get("remote_port", model.radar.remote_port))
|
|
model.radar.driver_mode = str(radar_payload.get("driver_mode", model.radar.driver_mode))
|
|
model.radar.mock_signal_hz = float(radar_payload.get("mock_signal_hz", model.radar.mock_signal_hz))
|
|
model.radar.visa_library = str(radar_payload.get("visa_library", model.radar.visa_library))
|
|
|
|
model.radar.sweep.start_hz = float(sweep_payload.get("start_hz", model.radar.sweep.start_hz))
|
|
model.radar.sweep.stop_hz = float(sweep_payload.get("stop_hz", model.radar.sweep.stop_hz))
|
|
model.radar.sweep.points = int(sweep_payload.get("points", model.radar.sweep.points))
|
|
model.radar.sweep.if_bandwidth_hz = float(
|
|
sweep_payload.get("if_bandwidth_hz", model.radar.sweep.if_bandwidth_hz)
|
|
)
|
|
model.radar.sweep.power_dbm = float(sweep_payload.get("stimulus_power_dbm", model.radar.sweep.power_dbm))
|
|
slave_serials_payload = multi_device_payload.get(
|
|
"slave_serials",
|
|
multi_device_payload.get("slave_serial_numbers", model.radar.multi_device.slave_serials),
|
|
)
|
|
if isinstance(slave_serials_payload, list):
|
|
model.radar.multi_device.slave_serials = [str(value).strip() for value in slave_serials_payload if str(value).strip()]
|
|
elif isinstance(slave_serials_payload, str):
|
|
model.radar.multi_device.slave_serials = [
|
|
value.strip()
|
|
for value in slave_serials_payload.split(",")
|
|
if value.strip()
|
|
]
|
|
model.radar.multi_device.force_external_reference = bool(
|
|
multi_device_payload.get(
|
|
"force_external_reference",
|
|
model.radar.multi_device.force_external_reference,
|
|
)
|
|
)
|
|
model.radar.multi_device.recovery_attempts = int(
|
|
multi_device_payload.get(
|
|
"recovery_attempts",
|
|
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)
|
|
load_control_button_payload(control_button_payload, model.control_button)
|
|
model.apply_device_model_constraints()
|
|
|
|
model.runtime.settling_ms = int(run_payload.get("settling_ms", model.runtime.settling_ms))
|
|
model.runtime.idle_sleep_ms = int(run_payload.get("idle_sleep_ms", model.runtime.idle_sleep_ms))
|
|
model.runtime.continuous = bool(run_payload.get("continuous", model.runtime.continuous))
|
|
model.runtime.processing_live_config_path = str(
|
|
run_payload.get("processing_live_config_path", model.runtime.processing_live_config_path)
|
|
)
|
|
model.runtime.locator_server.device_id = int(
|
|
locator_server_payload.get("device_id", model.runtime.locator_server.device_id)
|
|
)
|
|
model.runtime.locator_server.protocol_version = int(
|
|
locator_server_payload.get(
|
|
"protocol_version",
|
|
model.runtime.locator_server.protocol_version,
|
|
)
|
|
)
|
|
model.runtime.locator_server.host = str(
|
|
locator_server_payload.get("host", model.runtime.locator_server.host)
|
|
)
|
|
model.runtime.locator_server.port = int(
|
|
locator_server_payload.get("port", model.runtime.locator_server.port)
|
|
)
|
|
model.runtime.locator_server.max_payload_bytes = int(
|
|
locator_server_payload.get(
|
|
"max_payload_bytes",
|
|
model.runtime.locator_server.max_payload_bytes,
|
|
)
|
|
)
|
|
model.runtime.locator_server.client_queue_size = int(
|
|
locator_server_payload.get(
|
|
"client_queue_size",
|
|
model.runtime.locator_server.client_queue_size,
|
|
)
|
|
)
|
|
model.runtime.locator_server.logger_name = str(
|
|
locator_server_payload.get(
|
|
"logger_name",
|
|
model.runtime.locator_server.logger_name,
|
|
)
|
|
)
|
|
|
|
s21_preprocess_payload = _as_dict(preprocess_payload.get("s21"), "preprocess.s21")
|
|
_load_preprocess_asset(
|
|
_as_dict(s21_preprocess_payload.get("calibration"), "preprocess.s21.calibration"),
|
|
model.preprocess.s21.calibration,
|
|
)
|
|
_load_preprocess_asset(
|
|
_as_dict(s21_preprocess_payload.get("reference"), "preprocess.s21.reference"),
|
|
model.preprocess.s21.reference,
|
|
)
|
|
|
|
s11_preprocess_payload = _as_dict(preprocess_payload.get("s11"), "preprocess.s11")
|
|
s11_calibration_payload = _as_dict(s11_preprocess_payload.get("calibration"), "preprocess.s11.calibration")
|
|
_load_preprocess_asset(
|
|
_as_dict(s11_calibration_payload.get("open"), "preprocess.s11.calibration.open"),
|
|
model.preprocess.s11.calibration.open,
|
|
)
|
|
_load_preprocess_asset(
|
|
_as_dict(s11_calibration_payload.get("short"), "preprocess.s11.calibration.short"),
|
|
model.preprocess.s11.calibration.short,
|
|
)
|
|
_load_preprocess_asset(
|
|
_as_dict(s11_calibration_payload.get("load"), "preprocess.s11.calibration.load"),
|
|
model.preprocess.s11.calibration.load,
|
|
)
|
|
_load_preprocess_asset(
|
|
_as_dict(s11_preprocess_payload.get("reference"), "preprocess.s11.reference"),
|
|
model.preprocess.s11.reference,
|
|
)
|
|
notch_payload = _as_dict(preprocess_payload.get("notch"), "preprocess.notch")
|
|
model.preprocess.notch = PreprocessNotchModel(
|
|
enabled=bool(notch_payload.get("enabled", model.preprocess.notch.enabled)),
|
|
taper_width_hz=float(notch_payload.get("taper_width_hz", model.preprocess.notch.taper_width_hz)),
|
|
taper_type=str(notch_payload.get("taper_type", model.preprocess.notch.taper_type)),
|
|
bands_hz=[],
|
|
)
|
|
bands_payload = notch_payload.get("bands_hz", [])
|
|
if isinstance(bands_payload, list):
|
|
for band in bands_payload:
|
|
if isinstance(band, (list, tuple)) and len(band) == 2:
|
|
model.preprocess.notch.bands_hz.append((float(band[0]), float(band[1])))
|
|
|
|
model.gpr.relative_permittivity = float(
|
|
gpr_payload.get("relative_permittivity", model.gpr.relative_permittivity)
|
|
)
|
|
model.gpr.tx_geometry = []
|
|
tx_geometry_payload = gpr_payload.get("tx_geometry", [])
|
|
if isinstance(tx_geometry_payload, list):
|
|
for entry in tx_geometry_payload:
|
|
entry_payload = _as_dict(entry, "gpr.tx_geometry[]")
|
|
model.gpr.tx_geometry.append(
|
|
GprTxGeometryModel(
|
|
output_pos=int(entry_payload.get("output_pos", 0)),
|
|
x_m=float(entry_payload.get("x_m", 0.0)),
|
|
y_m=float(entry_payload.get("y_m", 0.0)),
|
|
z_m=float(entry_payload.get("z_m", 0.0)),
|
|
)
|
|
)
|
|
model.gpr.rx_geometry = []
|
|
rx_geometry_payload = gpr_payload.get("rx_geometry", [])
|
|
if isinstance(rx_geometry_payload, list):
|
|
for entry in rx_geometry_payload:
|
|
entry_payload = _as_dict(entry, "gpr.rx_geometry[]")
|
|
model.gpr.rx_geometry.append(
|
|
GprRxGeometryModel(
|
|
input_pos=int(entry_payload.get("input_pos", 0)),
|
|
x_m=float(entry_payload.get("x_m", 0.0)),
|
|
y_m=float(entry_payload.get("y_m", 0.0)),
|
|
z_m=float(entry_payload.get("z_m", 0.0)),
|
|
)
|
|
)
|
|
model.apply_device_model_constraints()
|
|
validate_gpr_model(
|
|
model.gpr,
|
|
input_switch_positions=model.input_switch.positions,
|
|
output_switch_positions=model.output_switch.positions,
|
|
)
|
|
|
|
load_ring_payload(raw_ring_payload, model.rings.raw)
|
|
load_ring_payload(raw_tap_ring_payload, model.rings.raw_tap)
|
|
load_ring_payload(pre_ring_payload, model.rings.preprocessed)
|
|
load_ring_payload(pre_tap_ring_payload, model.rings.preprocessed_tap)
|
|
load_ring_payload(result_ring_payload, model.rings.results)
|
|
|
|
combos_payload = run_payload.get("combos", [])
|
|
model.combos = []
|
|
if isinstance(combos_payload, list):
|
|
for combo in combos_payload:
|
|
combo_payload = _as_dict(combo, "run.combos[]")
|
|
model.combos.append(
|
|
ComboModel(
|
|
input=int(combo_payload.get("input", 0)),
|
|
output=int(combo_payload.get("output", 0)),
|
|
)
|
|
)
|
|
|
|
model.ensure_combos()
|
|
return model
|
|
|
|
|
|
def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]:
|
|
"""Encode :class:`RunConfigModel` to C++ pipeline-compatible JSON structure."""
|
|
model.ensure_combos()
|
|
sweep_payload = {
|
|
"start_hz": model.radar.sweep.start_hz,
|
|
"stop_hz": model.radar.sweep.stop_hz,
|
|
"if_bandwidth_hz": model.radar.sweep.if_bandwidth_hz,
|
|
"stimulus_power_dbm": model.radar.sweep.power_dbm,
|
|
}
|
|
if not model.is_kamil_adc:
|
|
sweep_payload["points"] = model.radar.sweep.points
|
|
|
|
return {
|
|
"radar": {
|
|
"model": model.radar.model,
|
|
"serial": model.radar.serial,
|
|
"remote_host": model.radar.remote_host,
|
|
"remote_port": model.radar.remote_port,
|
|
"driver_mode": model.radar.driver_mode,
|
|
"mock_signal_hz": model.radar.mock_signal_hz,
|
|
"visa_library": model.radar.visa_library,
|
|
"multi_device": {
|
|
"slave_serials": list(model.radar.multi_device.slave_serials),
|
|
"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": sweep_payload,
|
|
},
|
|
"switches": {
|
|
"port1": {
|
|
"name": model.output_switch.name,
|
|
"driver_mode": model.output_switch.driver_mode,
|
|
"driver": model.output_switch.driver,
|
|
"radar_port": model.output_switch.radar_port,
|
|
"positions": model.output_switch.positions,
|
|
"default_position": model.output_switch.default_position,
|
|
"gpio_chip": model.output_switch.gpio_chip,
|
|
"pin_a": model.output_switch.pin_a,
|
|
"pin_b": model.output_switch.pin_b,
|
|
"invert_logic": model.output_switch.invert_logic,
|
|
},
|
|
"port2": {
|
|
"name": model.input_switch.name,
|
|
"driver_mode": model.input_switch.driver_mode,
|
|
"driver": model.input_switch.driver,
|
|
"radar_port": model.input_switch.radar_port,
|
|
"positions": model.input_switch.positions,
|
|
"default_position": model.input_switch.default_position,
|
|
"gpio_chip": model.input_switch.gpio_chip,
|
|
"pin_a": model.input_switch.pin_a,
|
|
"pin_b": model.input_switch.pin_b,
|
|
"invert_logic": model.input_switch.invert_logic,
|
|
},
|
|
},
|
|
"control_button": {
|
|
"enabled": model.control_button.enabled,
|
|
"gpio_chip": model.control_button.gpio_chip,
|
|
"pin": model.control_button.pin,
|
|
"active_low": model.control_button.active_low,
|
|
"bias": model.control_button.bias,
|
|
"debounce_ms": model.control_button.debounce_ms,
|
|
"action": model.control_button.action,
|
|
},
|
|
"run": {
|
|
"settling_ms": model.runtime.settling_ms,
|
|
"idle_sleep_ms": model.runtime.idle_sleep_ms,
|
|
"continuous": model.runtime.continuous,
|
|
"processing_live_config_path": model.runtime.processing_live_config_path,
|
|
"locator_server": {
|
|
"device_id": model.runtime.locator_server.device_id,
|
|
"protocol_version": model.runtime.locator_server.protocol_version,
|
|
"host": model.runtime.locator_server.host,
|
|
"port": model.runtime.locator_server.port,
|
|
"max_payload_bytes": model.runtime.locator_server.max_payload_bytes,
|
|
"client_queue_size": model.runtime.locator_server.client_queue_size,
|
|
"logger_name": model.runtime.locator_server.logger_name,
|
|
},
|
|
"combos": [{"input": combo.input, "output": combo.output} for combo in model.combos],
|
|
},
|
|
"preprocess": {
|
|
"s21": {
|
|
"calibration": {
|
|
"set_name": model.preprocess.s21.calibration.set_name,
|
|
"bundle_path": model.preprocess.s21.calibration.bundle_path,
|
|
},
|
|
"reference": {
|
|
"set_name": model.preprocess.s21.reference.set_name,
|
|
"bundle_path": model.preprocess.s21.reference.bundle_path,
|
|
},
|
|
},
|
|
"s11": {
|
|
"calibration": {
|
|
"open": {
|
|
"set_name": model.preprocess.s11.calibration.open.set_name,
|
|
"bundle_path": model.preprocess.s11.calibration.open.bundle_path,
|
|
},
|
|
"short": {
|
|
"set_name": model.preprocess.s11.calibration.short.set_name,
|
|
"bundle_path": model.preprocess.s11.calibration.short.bundle_path,
|
|
},
|
|
"load": {
|
|
"set_name": model.preprocess.s11.calibration.load.set_name,
|
|
"bundle_path": model.preprocess.s11.calibration.load.bundle_path,
|
|
},
|
|
},
|
|
"reference": {
|
|
"set_name": model.preprocess.s11.reference.set_name,
|
|
"bundle_path": model.preprocess.s11.reference.bundle_path,
|
|
},
|
|
},
|
|
"notch": {
|
|
"enabled": model.preprocess.notch.enabled,
|
|
"bands_hz": [[low_hz, high_hz] for low_hz, high_hz in model.preprocess.notch.bands_hz],
|
|
"taper_width_hz": model.preprocess.notch.taper_width_hz,
|
|
"taper_type": model.preprocess.notch.taper_type,
|
|
},
|
|
},
|
|
"gpr": {
|
|
"relative_permittivity": model.gpr.relative_permittivity,
|
|
"tx_geometry": [
|
|
{
|
|
"output_pos": entry.output_pos,
|
|
"x_m": entry.x_m,
|
|
"y_m": entry.y_m,
|
|
"z_m": entry.z_m,
|
|
}
|
|
for entry in model.gpr.tx_geometry
|
|
],
|
|
"rx_geometry": [
|
|
{
|
|
"input_pos": entry.input_pos,
|
|
"x_m": entry.x_m,
|
|
"y_m": entry.y_m,
|
|
"z_m": entry.z_m,
|
|
}
|
|
for entry in model.gpr.rx_geometry
|
|
],
|
|
},
|
|
"rings": {
|
|
"raw": {
|
|
"name": model.rings.raw.name,
|
|
"capacity": model.rings.raw.capacity,
|
|
"slot_size_bytes": model.rings.raw.slot_size_bytes,
|
|
},
|
|
"raw_tap": {
|
|
"name": model.rings.raw_tap.name,
|
|
"capacity": model.rings.raw_tap.capacity,
|
|
"slot_size_bytes": model.rings.raw_tap.slot_size_bytes,
|
|
},
|
|
"preprocessed": {
|
|
"name": model.rings.preprocessed.name,
|
|
"capacity": model.rings.preprocessed.capacity,
|
|
"slot_size_bytes": model.rings.preprocessed.slot_size_bytes,
|
|
},
|
|
"preprocessed_tap": {
|
|
"name": model.rings.preprocessed_tap.name,
|
|
"capacity": model.rings.preprocessed_tap.capacity,
|
|
"slot_size_bytes": model.rings.preprocessed_tap.slot_size_bytes,
|
|
},
|
|
"results": {
|
|
"name": model.rings.results.name,
|
|
"capacity": model.rings.results.capacity,
|
|
"slot_size_bytes": model.rings.results.slot_size_bytes,
|
|
},
|
|
},
|
|
}
|