agrobot_base/AgroBase/livox_visual_debugger/status_snapshot.hpp

342 lines
12 KiB
C++

#pragma once
#include <string>
#include <vector>
#include <atomic>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <iostream>
// ===== Depende do teu projeto =====
#include "core.hpp"
#include "redis_publisher.hpp"
// Se quiser habilitar nlohmann/json, remova o comentário:
// #include <nlohmann/json.hpp>
// ======= Estruturas =======
struct SnapshotFreq {
double pcl{ 0.0 };
double imu{ 0.0 };
};
struct SnapshotIMU {
uint64_t timestamp = 0;
double roll_deg{ 0.0 };
double pitch_deg{ 0.0 };
double yaw_deg{ 0.0 };
};
struct SnapshotStatus {
double temperatura_c{ 0.0 };
uint32_t vezes_ligado{ 0 };
};
struct SnapshotDist {
double frente{ 0.0 };
double tras{ 0.0 };
double esquerda{ 0.0 };
double direita{ 0.0 };
double sup{ 0.0 };
double inf{ 0.0 };
};
struct StatusSnapshot {
uint64_t timestamp_ms{ 0 };
bool conectado{ true }; // placeholder por enquanto
bool executando{ false };
bool sensor_ativo{ false };
std::string lidar_ip;
std::string host_ip;
std::string firmware_version;
std::string dev_type;
uint32_t handle{ 0 };
SnapshotFreq freq;
SnapshotIMU imu;
SnapshotStatus status;
SnapshotDist dist;
std::vector<BBox3D> bboxes;
static std::unique_ptr<RedisPublisher> g_pub;
static void start_publisher() {
const std::string uri = "tcp://localhost:6379";
StatusSnapshot::g_pub = std::make_unique<RedisPublisher>(uri);
}
// ===== Coletor: puxa dados do sistema e retorna um Snapshot pronto =====
static StatusSnapshot collect_now() {
StatusSnapshot s{};
// tempo
using namespace std::chrono;
s.timestamp_ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
// flags globais/atômicos do teu runtime
s.conectado = Core::connected.load(std::memory_order_relaxed);
s.executando = Core::running.load(std::memory_order_relaxed);
s.sensor_ativo = Core::streaming.load(std::memory_order_relaxed);
s.handle = Core::handle.load(std::memory_order_relaxed);
s.host_ip = Core::get_host_ip();
s.lidar_ip = Core::get_lidar_ip();
s.firmware_version = Core::get_fw_version();
s.dev_type = Core::get_dev_type();
// freq (Hz). Se teus g_freq_* guardam Hz, só converte p/ double:
s.freq.pcl = static_cast<double>(Core::freq_pcl.load(std::memory_order_relaxed));
s.freq.imu = static_cast<double>(Core::freq_imu.load(std::memory_order_relaxed));
// IMU (em graus)
const auto att = Core::get_attitude();
s.imu.timestamp = att.timestamp;
s.imu.roll_deg = Core::rad2deg(att.roll);
s.imu.pitch_deg = Core::rad2deg(att.pitch);
s.imu.yaw_deg = Core::rad2deg(att.yaw);
// status (temperatura e contagem de ligadas)
s.status.temperatura_c = Core::get_temperature_c();
s.status.vezes_ligado = Core::get_power_count();
// distâncias (ajusta os nomes conforme teu Distance6)
const auto d = Core::get_distances();
s.dist.frente = d.front;
s.dist.tras = d.back;
s.dist.esquerda = d.left;
s.dist.direita = d.right;
s.dist.sup = d.up;
s.dist.inf = d.down;
// bboxes — adapta para tua fonte. Exemplo genérico:
// Supondo que exista Core::get_bboxes() -> std::vector<Deteccao>
// com campos equivalentes.
for (const auto& b : Core::get_bboxes()) {
s.bboxes.push_back(std::move(b));
}
return s;
}
// ===== Serialização manual (sem dependências) =====
std::string to_json_string() const {
std::ostringstream os;
os << std::fixed << std::setprecision(3);
os << "{";
os << "\"timestamp\":" << timestamp_ms << ",";
os << "\"conectado\":" << (conectado ? "true" : "false") << ",";
os << "\"executando\":" << (executando ? "true" : "false") << ",";
os << "\"sensor_ativo\":" << (sensor_ativo ? "true" : "false") << ",";
os << "\"host_ip\":" << host_ip << ",";
os << "\"lidar_ip\":" << lidar_ip << ",";
os << "\"firmware_version\":" << firmware_version << ",";
os << "\"dev_type\":" << dev_type << ",";
os << "\"handle\":" << handle << ",";
os << "\"freq\":{"
<< "\"pcl\":" << freq.pcl << ","
<< "\"imu\":" << freq.imu << "},";
os << "\"imu\":{"
<< "\"timestamp\":" << imu.timestamp << ","
<< "\"roll\":" << imu.roll_deg << ","
<< "\"pitch\":" << imu.pitch_deg << ","
<< "\"yaw\":" << imu.yaw_deg << "},";
os << "\"status\":{"
<< "\"temperatura\":" << status.temperatura_c << ","
<< "\"vezes_ligado\":" << status.vezes_ligado << "},";
os << "\"distancias\":{"
<< "\"frente\":" << dist.frente << ","
<< "\"tras\":" << dist.tras << ","
<< "\"esquerda\":" << dist.esquerda << ","
<< "\"direita\":" << dist.direita << ","
<< "\"sup\":" << dist.sup << ","
<< "\"inf\":" << dist.inf << "},";
os << "\"bboxes\":[";
for (size_t i = 0; i < bboxes.size(); ++i) {
const auto& b = bboxes[i];
os << "{"
// limites da caixa
<< "\"min_x\":" << b.min_x << ","
<< "\"min_y\":" << b.min_y << ","
<< "\"min_z\":" << b.min_z << ","
<< "\"max_x\":" << b.max_x << ","
<< "\"max_y\":" << b.max_y << ","
<< "\"max_z\":" << b.max_z << ","
// tempo e voxel info
<< "\"t_ms\":" << b.t_ms << ","
<< "\"voxels\":" << b.voxels << ","
// centro e dimensões
<< "\"cx\":" << b.cx << ","
<< "\"cy\":" << b.cy << ","
<< "\"cz\":" << b.cz << ","
<< "\"w\":" << b.w << ","
<< "\"h\":" << b.h << ","
<< "\"d\":" << b.d << ","
// distâncias derivadas
<< "\"dist_m\":" << b.dist_m << ","
<< "\"dist_xy_m\":" << b.dist_xy_m << ","
<< "\"frente_m\":" << b.frente_m
// fecha objeto
<< "}";
if (i + 1 < bboxes.size())
os << ",";
}
os << "]";
os << "}";
return os.str();
}
// --- 2.1 Compactador de BBoxes ---
std::string bboxes_to_json_compact(std::size_t max_boxes) const {
if (bboxes.empty()) return "[]";
// ordena por proximidade frontal (menor frente_m primeiro)
std::vector<BBox3D> v = bboxes;
std::sort(v.begin(), v.end(),
[](const BBox3D& a, const BBox3D& b) { return a.frente_m < b.frente_m; });
if (v.size() > max_boxes) v.resize(max_boxes);
std::ostringstream os;
os.setf(std::ios::fixed); os << std::setprecision(3);
os << "[";
for (std::size_t i = 0; i < v.size(); ++i) {
const auto& b = v[i];
os << "{"
<< "\"min_x\":" << b.min_x << ","
<< "\"min_y\":" << b.min_y << ","
<< "\"min_z\":" << b.min_z << ","
<< "\"max_x\":" << b.max_x << ","
<< "\"max_y\":" << b.max_y << ","
<< "\"max_z\":" << b.max_z << ","
<< "\"t_ms\":" << b.t_ms << ","
<< "\"voxels\":" << b.voxels << ","
<< "\"cx\":" << b.cx << ","
<< "\"cy\":" << b.cy << ","
<< "\"cz\":" << b.cz << ","
<< "\"w\":" << b.w << ","
<< "\"h\":" << b.h << ","
<< "\"d\":" << b.d << ","
<< "\"dist_m\":" << b.dist_m << ","
<< "\"dist_xy_m\":" << b.dist_xy_m << ","
<< "\"frente_m\":" << b.frente_m
<< "}";
if (i + 1 < v.size()) os << ",";
}
os << "]";
return os.str();
}
// --- 2.2 Publisher parcial (Hash + Stream) ---
bool publish_state_partial(int ttl_sec_hash, std::size_t max_bboxes) const
{
if (!g_pub) {
std::cerr << "[STATUS] RedisPublisher não inicializado\n";
return false;
}
// monta pares para o JSON (paths com '.')
std::vector<std::pair<std::string, std::string>> J;
J.reserve(32);
auto addj_i = [&](std::string path, long long v) {
J.emplace_back(std::move(path), std::to_string(v));
};
auto addj_b = [&](std::string path, bool v) {
// booleans como true/false (sem aspas) viram bool real no JSON
J.emplace_back(std::move(path), v ? "true" : "false");
};
auto addj_f = [&](std::string path, double v, int prec = 3) {
std::ostringstream oss;
oss.setf(std::ios::fixed); oss << std::setprecision(prec) << v;
J.emplace_back(std::move(path), oss.str()); // número no JSON
};
auto addj_s = [&](std::string path, const std::string& v) {
// adiciona string com aspas no JSON
std::ostringstream oss;
oss << "\"" << v << "\"";
J.emplace_back(std::move(path), oss.str());
};
// ---- mesmos campos que você já preencheu no HASH ----
addj_i("timestamp", static_cast<long long>(timestamp_ms));
addj_b("conectado", conectado);
addj_b("executando", executando);
addj_b("sensor_ativo", sensor_ativo);
addj_s("host_ip", host_ip);
addj_s("lidar_ip", lidar_ip);
addj_s("firmware_version", Core::get_fw_version());
addj_s("dev_type", Core::get_dev_type());
addj_i("handle", static_cast<long long>(handle));
addj_f("freq.pcl", freq.pcl, 2);
addj_f("freq.imu", freq.imu, 2);
addj_f("imu.timestamp", imu.timestamp, 2);
addj_f("imu.roll", imu.roll_deg, 2);
addj_f("imu.pitch", imu.pitch_deg, 2);
addj_f("imu.yaw", imu.yaw_deg, 2);
addj_f("status.temperatura", status.temperatura_c, 2);
addj_i("status.vezes_ligado", static_cast<long long>(status.vezes_ligado));
addj_f("dist.frente", dist.frente, 2);
addj_f("dist.tras", dist.tras, 2);
addj_f("dist.esquerda", dist.esquerda, 2);
addj_f("dist.direita", dist.direita, 2);
addj_f("dist.sup", dist.sup, 2);
addj_f("dist.inf", dist.inf, 2);
// opcional: bboxes como array JSON no snapshot (no mesmo documento)
if (!bboxes.empty()) {
std::string bb_json = bboxes_to_json_compact(max_bboxes); // deve ser JSON válido
if (!bb_json.empty()) {
// como o merge tenta json::parse(value), passar o array/objeto funciona direto
J.emplace_back("bboxes", bb_json); // ex.: "[{...},{...}]"
}
}
// grava/mescla no snapshot JSON (sem apagar outros campos já existentes)
bool ok_json = g_pub->set_json_merge_paths("ctx:dados_modulo_Lvx", J, ttl_sec_hash);
//std::cout << "OK_JSON: " << ok_json << "\n";
return ok_json;
}
bool publish_state_imu() {
const auto att = Core::get_attitude();
std::vector<std::pair<std::string, std::string>> J;
J.reserve(32);
auto addj_i = [&](std::string path, long long v) {
J.emplace_back(std::move(path), std::to_string(v));
};
auto addj_f = [&](std::string path, double v, int prec = 3) {
std::ostringstream oss;
oss.setf(std::ios::fixed); oss << std::setprecision(prec) << v;
J.emplace_back(std::move(path), oss.str()); // número no JSON
};
addj_i("timestamp", static_cast<long long>(att.timestamp));
addj_f("frequencia", att.frequencia, 4);
addj_f("latencia", att.latencia, 4);
addj_f("roll", Core::rad2deg(att.roll), 3);
addj_f("pitch", Core::rad2deg(att.pitch), 3);
addj_f("yaw", Core::rad2deg(att.yaw), 3);
// grava/mescla no snapshot JSON (sem apagar outros campos já existentes)
bool ok_json = g_pub->set_json_merge_paths("ctx:dados_modulo_Imu", J, 3);
}
};