2025-09-08 14:55:07 +08:00
|
|
|
// app_config.hpp
|
|
|
|
|
#pragma once
|
|
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
#include <limits.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
2025-09-08 14:55:07 +08:00
|
|
|
#include <fstream>
|
|
|
|
|
#include <iostream>
|
2025-11-13 13:21:10 +08:00
|
|
|
#include <map>
|
2025-09-08 14:55:07 +08:00
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
#include <stdexcept>
|
2025-11-13 13:21:10 +08:00
|
|
|
#include <string>
|
|
|
|
|
|
2025-09-08 14:55:07 +08:00
|
|
|
#include "logger.hpp"
|
|
|
|
|
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
using ordered_json = nlohmann::ordered_json;
|
|
|
|
|
|
|
|
|
|
// ------------------- 摄像头结构体 -------------------
|
2025-10-14 17:13:08 +08:00
|
|
|
enum class StreamType
|
|
|
|
|
{
|
|
|
|
|
MAIN,
|
|
|
|
|
SUB
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-08 14:55:07 +08:00
|
|
|
struct Camera
|
|
|
|
|
{
|
|
|
|
|
std::string device;
|
|
|
|
|
std::string name;
|
|
|
|
|
int width, height, fps;
|
2025-10-14 17:13:08 +08:00
|
|
|
int bitrate;
|
2025-09-08 14:55:07 +08:00
|
|
|
bool enabled;
|
2025-11-13 13:21:10 +08:00
|
|
|
StreamType stream_type; // 新增字段
|
2025-09-08 14:55:07 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ------------------- MQTT Topic -------------------
|
|
|
|
|
struct VehicleMQTTTopics
|
|
|
|
|
{
|
|
|
|
|
std::string heartbeat_up;
|
|
|
|
|
std::string video_down;
|
2025-11-14 15:04:12 +08:00
|
|
|
std::string record_query;
|
|
|
|
|
std::string record_play;
|
2025-12-29 17:45:57 +08:00
|
|
|
std::string vehicle_ctrl;
|
2025-09-08 14:55:07 +08:00
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
void fill_with_veh_id(const std::string& vehId)
|
2025-09-08 14:55:07 +08:00
|
|
|
{
|
2026-01-07 13:32:09 +08:00
|
|
|
heartbeat_up = "/zxwl/sweeper/" + vehId + "/video/status";
|
|
|
|
|
video_down = "/zxwl/sweeper/" + vehId + "/video/query";
|
|
|
|
|
record_query = "/zxwl/sweeper/" + vehId + "/record/query";
|
|
|
|
|
record_play = "/zxwl/sweeper/" + vehId + "/record/play";
|
|
|
|
|
vehicle_ctrl = "/zxwl/sweeper/" + vehId + "/ctrl";
|
2025-09-08 14:55:07 +08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ------------------- MQTT 配置 -------------------
|
|
|
|
|
struct MQTTConfig
|
|
|
|
|
{
|
|
|
|
|
std::string vehicle_id;
|
|
|
|
|
std::string server_ip;
|
|
|
|
|
int server_port;
|
|
|
|
|
bool need_username_pwd;
|
|
|
|
|
std::string client_id;
|
|
|
|
|
std::string username;
|
|
|
|
|
std::string password;
|
|
|
|
|
int keep_alive;
|
2025-09-08 15:11:52 +08:00
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
int qos = 1; // 默认 QoS 级别
|
|
|
|
|
bool clean_session = true; // 默认 clean session
|
2025-09-08 15:11:52 +08:00
|
|
|
|
2025-09-08 14:55:07 +08:00
|
|
|
VehicleMQTTTopics topics;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ------------------- 总配置 -------------------
|
|
|
|
|
struct AppConfig
|
|
|
|
|
{
|
2026-01-22 09:29:10 +08:00
|
|
|
bool record_enabled = true;
|
2025-09-08 14:55:07 +08:00
|
|
|
std::vector<Camera> cameras;
|
|
|
|
|
MQTTConfig mqtt;
|
|
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
static AppConfig load_from_file(const std::string& filepath)
|
2025-09-08 14:55:07 +08:00
|
|
|
{
|
|
|
|
|
AppConfig cfg;
|
|
|
|
|
|
|
|
|
|
std::ifstream ifs(filepath);
|
|
|
|
|
if (!ifs.is_open())
|
|
|
|
|
{
|
|
|
|
|
LOG_ERROR("[Config] Failed to open config file: " + filepath);
|
|
|
|
|
throw std::runtime_error("Failed to open config file: " + filepath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
json j;
|
|
|
|
|
ifs >> j;
|
|
|
|
|
|
2026-01-22 09:29:10 +08:00
|
|
|
cfg.record_enabled = j.value("record_enabled", true);
|
|
|
|
|
LOG_INFO("[Config] Global record_enabled = " + std::string(cfg.record_enabled ? "true" : "false"));
|
|
|
|
|
|
2025-09-08 14:55:07 +08:00
|
|
|
// 读取摄像头
|
|
|
|
|
if (j.contains("cameras"))
|
|
|
|
|
{
|
2025-11-13 13:21:10 +08:00
|
|
|
for (auto& c : j["cameras"])
|
2025-09-08 14:55:07 +08:00
|
|
|
{
|
|
|
|
|
Camera cam;
|
|
|
|
|
cam.device = c.value("device", "");
|
|
|
|
|
cam.name = c.value("name", "");
|
|
|
|
|
cam.width = c.value("width", 1280);
|
|
|
|
|
cam.height = c.value("height", 720);
|
|
|
|
|
cam.fps = c.value("fps", 30);
|
2025-09-23 08:43:02 +08:00
|
|
|
cam.bitrate = c.value("bitrate", 2000000);
|
2025-09-08 14:55:07 +08:00
|
|
|
cam.enabled = c.value("enabled", false);
|
|
|
|
|
cfg.cameras.push_back(cam);
|
2026-01-05 14:51:22 +08:00
|
|
|
if (cam.enabled)
|
|
|
|
|
{
|
|
|
|
|
LOG_INFO("[Config] Loaded camera: " + cam.name + " (" + cam.device +
|
|
|
|
|
"), enabled=" + std::to_string(cam.enabled) + ", bitrate=" + std::to_string(cam.bitrate));
|
|
|
|
|
}
|
2025-09-08 14:55:07 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 读取 MQTT
|
|
|
|
|
if (!j.contains("mqtt_server"))
|
|
|
|
|
{
|
|
|
|
|
LOG_ERROR("[Config] Missing 'mqtt_server' section");
|
|
|
|
|
throw std::runtime_error("Config file missing 'mqtt_server'");
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
auto& m = j["mqtt_server"];
|
2025-12-30 09:19:59 +08:00
|
|
|
if (m.contains("veh_id"))
|
|
|
|
|
{
|
|
|
|
|
if (m["veh_id"].is_string())
|
|
|
|
|
{
|
|
|
|
|
cfg.mqtt.vehicle_id = m["veh_id"].get<std::string>();
|
|
|
|
|
}
|
|
|
|
|
else if (m["veh_id"].is_number_integer())
|
|
|
|
|
{
|
|
|
|
|
cfg.mqtt.vehicle_id = std::to_string(m["veh_id"].get<int>());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("mqtt_server.veh_id must be string or integer");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("mqtt_server.veh_id missing");
|
|
|
|
|
}
|
2025-09-08 14:55:07 +08:00
|
|
|
cfg.mqtt.server_ip = m.value("address", "");
|
|
|
|
|
cfg.mqtt.server_port = m.value("port", 1883);
|
|
|
|
|
cfg.mqtt.need_username_pwd = m.value("need_username_pwd", true);
|
|
|
|
|
cfg.mqtt.client_id = m.value("client_id", "");
|
|
|
|
|
cfg.mqtt.username = m.value("username", "");
|
|
|
|
|
cfg.mqtt.password = m.value("password", "");
|
|
|
|
|
cfg.mqtt.keep_alive = m.value("mqtt_heart_threshold", 2000);
|
|
|
|
|
cfg.mqtt.topics.fill_with_veh_id(cfg.mqtt.vehicle_id);
|
|
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
LOG_INFO("[Config] Loaded MQTT server: " + cfg.mqtt.server_ip + ":" + std::to_string(cfg.mqtt.server_port));
|
2025-09-08 14:55:07 +08:00
|
|
|
LOG_INFO("[Config] MQTT client ID: " + cfg.mqtt.client_id);
|
2025-11-13 13:21:10 +08:00
|
|
|
LOG_INFO("[Config] MQTT Credentials - username: " + cfg.mqtt.username + ", password: " + cfg.mqtt.password);
|
|
|
|
|
LOG_INFO("[Config] MQTT Topics: " + cfg.mqtt.topics.heartbeat_up + ", " + cfg.mqtt.topics.video_down);
|
2025-09-09 16:21:06 +08:00
|
|
|
LOG_INFO("[Config] MQTT keepAlive: " + std::to_string(cfg.mqtt.keep_alive));
|
2025-09-08 14:55:07 +08:00
|
|
|
|
|
|
|
|
return cfg;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ------------------- 辅助函数 -------------------
|
|
|
|
|
inline std::string get_executable_dir()
|
|
|
|
|
{
|
|
|
|
|
char result[PATH_MAX] = {0};
|
|
|
|
|
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
|
|
|
|
|
if (count == -1)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERROR("[Config] Failed to read /proc/self/exe");
|
|
|
|
|
throw std::runtime_error("Failed to read /proc/self/exe");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string full_path(result, count);
|
|
|
|
|
auto pos = full_path.find_last_of('/');
|
|
|
|
|
if (pos == std::string::npos)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERROR("[Config] Failed to find executable directory");
|
|
|
|
|
throw std::runtime_error("Failed to find executable directory");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return full_path.substr(0, pos);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 13:21:10 +08:00
|
|
|
inline std::string get_executable_dir_file_path(const std::string& filename)
|
2025-09-08 14:55:07 +08:00
|
|
|
{
|
|
|
|
|
std::string dir = get_executable_dir();
|
2025-11-13 13:21:10 +08:00
|
|
|
if (dir.back() != '/') dir += '/';
|
2025-09-08 14:55:07 +08:00
|
|
|
return dir + filename;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 全局配置变量
|
|
|
|
|
extern AppConfig g_app_config;
|