Rework radar system

This commit is contained in:
Ayzen
2026-10-06 12:32:26 +03:00
parent 552cdc688a
commit 5bb86b7165
299 changed files with 13537 additions and 27763 deletions
@@ -2,11 +2,11 @@
Standalone acquisition binary for the **L-Card E-502** ADC. It captures the
dual-channel main + reference IQ stream and publishes it as 8-byte framed packets
over a PTY, which the Python `kamil_adc` producer
(`python_app/hardware_full/kamil_adc`) reads and processes.
over a PTY. `KamilAdcService` in `python_app/hardware_full/kamil_adc` reads and
processes that stream for `python_app.scripts.kamil_adc_raw_producer`.
This is the data *source* for `radar.model == "kamil_adc"`; it is the Kamil ADC
counterpart to the C++ `sweep_orchestrator` used by LibreVNA devices.
This is the data *source* for `radar.model == "kamil_adc"`, paired with the Python
`kamil_adc_raw_producer`. LibreVNA acquisition uses Python drivers.
## Layout
@@ -16,15 +16,13 @@ counterpart to the C++ `sweep_orchestrator` used by LibreVNA devices.
| `include/` | Headers for the writers plus `switch_sequencer.h`. |
| `vendor/lcard/` | L-Card X502/E502 SDK headers (`x502api.h`, `e502api.h`, `lcard_pstdint.h`), vendored verbatim so the collector builds with no external include path. |
The sources started as a verbatim import of the original standalone tool, but are
**no longer identical to it**: the collector now drives the RF switches itself
(switch-aware mode, `SwitchSequencer`), stepping to the next combination inside
the hardware idle gap between sweeps and flagging a sweep *dirty* when a readout
backlog means the toggle could not have landed in that gap. It therefore reuses
the orchestrator's switch drivers and the shared `run_config` loader, which is why
its build has its own include set and its own `build/kamil/` object tree.
The wire format towards the Python producer is unchanged.
In switch-aware mode, `SwitchSequencer` drives the RF switches, stepping to the
next combination inside the hardware idle gap between sweeps and flagging a
sweep *dirty* when a readout backlog means the toggle could not have landed in
that gap. The Python reader
drops dirty sweeps; the collector repeats that combination without another
switch transition. The collector uses the common H7992 driver and `run_config`
loader, with its own include set and `build/kamil/` object tree.
## Build
@@ -33,16 +31,25 @@ make kamil_adc_collector # -> build/bin/kamil_adc_collector
```
It is **not** part of `make all`; `start.sh` builds it automatically when the
active config selects `radar.model == "kamil_adc"`. The build is self-contained:
it needs only the vendored headers and links `-ldl -lutil -lpthread`.
active config selects `radar.model == "kamil_adc"`, unless `--skip-build` is set.
The Linux build uses the repository's common sources and vendored headers and
links with `-pthread -ldl -lutil`; no installed L-Card SDK is needed to build it.
## Runtime
The collector loads the proprietary `libx502api.so` / `libe502api.so` at run time
via `dlopen`, so those libraries must be on `LD_LIBRARY_PATH`. The Kamil ADC
service prepends `~/.local/lib` (their default install location) unless the config
pins `LD_LIBRARY_PATH` in `radar.kamil_adc.env`.
via `dlopen`, so those libraries must be discoverable by the dynamic loader.
The Kamil ADC service prepends `~/.local/lib` (their default install location)
unless the config pins `LD_LIBRARY_PATH` in `radar.kamil_adc.env`.
The command-line arguments (capture profile, clocking, the `do8_freq_ref`
reference overlay, etc.) are supplied by `radar.kamil_adc.args` in the run config;
the service appends the generated `tty:<path>` argument.
the service appends `tty:<path>` from `radar.kamil_adc.tty_path`.
When both RF switches use `driver_mode: "native"`, the producer also passes
`config:<path>` to the collector. After each sweep boundary, the collector emits
an 8-byte combo tag `[0x00C0, input_pos, output_pos, dirty]` (four little-endian
16-bit words). The producer accepts only clean sweeps for the requested combo.
Otherwise, the producer selects and settles the switches itself; the collector
runs without `config:<path>` and emits no combo tags. Calibration captures also
use this Python-controlled path.
@@ -6,7 +6,7 @@
#include <vector>
#include "shared_types.hpp"
#include "switch_driver.hpp"
#include "h7992_minimal_driver.hpp"
namespace radar::kamil {
@@ -47,8 +47,8 @@ class SwitchSequencer {
SwitchSequencer(
Settings settings,
std::unique_ptr<drivers::SwitchDriver> input_switch,
std::unique_ptr<drivers::SwitchDriver> output_switch
std::unique_ptr<drivers::H7992MinimalDriver> input_switch,
std::unique_ptr<drivers::H7992MinimalDriver> output_switch
)
: settings_(std::move(settings)),
input_switch_(std::move(input_switch)),
@@ -130,15 +130,14 @@ class SwitchSequencer {
private:
void apply(const radar::ipc::ComboKey& combo) {
// Output first, then input — same order the sweep orchestrator uses, so
// both acquisition paths drive an identical hardware sequence.
// Set the transmitter before the receiver.
output_switch_->switch_to(combo.output_pos);
input_switch_->switch_to(combo.input_pos);
}
Settings settings_;
std::unique_ptr<drivers::SwitchDriver> input_switch_;
std::unique_ptr<drivers::SwitchDriver> output_switch_;
std::unique_ptr<drivers::H7992MinimalDriver> input_switch_;
std::unique_ptr<drivers::H7992MinimalDriver> output_switch_;
std::size_t index_ = 0;
radar::ipc::ComboKey applied_{};
bool upcoming_dirty_ = false;
@@ -20,7 +20,6 @@
#include "switch_sequencer.h"
#include "run_config.hpp"
#include "h7992_minimal_driver.hpp"
#include "hmc349a_minimal_driver.hpp"
#include <algorithm>
#include <array>
@@ -1830,21 +1829,8 @@ void print_device_info(const t_x502_info& info) {
}
// Build one switch driver (native GPIO or mock) from a parsed switch config,
// reusing the exact driver implementations the sweep orchestrator uses so both
// acquisition paths drive identical hardware.
std::unique_ptr<radar::drivers::SwitchDriver> make_switch_driver(const radar::config::SwitchConfig& sc) {
if (sc.driver_kind == radar::config::SwitchDriverKind::HMC349A) {
return std::make_unique<radar::drivers::HMC349AMinimalDriver>(
radar::drivers::HMC349AMinimalDriverSettings{
.name = sc.name,
.mode = sc.driver_mode,
.positions = sc.positions,
.default_position = sc.default_position,
.gpio_chip = sc.gpio_chip,
.pin_a = sc.pin_a,
.invert_logic = sc.invert_logic,
});
}
// using the shared H7992 GPIO implementation.
std::unique_ptr<radar::drivers::H7992MinimalDriver> make_switch_driver(const radar::config::SwitchConfig& sc) {
return std::make_unique<radar::drivers::H7992MinimalDriver>(
radar::drivers::H7992MinimalDriverSettings{
.name = sc.name,