Rework radar system
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "run_config.hpp"
|
||||
|
||||
namespace radar::drivers {
|
||||
|
||||
/**
|
||||
* @brief Runtime settings for the H7992 GPIO switch driver.
|
||||
*/
|
||||
struct H7992MinimalDriverSettings {
|
||||
std::string name{};
|
||||
config::DriverMode mode = config::DriverMode::Native;
|
||||
std::uint32_t positions = 4;
|
||||
std::uint32_t default_position = 0;
|
||||
std::string gpio_chip = "/dev/gpiochip0";
|
||||
std::int32_t pin_a = 17;
|
||||
std::int32_t pin_b = 27;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Minimal H7992 implementation for fast position switching.
|
||||
*
|
||||
* Native mode uses Linux GPIO character-device API (uAPI v2).
|
||||
* Mock mode keeps state in memory without touching hardware.
|
||||
*/
|
||||
class H7992MinimalDriver {
|
||||
public:
|
||||
explicit H7992MinimalDriver(H7992MinimalDriverSettings settings);
|
||||
~H7992MinimalDriver(); // Leaves RF path safe even if close() was skipped.
|
||||
|
||||
void open();
|
||||
void close();
|
||||
void switch_to(std::uint32_t position);
|
||||
|
||||
private:
|
||||
void open_native();
|
||||
void close_native();
|
||||
void switch_native(std::uint32_t position);
|
||||
|
||||
H7992MinimalDriverSettings settings_{};
|
||||
bool is_open_ = false;
|
||||
std::uint32_t current_position_ = 0;
|
||||
int chip_fd_ = -1;
|
||||
int line_fd_ = -1;
|
||||
};
|
||||
|
||||
} // namespace radar::drivers
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "h7992_minimal_driver.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <stdexcept>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
namespace radar::drivers {
|
||||
namespace {
|
||||
|
||||
// A transient ioctl failure (e.g. EINTR/EAGAIN/EBUSY under load) should not be
|
||||
// immediately fatal; retry a small number of times before giving up.
|
||||
constexpr int kSwitchIoctlRetries = 3;
|
||||
|
||||
// Position mapping for H7992 control pins:
|
||||
// position -> (A, B)
|
||||
constexpr std::array<std::array<std::uint8_t, 2>, 4> kPositionToAB = {
|
||||
std::array<std::uint8_t, 2>{0, 0},
|
||||
std::array<std::uint8_t, 2>{0, 1},
|
||||
std::array<std::uint8_t, 2>{1, 0},
|
||||
std::array<std::uint8_t, 2>{1, 1},
|
||||
};
|
||||
|
||||
void validate_open_settings(const H7992MinimalDriverSettings& settings) {
|
||||
if (settings.positions == 0U || settings.positions > 4U) {
|
||||
throw std::runtime_error("H7992 switch positions must be in range [1, 4] for " + settings.name);
|
||||
}
|
||||
if (settings.default_position >= settings.positions) {
|
||||
throw std::runtime_error("Switch default_position out of range for " + settings.name);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto make_line_values(std::uint8_t pin_a_state, std::uint8_t pin_b_state) -> gpio_v2_line_values {
|
||||
gpio_v2_line_values values{};
|
||||
values.mask = (1ULL << 0U) | (1ULL << 1U);
|
||||
values.bits =
|
||||
(static_cast<std::uint64_t>(pin_a_state) << 0U) | (static_cast<std::uint64_t>(pin_b_state) << 1U);
|
||||
return values;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
H7992MinimalDriver::H7992MinimalDriver(H7992MinimalDriverSettings settings) : settings_(std::move(settings)) {}
|
||||
|
||||
H7992MinimalDriver::~H7992MinimalDriver() {
|
||||
// Safety net: if the owner never called close() (e.g. on crash/unwind),
|
||||
// still drive the RF path to a known-safe state and release the GPIO lines.
|
||||
try {
|
||||
close();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void H7992MinimalDriver::open() {
|
||||
if (is_open_) {
|
||||
return;
|
||||
}
|
||||
validate_open_settings(settings_);
|
||||
|
||||
if (settings_.mode == config::DriverMode::Native) {
|
||||
open_native();
|
||||
}
|
||||
|
||||
is_open_ = true;
|
||||
switch_to(settings_.default_position);
|
||||
}
|
||||
|
||||
void H7992MinimalDriver::close() {
|
||||
if (!is_open_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings_.mode == config::DriverMode::Native) {
|
||||
close_native();
|
||||
}
|
||||
|
||||
is_open_ = false;
|
||||
}
|
||||
|
||||
void H7992MinimalDriver::switch_to(std::uint32_t position) {
|
||||
if (!is_open_) {
|
||||
throw std::runtime_error("Switch driver is not open for " + settings_.name);
|
||||
}
|
||||
if (position >= settings_.positions) {
|
||||
throw std::runtime_error("Switch position out of range for " + settings_.name);
|
||||
}
|
||||
|
||||
switch (settings_.mode) {
|
||||
case config::DriverMode::Mock:
|
||||
break;
|
||||
case config::DriverMode::Native:
|
||||
switch_native(position);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Unsupported switch driver mode for " + settings_.name);
|
||||
}
|
||||
|
||||
current_position_ = position;
|
||||
}
|
||||
|
||||
void H7992MinimalDriver::open_native() {
|
||||
if (settings_.gpio_chip.empty()) {
|
||||
throw std::runtime_error("gpio_chip is empty for native switch " + settings_.name);
|
||||
}
|
||||
if (settings_.pin_a < 0 || settings_.pin_b < 0 || settings_.pin_a == settings_.pin_b) {
|
||||
throw std::runtime_error("Invalid pin_a/pin_b for native switch " + settings_.name);
|
||||
}
|
||||
|
||||
chip_fd_ = ::open(settings_.gpio_chip.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (chip_fd_ < 0) {
|
||||
throw std::runtime_error(
|
||||
"Failed to open gpio chip '" + settings_.gpio_chip + "' for " + settings_.name + ": " + std::strerror(errno)
|
||||
);
|
||||
}
|
||||
|
||||
gpio_v2_line_request request{};
|
||||
// The first requested line maps to logical bit 0, the second to bit 1.
|
||||
request.offsets[0] = static_cast<std::uint32_t>(settings_.pin_a);
|
||||
request.offsets[1] = static_cast<std::uint32_t>(settings_.pin_b);
|
||||
request.num_lines = 2;
|
||||
request.config.flags = GPIO_V2_LINE_FLAG_OUTPUT;
|
||||
std::snprintf(request.consumer, sizeof(request.consumer), "radar_%s", settings_.name.c_str());
|
||||
|
||||
if (::ioctl(chip_fd_, GPIO_V2_GET_LINE_IOCTL, &request) != 0) {
|
||||
const auto error = std::strerror(errno);
|
||||
::close(chip_fd_);
|
||||
chip_fd_ = -1;
|
||||
throw std::runtime_error("Failed to request GPIO lines for " + settings_.name + ": " + error);
|
||||
}
|
||||
|
||||
line_fd_ = request.fd;
|
||||
if (line_fd_ < 0) {
|
||||
::close(chip_fd_);
|
||||
chip_fd_ = -1;
|
||||
throw std::runtime_error("GPIO line request returned invalid fd for " + settings_.name);
|
||||
}
|
||||
}
|
||||
|
||||
void H7992MinimalDriver::close_native() {
|
||||
// Leave the RF path in its known-safe default position before releasing the
|
||||
// lines, so shutdown/crash never strands the switch in an arbitrary state.
|
||||
if (line_fd_ >= 0 && settings_.default_position < settings_.positions) {
|
||||
const auto [pin_a_state, pin_b_state] = kPositionToAB[settings_.default_position];
|
||||
auto values = make_line_values(pin_a_state, pin_b_state);
|
||||
// Best-effort: do not throw out of the teardown path.
|
||||
::ioctl(line_fd_, GPIO_V2_LINE_SET_VALUES_IOCTL, &values);
|
||||
}
|
||||
|
||||
if (line_fd_ >= 0) {
|
||||
::close(line_fd_);
|
||||
line_fd_ = -1;
|
||||
}
|
||||
if (chip_fd_ >= 0) {
|
||||
::close(chip_fd_);
|
||||
chip_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void H7992MinimalDriver::switch_native(std::uint32_t position) {
|
||||
if (line_fd_ < 0) {
|
||||
throw std::runtime_error("Native GPIO line fd is not open for " + settings_.name);
|
||||
}
|
||||
|
||||
const auto [pin_a_state, pin_b_state] = kPositionToAB[position];
|
||||
auto values = make_line_values(pin_a_state, pin_b_state);
|
||||
|
||||
// Retry transient ioctl failures so a momentary hiccup is recoverable
|
||||
// instead of aborting the whole sweep; only the final attempt is fatal.
|
||||
int last_errno = 0;
|
||||
for (int attempt = 0; attempt < kSwitchIoctlRetries; ++attempt) {
|
||||
if (::ioctl(line_fd_, GPIO_V2_LINE_SET_VALUES_IOCTL, &values) == 0) {
|
||||
return;
|
||||
}
|
||||
last_errno = errno;
|
||||
}
|
||||
throw std::runtime_error("Failed to switch GPIO state for " + settings_.name + ": " + std::strerror(last_errno));
|
||||
}
|
||||
|
||||
} // namespace radar::drivers
|
||||
Reference in New Issue
Block a user