init commit

This commit is contained in:
Ayzen
2026-03-05 14:42:33 +03:00
commit fd4618b20d
964 changed files with 325114 additions and 0 deletions
@@ -0,0 +1,48 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "processor_interface.hpp"
#include "processing_live_config.hpp"
#include "run_config.hpp"
#include "shm_ring.hpp"
namespace radar::processing {
using ProcessorRegistry = std::unordered_map<std::string, std::unique_ptr<ProcessorInterface>>;
class DataProcessor {
public:
DataProcessor(
const config::RunConfig& config,
ipc::ShmRing& preprocessed_ring,
ipc::ShmRing& results_ring,
ProcessorRegistry processors
);
void run(const std::atomic<bool>& stop_requested);
private:
[[nodiscard]] auto process_collection(
const ipc::PreprocessedCollection& preprocessed,
ProcessorInterface& processor,
const ProcessingLiveConfig& live_config
) -> ipc::ResultCollection;
[[nodiscard]] auto resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface&;
const config::RunConfig& config_;
ipc::ShmRing& preprocessed_ring_;
ipc::ShmRing& results_ring_;
ProcessorRegistry processors_{};
std::string default_processor_mode_{};
ProcessingLiveConfigLoader live_config_loader_;
};
[[nodiscard]] auto create_default_processors() -> ProcessorRegistry;
} // namespace radar::processing
@@ -0,0 +1,49 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <string>
namespace radar::processing {
enum class HistoryCommand {
None,
RemoveLast,
ClearAll,
};
struct ProcessingLiveConfig {
std::string processor_mode = "pass_through";
float gain_db = 0.0F;
float phase_deg = 0.0F;
std::string bscan_axis = "abs";
float bscan_cut_m = 0.824F;
float bscan_max_depth_m = 1.0F;
float bscan_gain = 1.0F;
float bscan_start_freq_mhz = 100.0F;
float bscan_stop_freq_mhz = 8800.0F;
std::uint64_t history_command_seq = 0;
HistoryCommand history_command = HistoryCommand::None;
};
class ProcessingLiveConfigLoader {
public:
explicit ProcessingLiveConfigLoader(std::string path);
[[nodiscard]] auto current() const -> ProcessingLiveConfig;
[[nodiscard]] auto refresh_if_needed() -> ProcessingLiveConfig;
[[nodiscard]] auto revision() const -> std::uint64_t;
private:
[[nodiscard]] auto read_from_file() const -> ProcessingLiveConfig;
std::string path_{};
ProcessingLiveConfig current_{};
std::filesystem::file_time_type last_write_time_{};
bool has_last_write_time_ = false;
std::uint64_t revision_ = 0;
std::chrono::steady_clock::time_point next_check_at_{};
};
} // namespace radar::processing
@@ -0,0 +1,156 @@
#include "data_processor.hpp"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <deque>
#include <stdexcept>
#include <thread>
#include <utility>
#include "bscan_processor.hpp"
#include "passthrough_processor.hpp"
namespace radar::processing {
namespace {
constexpr const char* kDefaultProcessorMode = "pass_through";
constexpr std::size_t kBscanReplayWindow = 50U;
[[nodiscard]] auto replay_history_limit(const config::RunConfig& config) -> std::size_t {
const auto preprocessed_capacity = static_cast<std::size_t>(std::max<std::uint32_t>(1U, config.rings.preprocessed.capacity));
const auto results_capacity = static_cast<std::size_t>(std::max<std::uint32_t>(1U, config.rings.results.capacity));
return std::min({preprocessed_capacity, results_capacity, kBscanReplayWindow});
}
void publish_result_collection(const ipc::ResultCollection& collection, ipc::ShmRing& results_ring) {
const auto out_bytes = ipc::serialize_result_collection(collection);
if (!results_ring.push(out_bytes)) {
throw std::runtime_error("Results ring slot is too small for serialized collection");
}
}
} // namespace
DataProcessor::DataProcessor(
const config::RunConfig& config,
ipc::ShmRing& preprocessed_ring,
ipc::ShmRing& results_ring,
ProcessorRegistry processors
)
: config_(config),
preprocessed_ring_(preprocessed_ring),
results_ring_(results_ring),
processors_(std::move(processors)),
default_processor_mode_(kDefaultProcessorMode),
live_config_loader_(config.runtime.processing_live_config_path) {
if (processors_.empty()) {
throw std::runtime_error("DataProcessor requires at least one processor");
}
if (const auto found = processors_.find(default_processor_mode_); found == processors_.end()) {
default_processor_mode_ = processors_.begin()->first;
}
}
void DataProcessor::run(const std::atomic<bool>& stop_requested) {
std::vector<std::uint8_t> bytes{};
std::deque<ipc::PreprocessedCollection> preprocessed_history{};
const std::size_t history_limit = replay_history_limit(config_);
std::uint64_t last_replayed_revision = live_config_loader_.revision();
std::uint64_t last_applied_history_command_seq = 0;
while (!stop_requested.load(std::memory_order_relaxed)) {
const auto live_config = live_config_loader_.refresh_if_needed();
const auto live_revision = live_config_loader_.revision();
auto& processor = resolve_processor(live_config);
if (live_revision != last_replayed_revision) {
if (live_config.history_command_seq > last_applied_history_command_seq) {
if (live_config.history_command == HistoryCommand::RemoveLast) {
if (!preprocessed_history.empty()) {
preprocessed_history.pop_back();
}
} else if (live_config.history_command == HistoryCommand::ClearAll) {
preprocessed_history.clear();
}
last_applied_history_command_seq = live_config.history_command_seq;
}
if (live_config.processor_mode == "bscan") {
for (const auto& cached : preprocessed_history) {
const auto replay_result = process_collection(cached, processor, live_config);
publish_result_collection(replay_result, results_ring_);
}
} else if (!preprocessed_history.empty()) {
const auto replay_result = process_collection(preprocessed_history.back(), processor, live_config);
publish_result_collection(replay_result, results_ring_);
}
last_replayed_revision = live_revision;
}
if (preprocessed_ring_.pop(bytes)) {
auto preprocessed = ipc::deserialize_preprocessed_collection(bytes);
preprocessed_history.push_back(std::move(preprocessed));
while (preprocessed_history.size() > history_limit) {
preprocessed_history.pop_front();
}
const auto result_collection = process_collection(preprocessed_history.back(), processor, live_config);
publish_result_collection(result_collection, results_ring_);
continue;
}
std::this_thread::sleep_for(std::chrono::milliseconds(config_.runtime.idle_sleep_ms));
}
}
auto DataProcessor::process_collection(
const ipc::PreprocessedCollection& preprocessed,
ProcessorInterface& processor,
const ProcessingLiveConfig& live_config
) -> ipc::ResultCollection {
ipc::ResultCollection results{};
results.collection_id = preprocessed.collection_id;
// Keep source monotonic timestamp stable across live-config replays.
results.monotonic_ns = preprocessed.monotonic_ns;
results.blocks.reserve(preprocessed.traces.size());
for (const auto& trace : preprocessed.traces) {
ipc::ResultBlock block{};
block.combo = trace.combo;
block.payloads.reserve(1U);
block.payloads.push_back(processor.process(trace, live_config));
results.blocks.push_back(std::move(block));
}
return results;
}
auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface& {
const std::string requested_mode =
live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
if (auto found = processors_.find(requested_mode); found != processors_.end()) {
return *(found->second);
}
if (auto fallback = processors_.find(default_processor_mode_); fallback != processors_.end()) {
return *(fallback->second);
}
return *(processors_.begin()->second);
}
auto create_default_processors() -> ProcessorRegistry {
ProcessorRegistry processors{};
{
auto processor = std::make_unique<PassThroughProcessor>();
processors.emplace(processor->name(), std::move(processor));
}
{
auto processor = std::make_unique<BScanProcessor>();
processors.emplace(processor->name(), std::move(processor));
}
return processors;
}
} // namespace radar::processing
@@ -0,0 +1,69 @@
#include <atomic>
#include <csignal>
#include <exception>
#include <iostream>
#include <string>
#include "data_processor.hpp"
#include "run_config.hpp"
#include "shm_ring.hpp"
namespace {
constexpr const char* kDefaultConfigPath = "run_config.json";
std::atomic<bool> g_stop_requested{false};
void signal_handler(int /*signal*/) {
g_stop_requested.store(true, std::memory_order_relaxed);
}
void install_signal_handlers() {
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);
}
[[nodiscard]] auto read_config_path(int argc, char** argv) -> std::string {
std::string config_path = kDefaultConfigPath;
for (int index = 1; index < argc; ++index) {
const std::string arg = argv[index];
if (arg == "--config" && (index + 1) < argc) {
config_path = argv[++index];
}
}
return config_path;
}
} // namespace
int main(int argc, char** argv) {
install_signal_handlers();
try {
const auto config_path = read_config_path(argc, argv);
const auto config = radar::config::load_run_config(config_path);
auto preprocessed_ring = radar::ipc::ShmRing::open_or_create(
config.rings.preprocessed.name,
config.rings.preprocessed.capacity,
config.rings.preprocessed.slot_size_bytes
);
auto results_ring = radar::ipc::ShmRing::open_or_create(
config.rings.results.name,
config.rings.results.capacity,
config.rings.results.slot_size_bytes
);
radar::processing::DataProcessor processor(
config,
preprocessed_ring,
results_ring,
radar::processing::create_default_processors()
);
processor.run(g_stop_requested);
return 0;
} catch (const std::exception& exception) {
std::cerr << "data_processor error: " << exception.what() << '\n';
return 1;
}
}
@@ -0,0 +1,189 @@
#include "processing_live_config.hpp"
#include <chrono>
#include <cmath>
#include <fstream>
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <nlohmann/json.hpp>
namespace radar::processing {
namespace {
using Json = nlohmann::json;
[[nodiscard]] auto parse_history_command(const std::string& value) -> HistoryCommand {
if (value == "none") {
return HistoryCommand::None;
}
if (value == "remove_last") {
return HistoryCommand::RemoveLast;
}
if (value == "clear_all") {
return HistoryCommand::ClearAll;
}
throw std::runtime_error("processing.history_command must be one of: none, remove_last, clear_all");
}
[[nodiscard]] auto parse_u64_number(const Json& value, const std::string& field_name) -> std::uint64_t {
if (!value.is_number()) {
throw std::runtime_error(field_name + " must be number");
}
const double numeric = value.get<double>();
if (numeric < 0.0 || numeric > static_cast<double>(std::numeric_limits<std::uint64_t>::max())) {
throw std::runtime_error(field_name + " is out of uint64 range");
}
const double rounded = std::round(numeric);
if (std::fabs(numeric - rounded) > 1e-6) {
throw std::runtime_error(field_name + " must be integer");
}
return static_cast<std::uint64_t>(rounded);
}
[[nodiscard]] auto parse_live_config(const std::string& json_text, const std::string& path) -> ProcessingLiveConfig {
if (json_text.empty()) {
throw std::runtime_error("Processing live config is empty: " + path);
}
Json root{};
try {
root = Json::parse(json_text);
} catch (const Json::parse_error& error) {
throw std::runtime_error("Failed to parse processing live config: " + std::string(error.what()));
}
if (!root.is_object()) {
throw std::runtime_error("Processing live config root must be JSON object");
}
ProcessingLiveConfig config{};
if (const auto found = root.find("processor_mode"); found != root.end()) {
if (!found->is_string()) {
throw std::runtime_error("processing.processor_mode must be string");
}
config.processor_mode = found->get<std::string>();
}
if (const auto found = root.find("gain_db"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.gain_db must be number");
}
config.gain_db = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("phase_deg"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.phase_deg must be number");
}
config.phase_deg = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("bscan_axis"); found != root.end()) {
if (!found->is_string()) {
throw std::runtime_error("processing.bscan_axis must be string");
}
config.bscan_axis = found->get<std::string>();
}
if (const auto found = root.find("bscan_cut_m"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.bscan_cut_m must be number");
}
config.bscan_cut_m = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("bscan_max_depth_m"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.bscan_max_depth_m must be number");
}
config.bscan_max_depth_m = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("bscan_gain"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.bscan_gain must be number");
}
config.bscan_gain = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("bscan_start_freq_mhz"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.bscan_start_freq_mhz must be number");
}
config.bscan_start_freq_mhz = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("bscan_stop_freq_mhz"); found != root.end()) {
if (!found->is_number()) {
throw std::runtime_error("processing.bscan_stop_freq_mhz must be number");
}
config.bscan_stop_freq_mhz = static_cast<float>(found->get<double>());
}
if (const auto found = root.find("history_command_seq"); found != root.end()) {
config.history_command_seq = parse_u64_number(*found, "processing.history_command_seq");
}
if (const auto found = root.find("history_command"); found != root.end()) {
if (!found->is_string()) {
throw std::runtime_error("processing.history_command must be string");
}
config.history_command = parse_history_command(found->get<std::string>());
}
return config;
}
} // namespace
ProcessingLiveConfigLoader::ProcessingLiveConfigLoader(std::string path)
: path_(std::move(path)),
next_check_at_(std::chrono::steady_clock::now()) {
if (path_.empty()) {
throw std::runtime_error("Processing live config path must not be empty");
}
}
auto ProcessingLiveConfigLoader::current() const -> ProcessingLiveConfig {
return current_;
}
auto ProcessingLiveConfigLoader::revision() const -> std::uint64_t {
return revision_;
}
auto ProcessingLiveConfigLoader::refresh_if_needed() -> ProcessingLiveConfig {
const auto now = std::chrono::steady_clock::now();
if (now < next_check_at_) {
return current_;
}
next_check_at_ = now + std::chrono::milliseconds(100);
std::error_code time_error{};
const auto file_time = std::filesystem::last_write_time(path_, time_error);
if (time_error) {
return current_;
}
if (has_last_write_time_ && file_time == last_write_time_) {
return current_;
}
try {
current_ = read_from_file();
last_write_time_ = file_time;
has_last_write_time_ = true;
++revision_;
} catch (const std::exception& error) {
std::cerr << "data_processor warning: failed to refresh live processing config: " << error.what() << '\n';
}
return current_;
}
auto ProcessingLiveConfigLoader::read_from_file() const -> ProcessingLiveConfig {
std::ifstream stream(path_);
if (!stream.is_open()) {
throw std::runtime_error("Failed to open live processing config: " + path_);
}
std::stringstream buffer{};
buffer << stream.rdbuf();
return parse_live_config(buffer.str(), path_);
}
} // namespace radar::processing