# Run Config Reference

`run_config.json` is the stable runtime configuration consumed by the GUI,
Python helpers, and C++ pipeline binaries. The active file is normally
`run_config.json`; root-level `*.example.json` files are templates.

JSON does not support comments. Keep notes in docs, not inside config files.

## Top-Level Sections

```json
{
  "radar": {},
  "switches": {},
  "run": {},
  "preprocess": {},
  "gpr": {},
  "rings": {}
}
```

## `radar`

Selects the radar model and sweep settings.

```json
"radar": {
  "model": "compact_m_k209",
  "serial": "",
  "remote_host": "127.0.0.1",
  "remote_port": 50209,
  "driver_mode": "native",
  "mock_signal_hz": 5000000.0,
  "multi_device": {},
  "kamil_adc": {},
  "laser_control": {},
  "sweep": {}
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `model` | `librevna`, `librevna_multi`, `compact_m_k209`, `sn9000`, or `kamil_adc`. |
| `serial` | LibreVNA serial. Empty means first device for single LibreVNA. For `librevna_multi`, this is the master serial. Unused by `compact_m_k209` and `sn9000`. |
| `remote_host` | SCPI server host. For `compact_m_k209` it is the K209 relay server host; for `sn9000` it is the SNVNA HiSLIP host. Ignored by LibreVNA modes. |
| `remote_port` | SCPI server TCP port. Default `50209` for `compact_m_k209` (relay), `4880` for `sn9000` (SNVNA HiSLIP). |
| `driver_mode` | `native` for hardware, `mock` for supported synthetic LibreVNA modes. K209, SN9000, and Kamil ADC require `native`. |
| `mock_signal_hz` | Existing LibreVNA mock signal parameter used by C++ mock acquisition. |
| `multi_device` | Extra settings for `librevna_multi`. |
| `kamil_adc` | External collector process and TTY settings for `kamil_adc`. |
| `laser_control` | Laser board settings applied before `kamil_adc` collection starts. |
| `sweep` | Frequency, point count, IFBW, and power settings. |

### `radar.sweep`

```json
"sweep": {
  "start_hz": 1000000.0,
  "stop_hz": 6000000000.0,
  "points": 201,
  "if_bandwidth_hz": 50000.0,
  "stimulus_power_dbm": -10.0
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `start_hz` | Sweep start frequency in Hz. |
| `stop_hz` | Sweep stop frequency in Hz. Must be `>= start_hz`. |
| `points` | Number of frequency points. |
| `if_bandwidth_hz` | IF bandwidth in Hz. |
| `stimulus_power_dbm` | Output power in dBm. |

K209 limits reported by the tested device:

```text
frequency_hz: 9000 .. 9000000000
ifbw_hz:      1 .. 300000
power_dbm:    -55 .. +5
points:       2 .. 500001
```

### `radar.multi_device`

Used only when `radar.model == "librevna_multi"`.

```json
"multi_device": {
  "slave_serials": [
    "SLAVE_SERIAL_1",
    "SLAVE_SERIAL_2"
  ],
  "force_external_reference": true,
  "recovery_attempts": 3
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `slave_serials` | Exactly two slave LibreVNA serials. |
| `force_external_reference` | Configure the synchronized external reference path. |
| `recovery_attempts` | Reopen/retry attempts after native multi-device acquisition errors. |

### `radar.kamil_adc`

Used only when `radar.model == "kamil_adc"`.

```json
"kamil_adc": {
  "project_dir": "/home/europa/Documents/kamil_adc",
  "executable_path": "/home/europa/Documents/kamil_adc/kamil_adc_capture",
  "tty_path": "/tmp/ttyADC_data",
  "args": [
    "profile:phase",
    "clock:internal",
    "internal_ref_hz:2000000",
    "mode:diff",
    "channels:2",
    "ch1:2",
    "ch2:3",
    "do1_toggle_per_frame",
    "do1_pair_subtract_avg"
  ],
  "env": {},
  "startup_timeout_s": 5.0,
  "sweep_timeout_s": 5.0,
  "stop_timeout_s": 2.0
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `project_dir` | Working directory for the external ADC collector. Required. |
| `executable_path` | Full path to the Raspberry Pi executable. Required; no filename is assumed. |
| `tty_path` | TTY stream path, for example `/tmp/ttyADC_data`. The producer appends `tty:<tty_path>`. |
| `args` | Explicit collector arguments, excluding any `tty:` argument. |
| `env` | Extra environment variables for the collector process. |
| `startup_timeout_s` | Time allowed for the collector to create a fresh TTY path. |
| `sweep_timeout_s` | Time allowed to receive one full sweep packet. |
| `stop_timeout_s` | Graceful stop timeout before killing the collector process. |

The TTY frame format is strict: packet start is `0x000A 0xFFFF 0xFFFF 0xFFFF`,
then each sweep point is `0x000A step data1 data2`. Steps must arrive as
`1..N`; `N` is derived from the stream when the next packet start arrives.
`radar.sweep.points` is not used by the Kamil ADC producer. `S21` is
`data1 + j*data2`; `S11` is stored as explicit zeros.

### `radar.laser_control`

Used with `kamil_adc` when the laser board must be configured before ADC
collection starts. `laser_control` and `kamil_adc` are one hardware
configuration unit: changing either section requires restarting acquisition.

```json
"laser_control": {
  "enabled": true,
  "port": "/dev/ttyUSB0",
  "mode": "variation",
  "pi_coeff1_p": 2560,
  "pi_coeff1_i": 128,
  "pi_coeff2_p": 2560,
  "pi_coeff2_i": 128,
  "manual": {
    "temp1": 25.0,
    "temp2": 25.0,
    "current1": 30.0,
    "current2": 30.0
  },
  "variation": {
    "variation_type": "CHANGE_CURRENT_LD1",
    "static_temp1": 28.0,
    "static_temp2": 28.9,
    "static_current1": 33.0,
    "static_current2": 35.0,
    "min_value": 33.0,
    "max_value": 60.0,
    "step": 0.05,
    "time_step": 50,
    "delay_time": 10
  }
}
```

`mode="manual"` uses `manual`. `mode="variation"` uses `variation`.
`variation_type` is the enum name from `laser_control`, for example
`CHANGE_CURRENT_LD1` or `CHANGE_TEMPERATURE_LD2`.

## `switches`

Two RF switch sections are used:

```json
"switches": {
  "port1": {},
  "port2": {}
}
```

By convention in the C++ pipeline:

```text
port1 -> output switch
port2 -> input switch
```

Switch fields:

| Field | Meaning |
| --- | --- |
| `name` | Human-readable switch name. |
| `driver_mode` | `native` for GPIO, `mock` to avoid GPIO access. |
| `driver` | `h7992` or `hmc349a`. |
| `radar_port` | Physical radar port mapping, must be unique and either `1` or `2`. |
| `positions` | Number of switch positions. |
| `default_position` | Position selected on open. Zero-based. |
| `gpio_chip` | Linux GPIO chip path, usually `/dev/gpiochip0`. |
| `pin_a` | First GPIO control pin. |
| `pin_b` | Second GPIO control pin for `h7992`. |
| `invert_logic` | Logic inversion for supported switch drivers. |

Use mock switches on a laptop without GPIO:

```json
"driver_mode": "mock"
```

## `run`

Runtime behavior and combo selection.

```json
"run": {
  "settling_ms": 0,
  "idle_sleep_ms": 2,
  "continuous": true,
  "processing_live_config_path": "python_app/runtime/processing_live.json",
  "locator_server": {},
  "combos": [
    {"input": 0, "output": 0}
  ]
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `settling_ms` | Delay after switching before measuring. |
| `idle_sleep_ms` | Sleep between continuous collections. |
| `continuous` | `true` loops until stopped; `false` captures one collection and exits. |
| `processing_live_config_path` | Runtime path used by processing live settings. |
| `locator_server` | Embedded TCP server settings for publishing locator results. |
| `combos` | Zero-based switch combinations to acquire. |

`combos` entries use input/output switch positions:

```json
{"input": 2, "output": 1}
```

For `librevna_multi` and `sn9000`, the model constraints force the canonical
virtual matrix:

```text
input:  0..3
output: 0..1
```

## `run.locator_server`

Settings for the embedded locator result TCP server.

| Field | Meaning |
| --- | --- |
| `device_id` | Device identifier in locator payloads. |
| `protocol_version` | Locator payload protocol version. |
| `host` | Bind host, commonly `0.0.0.0`. |
| `port` | TCP port, commonly `8888`. |
| `max_payload_bytes` | Maximum result payload size. |
| `client_queue_size` | Per-client queue size. |
| `logger_name` | Logger name used by the service. |

## `preprocess`

Names or bundle paths for calibration/reference assets used by preprocessing.

```json
"preprocess": {
  "s21": {
    "calibration": {"set_name": "", "bundle_path": ""},
    "reference": {"set_name": "", "bundle_path": ""}
  },
  "s11": {
    "calibration": {
      "open": {"set_name": "", "bundle_path": ""},
      "short": {"set_name": "", "bundle_path": ""},
      "load": {"set_name": "", "bundle_path": ""}
    },
    "reference": {"set_name": "", "bundle_path": ""}
  },
  "notch": {
    "enabled": true,
    "bands_hz": [],
    "taper_width_hz": 40000000.0,
    "taper_type": "cosine"
  }
}
```

`set_name` selects a stored set for the active radar key. `bundle_path` can
point to an exported bundle. Empty values mean no asset is selected.

`notch.bands_hz` is a list of `[low_hz, high_hz]` ranges. `taper_type` is
`cosine` or `hard`.

## `gpr`

GPR geometry and processing configuration.

```json
"gpr": {
  "relative_permittivity": 1.0,
  "tx_geometry": [
    {"output_pos": 0, "x_m": 0.905}
  ],
  "rx_geometry": [
    {"input_pos": 0, "x_m": -0.18}
  ]
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `relative_permittivity` | Medium relative permittivity used for propagation speed. |
| `tx_geometry` | Transmitter positions keyed by output switch position. |
| `rx_geometry` | Receiver positions keyed by input switch position. |

Geometry positions must match configured switch positions. For example, an
`output_pos` of `1` requires the output switch to have at least 2 positions.

## `rings`

Shared-memory ring endpoints used by native processes.

```json
"rings": {
  "raw": {"name": "/radar_raw", "capacity": 50, "slot_size_bytes": 2097152},
  "raw_tap": {"name": "/radar_raw_tap", "capacity": 50, "slot_size_bytes": 2097152},
  "preprocessed": {"name": "/radar_preprocessed", "capacity": 50, "slot_size_bytes": 2097152},
  "preprocessed_tap": {"name": "/radar_preprocessed_tap", "capacity": 50, "slot_size_bytes": 2097152},
  "results": {"name": "/radar_results", "capacity": 50, "slot_size_bytes": 2097152}
}
```

Fields:

| Field | Meaning |
| --- | --- |
| `name` | POSIX shared-memory object name. |
| `capacity` | Number of slots. |
| `slot_size_bytes` | Maximum serialized payload size per slot. |

Use unique ring names for parallel tests to avoid collisions with a running GUI
session.

## Minimal Model Examples

Single LibreVNA:

```json
"radar": {
  "model": "librevna",
  "serial": "",
  "driver_mode": "native"
}
```

Multi-device LibreVNA:

```json
"radar": {
  "model": "librevna_multi",
  "serial": "MASTER_SERIAL",
  "driver_mode": "native",
  "multi_device": {
    "slave_serials": ["SLAVE_1", "SLAVE_2"],
    "force_external_reference": true,
    "recovery_attempts": 3
  }
}
```

Compact-M K209 via remote server:

```json
"radar": {
  "model": "compact_m_k209",
  "remote_host": "192.168.1.10",
  "remote_port": 50209,
  "driver_mode": "native"
}
```

SN9000 (PLANAR Иридиум) via SNVNA HiSLIP:

```json
"radar": {
  "model": "sn9000",
  "remote_host": "192.168.1.10",
  "remote_port": 4880,
  "driver_mode": "native"
}
```

Kamil ADC:

```json
"radar": {
  "model": "kamil_adc",
  "serial": "kamil_adc",
  "driver_mode": "native",
  "kamil_adc": {
    "project_dir": "/home/europa/Documents/kamil_adc",
    "executable_path": "/home/europa/Documents/kamil_adc/kamil_adc_capture",
    "tty_path": "/tmp/ttyADC_data"
  },
  "laser_control": {
    "enabled": true,
    "port": "/dev/ttyUSB0",
    "mode": "variation"
  }
}
```
