更新配置文件获取方式

This commit is contained in:
cxh 2026-02-06 09:03:24 +08:00
parent 592f33ecf8
commit 7810209196
17 changed files with 12716 additions and 1200 deletions

View File

@ -1,120 +0,0 @@
// Read an INI file into easy-to-access name/value pairs.
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (C) 2009-2025, Ben Hoyt
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
// Go to the project home page for more info:
//
// https://github.com/benhoyt/inih
#ifndef INIREADER_H
#define INIREADER_H
#include <map>
#include <string>
#include <cstdint>
#include <vector>
#include <set>
// Visibility symbols, required for Windows DLLs
#ifndef INI_API
#if defined _WIN32 || defined __CYGWIN__
# ifdef INI_SHARED_LIB
# ifdef INI_SHARED_LIB_BUILDING
# define INI_API __declspec(dllexport)
# else
# define INI_API __declspec(dllimport)
# endif
# else
# define INI_API
# endif
#else
# if defined(__GNUC__) && __GNUC__ >= 4
# define INI_API __attribute__ ((visibility ("default")))
# else
# define INI_API
# endif
#endif
#endif
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
// for simplicity here rather than speed, but it should be pretty decent.)
class INIReader
{
public:
// Construct INIReader and parse given filename. See ini.h for more info
// about the parsing.
INI_API explicit INIReader(const std::string& filename);
// Construct INIReader and parse given buffer. See ini.h for more info
// about the parsing.
INI_API explicit INIReader(const char *buffer, size_t buffer_size);
// Return the result of ini_parse(), i.e., 0 on success, line number of
// first error on parse error, -1 on file open error, or -2 if there was a
// memory allocation error.
INI_API int ParseError() const;
// Return a message that describes the type of error that occurred.
// It will return "" (empty string) if there was no error.
INI_API std::string ParseErrorMessage() const;
// Get a string value from INI file, returning default_value if not found.
INI_API std::string Get(const std::string& section, const std::string& name,
const std::string& default_value) const;
// Get a string value from INI file, returning default_value if not found,
// empty, or contains only whitespace.
INI_API std::string GetString(const std::string& section, const std::string& name,
const std::string& default_value) const;
// Get an integer (long) value from INI file, returning default_value if
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const;
// Get a 64-bit integer (int64_t) value from INI file, returning default_value if
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
INI_API int64_t GetInteger64(const std::string& section, const std::string& name, int64_t default_value) const;
// Get an unsigned integer (unsigned long) value from INI file, returning default_value if
// not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2").
INI_API unsigned long GetUnsigned(const std::string& section, const std::string& name, unsigned long default_value) const;
// Get an unsigned 64-bit integer (uint64_t) value from INI file, returning default_value if
// not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2").
INI_API uint64_t GetUnsigned64(const std::string& section, const std::string& name, uint64_t default_value) const;
// Get a real (floating point double) value from INI file, returning
// default_value if not found or not a valid floating point value
// according to strtod().
INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const;
// Get a boolean value from INI file, returning default_value if not found or if
// not a valid true/false value. Valid true values are "true", "yes", "on", "1",
// and valid false values are "false", "no", "off", "0" (not case sensitive).
INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const;
// Return a newly-allocated vector of all section names, in alphabetical order.
INI_API std::vector<std::string> Sections() const;
// Return a newly-allocated vector of keys in the given section, in alphabetical order.
INI_API std::vector<std::string> Keys(const std::string& section) const;
// Return true if the given section exists (section must contain at least
// one name=value pair).
INI_API bool HasSection(const std::string& section) const;
// Return true if a value exists with the given section and field names.
INI_API bool HasValue(const std::string& section, const std::string& name) const;
protected:
int _error;
std::map<std::string, std::string> _values;
static std::string MakeKey(const std::string& section, const std::string& name);
static int ValueHandler(void* user, const char* section, const char* name,
const char* value);
};
#endif // INIREADER_H

View File

@ -0,0 +1,48 @@
#pragma once
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include "nlohmann/json.hpp"
// 前置声明,避免头文件膨胀
namespace httplib
{
class Client;
}
class TboxConfigClient
{
public:
explicit TboxConfigClient(std::string server = "127.0.0.1", int port = 18080);
// ================= 拉取配置 =================
bool fetch();
uint64_t version() const;
bool isDirty() const;
// ================= 读取 =================
std::optional<std::string> getString(std::string_view semantic) const;
std::optional<int> getInt(std::string_view semantic) const;
std::optional<bool> getBool(std::string_view semantic) const;
// ================= 修改(仅本地 staging =================
void setString(std::string_view semantic, const std::string& v);
void setInt(std::string_view semantic, int v);
void setBool(std::string_view semantic, bool v);
// ================= 提交并确认 =================
bool commitAndFetch();
private:
std::string server_;
int port_;
uint64_t version_{0};
bool dirty_{false};
std::unordered_map<std::string, nlohmann::json> cache_;
std::unordered_map<std::string, nlohmann::json> pending_;
};

View File

