added remote k209 setup

This commit is contained in:
Ayzen
2026-04-29 16:32:39 +03:00
parent e05c06bcbe
commit 5a70235ef3
30 changed files with 2373 additions and 95 deletions
+33 -3
View File
@@ -7,6 +7,11 @@ The production path is:
K209 --USB-C--> S2VNA --HiSLIP/VISA--> radar_system
```
For complete run-mode instructions, including what runs on the x86_64 S2VNA
computer and what runs on Raspberry Pi, see
[`docs/operation_modes.md`](operation_modes.md). For `run_config.json` fields,
see [`docs/run_config.md`](run_config.md).
There is no direct USB driver for K209 in this project. Do not use mock
transports, socket fallbacks, or `pyvisa-py` for the K209 path. The required
transport dependency is an IVI/Vendor VISA implementation that provides both
@@ -140,6 +145,31 @@ TCPIP0::127.0.0.1::hislip0,4880::INSTR
If S2VNA runs on another machine, replace `127.0.0.1` with that machine's IP
address.
## Remote Raspberry Pi Mode
For Raspberry Pi runs, keep S2VNA and NI-VISA on the x86_64 computer connected
to the K209, and run only the project pipeline/GPIO on the Raspberry Pi.
On the x86_64 computer with S2VNA running:
```bash
cd /path/to/radar_system
.venv/bin/python -m python_app.scripts.k209_remote_server --host 0.0.0.0 --port 50209
```
On the Raspberry Pi, set the K209 config to the server address:
```json
"radar": {
"model": "compact_m_k209",
"remote_host": "192.168.1.10",
"remote_port": 50209,
"driver_mode": "native"
}
```
The Raspberry Pi does not need S2VNA or NI-VISA for this mode.
## Python Smoke Test
Use the project virtual environment:
@@ -263,9 +293,9 @@ are available for ARM64:
TCPIP HiSLIP support.
If those ARM64 dependencies are not available, run S2VNA and the acquisition
process on an Ubuntu x86_64 machine. Raspberry Pi integration should then be
handled at the system/pipeline level, not by replacing the K209 driver transport
with a fallback.
server on an Ubuntu x86_64 machine and use the remote K209 mode documented
above. In that mode, Raspberry Pi runs the project pipeline and GPIO switch
drivers, while the x86_64 machine runs S2VNA and the K209 remote server.
## Expected Hardware Test Result
+213
View File
@@ -0,0 +1,213 @@
# Operation Modes
The active radar backend is selected manually in JSON by `radar.model`.
The GUI does not expose a model selector.
Available models:
```text
librevna
librevna_multi
compact_m_k209
```
Example configs in the repository root:
```text
run_config_librevna.example.json
run_config_librevna_multi.example.json
run_config_compact_m_k209.example.json
run_config_compact_m_k209_local_mock_switches.example.json
```
## Common Commands
Build native binaries:
```bash
cd /path/to/radar_system
make
```
Run the GUI:
```bash
.venv/bin/python -m python_app.gui.main
```
Run a single acquisition producer manually:
```bash
build/bin/sweep_orchestrator --config run_config.json
```
The GUI process supervisor starts the correct producer automatically:
- `librevna` -> `build/bin/sweep_orchestrator`
- `compact_m_k209` -> `build/bin/sweep_orchestrator`
- `librevna_multi` -> `python_app.scripts.multi_device_raw_producer`
## Single LibreVNA
Use this mode when one LibreVNA is connected directly over USB to the machine
running the project.
Config:
```json
"radar": {
"model": "librevna",
"serial": "",
"driver_mode": "native"
}
```
Notes:
- Empty `serial` means use the first compatible LibreVNA found.
- Set `serial` when multiple LibreVNAs are connected.
- `driver_mode: "native"` uses the direct USB LibreVNA driver.
- `driver_mode: "mock"` generates synthetic radar data for UI/development.
- Switch GPIO is controlled by the same machine unless switch `driver_mode` is
set to `mock`.
Typical local check without GPIO:
```bash
cp run_config_librevna.example.json /tmp/librevna_mock_switches.json
# edit both switches to driver_mode="mock" if needed
build/bin/sweep_orchestrator --config /tmp/librevna_mock_switches.json
```
## LibreVNA Multi-Device
Use this mode for one master LibreVNA and two slave LibreVNAs. This mode does
not use physical RF switch GPIO in the acquisition producer. It exposes a fixed
virtual matrix:
```text
inputs: 0..3
outputs: 0..1
combos: 8
```
Config:
```json
"radar": {
"model": "librevna_multi",
"serial": "MASTER_SERIAL",
"driver_mode": "native",
"multi_device": {
"slave_serials": [
"SLAVE_SERIAL_1",
"SLAVE_SERIAL_2"
],
"force_external_reference": true,
"recovery_attempts": 3
}
}
```
Notes:
- Exactly two slave serials are required.
- `force_external_reference` configures the synchronized reference workflow.
- `recovery_attempts` controls reopen/retry attempts after native acquisition
errors.
- The Python producer is selected automatically by the GUI. Manual raw-producer
run:
```bash
.venv/bin/python -m python_app.scripts.multi_device_raw_producer \
--config run_config_librevna_multi.example.json
```
## Compact-M K209 On The Same Computer
Use this for local development on the x86_64 computer that runs S2VNA and has
the K209 connected over USB-C. GPIO can be disabled with mock switches.
1. Start S2VNA and enable HiSLIP on port `4880`.
2. Start the local project K209 server:
```bash
.venv/bin/python -m python_app.scripts.k209_remote_server \
--host 127.0.0.1 \
--port 50209
```
3. In another terminal, smoke-test the server:
```bash
.venv/bin/python -m python_app.scripts.k209_remote_smoke_test \
--host 127.0.0.1 \
--port 50209
```
4. Run one acquisition with mock switches:
```bash
build/bin/sweep_orchestrator \
--config run_config_compact_m_k209_local_mock_switches.example.json
```
This mode is useful on a laptop because it avoids GPIO dependencies.
## Compact-M K209 With Raspberry Pi GPIO
Use this for the real K209 + Raspberry Pi setup:
```text
K209 --USB-C--> x86_64 computer running S2VNA
x86_64 computer --Ethernet--> Raspberry Pi 5
Raspberry Pi 5 --GPIO--> RF switches
```
On the x86_64 computer:
```bash
cd /path/to/radar_system
.venv/bin/python -m python_app.scripts.k209_remote_server \
--host 0.0.0.0 \
--port 50209
```
On the Raspberry Pi, set `radar.remote_host` to the Ethernet IP address of the
x86_64 computer:
```json
"radar": {
"model": "compact_m_k209",
"remote_host": "192.168.1.10",
"remote_port": 50209,
"driver_mode": "native"
}
```
Then run the GUI or producer on the Raspberry Pi:
```bash
.venv/bin/python -m python_app.gui.main
```
For a command-line connection check from the Raspberry Pi:
```bash
.venv/bin/python -m python_app.scripts.k209_remote_smoke_test \
--host 192.168.1.10 \
--port 50209
```
The Raspberry Pi does not need S2VNA or NI-VISA in this remote mode.
## K209 Remote Performance
The remote K209 path keeps one persistent TCP connection open. Configuration
sends sweep settings once and receives the frequency axis once. Each sweep then
sends one command byte and receives only binary `S11` and `S21` `float32`
arrays.
Use wired Ethernet. Wi-Fi works for tests but adds jitter.
+324
View File
@@ -0,0 +1,324 @@
# 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": {},
"sweep": {}
}
```
Fields:
| Field | Meaning |
| --- | --- |
| `model` | `librevna`, `librevna_multi`, or `compact_m_k209`. |
| `serial` | LibreVNA serial. Empty means first device for single LibreVNA. For `librevna_multi`, this is the master serial. |
| `remote_host` | K209 remote server host. Used by `compact_m_k209`; ignored by LibreVNA modes. |
| `remote_port` | K209 remote server TCP port. Default is `50209`. |
| `driver_mode` | `native` for hardware, `mock` for supported synthetic LibreVNA modes. K209 requires `native`. |
| `mock_signal_hz` | Existing LibreVNA mock signal parameter used by C++ mock acquisition. |
| `multi_device` | Extra settings for `librevna_multi`. |
| `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. |
## `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`, 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": {
"mode": "point",
"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 |
| --- | --- |
| `mode` | GPR processing mode. |
| `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"
}
```