added remote k209 setup
This commit is contained in:
@@ -31,6 +31,8 @@ struct RadarConfig {
|
||||
// Radar device identity and runtime mode.
|
||||
std::string model = "librevna";
|
||||
std::string serial{};
|
||||
std::string remote_host = "127.0.0.1";
|
||||
std::uint32_t remote_port = 50209;
|
||||
DriverMode driver_mode = DriverMode::Mock;
|
||||
float mock_signal_hz = 5'000'000.0F;
|
||||
RadarSweepSettings sweep{};
|
||||
|
||||
@@ -413,6 +413,14 @@ auto load_run_config(const std::string& path) -> RunConfig {
|
||||
const auto* radar_obj = as_object(required_field(*root_obj, "radar"), "radar");
|
||||
config.radar.model = optional_string(*radar_obj, "model", "librevna");
|
||||
config.radar.serial = optional_string(*radar_obj, "serial", "");
|
||||
config.radar.remote_host = optional_string(*radar_obj, "remote_host", "127.0.0.1");
|
||||
config.radar.remote_port = optional_u32(*radar_obj, "remote_port", 50209);
|
||||
if (config.radar.remote_host.empty()) {
|
||||
throw std::runtime_error("radar.remote_host must not be empty");
|
||||
}
|
||||
if (config.radar.remote_port == 0U || config.radar.remote_port > 65535U) {
|
||||
throw std::runtime_error("radar.remote_port must be in 1..65535");
|
||||
}
|
||||
config.radar.driver_mode = parse_driver_mode(optional_string(*radar_obj, "driver_mode", "mock"));
|
||||
config.radar.mock_signal_hz = optional_f32(*radar_obj, "mock_signal_hz", 5'000'000.0F);
|
||||
|
||||
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
#include "../remote_compact_m_k209_driver.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace radar::drivers {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint8_t kCommandConfigure = static_cast<std::uint8_t>('C');
|
||||
constexpr std::uint8_t kCommandAcquire = static_cast<std::uint8_t>('A');
|
||||
constexpr std::uint8_t kStatusOk = static_cast<std::uint8_t>('O');
|
||||
constexpr std::uint8_t kStatusError = static_cast<std::uint8_t>('E');
|
||||
|
||||
[[nodiscard]] auto system_error_message(const std::string& context) -> std::string {
|
||||
return context + ": " + std::strerror(errno);
|
||||
}
|
||||
|
||||
void append_u32_be(std::vector<std::uint8_t>& buffer, std::uint32_t value) {
|
||||
buffer.push_back(static_cast<std::uint8_t>((value >> 24U) & 0xFFU));
|
||||
buffer.push_back(static_cast<std::uint8_t>((value >> 16U) & 0xFFU));
|
||||
buffer.push_back(static_cast<std::uint8_t>((value >> 8U) & 0xFFU));
|
||||
buffer.push_back(static_cast<std::uint8_t>(value & 0xFFU));
|
||||
}
|
||||
|
||||
void append_u64_be(std::vector<std::uint8_t>& buffer, std::uint64_t value) {
|
||||
for (int shift = 56; shift >= 0; shift -= 8) {
|
||||
buffer.push_back(static_cast<std::uint8_t>((value >> static_cast<unsigned>(shift)) & 0xFFU));
|
||||
}
|
||||
}
|
||||
|
||||
void append_double_be(std::vector<std::uint8_t>& buffer, double value) {
|
||||
std::uint64_t bits = 0;
|
||||
static_assert(sizeof(bits) == sizeof(value));
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
append_u64_be(buffer, bits);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto parse_u32_be(const std::uint8_t* data) -> std::uint32_t {
|
||||
return (static_cast<std::uint32_t>(data[0]) << 24U) |
|
||||
(static_cast<std::uint32_t>(data[1]) << 16U) |
|
||||
(static_cast<std::uint32_t>(data[2]) << 8U) |
|
||||
static_cast<std::uint32_t>(data[3]);
|
||||
}
|
||||
|
||||
void send_all(int socket_fd, const void* data, std::size_t size) {
|
||||
const auto* cursor = static_cast<const std::uint8_t*>(data);
|
||||
auto remaining = size;
|
||||
while (remaining > 0U) {
|
||||
const auto sent = ::send(socket_fd, cursor, remaining, MSG_NOSIGNAL);
|
||||
if (sent < 0) {
|
||||
throw std::runtime_error(system_error_message("send to K209 remote server failed"));
|
||||
}
|
||||
if (sent == 0) {
|
||||
throw std::runtime_error("send to K209 remote server returned zero bytes");
|
||||
}
|
||||
cursor += sent;
|
||||
remaining -= static_cast<std::size_t>(sent);
|
||||
}
|
||||
}
|
||||
|
||||
void recv_exact(int socket_fd, void* data, std::size_t size) {
|
||||
auto* cursor = static_cast<std::uint8_t*>(data);
|
||||
auto remaining = size;
|
||||
while (remaining > 0U) {
|
||||
const auto received = ::recv(socket_fd, cursor, remaining, 0);
|
||||
if (received < 0) {
|
||||
throw std::runtime_error(system_error_message("read from K209 remote server failed"));
|
||||
}
|
||||
if (received == 0) {
|
||||
throw std::runtime_error("K209 remote server closed the connection");
|
||||
}
|
||||
cursor += received;
|
||||
remaining -= static_cast<std::size_t>(received);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto recv_u32(int socket_fd) -> std::uint32_t {
|
||||
std::uint8_t bytes[4]{};
|
||||
recv_exact(socket_fd, bytes, sizeof(bytes));
|
||||
return parse_u32_be(bytes);
|
||||
}
|
||||
|
||||
void read_status(int socket_fd) {
|
||||
std::uint8_t status = 0;
|
||||
recv_exact(socket_fd, &status, 1);
|
||||
if (status == kStatusOk) {
|
||||
return;
|
||||
}
|
||||
if (status == kStatusError) {
|
||||
const auto message_size = recv_u32(socket_fd);
|
||||
std::string message(message_size, '\0');
|
||||
if (message_size > 0U) {
|
||||
recv_exact(socket_fd, message.data(), message.size());
|
||||
}
|
||||
throw std::runtime_error("K209 remote server error: " + message);
|
||||
}
|
||||
throw std::runtime_error("K209 remote server returned invalid status byte");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto recv_float32_array(int socket_fd, std::uint32_t expected_values, const std::string& context)
|
||||
-> std::vector<float> {
|
||||
const auto payload_size = recv_u32(socket_fd);
|
||||
const auto expected_size = expected_values * static_cast<std::uint32_t>(sizeof(float));
|
||||
if (payload_size != expected_size) {
|
||||
throw std::runtime_error(
|
||||
"K209 remote " + context + " payload has " + std::to_string(payload_size) +
|
||||
" bytes, expected " + std::to_string(expected_size)
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<float> values(expected_values);
|
||||
if (payload_size > 0U) {
|
||||
recv_exact(socket_fd, values.data(), payload_size);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto connect_socket(const std::string& host, std::uint32_t port, std::uint32_t timeout_ms) -> int {
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
|
||||
addrinfo* result = nullptr;
|
||||
const auto port_text = std::to_string(port);
|
||||
const auto rc = ::getaddrinfo(host.c_str(), port_text.c_str(), &hints, &result);
|
||||
if (rc != 0) {
|
||||
throw std::runtime_error("getaddrinfo failed for K209 remote host " + host + ": " + ::gai_strerror(rc));
|
||||
}
|
||||
|
||||
int connected_fd = -1;
|
||||
std::string last_error;
|
||||
for (auto* item = result; item != nullptr; item = item->ai_next) {
|
||||
const int fd = ::socket(item->ai_family, item->ai_socktype, item->ai_protocol);
|
||||
if (fd < 0) {
|
||||
last_error = system_error_message("socket");
|
||||
continue;
|
||||
}
|
||||
|
||||
const int one = 1;
|
||||
static_cast<void>(::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)));
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = static_cast<long>(timeout_ms / 1000U);
|
||||
timeout.tv_usec = static_cast<long>((timeout_ms % 1000U) * 1000U);
|
||||
static_cast<void>(::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)));
|
||||
static_cast<void>(::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)));
|
||||
|
||||
if (::connect(fd, item->ai_addr, item->ai_addrlen) == 0) {
|
||||
connected_fd = fd;
|
||||
break;
|
||||
}
|
||||
last_error = system_error_message("connect");
|
||||
::close(fd);
|
||||
}
|
||||
::freeaddrinfo(result);
|
||||
|
||||
if (connected_fd < 0) {
|
||||
throw std::runtime_error("Failed to connect to K209 remote server " + host + ":" + port_text + ": " + last_error);
|
||||
}
|
||||
return connected_fd;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto complex_from_interleaved(const std::vector<float>& values, std::uint32_t points, const std::string& context)
|
||||
-> std::vector<ipc::Complex32> {
|
||||
if (values.size() != static_cast<std::size_t>(points) * 2U) {
|
||||
throw std::runtime_error("K209 remote " + context + " returned unexpected scalar count");
|
||||
}
|
||||
|
||||
std::vector<ipc::Complex32> output(points);
|
||||
for (std::uint32_t index = 0; index < points; ++index) {
|
||||
output[index] = ipc::Complex32{
|
||||
.re = values[static_cast<std::size_t>(index) * 2U],
|
||||
.im = values[static_cast<std::size_t>(index) * 2U + 1U],
|
||||
};
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RemoteCompactMK209Driver::RemoteCompactMK209Driver(RemoteCompactMK209DriverSettings settings)
|
||||
: settings_(std::move(settings)) {
|
||||
if (settings_.host.empty()) {
|
||||
throw std::runtime_error("K209 remote host must not be empty");
|
||||
}
|
||||
if (settings_.port == 0U || settings_.port > 65535U) {
|
||||
throw std::runtime_error("K209 remote port must be in 1..65535");
|
||||
}
|
||||
if (settings_.timeout_ms == 0U) {
|
||||
throw std::runtime_error("K209 remote timeout_ms must be > 0");
|
||||
}
|
||||
}
|
||||
|
||||
RemoteCompactMK209Driver::~RemoteCompactMK209Driver() {
|
||||
try {
|
||||
close();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteCompactMK209Driver::open() {
|
||||
if (socket_fd_ >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket_fd_ = connect_socket(settings_.host, settings_.port, settings_.timeout_ms);
|
||||
|
||||
try {
|
||||
std::vector<std::uint8_t> payload{};
|
||||
payload.reserve(1U + 8U + 8U + 4U + 8U + 8U);
|
||||
payload.push_back(kCommandConfigure);
|
||||
append_double_be(payload, static_cast<double>(settings_.sweep.start_hz));
|
||||
append_double_be(payload, static_cast<double>(settings_.sweep.stop_hz));
|
||||
append_u32_be(payload, settings_.sweep.points);
|
||||
append_double_be(payload, static_cast<double>(settings_.sweep.if_bandwidth_hz));
|
||||
append_double_be(payload, static_cast<double>(settings_.sweep.power_dbm));
|
||||
send_all(socket_fd_, payload.data(), payload.size());
|
||||
|
||||
read_status(socket_fd_);
|
||||
const auto points = recv_u32(socket_fd_);
|
||||
if (points != settings_.sweep.points) {
|
||||
throw std::runtime_error("K209 remote server returned unexpected configured point count");
|
||||
}
|
||||
frequency_hz_ = recv_float32_array(socket_fd_, points, "frequency");
|
||||
} catch (...) {
|
||||
close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteCompactMK209Driver::close() {
|
||||
if (socket_fd_ >= 0) {
|
||||
::close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
frequency_hz_.clear();
|
||||
}
|
||||
|
||||
auto RemoteCompactMK209Driver::acquire_sweep() -> SweepTrace {
|
||||
if (socket_fd_ < 0) {
|
||||
throw std::runtime_error("K209 remote driver is not open");
|
||||
}
|
||||
const std::uint8_t command = kCommandAcquire;
|
||||
send_all(socket_fd_, &command, 1);
|
||||
read_status(socket_fd_);
|
||||
|
||||
const auto points = recv_u32(socket_fd_);
|
||||
if (points != settings_.sweep.points) {
|
||||
throw std::runtime_error("K209 remote server returned unexpected sweep point count");
|
||||
}
|
||||
const auto s11_values = recv_float32_array(socket_fd_, points * 2U, "S11");
|
||||
const auto s21_values = recv_float32_array(socket_fd_, points * 2U, "S21");
|
||||
|
||||
return SweepTrace{
|
||||
.frequency_hz = frequency_hz_,
|
||||
.s11 = complex_from_interleaved(s11_values, points, "S11"),
|
||||
.s21 = complex_from_interleaved(s21_values, points, "S21"),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace radar::drivers
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "radar_driver.hpp"
|
||||
#include "run_config.hpp"
|
||||
|
||||
namespace radar::drivers {
|
||||
|
||||
struct RemoteCompactMK209DriverSettings {
|
||||
std::string host = "127.0.0.1";
|
||||
std::uint32_t port = 50209;
|
||||
config::RadarSweepSettings sweep{};
|
||||
std::uint32_t timeout_ms = 20'000;
|
||||
};
|
||||
|
||||
class RemoteCompactMK209Driver final : public RadarDriver {
|
||||
public:
|
||||
explicit RemoteCompactMK209Driver(RemoteCompactMK209DriverSettings settings);
|
||||
~RemoteCompactMK209Driver() override;
|
||||
|
||||
void open() override;
|
||||
void close() override;
|
||||
[[nodiscard]] auto acquire_sweep() -> SweepTrace override;
|
||||
|
||||
private:
|
||||
RemoteCompactMK209DriverSettings settings_{};
|
||||
int socket_fd_ = -1;
|
||||
std::vector<float> frequency_hz_{};
|
||||
};
|
||||
|
||||
} // namespace radar::drivers
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "h7992_minimal_driver.hpp"
|
||||
#include "hmc349a_minimal_driver.hpp"
|
||||
#include "librevna_minimal_driver.hpp"
|
||||
#include "radar_driver.hpp"
|
||||
#include "remote_compact_m_k209_driver.hpp"
|
||||
#include "run_config.hpp"
|
||||
#include "shm_ring.hpp"
|
||||
#include "sweep_orchestrator.hpp"
|
||||
@@ -16,6 +18,9 @@
|
||||
namespace {
|
||||
|
||||
constexpr const char* kDefaultConfigPath = "run_config.json";
|
||||
constexpr const char* kLibreVnaModel = "librevna";
|
||||
constexpr const char* kLibreVnaMultiModel = "librevna_multi";
|
||||
constexpr const char* kCompactMK209Model = "compact_m_k209";
|
||||
std::atomic<bool> g_stop_requested{false};
|
||||
|
||||
void signal_handler(int /*signal*/) {
|
||||
@@ -40,13 +45,38 @@ void install_signal_handlers() {
|
||||
return config_path;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto make_radar_driver(const radar::config::RadarConfig& config) -> radar::drivers::LibreVnaMinimalDriver {
|
||||
return radar::drivers::LibreVnaMinimalDriver({
|
||||
.mode = config.driver_mode,
|
||||
.serial = config.serial,
|
||||
.sweep = config.sweep,
|
||||
.mock_signal_hz = config.mock_signal_hz,
|
||||
});
|
||||
[[nodiscard]] auto make_radar_driver(const radar::config::RadarConfig& config)
|
||||
-> std::unique_ptr<radar::drivers::RadarDriver> {
|
||||
if (config.model == kLibreVnaModel || config.model.empty()) {
|
||||
return std::make_unique<radar::drivers::LibreVnaMinimalDriver>(
|
||||
radar::drivers::LibreVnaMinimalDriverSettings{
|
||||
.mode = config.driver_mode,
|
||||
.serial = config.serial,
|
||||
.sweep = config.sweep,
|
||||
.mock_signal_hz = config.mock_signal_hz,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (config.model == kCompactMK209Model) {
|
||||
if (config.driver_mode != radar::config::DriverMode::Native) {
|
||||
throw std::runtime_error("compact_m_k209 requires radar.driver_mode='native'");
|
||||
}
|
||||
return std::make_unique<radar::drivers::RemoteCompactMK209Driver>(
|
||||
radar::drivers::RemoteCompactMK209DriverSettings{
|
||||
.host = config.remote_host,
|
||||
.port = config.remote_port,
|
||||
.sweep = config.sweep,
|
||||
.timeout_ms = 20'000,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (config.model == kLibreVnaMultiModel) {
|
||||
throw std::runtime_error("librevna_multi is handled by python_app.scripts.multi_device_raw_producer");
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unsupported radar.model for sweep_orchestrator: " + config.model);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto make_h7992_driver(const radar::config::SwitchConfig& config)
|
||||
@@ -120,7 +150,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
radar::acq::SweepOrchestrator orchestrator(
|
||||
config,
|
||||
radar_driver,
|
||||
*radar_driver,
|
||||
*input_switch,
|
||||
*output_switch,
|
||||
raw_ring,
|
||||
|
||||
Reference in New Issue
Block a user