@ -0,0 +1,158 @@
#pragma once
#include <chrono>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include "TboxConfigClient.h"
#include "TboxConfigSemantic.h"
// =======================================================
// TboxConfigManager
// - 表驱动生成 getter / setter
// - 支持多项修改 + commit
// - 自动 version 检测 + reload
// =======================================================
class TboxConfigManager
{
public:
static TboxConfigManager& instance()
{
static TboxConfigManager inst;
return inst;
}
// ================= 初始化 =================
bool init(const std::string& server = "127.0.0.1", int port = 18080)
{
std::lock_guard<std::mutex> lock(mtx_);
client_ = std::make_unique<TboxConfigClient>(server, port);
bool ok = client_->fetch();
last_check_ = std::chrono::steady_clock::now();
return ok;
}
uint64_t version()
{
ensure();
return client_ ? client_->version() : 0;
}
// ===================================================
// 🔑 配置字段定义表(唯一维护点)
// type, Name, semantic, default
// ===================================================
#define TBOX_CONFIG_FIELDS(X) \
/* -------- 基础身份 -------- */ \
X(std::string, DeviceNo, tbox::semantic::DEVICE_NO, "KL001") \
X(std::string, VehicleId, tbox::semantic::VEHICLE_ID, "V010001") \
X(std::string, Vin, tbox::semantic::VIN, "") \
\
/* -------- 云平台 -------- */ \
X(std::string, PlatformIp, tbox::semantic::PLATFORM_IP, "") \
X(int, PlatformPort, tbox::semantic::PLATFORM_PORT, 0) \
X(std::string, MqttIp, tbox::semantic::MQTT_IP, "") \
X(int, MqttPort, tbox::semantic::MQTT_PORT, 1883) \
X(std::string, MqttUsername, tbox::semantic::MQTT_USERNAME, "") \
X(std::string, MqttPassword, tbox::semantic::MQTT_PASSWORD, "") \
\
/* -------- 驾驶舱 -------- */ \
X(std::string, CockpitMqttIp, tbox::semantic::COCKPIT_MQTT_IP, "") \
X(int, CockpitMqttPort, tbox::semantic::COCKPIT_MQTT_PORT, 1883) \
\
/* -------- 协议相关 -------- */ \
X(int, LoginSeq, tbox::semantic::LOGIN_SEQ, 1) \
X(std::string, LoginSeqDate, tbox::semantic::LOGIN_SEQ_DATE, "000000") \
X(int, HeartbeatInterval, tbox::semantic::HEARTBEAT_INTERVAL, 60)
// ================= Getter 自动生成 =================
#define GEN_GET(type, name, semantic, def) \
type get##name() { return getValue<type>(semantic, def); }
TBOX_CONFIG_FIELDS(GEN_GET)
#undef GEN_GET
// ================= Setter写入缓存不立即提交 =================
#define GEN_SET(type, name, semantic, def) \
void set##name(const type& v) { setValue<type>(semantic, v); }
TBOX_CONFIG_FIELDS(GEN_SET)
#undef GEN_SET
// ================= 统一提交 =================
bool commit()
{
ensure();
return client_->commitAndFetch();
}
private:
TboxConfigManager() = default;
~TboxConfigManager() = default;
TboxConfigManager(const TboxConfigManager&) = delete;
TboxConfigManager& operator=(const TboxConfigManager&) = delete;
// ================= 自动刷新机制 =================
void ensureFresh()
{
auto now = std::chrono::steady_clock::now();
if (now - last_check_ < check_interval_) return;
last_check_ = now;
// 简单粗暴,但稳定:直接 fetch
client_->fetch();
}
void ensure()
{
if (!client_)
{
client_ = std::make_unique<TboxConfigClient>();
client_->fetch();
}
ensureFresh();
}
// ================= 泛型访问 =================
template <typename T>
T getValue(std::string_view semantic, const T& def)
{
ensure();
if constexpr (std::is_same_v<T, int>)
{
auto v = client_->getInt(semantic);
return v ? *v : def;
}
else if constexpr (std::is_same_v<T, bool>)
{
auto v = client_->getBool(semantic);
return v ? *v : def;
}
else
{
auto v = client_->getString(semantic);
return v ? *v : def;
}
}
template <typename T>
void setValue(std::string_view semantic, const T& v)
{
ensure();
if constexpr (std::is_same_v<T, int>)
client_->setInt(semantic, v);
else if constexpr (std::is_same_v<T, bool>)
client_->setBool(semantic, v);
else
client_->setString(semantic, v);
}
private:
std::mutex mtx_;
std::unique_ptr<TboxConfigClient> client_;
std::chrono::steady_clock::time_point last_check_{};
const std::chrono::seconds check_interval_{30};
};

View File

@ -0,0 +1,28 @@
#pragma once
#include <string_view>
namespace tbox::semantic
{
// ================= 基础身份 =================
inline constexpr std::string_view DEVICE_NO = "tbox.device_no";
inline constexpr std::string_view VEHICLE_ID = "tbox.vehicle_id";
inline constexpr std::string_view VIN = "tbox.vin";
// ================= 云平台 =================
inline constexpr std::string_view PLATFORM_IP = "tbox.platform_ip";
inline constexpr std::string_view PLATFORM_PORT = "tbox.platform_port";
inline constexpr std::string_view MQTT_IP = "tbox.mqtt_ip";
inline constexpr std::string_view MQTT_PORT = "tbox.mqtt_port";
inline constexpr std::string_view MQTT_USERNAME = "tbox.mqtt_username";
inline constexpr std::string_view MQTT_PASSWORD = "tbox.mqtt_password";
// ================= 驾驶舱 =================
inline constexpr std::string_view COCKPIT_MQTT_IP = "tbox.cockpit_mqtt_ip";
inline constexpr std::string_view COCKPIT_MQTT_PORT = "tbox.cockpit_mqtt_port";
// ================= 协议相关 =================
inline constexpr std::string_view LOGIN_SEQ = "tbox.login_seq";
inline constexpr std::string_view LOGIN_SEQ_DATE = "tbox.login_seq_date";
inline constexpr std::string_view HEARTBEAT_INTERVAL = "tbox.heartbeat_interval";
} // namespace tbox::semantic

View File

@ -1,240 +0,0 @@
#pragma once
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>
#include "INIReader.h"
// ------------------------
// 线程安全配置管理单例类
// ------------------------
class ConfigManager
{
public:
// 获取单例
static ConfigManager& instance()
{
static ConfigManager inst;
return inst;
}
// 加载 INI 文件
bool load(const std::string& path)
{
std::lock_guard<std::mutex> lock(mtx);
file_path = path;
auto r = std::make_unique<INIReader>(path);
if (r->ParseError() < 0)
{
std::cerr << "Can't load config file: " << path << ", will create default on save\n";
reader.reset();
buildMapFromReader(); // 用 schema 默认值初始化
return false;
}
reader = std::move(r);
buildMapFromReader();
return true;
}
// ==================== Vehicle ====================
std::string getVin() { return getValue("vehicle", "vin", "LSV1234567890KUNL"); }
std::string getVehicleId() { return getValue("vehicle", "vehicle_id", "V010001"); }
void setVin(const std::string& vin) { setValue("vehicle", "vin", vin); }
void setVehicleId(const std::string& vid) { setValue("vehicle", "vehicle_id", vid); }
// ==================== Cloud ====================
std::string getDeviceNo() { return getValue("cloud", "device_no", "KL001"); }
std::string getPlatformIp() { return getValue("cloud", "platform_ip", "192.168.1.100"); }
int getPlatformPort() { return getInt("cloud", "platform_port", 8888); }
std::string getMqttIp() { return getValue("cloud", "mqtt_ip", "192.168.1.101"); }
int getMqttPort() { return getInt("cloud", "mqtt_port", 1883); }
// ✅ 新增
std::string getMqttUsername() { return getValue("cloud", "mqtt_username", ""); }
std::string getMqttPassword() { return getValue("cloud", "mqtt_password", ""); }
int getHeartbeatInterval() { return getInt("cloud", "heartbeat_interval", 60); }
void setDeviceNo(const std::string& v) { setValue("cloud", "device_no", v); }
void setPlatformIp(const std::string& v) { setValue("cloud", "platform_ip", v); }
void setPlatformPort(int v) { setValue("cloud", "platform_port", std::to_string(v)); }
void setMqttIp(const std::string& v) { setValue("cloud", "mqtt_ip", v); }
void setMqttPort(int v) { setValue("cloud", "mqtt_port", std::to_string(v)); }
// ✅ 新增
void setMqttUsername(const std::string& v) { setValue("cloud", "mqtt_username", v); }
void setMqttPassword(const std::string& v) { setValue("cloud", "mqtt_password", v); }
void setHeartbeatInterval(int interval) { setValue("cloud", "heartbeat_interval", std::to_string(interval)); }
// ==================== Cockpit ====================
std::string getCockpitId() { return getValue("cockpit", "cockpit_id", "C010001"); }
std::string getCockpitMqttIp() { return getValue("cockpit", "mqtt_ip", "192.168.1.110"); }
int getCockpitMqttPort() { return getInt("cockpit", "mqtt_port", 1883); }
void setCockpitId(const std::string& v) { setValue("cockpit", "cockpit_id", v); }
void setCockpitMqttIp(const std::string& v) { setValue("cockpit", "mqtt_ip", v); }
void setCockpitMqttPort(int v) { setValue("cockpit", "mqtt_port", std::to_string(v)); }
// ==================== Serial ====================
std::string getSerialDev() { return getValue("serial", "dev_name", "/dev/ttyUSB3"); }
int getSerialBaudrate() { return getInt("serial", "baudrate", 115200); }
void setSerialDev(const std::string& v) { setValue("serial", "dev_name", v); }
void setSerialBaudrate(int v) { setValue("serial", "baudrate", std::to_string(v)); }
// ==================== TBox ====================
int getLoginSeq() { return getInt("tbox", "login_seq", 1); }
std::string getLoginSeqDate() { return getValue("tbox", "login_seq_date", "000000"); }
void setLoginSeq(int v) { setValue("tbox", "login_seq", std::to_string(v)); }
void setLoginSeqDate(const std::string& v) { setValue("tbox", "login_seq_date", v); }
// 保存当前内存 map 到文件
bool save()
{
std::lock_guard<std::mutex> lock(mtx);
std::ofstream ofs(file_path);
if (!ofs.is_open()) return false;
for (const auto& [section, items] : schema)
{
ofs << "[" << section << "]\n";
for (const auto& item : items)
{
ofs << item.key << "=" << getValueUnlocked(section, item.key, item.default_value) << "\n";
}
ofs << "\n";
}
return true;
}
private:
ConfigManager() = default;
~ConfigManager() = default;
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
struct ConfigItem
{
std::string key;
std::string default_value;
};
const std::vector<std::pair<std::string, std::vector<ConfigItem>>> schema = {
{"vehicle",
{
{"vin", "LSV1234567890KUNL"},
{"vehicle_id", "V010001"},
}},
{"cloud",
{
{"device_no", "KL001"},
{"platform_ip", "192.168.1.100"},
{"platform_port", "8888"},
{"mqtt_ip", "192.168.1.101"},
{"mqtt_port", "1883"},
{"mqtt_username", ""}, // ✅ 新增
{"mqtt_password", ""}, // ✅ 新增
{"heartbeat_interval", "60"},
}},
{"cockpit",
{
{"cockpit_id", "C010001"},
{"mqtt_ip", "192.168.1.110"},
{"mqtt_port", "1883"},
}},
{"serial",
{
{"dev_name", "/dev/ttyUSB3"},
{"baudrate", "115200"},
}},
{"tbox",
{
{"login_seq", "1"},
{"login_seq_date", "000000"},
}},
};
std::unique_ptr<INIReader> reader;
std::string file_path;
std::map<std::string, std::map<std::string, std::string>> sections;
std::mutex mtx; // 多线程保护
// 根据 INIReader 初始化 map
void buildMapFromReader()
{
sections.clear();
for (const auto& [section, items] : schema)
{
for (const auto& item : items)
{
if (reader) { sections[section][item.key] = reader->Get(section, item.key, item.default_value); }
else
{
sections[section][item.key] = item.default_value;
}
}
}
}
int getInt(const std::string& section, const std::string& key, int def)
{
std::string v = getValue(section, key, std::to_string(def));
try
{
size_t idx = 0;
int result = std::stoi(v, &idx);
if (idx != v.size()) throw std::invalid_argument("trailing chars");
return result;
}
catch (...)
{
// 自动纠错 + 修复配置
setValue(section, key, std::to_string(def));
return def;
}
}
bool getBool(const std::string& section, const std::string& key, bool def)
{
std::string v = getValue(section, key, def ? "1" : "0");
std::string lv = v;
std::transform(lv.begin(), lv.end(), lv.begin(), [](unsigned char c) { return std::tolower(c); });
if (lv == "1" || lv == "true" || lv == "yes") return true;
if (lv == "0" || lv == "false" || lv == "no") return false;
setValue(section, key, def ? "1" : "0");
return def;
}
std::string getValueUnlocked(const std::string& section, const std::string& key, const std::string& def)
{
auto& sec = sections[section];
auto it = sec.find(key);
if (it != sec.end()) return it->second;
sec[key] = def;
return def;
}
// 从 map 获取值
std::string getValue(const std::string& section, const std::string& key, const std::string& def)
{
std::lock_guard<std::mutex> lock(mtx);
return getValueUnlocked(section, key, def);
}
// 写入 map
void setValue(const std::string& section, const std::string& key, const std::string& value)
{
std::lock_guard<std::mutex> lock(mtx);
sections[section][key] = value;
}
};

12337
include/config/httplib.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,189 +0,0 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2025, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef INI_H
#define INI_H
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Visibility symbols, required for Windows DLLs */
#ifndef INI_API
#if defined _WIN32 || defined __CYGWIN__
# ifdef INI_SHARED_LIB
# ifdef INI_SHARED_LIB_BUILDING
# define INI_API __declspec(dllexport)
# else
# define INI_API __declspec(dllimport)
# endif
# else
# define INI_API
# endif
#else
# if defined(__GNUC__) && __GNUC__ >= 4
# define INI_API __attribute__ ((visibility ("default")))
# else
# define INI_API
# endif
#endif
#endif
/* Typedef for prototype of handler function.
Note that even though the value parameter has type "const char*", the user
may cast to "char*" and modify its content, as the value is not used again
after the call to ini_handler. This is not true of section and name --
those must not be modified.
*/
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
INI_API int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
which is already in memory. */
INI_API int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Same as ini_parse_string(), but takes a string and its length, avoiding
strlen(). Useful for parsing INI data from a network socket or which is
already in memory, or interfacing with C++ std::string_view. */
INI_API int ini_parse_string_length(const char* string, size_t length, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See https://github.com/benhoyt/inih/issues/21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
/* Nonzero to call the handler at the start of each new section (with
name and value NULL). Default is to only call the handler on
each name=value pair. */
#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
#define INI_CALL_HANDLER_ON_NEW_SECTION 0
#endif
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
call the handler with value NULL in this case. Default is to treat
no-value lines as an error. */
#ifndef INI_ALLOW_NO_VALUE
#define INI_ALLOW_NO_VALUE 0
#endif
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
allocation functions (INI_USE_STACK must also be 0). These functions must
have the same signatures as malloc/free/realloc and behave in a similar
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
#ifndef INI_CUSTOM_ALLOCATOR
#define INI_CUSTOM_ALLOCATOR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* INI_H */

View File

@ -4,8 +4,8 @@
#include <string> #include <string>
#include <thread> #include <thread>
#include "TboxConfigManager.h"
#include "can_bus_instance.h" #include "can_bus_instance.h"
#include "config.h"
#include "logger.h" #include "logger.h"
#include "mqtt_client_instance.h" #include "mqtt_client_instance.h"
#include "serial_instance.h" #include "serial_instance.h"

View File

@ -7,9 +7,10 @@
std::unique_ptr<MqttClient> mqtt_client_rc_ctrl; std::unique_ptr<MqttClient> mqtt_client_rc_ctrl;
static const std::string veh_rc_topic = "/kun/vehicle/ctrl/" + ConfigManager::instance().getVehicleId(); // 远控主题 static const std::string veh_rc_topic =
"/kun/vehicle/ctrl/" + TboxConfigManager::instance().getVehicleId(); // 远控主题
static const std::string veh_report_topic = static const std::string veh_report_topic =
"/kun/vehicle/info/" + ConfigManager::instance().getVehicleId(); // 状态上报主题 "/kun/vehicle/info/" + TboxConfigManager::instance().getVehicleId(); // 状态上报主题
std::atomic<bool> watchdogEnabled{false}; std::atomic<bool> watchdogEnabled{false};
std::atomic<bool> emergencyActive{false}; std::atomic<bool> emergencyActive{false};

View File

@ -66,8 +66,9 @@ static void serial_at_send_loop()
if (task.cmd == "AT+CCID") if (task.cmd == "AT+CCID")
{ {
init_tcp_client_tbox_router( init_tcp_client_tbox_router(
ConfigManager::instance().getPlatformIp(), TboxConfigManager::instance().getPlatformIp(),
ConfigManager::instance().getPlatformPort()); // CCID 获取失败,继续初始化 TBoxRouter TboxConfigManager::instance()
.getPlatformPort()); // CCID 获取失败,继续初始化 TBoxRouter
} }
it = at_tasks.erase(it); // 移除任务 it = at_tasks.erase(it); // 移除任务
@ -113,8 +114,8 @@ static void handle_serial_at_data(const std::string &data)
LOG_INFO(veh_rc_logger, "[serial_at] Extracted CCID = " + ccid); LOG_INFO(veh_rc_logger, "[serial_at] Extracted CCID = " + ccid);
init_tcp_client_tbox_router( init_tcp_client_tbox_router(
ConfigManager::instance().getPlatformIp(), TboxConfigManager::instance().getPlatformIp(),
ConfigManager::instance().getPlatformPort()); // 成功获取 CCID 后,初始化 TBoxRouter TboxConfigManager::instance().getPlatformPort()); // 成功获取 CCID 后,初始化 TBoxRouter
{ {
// 收到 CCID 响应,移除对应任务 // 收到 CCID 响应,移除对应任务

View File

@ -31,7 +31,7 @@ static const CmdMqttMap kCmdMqttTable[] = {
std::string build_mqtt_topic_by_cmd(uint8_t cmd_id) std::string build_mqtt_topic_by_cmd(uint8_t cmd_id)
{ {
const auto deviceNo = ConfigManager::instance().getDeviceNo(); const auto deviceNo = TboxConfigManager::instance().getDeviceNo();
for (const auto& it : kCmdMqttTable) for (const auto& it : kCmdMqttTable)
{ {

View File

@ -6,7 +6,7 @@ static std::string build_packet(uint8_t command_id, F&& fill_data_unit)
FullPacket pkt; FullPacket pkt;
pkt.command_id = command_id; pkt.command_id = command_id;
pkt.response_flag = 0xFE; pkt.response_flag = 0xFE;
pkt.vin = ConfigManager::instance().getVin(); pkt.vin = TboxConfigManager::instance().getVin();
pkt.encryption_method = 0x01; pkt.encryption_method = 0x01;
pkt.data_unit.clear(); pkt.data_unit.clear();
@ -46,8 +46,8 @@ static std::string build_login_request()
<< (int)time_bytes[2]; << (int)time_bytes[2];
std::string date_str = oss.str(); std::string date_str = oss.str();
auto last_login_date = ConfigManager::instance().getLoginSeqDate(); auto last_login_date = TboxConfigManager::instance().getLoginSeqDate();
int last_login_seq = ConfigManager::instance().getLoginSeq(); int last_login_seq = TboxConfigManager::instance().getLoginSeq();
uint16_t login_seq = 1; uint16_t login_seq = 1;
if (last_login_date == date_str) if (last_login_date == date_str)
@ -55,9 +55,9 @@ static std::string build_login_request()
login_seq = (last_login_seq >= 65531) ? 1 : last_login_seq + 1; login_seq = (last_login_seq >= 65531) ? 1 : last_login_seq + 1;
} }
ConfigManager::instance().setLoginSeqDate(date_str); TboxConfigManager::instance().setLoginSeqDate(date_str);
ConfigManager::instance().setLoginSeq(login_seq); TboxConfigManager::instance().setLoginSeq(login_seq);
ConfigManager::instance().save(); TboxConfigManager::instance().commit();
du.push_back((login_seq >> 8) & 0xFF); du.push_back((login_seq >> 8) & 0xFF);
du.push_back(login_seq & 0xFF); du.push_back(login_seq & 0xFF);

View File

@ -37,7 +37,7 @@ static void handle_login_request(const ReceivedPacket& packet)
while (true) while (true)
{ {
int interval = ConfigManager::instance().getHeartbeatInterval(); int interval = TboxConfigManager::instance().getHeartbeatInterval();
auto client = wp.lock(); auto client = wp.lock();
if (!client) if (!client)
{ {
@ -240,8 +240,8 @@ static void handle_tbox_status(bool connected)
// 断线后,重新请求网关地址 // 断线后,重新请求网关地址
if (!tcp_client_router || !tcp_client_router->is_connected()) if (!tcp_client_router || !tcp_client_router->is_connected())
{ {
init_tcp_client_tbox_router(ConfigManager::instance().getPlatformIp(), init_tcp_client_tbox_router(TboxConfigManager::instance().getPlatformIp(),
ConfigManager::instance().getPlatformPort()); TboxConfigManager::instance().getPlatformPort());
} }
} }
} }

View File

@ -1,206 +0,0 @@
// Read an INI file into easy-to-access name/value pairs.
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (C) 2009-2025, Ben Hoyt
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
// Go to the project home page for more info:
//
// https://github.com/benhoyt/inih
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include "ini.h"
#include "INIReader.h"
using std::string;
INIReader::INIReader(const string &filename)
{
_error = ini_parse(filename.c_str(), ValueHandler, this);
}
INIReader::INIReader(const char *buffer, size_t buffer_size)
{
_error = ini_parse_string_length(buffer, buffer_size, ValueHandler, this);
}
int INIReader::ParseError() const
{
return _error;
}
string INIReader::ParseErrorMessage() const
{
// If _error is positive it means it is the line number on which a parse
// error occurred. This could be an overlong line, that ValueHandler
// indicated a user defined error, an unterminated section name, or a name
// without a value.
if (_error > 0)
{
return "parse error on line " + std::to_string(_error) + "; missing ']' or '='?";
}
// If _error is negative it is a system type error, and 0 means success.
switch (_error)
{
case -2:
return "unable to allocate memory";
case -1:
return "unable to open file";
case 0:
return "";
}
// This should never be reached. It probably means a new error code was
// added to the C API without updating this method.
return "unknown error " + std::to_string(_error);
}
string INIReader::Get(const string &section, const string &name, const string &default_value) const
{
string key = MakeKey(section, name);
// Use _values.find() here instead of _values.at() to support pre C++11 compilers
return _values.count(key) ? _values.find(key)->second : default_value;
}
string INIReader::GetString(const string &section, const string &name, const string &default_value) const
{
const string str = Get(section, name, "");
return str.empty() ? default_value : str;
}
long INIReader::GetInteger(const string &section, const string &name, long default_value) const
{
string valstr = Get(section, name, "");
const char *value = valstr.c_str();
char *end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
long n = strtol(value, &end, 0);
return end > value ? n : default_value;
}
INI_API int64_t INIReader::GetInteger64(const string &section, const string &name, int64_t default_value) const
{
string valstr = Get(section, name, "");
const char *value = valstr.c_str();
char *end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
int64_t n = strtoll(value, &end, 0);
return end > value ? n : default_value;
}
unsigned long INIReader::GetUnsigned(const string &section, const string &name, unsigned long default_value) const
{
string valstr = Get(section, name, "");
const char *value = valstr.c_str();
char *end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
unsigned long n = strtoul(value, &end, 0);
return end > value ? n : default_value;
}
INI_API uint64_t INIReader::GetUnsigned64(const string &section, const string &name, uint64_t default_value) const
{
string valstr = Get(section, name, "");
const char *value = valstr.c_str();
char *end;
// This parses "1234" (decimal) and also "0x4D2" (hex)
uint64_t n = strtoull(value, &end, 0);
return end > value ? n : default_value;
}
double INIReader::GetReal(const string &section, const string &name, double default_value) const
{
string valstr = Get(section, name, "");
const char *value = valstr.c_str();
char *end;
double n = strtod(value, &end);
return end > value ? n : default_value;
}
bool INIReader::GetBoolean(const string &section, const string &name, bool default_value) const
{
string valstr = Get(section, name, "");
// Convert to lower case to make string comparisons case-insensitive
std::transform(valstr.begin(), valstr.end(), valstr.begin(),
[](const unsigned char &ch)
{ return static_cast<unsigned char>(::tolower(ch)); });
if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
return true;
else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
return false;
else
return default_value;
}
std::vector<string> INIReader::Sections() const
{
std::set<string> sectionSet;
for (std::map<string, string>::const_iterator it = _values.begin(); it != _values.end(); ++it)
{
size_t pos = it->first.find('=');
if (pos != string::npos)
{
sectionSet.insert(it->first.substr(0, pos));
}
}
return std::vector<string>(sectionSet.begin(), sectionSet.end());
}
std::vector<string> INIReader::Keys(const string &section) const
{
std::vector<string> keys;
string keyPrefix = MakeKey(section, "");
for (std::map<string, string>::const_iterator it = _values.begin(); it != _values.end(); ++it)
{
if (it->first.compare(0, keyPrefix.length(), keyPrefix) == 0)
{
keys.push_back(it->first.substr(keyPrefix.length()));
}
}
return keys;
}
bool INIReader::HasSection(const string &section) const
{
const string key = MakeKey(section, "");
std::map<string, string>::const_iterator pos = _values.lower_bound(key);
if (pos == _values.end())
return false;
// Does the key at the lower_bound pos start with "section"?
return pos->first.compare(0, key.length(), key) == 0;
}
bool INIReader::HasValue(const string &section, const string &name) const
{
string key = MakeKey(section, name);
return _values.count(key);
}
string INIReader::MakeKey(const string &section, const string &name)
{
string key = section + "=" + name;
// Convert to lower case to make section/name lookups case-insensitive
std::transform(key.begin(), key.end(), key.begin(),
[](const unsigned char &ch)
{ return static_cast<unsigned char>(::tolower(ch)); });
return key;
}
int INIReader::ValueHandler(void *user, const char *section, const char *name,
const char *value)
{
if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled
return 1;
INIReader *reader = static_cast<INIReader *>(user);
string key = MakeKey(section, name);
if (reader->_values[key].size() > 0)
reader->_values[key] += "\n";
reader->_values[key] += value ? value : "";
return 1;
}

View File

@ -0,0 +1,89 @@
#include "TboxConfigClient.h"
#include "httplib.h"
// ================= 构造 =================
TboxConfigClient::TboxConfigClient(std::string server, int port) : server_(std::move(server)), port_(port) {}
// ================= 拉取配置 =================
bool TboxConfigClient::fetch()
{
httplib::Client cli(server_, port_);
auto res = cli.Get("/api/config");
if (!res || res->status != 200) return false;
auto json = nlohmann::json::parse(res->body);
if (!json.contains("tbox") || !json.contains("version")) return false;
cache_.clear();
for (auto& [k, v] : json["tbox"].items()) { cache_[k] = v; }
version_ = json["version"].get<uint64_t>();
dirty_ = false;
return true;
}
uint64_t TboxConfigClient::version() const { return version_; }
bool TboxConfigClient::isDirty() const { return dirty_; }
// ================= 读取 =================
std::optional<std::string> TboxConfigClient::getString(std::string_view semantic) const
{
auto it = cache_.find(std::string(semantic));
if (it == cache_.end() || !it->second.is_string()) return std::nullopt;
return it->second.get<std::string>();
}
std::optional<int> TboxConfigClient::getInt(std::string_view semantic) const
{
auto it = cache_.find(std::string(semantic));
if (it == cache_.end() || !it->second.is_number_integer()) return std::nullopt;
return it->second.get<int>();
}
std::optional<bool> TboxConfigClient::getBool(std::string_view semantic) const
{
auto it = cache_.find(std::string(semantic));
if (it == cache_.end() || !it->second.is_boolean()) return std::nullopt;
return it->second.get<bool>();
}
// ================= 修改(仅本地 staging =================
void TboxConfigClient::setString(std::string_view semantic, const std::string& v)
{
pending_[std::string(semantic)] = v;
dirty_ = true;
}
void TboxConfigClient::setInt(std::string_view semantic, int v)
{
pending_[std::string(semantic)] = v;
dirty_ = true;
}
void TboxConfigClient::setBool(std::string_view semantic, bool v)
{
pending_[std::string(semantic)] = v;
dirty_ = true;
}
// ================= 提交并确认 =================
bool TboxConfigClient::commitAndFetch()
{
if (pending_.empty()) return true;
nlohmann::json body;
body["changes"] = pending_;
body["base_version"] = version_;
httplib::Client cli(server_, port_);
auto res = cli.Post("/api/config/update", body.dump(), "application/json");
if (!res || res->status != 200) return false;
pending_.clear();
// 🔴 关键点:立刻重新拉取
return fetch();
}

View File

@ -1,326 +0,0 @@
/* inih -- simple .INI file parser
SPDX-License-Identifier: BSD-3-Clause
Copyright (C) 2009-2025, Ben Hoyt
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#if INI_CUSTOM_ALLOCATOR
#include <stddef.h>
void* ini_malloc(size_t size);
void ini_free(void* ptr);
void* ini_realloc(void* ptr, size_t size);
#else
#include <stdlib.h>
#define ini_malloc malloc
#define ini_free free
#define ini_realloc realloc
#endif
#endif
#define MAX_SECTION 50
#define MAX_NAME 50
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. end must be a
pointer to the NUL terminator at the end of the string. Return s. */
static char* ini_rstrip(char* s, char* end)
{
while (end > s && isspace((unsigned char)(*--end)))
*end = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* ini_lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to NUL at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* ini_find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Similar to strncpy, but ensures dest (size bytes) is
NUL-terminated, and doesn't pad with NULs. */
static char* ini_strncpy0(char* dest, const char* src, size_t size)
{
/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
size_t i;
for (i = 0; i < size - 1 && src[i]; i++)
dest[i] = src[i];
dest[i] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
size_t max_line = INI_MAX_LINE;
#else
char* line;
size_t max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC && !INI_USE_STACK
char* new_line;
#endif
char section[MAX_SECTION] = "";
#if INI_ALLOW_MULTILINE
char prev_name[MAX_NAME] = "";
#endif
size_t offset;
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
char abyss[16]; /* Used to consume input when a line is too long. */
#if !INI_USE_STACK
line = (char*)ini_malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, (int)max_line, stream) != NULL) {
offset = strlen(line);
#if INI_ALLOW_REALLOC && !INI_USE_STACK
while (max_line < INI_MAX_LINE &&
offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = ini_realloc(line, max_line);
if (!new_line) {
ini_free(line);
return -2;
}
line = new_line;
if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
/* If line exceeded INI_MAX_LINE bytes, discard till end of line. */
if (offset == max_line - 1 && line[offset - 1] != '\n') {
while (reader(abyss, sizeof(abyss), stream) != NULL) {
if (!error)
error = lineno;
if (abyss[strlen(abyss) - 1] == '\n')
break;
}
}
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = ini_rstrip(ini_lskip(start), line + offset);
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
#if INI_ALLOW_INLINE_COMMENTS
end = ini_find_chars_or_comment(start, NULL);
*end = '\0';
ini_rstrip(start, end);
#endif
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = ini_find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
ini_strncpy0(section, start + 1, sizeof(section));
#if INI_ALLOW_MULTILINE
*prev_name = '\0';
#endif
#if INI_CALL_HANDLER_ON_NEW_SECTION
if (!HANDLER(user, section, NULL, NULL) && !error)
error = lineno;
#endif
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = ini_find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = ini_rstrip(start, end);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = ini_find_chars_or_comment(value, NULL);
*end = '\0';
#endif
value = ini_lskip(value);
ini_rstrip(value, end);
#if INI_ALLOW_MULTILINE
ini_strncpy0(prev_name, name, sizeof(prev_name));
#endif
/* Valid name[=:]value pair found, call handler */
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else {
/* No '=' or ':' found on name[=:]value line */
#if INI_ALLOW_NO_VALUE
*end = '\0';
name = ini_rstrip(start, end);
if (!HANDLER(user, section, name, NULL) && !error)
error = lineno;
#else
if (!error)
error = lineno;
#endif
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
ini_free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
return ini_parse_string_length(string, strlen(string), handler, user);
}
/* See documentation in header file. */
int ini_parse_string_length(const char* string, size_t length,
ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = length;
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

View File

@ -1,87 +1,35 @@
#include "main.h" #include "main.h"
#include <unistd.h>
#include <csignal> #include <csignal>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <thread> #include <thread>
// ----------------------------------------------------------------------------- // ==========================
// 命令行参数解析 // 命令行参数
// ----------------------------------------------------------------------------- // ==========================
struct CmdOptions struct CmdOptions
{ {
bool show_help = false; bool show_help = false;
bool show_version = false; bool show_version = false;
bool init_config = false;
std::string config_path;
}; };
CmdOptions parseArgs(int argc, char* argv[]) CmdOptions parseArgs(int argc, char* argv[])
{ {
CmdOptions opts; CmdOptions opts;
if (argc == 1)
{
// 默认情况:使用当前目录下的 config.ini
opts.config_path = "./config.ini";
return opts;
}
for (int i = 1; i < argc; ++i) for (int i = 1; i < argc; ++i)
{ {
std::string arg = argv[i]; std::string arg = argv[i];
if (arg == "-h" || arg == "--help") if (arg == "-h" || arg == "--help") { opts.show_help = true; }
{ else if (arg == "-v" || arg == "--version") { opts.show_version = true; }
if (opts.show_help || opts.show_version || !opts.config_path.empty())
{
std::cerr << "Error: duplicate or conflicting options.\n";
exit(1);
}
opts.show_help = true;
}
else if (arg == "-v" || arg == "--version")
{
if (opts.show_help || opts.show_version || !opts.config_path.empty())
{
std::cerr << "Error: duplicate or conflicting options.\n";
exit(1);
}
opts.show_version = true;
}
else if (arg == "-c" || arg == "--config")
{
if (!opts.config_path.empty() || opts.show_help || opts.show_version)
{
std::cerr << "Error: duplicate or conflicting options.\n";
exit(1);
}
if (i + 1 < argc) { opts.config_path = argv[++i]; }
else
{
std::cerr << "Error: missing config file path after " << arg << "\n";
exit(1);
}
}
else if (arg == "--init-config")
{
if (opts.show_help || opts.show_version || opts.init_config)
{
std::cerr << "Error: duplicate or conflicting options.\n";
exit(1);
}
opts.init_config = true;
}
else else
{ {
std::cerr << "Unknown option: " << arg << "\n"; std::cerr << "Unknown option: " << arg << "\n";
std::cerr << "Use -h to see available options.\n"; std::cerr << "Use -h or --help for usage.\n";
exit(1); std::exit(1);
} }
} }
@ -102,28 +50,9 @@ int main(int argc, char* argv[])
{ {
CmdOptions opts = parseArgs(argc, argv); CmdOptions opts = parseArgs(argc, argv);
if (opts.init_config)
{
std::string path = opts.config_path.empty() ? "./config.ini" : opts.config_path;
if (std::filesystem::exists(path))
{
std::cerr << "Config file already exists at " << path << "\n";
return 1;
}
ConfigManager::instance().load(path); // 使用 schema 默认值
ConfigManager::instance().save(); // 写出完整配置
std::cout << "Default config file created at:\n " << path << "\n";
std::cout << "Please review and edit it before running the app.\n";
return 0;
}
if (opts.show_help) if (opts.show_help)
{ {
std::cout << "Usage: " << argv[0] << " [-v] [-h] [-c <config_path>]\n"; std::cout << "Usage: tbox [-h] [-v]\n";
std::cout << "If no arguments are given, './config.ini' will be used by default.\n";
return 0; return 0;
} }
@ -133,34 +62,40 @@ int main(int argc, char* argv[])
return 0; return 0;
} }
if (!opts.config_path.empty()) // =========================================================
// 1⃣ 初始化 TBox 配置客户端Config Server 是前提)
// =========================================================
LOG_INFO(tbox_logger, "Initializing TboxConfigManager...");
if (!TboxConfigManager::instance().init("127.0.0.1", 18080))
{ {
if (!std::filesystem::exists(opts.config_path)) LOG_ERROR(tbox_logger, "Failed to connect config server, exit");
{
std::cerr << "Error: config file not found at " << opts.config_path << "\n";
return 1; return 1;
} }
std::cout << "Using config file: " << opts.config_path << "\n";
ConfigManager::instance().load(opts.config_path);
}
// 启动线程 LOG_INFO(tbox_logger, "Config loaded, version=" + std::to_string(TboxConfigManager::instance().version()));
// init_can_bus_rc_ctrl("can0"); // 初始化远控 CAN 总线
// init_tcp_server_tbox_autodata("0.0.0.0", 50018); // 获取自驾数据
// init_tcp_client_vehicle_position("192.168.1.151", 3333); // 获取定位数据
// init_serial_at(ConfigManager::instance().getSerialDev(), ConfigManager::instance().getSerialBaudrate()); // =========================================================
// 2⃣ 启动各业务模块(开始真正运行)
// =========================================================
init_can_bus_rc_ctrl("can0"); // 初始化远控 CAN 总线
init_tcp_server_tbox_autodata("0.0.0.0", 50018); // 获取自驾数据
init_tcp_client_vehicle_position("192.168.1.151", 3333); // 获取定位数据
init_serial_at("/dev/ttyUSB3", 115200);
init_tcp_server_tbox_v2v("0.0.0.0", 10005); // 建立与域控间的V2V链路 init_tcp_server_tbox_v2v("0.0.0.0", 10005); // 建立与域控间的V2V链路
init_mqtt_client_tbox_v2v(ConfigManager::instance().getMqttIp(), ConfigManager::instance().getMqttPort(), init_mqtt_client_tbox_v2v(TboxConfigManager::instance().getMqttIp(), TboxConfigManager::instance().getMqttPort(),
ConfigManager::instance().getMqttUsername(), TboxConfigManager::instance().getMqttUsername(),
ConfigManager::instance().getMqttPassword()); // 连接平台V2V MQTT服务器 TboxConfigManager::instance().getMqttPassword()); // 连接平台V2V MQTT服务器
// init_mqtt_client_veh_rc(ConfigManager::instance().getCockpitMqttIp(), init_mqtt_client_veh_rc(TboxConfigManager::instance().getCockpitMqttIp(),
// ConfigManager::instance().getCockpitMqttPort()); // 连接台架的 MQTT 服务器 TboxConfigManager::instance().getCockpitMqttPort()); // 连接台架的 MQTT 服务器
// 阻塞 // =========================================================
// 3⃣ 主循环
// =========================================================
while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); } while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); }
return 0; return 0;