init: kunlang tbox base project

This commit is contained in:
cxh 2025-12-11 09:08:35 +08:00
commit 592f33ecf8
47 changed files with 6366 additions and 0 deletions

19
.clang-format Normal file
View File

@ -0,0 +1,19 @@
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 120
BreakBeforeBraces: Allman
# 允许短语句保持在单行
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
# 控制大括号换行策略
BraceWrapping:
AfterCaseLabel: false
AfterControlStatement: false
AfterFunction: true
AfterStruct: true
BeforeElse: true

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
# build artifacts
/build/
bin/
# cmake
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
.ninja*
compile_commands.json
# editor
.vscode/
.idea/
# logs
*.log
# OS
.DS_Store

108
CMakeLists.txt Normal file
View File

@ -0,0 +1,108 @@
cmake_minimum_required(VERSION 3.12)
project(kunlang_remake LANGUAGES C CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# =================================================
#
# =================================================
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG")
set(CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG")
# Release 便 gdb
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -g -DNDEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O2 -g -DNDEBUG")
# =================================================
#
# =================================================
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
# /bin
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
# =================================================
# /
# =================================================
#
add_compile_options(
-Wall
-Wextra
-ffunction-sections
-fdata-sections
)
#
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options(-O2)
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-O0)
endif()
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -static -Wl,--gc-sections"
)
# =================================================
#
# =================================================
file(GLOB_RECURSE SOURCES_CPP CONFIGURE_DEPENDS src/*.cpp)
file(GLOB_RECURSE SOURCES_C CONFIGURE_DEPENDS src/*.c)
set(SOURCES ${SOURCES_CPP} ${SOURCES_C})
add_executable(${PROJECT_NAME} ${SOURCES})
# =================================================
#
# =================================================
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/include/common
${CMAKE_SOURCE_DIR}/include/network
${CMAKE_SOURCE_DIR}/include/can
${CMAKE_SOURCE_DIR}/include/serial
${CMAKE_SOURCE_DIR}/include/protocol
${CMAKE_SOURCE_DIR}/include/business
${CMAKE_SOURCE_DIR}/include/business/instance
${CMAKE_SOURCE_DIR}/include/business/remote_ctrl
${CMAKE_SOURCE_DIR}/include/business/tbox
${CMAKE_SOURCE_DIR}/include/config
$ENV{HOME}/aarch64_env
$ENV{HOME}/aarch64_env/openssl/include
$ENV{HOME}/aarch64_env/paho/include
)
# =================================================
#
# =================================================
target_compile_options(${PROJECT_NAME} PRIVATE
-static
-static-libstdc++
-static-libgcc
)
# =================================================
#
# =================================================
target_link_libraries(${PROJECT_NAME}
$ENV{HOME}/aarch64_env/toolchain/aarch64-linux-musl/lib/libpthread.a
$ENV{HOME}/aarch64_env/toolchain/aarch64-linux-musl/lib/libdl.a
$ENV{HOME}/aarch64_env/toolchain/aarch64-linux-musl/lib/libm.a
$ENV{HOME}/aarch64_env/toolchain/aarch64-linux-musl/lib/libc.a
# $ENV{HOME}/aarch64_env/paho/lib/libpaho-mqtt3as.a
$ENV{HOME}/aarch64_env/paho/lib/libpaho-mqtt3a.a
$ENV{HOME}/aarch64_env/paho/lib/libpaho-mqttpp3.a
# $ENV{HOME}/aarch64_env/openssl/lib/libssl.a
# $ENV{HOME}/aarch64_env/openssl/lib/libcrypto.a
)

View File

@ -0,0 +1,34 @@
# ===== =====
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
# ===== =====
set(TOOLCHAIN_DIR $ENV{HOME}/aarch64_env/toolchain)
if(NOT EXISTS "${TOOLCHAIN_DIR}")
message(FATAL_ERROR "TOOLCHAIN_DIR not found: ${TOOLCHAIN_DIR}")
endif()
# ===== sysroot =====
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/aarch64-linux-musl)
# ===== =====
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-linux-musl-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-linux-musl-g++)
# ===== binutils=====
set(CMAKE_AR ${TOOLCHAIN_DIR}/bin/aarch64-linux-musl-ar)
set(CMAKE_NM ${TOOLCHAIN_DIR}/bin/aarch64-linux-musl-nm)
set(CMAKE_RANLIB ${TOOLCHAIN_DIR}/bin/aarch64-linux-musl-ranlib)
set(CMAKE_STRIP ${TOOLCHAIN_DIR}/bin/aarch64-linux-musl-strip)
# ===== =====
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
set(BUILD_SHARED_LIBS OFF)
# ===== 宿=====
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

View File

@ -0,0 +1,6 @@
#include "can_bus.h"
#include "main.h"
extern std::unique_ptr<CanBus> can_bus_rc_ctrl; // 远控 CAN 总线实例
void init_can_bus_rc_ctrl(const std::string &interface_name);

View File

@ -0,0 +1,12 @@
#pragma once
#include "main.h"
#include "mqtt_client.h"
extern std::unique_ptr<MqttClient> mqtt_client_tbox_v2v;
void init_mqtt_client_veh_rc(const std::string& ip, int port); // 初始化车辆远控 MQTT 客户端
void init_mqtt_client_tbox_v2v(const std::string& ip, int port, const std::string& username,
const std::string& password); // 初始化云控平台车-车 MQTT 客户端
std::string build_mqtt_topic_by_cmd(uint8_t cmd_id);

View File

@ -0,0 +1,7 @@
#include "main.h"
#include "serial_port.h"
#include "tcp_client_instance.h"
void init_serial_at(const std::string &device, int baudrate);
extern std::string ICCID;

View File

@ -0,0 +1,13 @@
#pragma once
#include "main.h"
#include "tbox_messages.h"
#include "tcp_client.h"
extern std::shared_ptr<TcpClient> tcp_client_router;
extern std::shared_ptr<TcpClient> tcp_client_gateway;
extern std::unique_ptr<TcpClient> tcp_client_vehicle_position;
void init_tcp_client_tbox_router(const std::string& ip, int port); // 初始化 TBoxRouter TCP 客户端
void init_tcp_client_tbox_gateway(const std::string& ip, int port); // 初始化 TBoxGateway TCP 客户端
void init_tcp_client_vehicle_position(const std::string& ip, int port);

View File

@ -0,0 +1,11 @@
#pragma once
#include "main.h"
#include "tbox_messages.h"
#include "tcp_server.h"
extern std::unique_ptr<TcpServer> tcp_server_tbox_v2v;
extern std::unique_ptr<TcpServer> tcp_server_tbox_autodata;
void init_tcp_server_tbox_v2v(const std::string& ip, int port); // 初始化 TCP 服务器实例
void init_tcp_server_tbox_autodata(const std::string& ip, int port); // 初始化 TBoxAutodata TCP 服务器

View File

@ -0,0 +1,194 @@
#pragma once
#include <linux/can.h>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include <unordered_map>
#include <vector>
// 远程控车结构体
typedef struct
{
// Byte 1
uint8_t throttlePercent; // 油门开度, 0~100%
// Byte 2-3
int16_t steeringAngle; // 目标转向角度, 单位:度或千分度
// Byte 4
uint8_t brakePercent; // 制动踏板开度, 0~100%
uint8_t targetGear = 0;
// Byte 5
uint8_t gear : 2; // 档位: 0=P,1=R,2=N,3=D
uint8_t steeringMode : 2; // 转向模式: 0=八字,1=斜行,2=前半八,3=后半八
uint8_t reserved1 : 1; // 保留
uint8_t highVoltage : 1; // 高压控制: 0=上电,1=下电
uint8_t resetFault : 1; // 智驾故障复位: 0=无效,1=有效
uint8_t overload : 1; // 重载信号: 0=轻载,1=重载
// Byte 6
uint8_t hazardLights : 1; // 双闪灯
uint8_t turnLeft : 1; // 左转向灯
uint8_t turnRight : 1; // 右转向灯
uint8_t horn : 1; // 喇叭
uint8_t eStop : 1; // 急停按钮
uint8_t drivingLights : 1; // 行车灯
uint8_t outlineLights : 1; // 示廓灯
uint8_t autoStandby : 1; // 智驾待机模式
// Byte 7
uint8_t frontLowBeam : 1; // 前近光灯
uint8_t frontHighBeam : 1; // 前远光灯
uint8_t rearLowBeam : 1; // 后近光灯
uint8_t rearHighBeam : 1; // 后远光灯
uint8_t fogLamp : 1; // 雾灯
uint8_t reserved2 : 3; // 保留
// Byte 8
uint8_t heartbeat : 4; // 心跳计数
uint8_t remoteMode : 1; // 远程接管模式
uint8_t vehicleLock : 1; // 锁车状态
uint8_t reserved3 : 2; // 保留
struct can_frame packCANFrame(uint32_t can_id, bool extended = true) const
{
struct can_frame frame;
frame.can_dlc = 8; // CAN 数据长度固定 8
if (extended)
frame.can_id = can_id | CAN_EFF_FLAG;
else
frame.can_id = can_id; // 标准帧
frame.data[0] = throttlePercent;
frame.data[1] = steeringAngle & 0xFF; // 低字节
frame.data[2] = (steeringAngle >> 8) & 0xFF; // 高字节
frame.data[3] = brakePercent;
frame.data[4] = (gear & 0x03) | ((steeringMode & 0x03) << 2) | ((reserved1 & 0x01) << 4) |
((highVoltage & 0x01) << 5) | ((resetFault & 0x01) << 6) | ((overload & 0x01) << 7);
frame.data[5] = (hazardLights & 0x01) | ((turnLeft & 0x01) << 1) | ((turnRight & 0x01) << 2) |
((horn & 0x01) << 3) | ((eStop & 0x01) << 4) | ((drivingLights & 0x01) << 5) |
((outlineLights & 0x01) << 6) | ((autoStandby & 0x01) << 7);
frame.data[6] = (frontLowBeam & 0x01) | ((frontHighBeam & 0x01) << 1) | ((rearLowBeam & 0x01) << 2) |
((rearHighBeam & 0x01) << 3) | ((fogLamp & 0x01) << 4) | ((reserved2 & 0x07) << 5);
frame.data[7] =
(heartbeat & 0x0F) | ((remoteMode & 0x01) << 4) | ((vehicleLock & 0x01) << 5) | ((reserved3 & 0x03) << 6);
return frame;
}
} Remote_Ctl_Def;
// 车辆信息上报结构体
typedef struct
{
int type = 0; // 车辆类型0徐工集卡 1:风润
int mode = 0; // 控制模式0手动驾驶1自动驾驶2遥控驾驶3远程驾驶
int driveMode = 0; // 行车模式1位置2后半八3前半八4八字5 斜行
int gear = 0; // 档位0N档1D档2R档3P档
int speed = 0; // 车辆速度单位km/h
int horn = 0; // 喇叭01
int turnLight = 0; // 转向灯01左转2右转
int load = 0; // 重载信号0轻载1重载
int voltage = 0; // 高压上下电0下电1上电
int positionLight = 0; // 示廓灯01
int warning = 0; // 双闪01
int fogLight = 0; // 雾灯01
int headlamp = 0; // 前大灯01近光灯2远光灯
int tailLight = 0; // 后大灯01近光灯2远光灯
int lock = 0; // 锁车0解锁1上锁
int mileage = 0; // 累计里程单位km
int power = 0; // 电量范围0~100单位%
long long timestamp = 0; // 上报时间戳13位精确到ms
int emergencyStop = 0; // 急停
int faultReset = 0; // 智驾故障复位
int standbyMode = 0; // 智驾待机模式
std::string to_json()
{
// 自动更新时间戳
timestamp =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
.count();
nlohmann::json j;
j["type"] = type;
j["mode"] = mode;
j["driveMode"] = driveMode;
j["gear"] = gear;
j["speed"] = speed;
j["horn"] = horn;
j["turnLight"] = turnLight;
j["load"] = load;
j["voltage"] = voltage;
j["positionLight"] = positionLight;
j["warning"] = warning;
j["fogLight"] = fogLight;
j["headlamp"] = headlamp;
j["tailLight"] = tailLight;
j["lock"] = lock;
j["mileage"] = mileage;
j["power"] = power;
j["timestamp"] = timestamp;
j["emergencyStop"] = emergencyStop;
j["faultReset"] = faultReset;
j["standbyMode"] = standbyMode;
return j.dump();
}
} VehicleInfoReport;
// MQTT 指令列表宏
#define COMMAND_LIST(X) \
X(Steering, "steering") /*转向*/ \
X(Throttle, "throttle") /*油门*/ \
X(Brake, "brake") /*刹车*/ \
X(Horn, "horn") /*喇叭*/ \
X(TurnLight, "turnLight") /*转向灯*/ \
X(EmergencyStop, "emergencyStop") /*急停*/ \
X(Mode, "mode") /*使能模式*/ \
X(Gear, "gear") /*挡位*/ \
X(DriveMode, "driveMode") /*行车模式*/ \
X(Load, "load") /*重载信号*/ \
X(Voltage, "voltage") /*高压上下电*/ \
X(PositionLight, "positionLight") /*示廓灯*/ \
X(Warning, "warning") /*双闪*/ \
X(FogLight, "fogLight") /*雾灯*/ \
X(Headlamp, "headlamp") /*前大灯*/ \
X(TailLight, "tailLight") /*后大灯*/ \
X(Lock, "lock") /*锁车*/ \
X(StopCtrl, "stopCtrl") /*停止远控*/ \
X(StartCtrl, "startCtrl") /*开始远控*/
// 枚举类型定义
enum class CommandType
{
#define GEN_ENUM(name, str) name,
COMMAND_LIST(GEN_ENUM)
#undef GEN_ENUM
Unknown
};
// 字符串到枚举的映射
static const std::unordered_map<std::string, CommandType> command_map = {
#define GEN_MAP(name, str) {str, CommandType::name},
COMMAND_LIST(GEN_MAP)
#undef GEN_MAP
};
extern Remote_Ctl_Def veh_rc_data;
extern std::mutex veh_rc_mutex;
extern VehicleInfoReport veh_report_data;
extern std::mutex veh_report_mutex;

View File

@ -0,0 +1,749 @@
#pragma once
#include <linux/can.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>
#include "utils.h"
// 整车数据
struct VehicleData
{
uint8_t vehicle_status; // 车辆状态
uint8_t charge_status; // 充电状态
uint8_t run_mode; // 运行模式
uint16_t speed; // 车速
uint32_t mileage; // 累计里程
uint16_t total_voltage; // 总电压
uint16_t total_current; // 总电流
uint8_t soc; // 电池SOC
uint8_t dc_dc_status; // DC-DC状态
uint8_t gear; // 档位
uint16_t insulation_resistance; // 绝缘电阻
uint8_t accel_pedal; // 油门踏板
uint8_t brake_pedal; // 制动踏板
VehicleData() { std::memset(this, 0xFF, sizeof(VehicleData)); }
// TLV 编码
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
// 类型固定为 0x01整车数据
result.push_back(0x01);
// 按字段顺序打包数据
result.push_back(vehicle_status);
result.push_back(charge_status);
result.push_back(run_mode);
result.push_back((speed >> 8) & 0xFF); // 高字节
result.push_back(speed & 0xFF); // 低字节
result.push_back((mileage >> 24) & 0xFF);
result.push_back((mileage >> 16) & 0xFF);
result.push_back((mileage >> 8) & 0xFF);
result.push_back(mileage & 0xFF);
result.push_back((total_voltage >> 8) & 0xFF);
result.push_back(total_voltage & 0xFF);
result.push_back((total_current >> 8) & 0xFF);
result.push_back(total_current & 0xFF);
result.push_back(soc);
result.push_back(dc_dc_status);
result.push_back(gear);
result.push_back((insulation_resistance >> 8) & 0xFF);
result.push_back(insulation_resistance & 0xFF);
result.push_back(accel_pedal);
result.push_back(brake_pedal);
return result;
}
};
// 电机数据
struct MotorData
{
uint8_t seq; // 驱动电机序号
uint8_t status; // 电机状态
uint8_t controller_temp; // 控制器温度
uint16_t speed; // 转速
uint16_t torque; // 转矩
uint8_t motor_temp; // 电机温度
uint16_t input_voltage; // 控制器输入电压
uint16_t dc_current; // 控制器直流母线电流
MotorData() { std::memset(this, 0xFF, sizeof(MotorData)); }
// 编码单个电机
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(seq);
result.push_back(status);
result.push_back(controller_temp);
result.push_back((speed >> 8) & 0xFF);
result.push_back(speed & 0xFF);
result.push_back((torque >> 8) & 0xFF);
result.push_back(torque & 0xFF);
result.push_back(motor_temp);
result.push_back((input_voltage >> 8) & 0xFF);
result.push_back(input_voltage & 0xFF);
result.push_back((dc_current >> 8) & 0xFF);
result.push_back(dc_current & 0xFF);
return result;
}
};
struct VehicleMotors
{
std::vector<MotorData> motors; // 驱动电机列表
VehicleMotors() { motors.resize(2); } // 默认支持 2 台电机
// 编码整个驱动电机信息体 (0x02)
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x02); // 类型标识
result.push_back(static_cast<uint8_t>(motors.size())); // 电机数量
for (const auto& m : motors)
{
auto motor_bytes = m.encode();
result.insert(result.end(), motor_bytes.begin(), motor_bytes.end());
}
return result;
}
};
// 车辆位置信息
struct VehiclePosition
{
uint8_t status; // 定位状态位
uint32_t longitude; // 经度 × 10^6
uint32_t latitude; // 纬度 × 10^6
uint16_t heading; // 方向 0~359, 精度0.1°
VehiclePosition() { std::memset(this, 0xFF, sizeof(VehiclePosition)); }
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x05); // 类型标识
result.push_back(status); // 状态字节
// 经度
result.push_back((longitude >> 24) & 0xFF);
result.push_back((longitude >> 16) & 0xFF);
result.push_back((longitude >> 8) & 0xFF);
result.push_back(longitude & 0xFF);
// 纬度
result.push_back((latitude >> 24) & 0xFF);
result.push_back((latitude >> 16) & 0xFF);
result.push_back((latitude >> 8) & 0xFF);
result.push_back(latitude & 0xFF);
// 方向
result.push_back((heading >> 8) & 0xFF);
result.push_back(heading & 0xFF);
return result;
}
};
// 极值数据
struct ExtremeData
{
uint8_t highest_voltage_subsys; // 最高电压子系统号
uint16_t highest_voltage_cell; // 最高电压单体代号
uint16_t highest_voltage_value; // 电池单体电压最高值
uint8_t lowest_voltage_subsys; // 最低电压子系统号
uint16_t lowest_voltage_cell; // 最低电压单体代号
uint16_t lowest_voltage_value; // 电池单体电压最低值
uint8_t highest_temp_subsys; // 最高温度子系统号
uint8_t highest_temp_probe; // 最高温度探针序号
uint8_t highest_temp_value; // 最高温度值
uint8_t lowest_temp_subsys; // 最低温度子系统号
uint8_t lowest_temp_probe; // 最低温度探针序号
uint8_t lowest_temp_value; // 最低温度值
ExtremeData() { std::memset(this, 0xFF, sizeof(ExtremeData)); }
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x06); // 类型标识
result.push_back(highest_voltage_subsys);
result.push_back((highest_voltage_cell >> 8) & 0xFF);
result.push_back(highest_voltage_cell & 0xFF);
result.push_back((highest_voltage_value >> 8) & 0xFF);
result.push_back(highest_voltage_value & 0xFF);
result.push_back(lowest_voltage_subsys);
result.push_back((lowest_voltage_cell >> 8) & 0xFF);
result.push_back(lowest_voltage_cell & 0xFF);
result.push_back((lowest_voltage_value >> 8) & 0xFF);
result.push_back(lowest_voltage_value & 0xFF);
result.push_back(highest_temp_subsys);
result.push_back(highest_temp_probe);
result.push_back(highest_temp_value);
result.push_back(lowest_temp_subsys);
result.push_back(lowest_temp_probe);
result.push_back(lowest_temp_value);
return result;
}
};
// 报警数据
struct AlarmData
{
uint8_t highest_level; // 最高报警等级
uint32_t common_flags; // 通用报警标志
uint8_t n1; // 可充电储能装置故障总数
std::vector<uint32_t> battery_fault_codes; // 电池故障代码列表 (4*N1)
uint8_t n2; // 驱动电机故障总数
std::vector<uint32_t> motor_fault_codes; // 驱动电机故障代码列表 (4*N2)
uint8_t n3; // 发动机故障总数
std::vector<uint32_t> engine_fault_codes; // 发动机故障代码列表 (4*N3)
uint8_t n4; // 其他故障总数
std::vector<uint32_t> other_fault_codes; // 其他故障代码列表 (4*N4)
uint8_t n5; // 普通故障总数
std::vector<uint32_t> normal_fault_codes; // 普通故障代码列表 (4*N5)
AlarmData() : highest_level(0xFF), common_flags(0xFFFFFFFF), n1(0), n2(0), n3(0), n4(0), n5(0)
{
// 清空所有 vector防止残留旧数据
battery_fault_codes.clear();
motor_fault_codes.clear();
engine_fault_codes.clear();
other_fault_codes.clear();
normal_fault_codes.clear();
}
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x07); // 类型标识
result.push_back(highest_level);
// 通用报警标志DWORD
result.push_back((common_flags >> 24) & 0xFF);
result.push_back((common_flags >> 16) & 0xFF);
result.push_back((common_flags >> 8) & 0xFF);
result.push_back(common_flags & 0xFF);
// 电池故障
result.push_back(n1);
for (uint32_t code : battery_fault_codes)
{
result.push_back((code >> 24) & 0xFF);
result.push_back((code >> 16) & 0xFF);
result.push_back((code >> 8) & 0xFF);
result.push_back(code & 0xFF);
}
// 驱动电机故障
result.push_back(n2);
for (uint32_t code : motor_fault_codes)
{
result.push_back((code >> 24) & 0xFF);
result.push_back((code >> 16) & 0xFF);
result.push_back((code >> 8) & 0xFF);
result.push_back(code & 0xFF);
}
// 发动机故障
result.push_back(n3);
for (uint32_t code : engine_fault_codes)
{
result.push_back((code >> 24) & 0xFF);
result.push_back((code >> 16) & 0xFF);
result.push_back((code >> 8) & 0xFF);
result.push_back(code & 0xFF);
}
// 其他故障
result.push_back(n4);
for (uint32_t code : other_fault_codes)
{
result.push_back((code >> 24) & 0xFF);
result.push_back((code >> 16) & 0xFF);
result.push_back((code >> 8) & 0xFF);
result.push_back(code & 0xFF);
}
// 普通故障
result.push_back(n5);
for (uint32_t code : normal_fault_codes)
{
result.push_back((code >> 24) & 0xFF);
result.push_back((code >> 16) & 0xFF);
result.push_back((code >> 8) & 0xFF);
result.push_back(code & 0xFF);
}
return result;
}
};
// 故障池
struct FaultPool
{
struct FaultItem
{
uint32_t code; // 故障码(唯一)
uint8_t ecu; // 来自哪个 ECU
uint8_t level; // 故障等级
uint64_t last_seen_ts; // 上次收到该故障的时间
};
mutable std::mutex mutex;
std::vector<FaultItem> items;
// 新故障 or 刷新已有故障
void add_or_update(uint8_t ecu, uint8_t level, uint32_t code)
{
std::lock_guard<std::mutex> lock(mutex);
uint64_t now = common::get_timestamp_ms();
for (auto& f : items)
{
if (f.code == code && f.ecu == ecu)
{
f.level = level;
f.last_seen_ts = now;
return;
}
}
// 新故障
items.push_back({code, ecu, level, now});
}
// 超时自动清除故障(例如 500ms 未收到 → 故障消失)
void purge_timeout(uint64_t timeout_ms)
{
std::lock_guard<std::mutex> lock(mutex);
uint64_t now = common::get_timestamp_ms();
items.erase(std::remove_if(items.begin(), items.end(),
[&](const FaultItem& f) { return (now - f.last_seen_ts > timeout_ms); }),
items.end());
}
// 获取快照(线程安全)
std::vector<FaultItem> snapshot() const
{
std::lock_guard<std::mutex> lock(mutex);
return items; // 拷贝副本
}
};
// 可充电储能装置电压数据
struct BatteryUnit
{
uint16_t voltage; // 单体电池电压
BatteryUnit() : voltage(0xFFFF) {} // 默认无效
};
struct StorageSubsystem
{
uint8_t subsystem_id; // 可充电储能子系统号
uint16_t voltage; // 储能装置总电压
uint16_t current; // 储能装置总电流
uint16_t total_cells; // 单体电池总数
uint16_t start_cell_index; // 本帧起始电池序号
uint16_t frame_cells; // 本帧单体电池数量
std::vector<BatteryUnit> cells; // 单体电池电压列表
StorageSubsystem()
: subsystem_id(0xFF), voltage(0xFFFF), current(0xFFFF), total_cells(0), start_cell_index(0xFFFF), frame_cells(0)
{
cells.clear(); // 空容器,防止脏数据
}
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(subsystem_id);
result.push_back((voltage >> 8) & 0xFF);
result.push_back(voltage & 0xFF);
result.push_back((current >> 8) & 0xFF);
result.push_back(current & 0xFF);
result.push_back((total_cells >> 8) & 0xFF);
result.push_back(total_cells & 0xFF);
result.push_back((start_cell_index >> 8) & 0xFF);
result.push_back(start_cell_index & 0xFF);
result.push_back((frame_cells >> 8) & 0xFF);
result.push_back(frame_cells & 0xFF);
for (const auto& cell : cells)
{
result.push_back((cell.voltage >> 8) & 0xFF);
result.push_back(cell.voltage & 0xFF);
}
return result;
}
};
struct StorageVoltageData
{
static constexpr uint8_t kSubsystemCount = 1; // 现实世界:只有 1 个储能系统
uint8_t subsystem_count; // 对外上报给 GB32960 的字段
StorageSubsystem subsystem; // 唯一子系统
StorageVoltageData() : subsystem_count(kSubsystemCount)
{
// 协议现实EV 默认 1
subsystem.subsystem_id = 1;
subsystem.start_cell_index = 1;
}
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x08); // 类型标识
result.push_back(subsystem_count);
auto sub_bytes = subsystem.encode();
result.insert(result.end(), sub_bytes.begin(), sub_bytes.end());
return result;
}
};
// 可充电储能装置温度数据
struct TemperatureProbe
{
uint8_t temp; // 温度值
TemperatureProbe() : temp(0xFF) {} // 默认无效
};
struct StorageTempSubsystem
{
uint8_t subsystem_id; // 可充电储能子系统号
uint16_t probe_count; // 温度探针个数
std::vector<TemperatureProbe> probes; // 探针列表
StorageTempSubsystem() : subsystem_id(0xFF), probe_count(0)
{
probes.clear(); // 空容器,防止脏数据
}
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(subsystem_id);
result.push_back((probe_count >> 8) & 0xFF);
result.push_back(probe_count & 0xFF);
for (const auto& p : probes) { result.push_back(p.temp); }
return result;
}
};
struct StorageTempData
{
static constexpr uint8_t kSubsystemCount = 1;
uint8_t subsystem_count; // 可充电储能子系统个数
StorageTempSubsystem subsystem; // 唯一子系统
StorageTempData() : subsystem_count(kSubsystemCount)
{
subsystem.subsystem_id = 1; // 与电压侧保持一致
}
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x09); // 类型标识
result.push_back(subsystem_count);
auto sub_bytes = subsystem.encode();
result.insert(result.end(), sub_bytes.begin(), sub_bytes.end());
return result;
}
};
// 驱动/制动数据
struct DriveBrakeData
{
uint16_t speed; // 车速
uint16_t reserved1; // 预留
uint16_t accel_opening; // 油门开度
uint16_t torque; // 扭矩
uint16_t brake_opening; // 制动踏板开度
uint16_t deceleration; // 减速度
uint8_t parking_status; // 驻车状态反馈
uint16_t wheel_speed1; // 轮速1
uint16_t wheel_speed2; // 轮速2
uint16_t wheel_speed3; // 轮速3
uint16_t wheel_speed4; // 轮速4
uint8_t reserved_status1; // 预留状态1
uint8_t reserved_status2; // 预留状态2
uint8_t reserved_status3; // 预留状态3
uint16_t reserved_status4; // 预留状态4
uint16_t reserved_status5; // 预留状态5
uint16_t reserved_status6; // 预留状态6
DriveBrakeData() { std::memset(this, 0xFF, sizeof(DriveBrakeData)); }
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x85); // 类型标识
auto pushWord = [&](uint16_t val)
{
result.push_back((val >> 8) & 0xFF);
result.push_back(val & 0xFF);
};
// 按顺序编码
pushWord(speed);
pushWord(reserved1);
pushWord(accel_opening);
pushWord(torque);
pushWord(brake_opening);
pushWord(deceleration);
result.push_back(parking_status);
pushWord(wheel_speed1);
pushWord(wheel_speed2);
pushWord(wheel_speed3);
pushWord(wheel_speed4);
result.push_back(reserved_status1);
result.push_back(reserved_status2);
result.push_back(reserved_status3);
pushWord(reserved_status4);
pushWord(reserved_status5);
pushWord(reserved_status6);
return result;
}
};
// 轮胎数据
struct TireInfo
{
uint8_t position; // 轮胎位置
uint16_t pressure; // 胎压
uint8_t temperature; // 胎温
uint16_t angle; // 转向角度
uint8_t warning; // 轮胎预警
uint8_t reserved1; // 预留状态1
uint8_t reserved2; // 预留状态2
uint16_t reserved3; // 预留状态3
uint16_t reserved4; // 预留状态4
TireInfo() { std::memset(this, 0xFF, sizeof(TireInfo)); }
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(position);
result.push_back((pressure >> 8) & 0xFF);
result.push_back(pressure & 0xFF);
result.push_back(temperature);
result.push_back((angle >> 8) & 0xFF);
result.push_back(angle & 0xFF);
result.push_back(warning);
result.push_back(reserved1);
result.push_back(reserved2);
result.push_back((reserved3 >> 8) & 0xFF);
result.push_back(reserved3 & 0xFF);
result.push_back((reserved4 >> 8) & 0xFF);
result.push_back(reserved4 & 0xFF);
return result;
}
};
struct TireData
{
uint8_t tire_count; // 轮胎数量
std::vector<TireInfo> tires; // 轮胎列表
TireData() : tire_count(0) { tires.clear(); }
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x86); // 类型标识
result.push_back(tire_count);
for (const auto& t : tires)
{
auto tire_bytes = t.encode();
result.insert(result.end(), tire_bytes.begin(), tire_bytes.end());
}
return result;
}
};
// 前域开关数据
struct FrontSwitchData
{
std::vector<uint8_t> switches; // 0x01 ~ 0x17 共 23 个参数
FrontSwitchData()
{
switches.resize(0x17, 0xFF); // 默认全部无效
}
void setParam(uint8_t id, uint8_t value)
{
if (id >= 1 && id <= 0x17) { switches[id - 1] = value; }
}
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
// 先统计有效参数
uint8_t count = 0;
for (auto v : switches)
if (v != 0xFF) ++count;
// 如果一个有效开关都没有
if (count == 0) return result;
result.reserve(2 + count * 2);
result.push_back(0x87); // 类型标识
result.push_back(count); // 实际参数个数
for (uint8_t id = 1; id <= 0x17; ++id)
{
uint8_t v = switches[id - 1];
if (v == 0xFF) continue;
result.push_back(id);
result.push_back(v);
}
return result;
}
};
// 整车数据补充
struct VehicleFullData
{
uint8_t control_mode; // 车辆控制模式
uint8_t emergency_btn; // 紧急按钮状态
uint16_t speed_limit; // 车辆限速
uint8_t lock_status; // 锁车状态
uint32_t total_charge; // 车辆累计充电量
uint8_t reserved1; // 预留状态1
uint8_t reserved2; // 预留状态2
uint8_t reserved3; // 预留状态3
uint16_t reserved4; // 预留状态4
uint16_t reserved5; // 预留状态5
uint16_t reserved6; // 预留状态6
VehicleFullData() { std::memset(this, 0xFF, sizeof(VehicleFullData)); }
std::vector<uint8_t> encode() const
{
std::vector<uint8_t> result;
result.push_back(0x88); // 类型标识
result.push_back(control_mode);
result.push_back(emergency_btn);
// WORD 高字节在前
result.push_back((speed_limit >> 8) & 0xFF);
result.push_back(speed_limit & 0xFF);
result.push_back(lock_status);
// DWORD 高字节在前
result.push_back((total_charge >> 24) & 0xFF);
result.push_back((total_charge >> 16) & 0xFF);
result.push_back((total_charge >> 8) & 0xFF);
result.push_back(total_charge & 0xFF);
result.push_back(reserved1);
result.push_back(reserved2);
result.push_back(reserved3);
result.push_back((reserved4 >> 8) & 0xFF);
result.push_back(reserved4 & 0xFF);
result.push_back((reserved5 >> 8) & 0xFF);
result.push_back(reserved5 & 0xFF);
result.push_back((reserved6 >> 8) & 0xFF);
result.push_back(reserved6 & 0xFF);
return result;
}
};
// 数据结构体定义
extern VehicleData current_vehicle_data;
extern std::mutex vehicle_data_mutex;
extern VehicleMotors current_vehicle_motors;
extern std::mutex vehicle_motors_mutex;
extern VehiclePosition current_vehicle_position;
extern std::mutex vehicle_position_mutex;
extern ExtremeData current_extreme_data;
extern std::mutex extreme_data_mutex;
extern AlarmData current_alarm_data;
extern std::mutex alarm_data_mutex;
extern StorageVoltageData current_storage_voltage_data;
extern std::mutex storage_voltage_mutex;
extern StorageTempData current_storage_temp_data;
extern std::mutex storage_temp_mutex;
extern std::vector<uint8_t> current_autodata;
extern std::mutex autodata_mutex;
extern DriveBrakeData current_drive_brake_data;
extern std::mutex drive_brake_mutex;
extern TireData current_tire_data;
extern std::mutex tire_data_mutex;
extern FrontSwitchData current_front_switch_data;
extern std::mutex front_switch_mutex;
extern VehicleFullData current_vehicle_full_data;
extern std::mutex vehicle_full_mutex;
void handle_can_msg(const can_frame& frame); // 处理 CAN 消息
std::vector<uint8_t> buildTboxRealtimePayload();

View File

@ -0,0 +1,26 @@
#pragma once
#include <span>
#include "protocol_codec.h"
#include "tbox_data_manager.h"
#include "tcp_client_instance.h"
struct ReceivedPacket
{
uint8_t command_id;
uint8_t response_flag;
std::vector<uint8_t> data_unit;
};
inline std::string byte_to_hex(uint8_t v)
{
std::ostringstream oss;
oss << "0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast<int>(v);
return oss.str();
}
// 信息上报使用
std::string build_command_packet(uint8_t command_id, std::span<const uint8_t> payload = {}); // 构建命令包
ReceivedPacket process_received_packet(const std::vector<uint8_t>& data); // 解析收到的报文

56
include/can/can_bus.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include <linux/can.h>
#include <atomic>
#include <functional>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include "logger.h"
class CanBus
{
public:
using ReceiveCallback = std::function<void(const can_frame &)>;
CanBus(const std::string &id, const std::string &interface_name, Logger &logger);
~CanBus();
bool start(); // 启动 CAN 接收线程并尝试初始化,失败会自动重试
void stop();
bool send_frame(const can_frame &frame);
void set_receive_callback(ReceiveCallback cb) { receive_callback_ = cb; }
bool is_running() const { return running_; }
// 黑名单管理
void add_blacklist_id(uint32_t can_id);
void remove_blacklist_id(uint32_t can_id);
void clear_blacklist();
private:
void receive_loop();
bool init_socket();
private:
std::string id_;
std::string interface_name_;
Logger &logger_;
int sockfd_ = -1;
std::atomic<bool> running_{false};
std::thread worker_;
std::mutex send_mutex_;
ReceiveCallback receive_callback_;
std::set<uint32_t> blacklist_;
std::mutex blacklist_mutex_;
// 自动重试策略
int retry_first_ = 2; // 初次失败间隔秒
int retry_max_ = 30; // 最大间隔秒
};

142
include/common/logger.h Normal file
View File

@ -0,0 +1,142 @@
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <filesystem>
#include <regex>
#include "utils.h"
enum class LogLevel
{
INFO,
WARN,
ERROR
};
class Logger
{
public:
explicit Logger(const std::string &base_filename, int retention_days = 14)
: base_filename(base_filename), retention_days(retention_days)
{
namespace fs = std::filesystem;
fs::path log_path(base_filename);
if (!log_path.parent_path().empty())
{
fs::create_directories(log_path.parent_path());
}
current_date = common::get_current_date_string();
current_log_filename = base_filename + "_" + current_date + ".log";
open_log_file();
cleanup_old_logs(); // 初始化时就清理一次
}
void log(LogLevel level, const std::string &msg)
{
std::string today = common::get_current_date_string();
if (today != current_date)
{
// 日期切换
current_date = today;
current_log_filename = base_filename + "_" + current_date + ".log";
open_log_file();
cleanup_old_logs();
}
std::string level_str;
switch (level)
{
case LogLevel::INFO:
level_str = "[INFO] ";
break;
case LogLevel::WARN:
level_str = "[WARN] ";
break;
case LogLevel::ERROR:
level_str = "[ERROR] ";
break;
}
std::string full_msg = common::get_current_time_string() + " " + level_str + msg;
std::cout << full_msg << std::endl;
if (log_file.is_open())
{
log_file << full_msg << std::endl;
log_file.flush();
}
}
private:
std::string base_filename; // 基础文件名(不带日期后缀)
std::string current_log_filename;
std::string current_date;
std::ofstream log_file;
int retention_days;
void open_log_file()
{
log_file.close();
log_file.open(current_log_filename, std::ios::app);
if (!log_file.is_open())
{
std::cerr << "[Logger] Failed to open log file: " << current_log_filename << std::endl;
}
}
void cleanup_old_logs()
{
namespace fs = std::filesystem;
fs::path base_path(base_filename);
fs::path dir = base_path.parent_path();
std::string stem = base_path.filename().string();
if (dir.empty())
dir = "."; // 没有指定目录就用当前目录
std::regex log_pattern(stem + "_(\\d{4}-\\d{2}-\\d{2})\\.log");
auto now = std::chrono::system_clock::now();
for (auto &entry : fs::directory_iterator(dir))
{
if (!entry.is_regular_file())
continue;
std::smatch match;
std::string fname = entry.path().filename().string();
if (std::regex_match(fname, match, log_pattern))
{
std::string date_str = match[1];
std::tm tm = {};
std::istringstream ss(date_str);
ss >> std::get_time(&tm, "%Y-%m-%d");
if (!ss.fail())
{
auto file_time = std::chrono::system_clock::from_time_t(std::mktime(&tm));
auto age_days = std::chrono::duration_cast<std::chrono::hours>(now - file_time).count() / 24;
if (age_days > retention_days)
{
try
{
fs::remove(entry.path());
}
catch (const std::exception &e)
{
std::cerr << "[Logger] Failed to remove old log: " << entry.path()
<< " (" << e.what() << ")" << std::endl;
}
}
}
}
}
}
};
// 宏
#define LOG_INFO(logger, msg) logger.log(LogLevel::INFO, msg)
#define LOG_WARN(logger, msg) logger.log(LogLevel::WARN, msg)
#define LOG_ERROR(logger, msg) logger.log(LogLevel::ERROR, msg)

80
include/common/utils.h Normal file
View File

@ -0,0 +1,80 @@
#pragma once
#include <limits.h>
#include <unistd.h>
#include <chrono>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <string>
namespace common
{
// 获取当前北京时间字符串 (精确到毫秒)
inline std::string get_current_time_string()
{
using namespace std::chrono;
auto now = system_clock::now();
auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
// 先转 UTC
auto t = system_clock::to_time_t(now);
std::tm utc_tm = *std::gmtime(&t);
utc_tm.tm_hour += 8;
mktime(&utc_tm);
std::ostringstream oss;
oss << std::put_time(&utc_tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setw(3) << std::setfill('0') << ms.count();
return oss.str();
}
// 获取当前日期字符串 (仅日期,用于日志文件名)
inline std::string get_current_date_string()
{
using namespace std::chrono;
auto now = system_clock::now();
auto t = system_clock::to_time_t(now);
// 转成 UTC
std::tm utc_tm = *std::gmtime(&t);
// UTC + 8 小时
utc_tm.tm_hour += 8;
mktime(&utc_tm); // 自动修正日期
std::ostringstream oss;
oss << std::put_time(&utc_tm, "%Y-%m-%d");
return oss.str();
}
// 获取文件大小
inline size_t get_file_size(const std::string& filename)
{
std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
return in.is_open() ? static_cast<size_t>(in.tellg()) : 0;
}
// 获取可执行文件目录下的文件完整路径
inline std::string get_executable_file_path(const std::string& filename)
{
char result[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
std::string path(result, count);
auto dir = path.substr(0, path.find_last_of('/'));
if (dir.back() != '/') dir += '/';
return dir + filename;
}
// 获取当前毫秒时间戳steady_clock不受系统时间影响
inline uint64_t get_timestamp_ms()
{
using namespace std::chrono;
return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
}
} // namespace common

120
include/config/INIReader.h Executable file
View File

@ -0,0 +1,120 @@
// 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

240
include/config/config.h Normal file
View File

@ -0,0 +1,240 @@
#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;
}
};

189
include/config/ini.h Executable file
View File

@ -0,0 +1,189 @@
/* 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 */

19
include/main.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <iostream>
#include <string>
#include <thread>
#include "can_bus_instance.h"
#include "config.h"
#include "logger.h"
#include "mqtt_client_instance.h"
#include "serial_instance.h"
#include "tcp_client_instance.h"
#include "tcp_server_instance.h"
#define SOFTWARE_VERSION "1.0.0"
extern Logger veh_rc_logger;
extern Logger tbox_logger;
extern Logger v2v_logger;

View File

@ -0,0 +1,67 @@
#pragma once
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include "logger.h"
#include "mqtt/async_client.h"
class MqttClient : public virtual mqtt::callback
{
public:
using StatusCallback = std::function<void(bool connected)>;
using MessageCallback = std::function<void(const std::string &topic, const std::string &payload)>;
MqttClient(const std::string &id, const std::string &server_ip, int server_port, Logger &logger,
const std::string &username = "", const std::string &password = "", const std::string &client_id = "",
bool clean_session = true, int keep_alive = 20, int qos = 1);
~MqttClient();
void start();
void stop();
bool is_connected() const;
void set_reconnect_policy(int first, int max);
void set_status_callback(StatusCallback cb);
void set_message_callback(MessageCallback cb);
bool publish(const std::string &topic, const std::string &payload, int qos = -1);
bool subscribe(const std::string &topic, int qos = -1);
private:
void client_loop();
bool try_connect();
void disconnect();
void connection_lost(const std::string &cause) override;
void message_arrived(mqtt::const_message_ptr msg) override;
private:
std::string id_;
std::string server_ip_;
int server_port_;
Logger &logger_;
std::string username_;
std::string password_;
std::string client_id_;
bool clean_session_;
int keep_alive_;
int qos_;
std::atomic<bool> running_{false};
std::atomic<bool> connected_{false};
std::thread worker_;
int reconnect_first_ = 5;
int reconnect_max_ = 60;
std::shared_ptr<mqtt::async_client> client_;
std::mutex send_mutex_;
StatusCallback status_callback_;
MessageCallback message_callback_;
};

View File

@ -0,0 +1,71 @@
#pragma once
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "logger.h"
class TcpClient
{
public:
using ReceiveCallback = std::function<void(const std::vector<uint8_t> &)>;
using ReceiveStringCallback = std::function<void(const std::string &)>;
using StatusCallback = std::function<void(bool connected)>;
TcpClient(const std::string &id, const std::string &ip, int port, Logger &logger);
~TcpClient();
void start(); // 启动连接与接收线程
void stop(); // 停止连接并关闭 socket
bool send_data(const std::string &data); // 发送数据(线程安全)
void set_receive_callback(ReceiveCallback cb) { receive_callback_ = cb; }
// 自动把 vector 转 string 后再回调
void set_receive_callback(ReceiveStringCallback cb)
{
receive_callback_ = [cb](const std::vector<uint8_t> &data) { cb(std::string(data.begin(), data.end())); };
}
void set_status_callback(StatusCallback cb) { status_callback_ = cb; }
bool is_connected() const;
// 设置重连策略,单位秒
void set_reconnect_policy(int first, int max)
{
reconnect_first_ = first;
reconnect_max_ = max;
}
private:
void client_loop(); // 主循环:连接、接收、重连
bool try_connect(); // 建立连接
void close_socket(); // 关闭连接
void handle_io(); // 抽取收包逻辑
private:
std::string id_;
std::string ip_;
int port_;
int sock_fd_ = -1;
std::atomic<bool> running_{false};
std::atomic<bool> connected_{false};
std::thread worker_;
std::mutex send_mutex_;
ReceiveCallback receive_callback_;
StatusCallback status_callback_;
Logger &logger_;
// 重连策略
int reconnect_first_ = 5; // 初始间隔(秒)
int reconnect_max_ = 300; // 最大间隔(秒)
};

View File

@ -0,0 +1,64 @@
#pragma once
#include <atomic>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "logger.h"
class TcpServer
{
public:
struct ClientInfo
{
int fd;
std::string ip;
int port;
};
using ReceiveCallback = std::function<void(const std::vector<uint8_t> &)>;
using ReceiveStringCallback = std::function<void(const std::string &)>;
using StatusCallback = std::function<void(int client_fd, bool connected, const std::string &ip, int port)>;
TcpServer(const std::string &id, const std::string &ip, int port, Logger &logger);
~TcpServer();
void start();
void stop();
bool send_data(int client_fd, const std::string &data);
void broadcast(const std::string &data);
void set_receive_callback(ReceiveCallback cb) { receive_callback_ = cb; }
void set_receive_callback(ReceiveStringCallback cb)
{
receive_callback_ = [cb](const std::vector<uint8_t> &data) { cb(std::string(data.begin(), data.end())); };
}
void set_status_callback(StatusCallback cb) { status_callback_ = cb; }
private:
void server_loop();
void handle_client(ClientInfo client);
void close_client(int client_fd);
private:
std::string id_;
std::string ip_;
int port_;
int listen_fd_ = -1;
std::atomic<bool> running_{false};
std::thread worker_;
std::mutex clients_mutex_;
std::map<int, ClientInfo> clients_;
ReceiveCallback receive_callback_;
StatusCallback status_callback_;
Logger &logger_;
};

View File

@ -0,0 +1,24 @@
#pragma once
#include "protocol_struct.h"
#include <vector>
#include <string>
#include <cstdint>
namespace ProtocolCodec
{
// 获取当前北京时间,返回 6 字节 vector {year, month, day, hour, minute, second}
std::vector<uint8_t> get_current_time_bytes();
// BCC 校验
uint8_t calculate_bcc(const std::vector<uint8_t> &data);
// 编码 FullPacket 为字节流(使用大端)
std::vector<uint8_t> encode_full_packet(const FullPacket &packet);
// 解码字节流为 FullPacket成功返回 packet否则 std::nullopt
std::optional<FullPacket> decode_full_packet(const std::vector<uint8_t> &buffer);
std::vector<uint8_t> make_ack_response(const FullPacket &request, bool result);
} // namespace ProtocolCodec

View File

@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <optional>
#include <cstring>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <map>
#include <unordered_map>
// 通用数据包结构(协议层)
struct FullPacket
{
uint8_t start_flag1 = 0x23; // 起始符 '#'
uint8_t start_flag2 = 0x23; // 起始符 '#'
uint8_t command_id; // 命令标识(如 0x81、0xD3 等)
uint8_t response_flag; // 应答标志0xFE 表示命令包)
std::string vin; // 17 字节 VIN 编码ASCII
uint8_t encryption_method; // 加密方式0x01 无加密)
uint16_t data_length; // 数据单元长度
std::vector<uint8_t> data_unit; // 数据单元(二进制)
uint8_t checksum; // BCC 校验值
};

View File

@ -0,0 +1,53 @@
#pragma once
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include "logger.h"
class SerialPort
{
public:
using ReceiveCallback = std::function<void(const std::vector<uint8_t>&)>;
using ReceiveStringCallback = std::function<void(const std::string&)>;
SerialPort(const std::string& id, const std::string& device, int baudrate, Logger& logger, int retry_interval = 5);
~SerialPort();
void start(); // 启动串口(含自动重连)
void stop(); // 停止串口
bool is_open() const;
bool send_data(const std::vector<uint8_t>& data);
bool send_data(const std::string& data);
void set_receive_callback(ReceiveCallback cb);
void set_receive_callback(ReceiveStringCallback cb);
private:
bool open_port();
void close_port();
void reader_loop();
void reconnect_loop();
bool configure_port(int fd);
private:
std::string id_;
std::string device_;
int baudrate_;
int fd_ = -1;
std::atomic<bool> running_{false};
std::atomic<bool> stop_flag_{false};
std::thread reader_thread_;
std::thread reconnect_thread_;
std::mutex send_mutex_;
ReceiveCallback receive_callback_;
Logger& logger_;
int retry_interval_;
};

View File

@ -0,0 +1,54 @@
#include "can_bus_instance.h"
#include "rc_data_manager.h"
#include "tbox_data_manager.h"
std::unique_ptr<CanBus> can_bus_rc_ctrl;
static void veh_rc_frame_send()
{
while (true)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (can_bus_rc_ctrl && can_bus_rc_ctrl->is_running())
{
Remote_Ctl_Def local_copy;
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
// 挡位逐级递进
if (veh_rc_data.gear < veh_rc_data.targetGear)
veh_rc_data.gear++;
else if (veh_rc_data.gear > veh_rc_data.targetGear)
veh_rc_data.gear--;
local_copy = veh_rc_data; // 复制数据以减少锁定时间
veh_rc_data.heartbeat += 1; // 心跳计数递增
}
auto frame = local_copy.packCANFrame(0x18ff1280, true);
can_bus_rc_ctrl->send_frame(frame); // 发送远控 CAN 帧
// 打印 CAN 帧内容
// std::ostringstream oss;
// oss << "[veh_rc_can] TX → ID: 0x" << std::hex << std::uppercase << std::setw(8) << std::setfill('0')
// << frame.can_id << " DLC:" << std::dec << static_cast<int>(frame.can_dlc) << " Data:";
// for (int i = 0; i < frame.can_dlc; ++i)
// {
// oss << " " << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
// << static_cast<int>(frame.data[i]);
// }
// LOG_INFO(veh_rc_logger, oss.str());
}
}
}
void init_can_bus_rc_ctrl(const std::string& interface_name)
{
can_bus_rc_ctrl = std::make_unique<CanBus>("veh_rc_can", interface_name, veh_rc_logger);
can_bus_rc_ctrl->set_receive_callback(handle_can_msg);
can_bus_rc_ctrl->add_blacklist_id(0x18FF1280);
can_bus_rc_ctrl->start();
std::thread(veh_rc_frame_send).detach(); // 启动发送线程
}

View File

@ -0,0 +1,347 @@
#include <sstream>
#include <string>
#include "mqtt_client_instance.h"
#include "nlohmann/json.hpp"
#include "rc_data_manager.h"
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_report_topic =
"/kun/vehicle/info/" + ConfigManager::instance().getVehicleId(); // 状态上报主题
std::atomic<bool> watchdogEnabled{false};
std::atomic<bool> emergencyActive{false};
static std::chrono::steady_clock::time_point lastBrakeTime = std::chrono::steady_clock::now();
std::atomic<bool> reportThreadRunning{false};
std::thread reportThread;
void watchdog_thread()
{
uint16_t counter = 0;
while (watchdogEnabled)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastBrakeTime).count() > 1)
{
if (!emergencyActive)
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.eStop = 1;
emergencyActive = true;
LOG_WARN(veh_rc_logger, "[veh_rc_mqtt] Brake message timeout! Emergency stop triggered. Counter: " +
std::to_string(++counter));
}
}
else
{
if (emergencyActive)
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.eStop = 0;
emergencyActive = false;
LOG_INFO(veh_rc_logger, "[veh_rc_mqtt] Brake message received — clearing emergency stop.");
}
}
}
}
// 同步车辆实时状态
static void sync_rc_data()
{
// 先把 veh_report_data 拷贝到本地(只锁 report
decltype(veh_report_data) report_snapshot;
{
std::lock_guard<std::mutex> lock(veh_report_mutex);
report_snapshot = veh_report_data;
}
// 再写 veh_rc_data只锁 rc
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
switch (report_snapshot.gear)
{
case 0: veh_rc_data.gear = 2; break;
case 1: veh_rc_data.gear = 3; break;
case 2: veh_rc_data.gear = 1; break;
default: veh_rc_data.gear = 0; break;
}
switch (report_snapshot.driveMode)
{
case 4: veh_rc_data.steeringMode = 0; break; // 八字
case 5: veh_rc_data.steeringMode = 1; break; // 斜行
case 3: veh_rc_data.steeringMode = 2; break; // 前半八
case 2: veh_rc_data.steeringMode = 3; break; // 后半八
default: veh_rc_data.steeringMode = 0; break;
}
veh_rc_data.overload = report_snapshot.load;
veh_rc_data.highVoltage = report_snapshot.voltage ? 0 : 1;
veh_rc_data.outlineLights = report_snapshot.positionLight;
veh_rc_data.hazardLights = report_snapshot.warning;
veh_rc_data.fogLamp = report_snapshot.fogLight;
veh_rc_data.frontLowBeam = (report_snapshot.headlamp == 1);
veh_rc_data.frontHighBeam = (report_snapshot.headlamp == 2);
veh_rc_data.rearLowBeam = (report_snapshot.tailLight == 1);
veh_rc_data.rearHighBeam = (report_snapshot.tailLight == 2);
veh_rc_data.vehicleLock = report_snapshot.lock;
veh_rc_data.resetFault = report_snapshot.faultReset;
veh_rc_data.autoStandby = report_snapshot.standbyMode;
}
}
// 消息回调
void handle_veh_rc_msg(const std::string& topic, const std::string& payload)
{
try
{
auto j = nlohmann::json::parse(payload);
// std::string compact_payload = j.dump();
// LOG_INFO(veh_rc_logger,
// "[veh_rc_mqtt] Received MQTT message on topic: " + topic + ", payload: " + compact_payload);
if (topic != veh_rc_topic) return;
std::string cmd = j.at("command").get<std::string>();
int value = j.at("value").get<int>();
// 查找命令类型
auto it = command_map.find(cmd);
CommandType type = (it != command_map.end()) ? it->second : CommandType::Unknown;
switch (type)
{
case CommandType::StartCtrl: // 开始远控
sync_rc_data();
break;
case CommandType::StopCtrl:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data = Remote_Ctl_Def{};
watchdogEnabled = false;
emergencyActive = false;
lastBrakeTime = std::chrono::steady_clock::now();
break;
}
case CommandType::Mode:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.remoteMode = (value == 3) ? 1 : 0;
if (veh_rc_data.remoteMode)
{
if (!watchdogEnabled)
{
watchdogEnabled = true;
std::thread(watchdog_thread).detach();
}
lastBrakeTime = std::chrono::steady_clock::now(); // 重置心跳
}
else
{
// 停止远控
watchdogEnabled = false;
emergencyActive = false;
}
break;
}
case CommandType::Gear:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
static const uint8_t gear_lut[] = {2, 3, 1, 0};
if (value >= 0 && value < 4)
veh_rc_data.targetGear = gear_lut[value];
else
veh_rc_data.targetGear = 0;
break;
}
case CommandType::Throttle:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
value = std::clamp(value, 0, 65535);
veh_rc_data.throttlePercent = static_cast<uint8_t>((value * 250) / 65535);
break;
}
case CommandType::Brake:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
value = std::clamp(value, 0, 65535);
veh_rc_data.brakePercent = static_cast<uint8_t>((value * 250) / 65535);
lastBrakeTime = std::chrono::steady_clock::now(); // 刷新心跳
break;
}
case CommandType::DriveMode:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
static const uint8_t steering_lut[4] = {3, 2, 0, 1};
veh_rc_data.steeringMode = (value >= 2 && value <= 5) ? steering_lut[value - 2] : 0;
break;
}
case CommandType::Steering:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
// 模拟方向盘采样值0 ~ 65535左到右
uint16_t wheel = value;
// 协议 raw 最大值根据精度和量程算出来180 / 0.022 ≈ 8182
constexpr double RAW_MAX = 8182.0;
constexpr double WHEEL_MAX = 65535.0;
// 映射wheel 0~65535 → raw 8182~0
double raw_f = (WHEEL_MAX - static_cast<double>(wheel)) * RAW_MAX / WHEEL_MAX;
// 四舍五入成 uint16_t
uint16_t raw = static_cast<uint16_t>(std::lround(raw_f));
// 防御性夹一下,避免因为浮点误差溢出去
if (raw > static_cast<uint16_t>(RAW_MAX)) raw = static_cast<uint16_t>(RAW_MAX);
veh_rc_data.steeringAngle = raw;
break;
}
case CommandType::Horn:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.horn = value;
break;
}
case CommandType::TurnLight:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.turnLeft = (value == 1) ? 1 : 0;
veh_rc_data.turnRight = (value == 2) ? 1 : 0;
break;
}
case CommandType::EmergencyStop:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.eStop = value;
break;
}
case CommandType::Load:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.overload = (value) ? 0 : 1;
break;
}
case CommandType::Voltage:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.highVoltage = value ? 0 : 1;
break;
}
case CommandType::PositionLight:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.outlineLights = value;
break;
}
case CommandType::Warning:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.hazardLights = value;
break;
}
case CommandType::FogLight:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.fogLamp = value;
break;
}
case CommandType::Headlamp:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.frontLowBeam = (value == 1) ? 1 : 0;
veh_rc_data.frontHighBeam = (value == 2) ? 1 : 0;
break;
}
case CommandType::TailLight:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.rearLowBeam = (value == 1) ? 1 : 0;
veh_rc_data.rearHighBeam = (value == 2) ? 1 : 0;
break;
}
case CommandType::Lock:
{
std::lock_guard<std::mutex> lock(veh_rc_mutex);
veh_rc_data.vehicleLock = value;
break;
}
default: LOG_WARN(veh_rc_logger, "[veh_rc_mqtt] Unknown command: " + cmd); break;
}
}
catch (const std::exception& e)
{
LOG_ERROR(veh_rc_logger, "[veh_rc_mqtt] Failed to parse MQTT payload: " + std::string(e.what()));
}
}
// 连接状态回调
void handle_veh_rc_status(bool connected)
{
if (connected)
{
// 连接成功后订阅主题
mqtt_client_rc_ctrl->subscribe(veh_rc_topic, 1);
// 启动定时上报线程
if (!reportThreadRunning)
{
reportThreadRunning = true;
reportThread = std::thread(
[]()
{
LOG_INFO(veh_rc_logger, "[veh_rc_mqtt] Report thread started.");
while (reportThreadRunning)
{
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::lock_guard<std::mutex> lock(veh_report_mutex);
std::string report_payload = veh_report_data.to_json();
mqtt_client_rc_ctrl->publish(veh_report_topic, report_payload, 1);
}
LOG_INFO(veh_rc_logger, "[veh_rc_mqtt] Report thread stopped.");
});
reportThread.detach();
}
}
else
{
// 断开连接时停止上报线程
reportThreadRunning = false;
}
}
void init_mqtt_client_veh_rc(const std::string& ip, int port)
{
mqtt_client_rc_ctrl = std::make_unique<MqttClient>("veh_rc_mqtt", ip, port, veh_rc_logger,
"", // username
"", // password
"", // client_id
true, // clean_session
10, // keep_alive
1 // qos
);
mqtt_client_rc_ctrl->set_message_callback(handle_veh_rc_msg);
mqtt_client_rc_ctrl->set_status_callback(handle_veh_rc_status);
mqtt_client_rc_ctrl->set_reconnect_policy(5, 60); // 重连策略初始5秒最大60秒
mqtt_client_rc_ctrl->start(); // 启动客户端
}

View File

@ -0,0 +1,7 @@
#include "rc_data_manager.h"
Remote_Ctl_Def veh_rc_data{};
std::mutex veh_rc_mutex; // 互斥锁,保护 veh_rc_data 线程安全
VehicleInfoReport veh_report_data{.type = 1};
std::mutex veh_report_mutex; // 互斥锁,保护 veh_report_data 线程安全

View File

@ -0,0 +1,139 @@
#include "serial_instance.h"
std::string ICCID = "89860022100000000000"; // 默认20位
struct AtTask
{
std::string cmd;
int interval; // 周期0 表示一次性任务
int max_retries; // -1 表示无限次
int sent_count;
std::chrono::steady_clock::time_point last_sent;
};
static std::unique_ptr<SerialPort> serial_at;
static std::thread serial_at_sender;
static std::mutex at_tasks_mutex; // 保护 at_tasks 访问的互斥锁
static std::vector<AtTask> at_tasks = {
// {"AT+CSQ", 5, -1, 0, {}}, // 周期查询
// {"AT+QENG=\"servingcell\"", 5, -1, 0, {}},
{"AT+CCID", 0, 3, 0, {}} // 只需要查询一次(最多重试 3 次)
};
// 发送线程函数
static void serial_at_send_loop()
{
while (true)
{
if (serial_at && serial_at->is_open())
{
auto now = std::chrono::steady_clock::now();
std::lock_guard<std::mutex> lock(at_tasks_mutex); // 保护任务列表
for (auto it = at_tasks.begin(); it != at_tasks.end();)
{
auto &task = *it;
bool should_send = false;
if (task.interval > 0)
{
// 周期性任务
if (task.last_sent.time_since_epoch().count() == 0 ||
std::chrono::duration_cast<std::chrono::seconds>(now - task.last_sent).count() >= task.interval)
{
should_send = true;
}
}
else
{
// 一次性任务,判断是否还需要重试
if ((task.max_retries < 0 || task.sent_count < task.max_retries) &&
(task.last_sent.time_since_epoch().count() == 0 ||
std::chrono::duration_cast<std::chrono::seconds>(now - task.last_sent).count() >=
5)) // 默认 5s 重试
{
should_send = true;
}
else if (task.sent_count >= task.max_retries && task.max_retries > 0)
{
LOG_ERROR(veh_rc_logger, std::string("[serial_at] AT task [") + task.cmd +
"] reached max retries (" + std::to_string(task.max_retries) +
")");
if (task.cmd == "AT+CCID")
{
init_tcp_client_tbox_router(
ConfigManager::instance().getPlatformIp(),
ConfigManager::instance().getPlatformPort()); // CCID 获取失败,继续初始化 TBoxRouter
}
it = at_tasks.erase(it); // 移除任务
continue; // 跳过 ++it
}
}
if (should_send)
{
serial_at->send_data(task.cmd + "\r\n");
task.sent_count++;
task.last_sent = now;
}
++it;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1)); // 控制循环节奏
}
}
static void handle_serial_at_data(const std::string &data)
{
// LOG_INFO(veh_rc_logger, "[serial_at] Received: " + data);
// 这里可以添加对接收到的AT命令响应的处理逻辑
if (data.find("+CCID:") != std::string::npos)
{
// 找到 "+CCID:" 后面的位置
auto pos = data.find("+CCID:") + 6;
// 从该位置开始找第一个换行符
auto end = data.find_first_of("\r\n", pos);
std::string ccid = data.substr(pos, end - pos);
// 去掉前后空格
ccid.erase(0, ccid.find_first_not_of(" \t\r\n"));
ccid.erase(ccid.find_last_not_of(" \t\r\n") + 1);
ICCID = ccid;
LOG_INFO(veh_rc_logger, "[serial_at] Extracted CCID = " + ccid);
init_tcp_client_tbox_router(
ConfigManager::instance().getPlatformIp(),
ConfigManager::instance().getPlatformPort()); // 成功获取 CCID 后,初始化 TBoxRouter
{
// 收到 CCID 响应,移除对应任务
std::lock_guard<std::mutex> lock(at_tasks_mutex);
at_tasks.erase(std::remove_if(at_tasks.begin(), at_tasks.end(),
[](const AtTask &task) { return task.cmd == "AT+CCID"; }),
at_tasks.end());
}
}
}
void init_serial_at(const std::string &device, int baudrate)
{
serial_at = std::make_unique<SerialPort>("serial_at", device, baudrate, veh_rc_logger, 5);
serial_at->set_receive_callback(handle_serial_at_data);
serial_at->start();
serial_at_sender = std::thread(serial_at_send_loop);
serial_at_sender.detach();
}

View File

@ -0,0 +1,130 @@
#include <sstream>
#include <string>
#include "mqtt_client_instance.h"
#include "nlohmann/json.hpp"
#include "tbox_messages.h"
#include "tcp_server_instance.h"
std::unique_ptr<MqttClient> mqtt_client_tbox_v2v;
enum class CmdDirection
{
Up,
Down
};
struct CmdMqttMap
{
uint8_t cmd_id;
const char* name;
const char* topic_prefix;
CmdDirection direction;
};
static const CmdMqttMap kCmdMqttTable[] = {
{0xD3, "vehicle_data", "vehicle/data/", CmdDirection::Up},
{0xD4, "vehicle_notify", "vehicle/notify/", CmdDirection::Up},
{0xD5, "vehicle_broadcast", "vehicle/broadcast/", CmdDirection::Down},
{0xD6, "vehicle_reply", "vehicle/reply/", CmdDirection::Up},
};
std::string build_mqtt_topic_by_cmd(uint8_t cmd_id)
{
const auto deviceNo = ConfigManager::instance().getDeviceNo();
for (const auto& it : kCmdMqttTable)
{
if (it.cmd_id == cmd_id) { return std::string(it.topic_prefix) + deviceNo; }
}
return {};
}
// 消息回调
static void handle_tbox_v2v_mqtt_msg(const std::string& topic, const std::string& payload_str)
{
nlohmann::json j;
try
{
j = nlohmann::json::parse(payload_str);
}
catch (...)
{
LOG_WARN(v2v_logger,
"[tbox_v2v][mqtt->tcp] drop, invalid json, payload_len=" + std::to_string(payload_str.size()));
return;
}
// 找到对应的 Down cmd
for (const auto& it : kCmdMqttTable)
{
if (it.direction != CmdDirection::Down) continue;
if (topic == build_mqtt_topic_by_cmd(it.cmd_id))
{
// 1. JSON → 紧凑字符串(无缩进)
std::string compact = j.dump(); // 默认就是紧凑格式
// 2. string → uint8 payload
std::span<const uint8_t> payload{reinterpret_cast<const uint8_t*>(compact.data()), compact.size()};
// 3. 构包
auto pkt = build_command_packet(it.cmd_id, payload);
if (pkt.empty())
{
LOG_WARN(v2v_logger, "[tbox_v2v][mqtt->tcp] drop, build packet failed, cmd=" + byte_to_hex(it.cmd_id));
return;
}
LOG_INFO(v2v_logger, "[tbox_v2v][mqtt->tcp] recv, cmd=" + byte_to_hex(it.cmd_id) + ", topic=" + topic +
", payload_len=" + std::to_string(compact.size()));
tcp_server_tbox_v2v->broadcast(pkt);
return;
}
}
LOG_WARN(v2v_logger, "[tbox_v2v][mqtt->tcp] drop, unknown topic: " + topic);
}
// 连接状态回调
static void handle_tbox_v2v_status(bool connected)
{
if (mqtt_client_tbox_v2v && connected)
{
for (const auto& it : kCmdMqttTable)
{
if (it.direction != CmdDirection::Down) continue;
auto topic = build_mqtt_topic_by_cmd(it.cmd_id);
if (topic.empty()) continue;
mqtt_client_tbox_v2v->subscribe(topic, 1);
}
}
}
void init_mqtt_client_tbox_v2v(const std::string& ip, int port, const std::string& username,
const std::string& password)
{
mqtt_client_tbox_v2v = std::make_unique<MqttClient>("tbox_v2v_mqtt", ip, port, v2v_logger,
username, // username
password, // password
"", // client_id
true, // clean_session
10, // keep_alive
1 // qos
);
mqtt_client_tbox_v2v->set_message_callback(handle_tbox_v2v_mqtt_msg);
mqtt_client_tbox_v2v->set_status_callback(handle_tbox_v2v_status);
mqtt_client_tbox_v2v->set_reconnect_policy(5, 60); // 重连策略初始5秒最大60秒
mqtt_client_tbox_v2v->start(); // 启动客户端
}

View File

@ -0,0 +1,796 @@
#include "tbox_data_manager.h"
#include "can_bus_instance.h"
#include "rc_data_manager.h"
// -----------------------------
// 车辆状态数据区定义
// -----------------------------
VehicleData current_vehicle_data{};
std::mutex vehicle_data_mutex;
VehicleMotors current_vehicle_motors{};
std::mutex vehicle_motors_mutex;
VehiclePosition current_vehicle_position{};
std::mutex vehicle_position_mutex;
ExtremeData current_extreme_data{};
std::mutex extreme_data_mutex;
FaultPool fault_pool;
AlarmData current_alarm_data{};
std::mutex alarm_data_mutex;
StorageVoltageData current_storage_voltage_data{};
std::mutex storage_voltage_mutex;
StorageTempData current_storage_temp_data{};
std::mutex storage_temp_mutex;
std::vector<uint8_t> current_autodata;
std::mutex autodata_mutex;
DriveBrakeData current_drive_brake_data{};
std::mutex drive_brake_mutex;
TireData current_tire_data{};
std::mutex tire_data_mutex;
FrontSwitchData current_front_switch_data{};
std::mutex front_switch_mutex;
VehicleFullData current_vehicle_full_data{};
std::mutex vehicle_full_mutex;
// ========================== VCU 区域 ==========================
static void parse_vcu_vehicle_info1(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
// Byte 0
uint8_t can_mode = (frame.data[0] >> 0) & 0x03; // 控制模式bit0~1
uint8_t can_driveMode = (frame.data[0] >> 2) & 0x07; // 转向模式bit2~4
uint8_t can_strobeLight = (frame.data[0] >> 5) & 0x01; // 爆闪灯bit5
// Byte 1
uint8_t can_dcdcStatus = (frame.data[1] >> 2) & 0x01; // DCDC 状态bit2
uint8_t can_lock = (frame.data[1] >> 4) & 0x01; // 锁车状态bit4
uint8_t can_voltage = (frame.data[1] >> 7) & 0x01; // 高压状态bit7
// Byte 2
uint8_t can_gear = (frame.data[2] >> 0) & 0x0F; // 档位bit0~3
uint8_t can_warning = (frame.data[2] >> 4) & 0x01; // 双闪bit4
uint8_t can_leftturn = (frame.data[2] >> 5) & 0x01; // 左转向灯bit5
uint8_t can_rightturn = (frame.data[2] >> 6) & 0x01; // 右转向灯bit6
uint8_t can_position = (frame.data[2] >> 7) & 0x01; // 位置灯bit7
// Byte 3
uint8_t can_front_lowbeam = (frame.data[3] >> 0) & 0x01; // 前近光灯bit0
uint8_t can_front_highbeam = (frame.data[3] >> 1) & 0x01; // 前远光灯bit1
uint8_t can_rear_lowbeam = (frame.data[3] >> 2) & 0x01; // 后近光灯bit2
uint8_t can_rear_highbeam = (frame.data[3] >> 3) & 0x01; // 后远光灯bit3
uint8_t can_fogLight = (frame.data[3] >> 5) & 0x01; // 雾灯bit5
uint8_t can_brakeLight = (frame.data[3] >> 7) & 0x01; // 制动灯bit7
// Byte 4
uint8_t can_resetFault = (frame.data[4] >> 6) & 0x01; // 故障复位bit6
// Byte 5
uint8_t can_standbyMode = (frame.data[5] >> 7) & 0x01; // 待机模式bit7
// Byte 7
uint8_t can_positionLight = (frame.data[7] >> 5) & 0x01; // 示廓灯bit5
uint8_t can_horn = (frame.data[7] >> 6) & 0x01; // 喇叭bit6
uint8_t can_emergencyStop = (frame.data[7] >> 7) & 0x01; // 急停bit7
{
std::lock_guard<std::mutex> lock(veh_report_mutex);
veh_report_data.horn = can_horn;
veh_report_data.voltage = can_voltage;
veh_report_data.positionLight = can_positionLight;
veh_report_data.warning = can_warning;
veh_report_data.fogLight = can_fogLight;
veh_report_data.lock = can_lock;
veh_report_data.emergencyStop = can_emergencyStop;
veh_report_data.faultReset = can_resetFault;
veh_report_data.standbyMode = can_standbyMode;
// 0: 全灭, 1: 左转, 2: 右转, 3: 全亮
veh_report_data.turnLight = (can_leftturn << 0) | (can_rightturn << 1);
// 01近光灯2远光灯
veh_report_data.headlamp = can_front_highbeam ? 2 : can_front_lowbeam ? 1 : 0;
veh_report_data.tailLight = can_rear_highbeam ? 2 : can_rear_lowbeam ? 1 : 0;
// ---- 映射表定义 ----
static const uint8_t modeMap[4] = {0, 2, 1, 3};
static const uint8_t driveModeMap[8] = {1, 1, 2, 3, 5, 4, 1, 1};
static const uint8_t gearMap[8] = {3, 2, 0, 1, 0, 0, 0, 0};
// ---- 查表映射 ----
veh_report_data.mode = (can_mode < 4) ? modeMap[can_mode] : 0;
veh_report_data.driveMode = (can_driveMode < 8) ? driveModeMap[can_driveMode] : 1;
veh_report_data.gear = (can_gear < 8) ? gearMap[can_gear] : 0;
}
{
std::lock_guard<std::mutex> lock(vehicle_data_mutex);
// ---- 映射表定义 ----
static const uint8_t gear_map[] = {0x0F, 0x0D, 0x00, 0x0E}; // P, R, N, D
current_vehicle_data.dc_dc_status = can_dcdcStatus ? 1 : 2;
// ---- 查表映射 ----
current_vehicle_data.gear = (can_gear < 4) ? gear_map[can_gear] : 0xFF; // 超范围无效
}
{
std::lock_guard<std::mutex> lock(drive_brake_mutex);
if (can_gear == 0)
current_drive_brake_data.parking_status = 0x01;
else if (can_gear >= 1 && can_gear <= 3)
current_drive_brake_data.parking_status = 0x02;
else
current_drive_brake_data.parking_status = 0xFE;
}
{
std::lock_guard<std::mutex> lock(front_switch_mutex);
current_front_switch_data.setParam(0x03, can_leftturn);
current_front_switch_data.setParam(0x04, can_rightturn);
current_front_switch_data.setParam(0x05, can_strobeLight);
current_front_switch_data.setParam(0x06, can_positionLight);
current_front_switch_data.setParam(0x08, can_positionLight);
current_front_switch_data.setParam(0x09, can_brakeLight);
current_front_switch_data.setParam(0x0A, can_brakeLight);
current_front_switch_data.setParam(0x0B, can_fogLight);
current_front_switch_data.setParam(0x0C, can_fogLight);
current_front_switch_data.setParam(0x0E, can_position); // 警示灯
current_front_switch_data.setParam(0x0F, can_front_highbeam | can_rear_highbeam);
current_front_switch_data.setParam(0x10, can_front_lowbeam | can_rear_lowbeam);
current_front_switch_data.setParam(0x11, can_horn);
current_front_switch_data.setParam(0x13, can_warning);
}
{
std::lock_guard<std::mutex> lock(vehicle_full_mutex);
switch (can_mode)
{
case 0: current_vehicle_full_data.control_mode = 0; break;
case 1: current_vehicle_full_data.control_mode = 2; break;
case 2: current_vehicle_full_data.control_mode = 1; break;
case 3: current_vehicle_full_data.control_mode = 3; break;
default: current_vehicle_full_data.control_mode = 0xFE; break;
}
current_vehicle_full_data.emergency_btn = can_emergencyStop ? 0 : 1;
current_vehicle_full_data.lock_status = can_lock + 1;
}
}
static void parse_vcu_mcu_info2(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint16_t can_motor1_rpm = frame.data[0] | (frame.data[1] << 8); // 驱动电机1转速
uint16_t can_motor2_rpm = frame.data[2] | (frame.data[3] << 8); // 驱动电机2转速
uint16_t can_motor1_torque = frame.data[4] | (frame.data[5] << 8); // 驱动电机1转矩
uint16_t can_motor2_torque = frame.data[6] | (frame.data[7] << 8); // 驱动电机2转矩
{
std::lock_guard<std::mutex> lock(vehicle_motors_mutex);
current_vehicle_motors.motors[0].speed = can_motor1_rpm;
current_vehicle_motors.motors[1].speed = can_motor2_rpm;
current_vehicle_motors.motors[0].torque = can_motor1_torque;
current_vehicle_motors.motors[1].torque = can_motor2_torque;
}
}
static void parse_vcu_vehicle_info4(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint8_t can_load = (frame.data[1] >> 6) & 0x03; // 重载信号
uint8_t can_soc = frame.data[2]; // 电量百分比
uint8_t can_veh_status = frame.data[1] & 0x07; // 车辆状态
uint8_t can_run_mode = (frame.data[1] >> 3) & 0x07; // 运行模式
uint16_t can_ins_res = frame.data[3] | (frame.data[4] << 8); // 绝缘电阻
uint8_t can_accel_pedal = frame.data[5]; // 油门踏板
uint8_t can_brake_pedal = frame.data[6]; // 制动踏板
{
std::lock_guard<std::mutex> lock(veh_report_mutex);
veh_report_data.load = (can_load == 0) ? 0 : 1; // 0轻载1重载
veh_report_data.power = static_cast<int>(can_soc); // 电量百分比
}
{
std::lock_guard<std::mutex> lock(vehicle_data_mutex);
// ---- 映射表定义 ----
static const uint8_t vehStatusMap[7] = {1, 4, 2, 3, 5, 0xfe, 0xff};
// ---- 查表映射 ----
current_vehicle_data.vehicle_status = (can_veh_status < 7) ? vehStatusMap[can_veh_status] : 0xff;
current_vehicle_data.run_mode = (can_run_mode < 3) ? can_run_mode + 1 : (can_run_mode == 3) ? 0xfe : 0xff;
current_vehicle_data.soc = (can_soc <= 100) ? can_soc : 0xFE;
current_vehicle_data.insulation_resistance = (can_ins_res <= 60000) ? can_ins_res : 0xFEFE;
current_vehicle_data.accel_pedal = (can_accel_pedal <= 100) ? can_accel_pedal : 0xFE;
current_vehicle_data.brake_pedal = (can_brake_pedal <= 101) ? can_brake_pedal : 0xFE;
}
{
std::lock_guard<std::mutex> lock(drive_brake_mutex);
current_drive_brake_data.accel_opening =
(can_accel_pedal <= 100) ? static_cast<uint16_t>(can_accel_pedal) * 10 : 0xFFFE;
current_drive_brake_data.brake_opening =
(can_brake_pedal <= 100) ? static_cast<uint16_t>(can_brake_pedal) * 10 : 0xFFFE;
}
}
static void parse_vcu_motor_temperature(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint8_t can_motor1_temp = frame.data[0]; // 驱动电机1温度
uint8_t can_motor2_temp = frame.data[1]; // 驱动电机2温度
uint8_t can_motor1_mcu_temp = frame.data[3]; // 驱动电机1控制器温度
uint8_t can_motor2_mcu_temp = frame.data[4]; // 驱动电机2控制器温度
{
std::lock_guard<std::mutex> lock(vehicle_motors_mutex);
current_vehicle_motors.motors[0].motor_temp = can_motor1_temp;
current_vehicle_motors.motors[0].controller_temp = can_motor1_mcu_temp;
current_vehicle_motors.motors[1].motor_temp = can_motor2_temp;
current_vehicle_motors.motors[1].controller_temp = can_motor2_mcu_temp;
}
}
static void parse_vcu_vehicle_info5(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint16_t can_speed = frame.data[1] | (frame.data[2] << 8); // 车速
uint8_t can_limitspeed = frame.data[3]; // 车辆限速
uint32_t can_mileage =
frame.data[4] | (frame.data[5] << 8) | (frame.data[6] << 16) | (frame.data[7] << 24); // 累计里程
uint8_t can_charge_status = frame.data[0] & 0x07; // 充电状态
{
std::lock_guard<std::mutex> lock(veh_report_mutex);
veh_report_data.speed = static_cast<int>(can_speed / 10); // 转换为 km/h
veh_report_data.mileage = static_cast<int>(can_mileage / 10); // 转换为 km
}
{
std::lock_guard<std::mutex> lock(vehicle_data_mutex);
current_vehicle_data.charge_status = (can_charge_status == 0) ? 0xff
: (can_charge_status < 7) ? can_charge_status
: 0xFE;
current_vehicle_data.speed = (can_speed <= 2000) ? can_speed : 0xFEFE;
current_vehicle_data.mileage = (can_mileage <= 9999999) ? can_mileage : 0xFFFFFFFE;
}
{
std::lock_guard<std::mutex> lock(drive_brake_mutex);
current_drive_brake_data.speed = can_speed;
}
{
std::lock_guard<std::mutex> lock(vehicle_full_mutex);
if (can_limitspeed == 0xFE)
current_vehicle_full_data.speed_limit = 0xFFFE;
else if (can_limitspeed == 0xFF)
current_vehicle_full_data.speed_limit = 0xFFFF;
else
current_vehicle_full_data.speed_limit = can_limitspeed;
}
}
static void parse_vcu_vehicle_info(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint16_t raw = frame.data[6] | (frame.data[7] << 8);
uint16_t decel_x10 = static_cast<uint16_t>((raw + 32768u) / 100u);
{
std::lock_guard<std::mutex> lock(drive_brake_mutex);
current_drive_brake_data.deceleration = decel_x10;
}
}
static void parse_vcu_mcu_info3(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint8_t can_motor1_seq = frame.data[0] >> 2 & 0x03; // 驱动电机1序号
uint8_t can_motor1_status = frame.data[0] >> 4 & 0x07; // 驱动电机1状态
uint8_t can_motor2_seq = frame.data[1] & 0x03; // 驱动电机2序号
uint8_t can_motor2_status = frame.data[1] >> 2 & 0x07; // 驱动电机2状态
{
std::lock_guard<std::mutex> lock(vehicle_motors_mutex);
current_vehicle_motors.motors[0].seq = can_motor1_seq;
current_vehicle_motors.motors[0].status = (can_motor1_status < 4) ? can_motor1_status + 1
: (can_motor1_status == 4) ? 0xfe
: 0xff;
current_vehicle_motors.motors[1].seq = can_motor2_seq;
current_vehicle_motors.motors[1].status = (can_motor2_status < 4) ? can_motor2_status + 1
: (can_motor2_status == 4) ? 0xfe
: 0xff;
}
}
static void parse_vcu_mcu_info4(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint16_t can_motor1_mcu_voltage = frame.data[0] | (frame.data[1] << 8); // 驱动电机1控制器电压
uint16_t can_motor1_mcu_current = frame.data[2] | (frame.data[3] << 8); // 驱动电机1控制器电流
uint16_t can_motor2_mcu_voltage = frame.data[4] | (frame.data[5] << 8); // 驱动电机2控制器电压
uint16_t can_motor2_mcu_current = frame.data[6] | (frame.data[7] << 8); // 驱动电机2控制器电流
{
std::lock_guard<std::mutex> lock(vehicle_motors_mutex);
current_vehicle_motors.motors[0].input_voltage = can_motor1_mcu_voltage;
current_vehicle_motors.motors[0].dc_current = can_motor1_mcu_current;
current_vehicle_motors.motors[1].input_voltage = can_motor2_mcu_voltage;
current_vehicle_motors.motors[1].dc_current = can_motor2_mcu_current;
}
}
static void parse_vcu_dtc_code(const can_frame& frame)
{
if (frame.can_dlc < 6) return; // 至少要到 data[5]
// ---- 按协议解码 ----
[[maybe_unused]] uint8_t project = frame.data[0] & 0x03; // 1850V0 / 1850V1
[[maybe_unused]] uint8_t drive_type = (frame.data[0] >> 2) & 0x07; // 直驱 / 四驱 / 前驱 / 后驱
[[maybe_unused]] uint8_t ecu_supplier = (frame.data[1] >> 5) & 0x07; // 一供/二供/其他
[[maybe_unused]] uint8_t fault_sum = frame.data[3]; // 故障总数
uint8_t can_ecu = frame.data[1] & 0x1F; // ECU_Name 0~11
uint8_t can_level = frame.data[2] & 0x07; // 故障等级 0~4
uint16_t can_code16 = frame.data[4] | (frame.data[5] << 8); // 故障码
// ---- 过滤无效情况 ----
if (can_level == 0) // 0 = 无故障
return;
if (can_ecu == 0) // 0 = 无效 ECU
return;
// 更新故障池
fault_pool.add_or_update(can_ecu, can_level, can_code16);
}
// VCU 16bit 故障码 → common_flags 的 bit 位置
static const std::unordered_map<uint16_t, uint8_t> fault_to_flag = {
{16, 0}, // 充电温差过大
{17, 0}, // 放电温差过大
{12, 1}, // 电芯高温报警(充电)
{13, 1}, // 电芯高温报警(放电)
{4, 2}, {5, 2}, // 总电压过压
{6, 3}, {7, 3}, // 总电压欠压
{24, 4}, // SOC过低
{0, 5}, {1, 5}, // 单体过压
{2, 6}, {3, 6}, // 单体欠压
{23, 7}, // SOC过高
{34, 8}, // SOC跳变
{10, 10}, {11, 10}, {14, 10}, {15, 10}, // 一致性差异报警
{25, 11}, {86, 11}, // 绝缘报警
{1009, 12}, {1010, 12}, {1017, 12}, {1019, 12}, // DCDC温度报警剩下的1000-1029 bit14
{1210, 15}, {1222, 15}, {1223, 15}, {1232, 15}, // TM1控制器温度报警
{1310, 15}, {1322, 15}, {1323, 15}, {1332, 15}, // TM2控制器温度报警
{44, 16}, // 高压互锁状态报警
{1211, 17}, {1224, 17}, {1311, 17}, {1324, 17}, // TM温度报警
};
static void update_alarm_data()
{
auto items = fault_pool.snapshot();
AlarmData new_data;
uint8_t max_level = 0;
uint32_t common_flags = 0;
// 各类 ECU 分类
for (auto& f : items)
{
// level 4 → 映射为 3
uint8_t mapped = (f.level >= 4) ? 3 : f.level;
if (mapped > max_level) max_level = mapped;
// 分类存 N1 N2 N5
switch (f.ecu)
{
case 1: new_data.battery_fault_codes.push_back(f.code); break; // BMS
case 4:
case 5: new_data.motor_fault_codes.push_back(f.code); break; // TM1/TM2
default: new_data.normal_fault_codes.push_back(f.code); break;
}
// ====== 通用报警标志位 ======
auto it = fault_to_flag.find(f.code);
if (it != fault_to_flag.end()) { common_flags |= (1u << it->second); }
else if (f.code >= 1000 && f.code <= 1029)
{
// DCDC 10001029 未枚举的全部 → bit14
common_flags |= (1u << 14);
}
}
// 设置最高等级
new_data.highest_level = (max_level == 0) ? 0 : max_level;
// 通用标志(目前没有来源,先置 0
new_data.common_flags = common_flags;
// 设置数量 N1~N5
new_data.n1 = new_data.battery_fault_codes.size();
new_data.n2 = new_data.motor_fault_codes.size();
new_data.n3 = new_data.engine_fault_codes.size();
new_data.n4 = new_data.other_fault_codes.size();
new_data.n5 = new_data.normal_fault_codes.size();
// 加锁写入全局 current_alarm_data
{
std::lock_guard<std::mutex> lock(alarm_data_mutex);
current_alarm_data = std::move(new_data);
}
}
// ========================== BMS 区域 ==========================
static void parse_b2v_st2(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint16_t can_pack_current = frame.data[2] | (frame.data[3] << 8); // 电池包总电流
uint16_t can_pack_voltage = frame.data[4] | (frame.data[5] << 8); // 电池包总电压
{
std::lock_guard<std::mutex> lock(vehicle_data_mutex);
current_vehicle_data.total_current = (can_pack_current <= 20000) ? can_pack_current : 0xFFFE;
current_vehicle_data.total_voltage = (can_pack_voltage <= 10000) ? can_pack_voltage : 0xFFFE;
}
{
std::lock_guard<std::mutex> lock(storage_voltage_mutex);
current_storage_voltage_data.subsystem.voltage = can_pack_voltage;
current_storage_voltage_data.subsystem.current = can_pack_current;
}
}
static void parse_b2v_st3(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
// 最高温度子系统号
uint8_t can_highest_temp_subsys = frame.data[3];
// 最高温度探针序号
uint8_t can_highest_temp_probe = frame.data[4];
// 最高温度值
uint8_t can_highest_temp_value = frame.data[0];
// 最低温度子系统号
uint8_t can_lowest_temp_subsys = frame.data[5];
// 最低温度探针序号
uint8_t can_lowest_temp_probe = frame.data[6];
// 最低温度值
uint8_t can_lowest_temp_value = frame.data[1];
{
std::lock_guard<std::mutex> lock(extreme_data_mutex);
current_extreme_data.highest_temp_subsys = can_highest_temp_subsys;
current_extreme_data.highest_temp_probe = can_highest_temp_probe;
current_extreme_data.highest_temp_value = can_highest_temp_value;
current_extreme_data.lowest_temp_subsys = can_lowest_temp_subsys;
current_extreme_data.lowest_temp_probe = can_lowest_temp_probe;
current_extreme_data.lowest_temp_value = can_lowest_temp_value;
}
}
static void parse_b2v_st5(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
// 电池单体电压最高值
uint16_t can_highest_voltage_value = frame.data[0] | (frame.data[1] << 8);
// 电池单体电压最低值
uint16_t can_lowest_voltage_value = frame.data[2] | (frame.data[3] << 8);
{
std::lock_guard<std::mutex> lock(extreme_data_mutex);
current_extreme_data.highest_voltage_value = can_highest_voltage_value;
current_extreme_data.lowest_voltage_value = can_lowest_voltage_value;
}
}
static void parse_b2v_st6(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
// 最高电压电池子系统号
uint8_t can_highest_voltage_subsys = frame.data[0];
// 最高电压电池单体代号
uint16_t can_highest_voltage_cell = frame.data[1] | (frame.data[2] << 8);
// 最低电压电池子系统号
uint8_t can_lowest_voltage_subsys = frame.data[3];
// 最低电压电池单体代号
uint16_t can_lowest_voltage_cell = frame.data[5] | (frame.data[6] << 8);
{
std::lock_guard<std::mutex> lock(extreme_data_mutex);
current_extreme_data.highest_voltage_subsys = can_highest_voltage_subsys;
current_extreme_data.highest_voltage_cell = can_highest_voltage_cell;
current_extreme_data.lowest_voltage_subsys = can_lowest_voltage_subsys;
current_extreme_data.lowest_voltage_cell = can_lowest_voltage_cell;
}
}
static void parse_batt_info2(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
[[maybe_unused]] uint8_t can_csc_num = frame.data[0];
uint16_t can_bat_num = frame.data[1] | (frame.data[2] << 8);
uint8_t can_temp_num = frame.data[3];
uint8_t can_sys_seq = frame.data[7];
{
std::lock_guard<std::mutex> lock(storage_voltage_mutex);
auto& pack = current_storage_voltage_data.subsystem;
pack.subsystem_id = can_sys_seq;
pack.total_cells = can_bat_num;
pack.frame_cells = can_bat_num;
pack.cells.resize(can_bat_num);
}
{
std::lock_guard<std::mutex> lock(storage_temp_mutex);
auto& pack = current_storage_temp_data.subsystem;
pack.probe_count = can_temp_num;
pack.probes.resize(can_temp_num);
}
}
static void parse_b2v_cell_voltage(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint8_t frame_no = frame.data[0];
uint16_t v0 = frame.data[2] | (frame.data[3] << 8);
uint16_t v1 = frame.data[4] | (frame.data[5] << 8);
uint16_t v2 = frame.data[6] | (frame.data[7] << 8);
size_t base = static_cast<size_t>(frame_no - 1) * 3;
{
std::lock_guard<std::mutex> lock(storage_voltage_mutex);
auto& cells = current_storage_voltage_data.subsystem.cells;
size_t total = cells.size();
if (total == 0) return;
if (base >= total)
{
std::stringstream ss;
ss << "[tbox_data] Invalid cell voltage frame index: "
<< "frame_no=" << std::dec << static_cast<int>(frame_no) << ", base=" << base
<< ", total_cells=" << total;
LOG_WARN(tbox_logger, ss.str());
return;
}
// 单个位置再做防御(最后一帧可能不满 3 个)
if (base + 0 < total) cells[base + 0].voltage = v0;
if (base + 1 < total) cells[base + 1].voltage = v1;
if (base + 2 < total) cells[base + 2].voltage = v2;
}
}
static void parse_b2v_cell_temp(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint8_t frame_no = frame.data[0];
// 6 个候选温度字节
uint8_t temps[6] = {frame.data[2], frame.data[3], frame.data[4], frame.data[5], frame.data[6], frame.data[7]};
// 奇数帧6 个有效;偶数帧:前 3 个有效(后 3 个无效 / 占位)
size_t valid_count = (frame_no & 0x01) ? 6 : 3;
size_t base = 0;
if (frame_no == 0)
{
// 理论上不会有 0 帧,防御一下
return;
}
else if (frame_no & 0x01)
{
// 奇数帧frame_no = 1,3,5,... → k = (frame_no-1)/2
// sum = (k)*9
base = static_cast<size_t>((frame_no - 1) / 2) * 9;
}
else
{
// 偶数帧frame_no = 2,4,6,... → k = frame_no/2
// sum = 9*k - 3
base = static_cast<size_t>(frame_no / 2) * 9 - 3;
}
{
std::lock_guard<std::mutex> lock(storage_temp_mutex);
auto& probes = current_storage_temp_data.subsystem.probes;
size_t total = probes.size();
if (total == 0) return;
if (base >= total || base + valid_count > total)
{
std::stringstream ss;
ss << "[tbox_data] Invalid cell temperature frame index: "
<< "frame_no=" << static_cast<int>(frame_no) << ", base=" << base << ", valid_count=" << valid_count
<< ", total_probes=" << total;
LOG_WARN(tbox_logger, ss.str());
return;
}
for (size_t i = 0; i < valid_count; ++i) { probes[base + i].temp = temps[i]; }
}
}
static void parse_b2v_st8_19(const can_frame& frame)
{
if (frame.can_dlc < 8) return;
uint32_t low = frame.data[0];
uint32_t mid = frame.data[1];
uint32_t high = frame.data[2];
// CAN 格式0.1 kWh
uint32_t raw_can = (high << 16) | (mid << 8) | low;
uint32_t kwh_value = 0;
// 特殊值处理
if (raw_can == 0xFFFFFF) // 原三字节都是 FF
{
kwh_value = 0xFFFFFFFF; // 无效
}
else if (raw_can == 0xFFFFFE) // FE
{
kwh_value = 0xFFFFFFFE; // 异常
}
else
{
// 转换到协议单位1 kWh
float real_kwh = raw_can * 0.1f;
kwh_value = static_cast<uint32_t>(real_kwh + 0.5f); // 四舍五入取整
}
{
std::lock_guard<std::mutex> lock(vehicle_full_mutex);
current_vehicle_full_data.total_charge = kwh_value;
}
}
// ========================== 主入口 ==========================
void handle_can_msg(const can_frame& frame)
{
uint32_t real_can_id =
(frame.can_id & CAN_EFF_FLAG) ? (frame.can_id & CAN_EFF_MASK) : (frame.can_id & CAN_SFF_MASK);
static uint64_t last_clean_ts = 0;
uint64_t now = common::get_timestamp_ms();
if (now - last_clean_ts > 100)
{
fault_pool.purge_timeout(3000); // 删除消失的故障
update_alarm_data();
last_clean_ts = now;
}
switch (real_can_id)
{
// -------- VCU --------
case 0x18FF8010: parse_vcu_vehicle_info1(frame); break;
case 0x18FF8110: parse_vcu_mcu_info2(frame); break;
case 0x18FF8310: parse_vcu_vehicle_info4(frame); break;
case 0x18FF8410: parse_vcu_motor_temperature(frame); break;
case 0x18FF8610: parse_vcu_vehicle_info5(frame); break;
case 0x18FF8710: parse_vcu_mcu_info3(frame); break;
case 0x18FF8810: parse_vcu_mcu_info4(frame); break;
case 0x18FE6686: parse_vcu_dtc_code(frame); break;
case 0x19: parse_vcu_vehicle_info(frame); break;
// -------- BMS --------
case 0x1884EFF3: parse_b2v_st2(frame); break;
case 0x1886EFF3: parse_b2v_st3(frame); break;
case 0x1887EFF3: parse_b2v_st5(frame); break;
case 0x1888EFF3: parse_b2v_st6(frame); break;
case 0x18E3EFF3: parse_batt_info2(frame); break;
case 0x18C1EFF3: parse_b2v_cell_voltage(frame); break;
case 0x18C2EFF3: parse_b2v_cell_temp(frame); break;
case 0x18F1D0F3: parse_b2v_st8_19(frame); break;
default: break;
}
}
template <typename T>
inline void append_encoded(std::vector<uint8_t>& out, std::mutex& mtx, const T& obj)
{
std::lock_guard<std::mutex> lk(mtx);
auto v = obj.encode();
out.reserve(out.size() + v.size()); // 动态扩容
out.insert(out.end(), v.begin(), v.end());
}
inline void append_raw(std::vector<uint8_t>& out, std::mutex& mtx, const std::vector<uint8_t>& v)
{
std::lock_guard<std::mutex> lk(mtx);
out.reserve(out.size() + v.size()); // 动态扩容
out.insert(out.end(), v.begin(), v.end());
}
std::vector<uint8_t> buildTboxRealtimePayload()
{
std::vector<uint8_t> result;
append_encoded(result, vehicle_data_mutex, current_vehicle_data);
append_encoded(result, vehicle_motors_mutex, current_vehicle_motors);
append_encoded(result, vehicle_position_mutex, current_vehicle_position);
append_encoded(result, extreme_data_mutex, current_extreme_data);
append_encoded(result, alarm_data_mutex, current_alarm_data);
append_encoded(result, storage_voltage_mutex, current_storage_voltage_data);
append_encoded(result, storage_temp_mutex, current_storage_temp_data);
{
std::lock_guard<std::mutex> lk(autodata_mutex);
result.insert(result.end(), current_autodata.begin(), current_autodata.end());
}
append_encoded(result, drive_brake_mutex, current_drive_brake_data);
append_encoded(result, tire_data_mutex, current_tire_data);
append_encoded(result, front_switch_mutex, current_front_switch_data);
append_encoded(result, vehicle_full_mutex, current_vehicle_full_data);
return result;
}

View File

@ -0,0 +1,16 @@
#include "tbox_messages.h"
// 接收报文处理
ReceivedPacket process_received_packet(const std::vector<uint8_t>& data)
{
auto pkt_opt = ProtocolCodec::decode_full_packet(data);
if (!pkt_opt)
{
LOG_WARN(tbox_logger, "[tbox_messages] Failed to decode received packet");
return {};
}
const auto& pkt = *pkt_opt;
return {pkt.command_id, pkt.response_flag, pkt.data_unit};
}

View File

@ -0,0 +1,132 @@
#include "tbox_messages.h"
template <typename F>
static std::string build_packet(uint8_t command_id, F&& fill_data_unit)
{
FullPacket pkt;
pkt.command_id = command_id;
pkt.response_flag = 0xFE;
pkt.vin = ConfigManager::instance().getVin();
pkt.encryption_method = 0x01;
pkt.data_unit.clear();
// 用户负责填充 data_unit
fill_data_unit(pkt.data_unit);
pkt.data_length = pkt.data_unit.size();
auto encoded = ProtocolCodec::encode_full_packet(pkt);
return {encoded.begin(), encoded.end()};
}
static std::string build_gateway_address_request()
{
return build_packet(0xC1,
[](std::vector<uint8_t>& du)
{
auto time_bytes = ProtocolCodec::get_current_time_bytes();
du.insert(du.end(), time_bytes.begin(), time_bytes.end());
du.insert(du.end(), 7, 0x00);
});
}
static std::string build_login_request()
{
return build_packet(0x01,
[](std::vector<uint8_t>& du)
{
auto time_bytes = ProtocolCodec::get_current_time_bytes();
du.insert(du.end(), time_bytes.begin(), time_bytes.end());
// --- 日流水号逻辑保留原样 ---
std::ostringstream oss;
oss << std::setw(2) << std::setfill('0') << (int)time_bytes[0] << std::setw(2)
<< std::setfill('0') << (int)time_bytes[1] << std::setw(2) << std::setfill('0')
<< (int)time_bytes[2];
std::string date_str = oss.str();
auto last_login_date = ConfigManager::instance().getLoginSeqDate();
int last_login_seq = ConfigManager::instance().getLoginSeq();
uint16_t login_seq = 1;
if (last_login_date == date_str)
{
login_seq = (last_login_seq >= 65531) ? 1 : last_login_seq + 1;
}
ConfigManager::instance().setLoginSeqDate(date_str);
ConfigManager::instance().setLoginSeq(login_seq);
ConfigManager::instance().save();
du.push_back((login_seq >> 8) & 0xFF);
du.push_back(login_seq & 0xFF);
// ICCID
std::string iccid = ICCID;
if (iccid.size() < 20)
iccid.resize(20, '0');
else if (iccid.size() > 20)
iccid.resize(20);
du.insert(du.end(), iccid.begin(), iccid.end());
du.insert(du.end(), 2, 0x00); // 预留字节
});
}
static std::string build_heartbeat_packet()
{
return build_packet(0x07, [](std::vector<uint8_t>& /*du*/) {});
}
static std::string build_time_sync_request()
{
return build_packet(0x08, [](std::vector<uint8_t>& /*du*/) {});
}
// 实时信息上报
static std::string build_realtime_msg_request()
{
return build_packet(0x02,
[](std::vector<uint8_t>& du)
{
auto time_bytes = ProtocolCodec::get_current_time_bytes();
du.insert(du.end(), time_bytes.begin(), time_bytes.end());
auto payload = buildTboxRealtimePayload();
du.insert(du.end(), payload.begin(), payload.end());
});
}
// 构建命令包
std::string build_command_packet(uint8_t command_id, std::span<const uint8_t> payload)
{
switch (command_id)
{
case 0xC1: // 网关地址获取
return build_gateway_address_request();
case 0x01: // 车辆登入请求
return build_login_request();
case 0x02: // 实时信息上报
return build_realtime_msg_request();
case 0x07: // 心跳包
return build_heartbeat_packet();
case 0x08: // 终端校时请求
return build_time_sync_request();
case 0xD5: // v2v broadcat消息下发
{
if (payload.empty())
{
LOG_WARN(tbox_logger, "[tbox_messages] payload required for cmd: " + std::to_string(command_id));
return "";
}
return build_packet(command_id,
[&](std::vector<uint8_t>& du) { du.insert(du.end(), payload.begin(), payload.end()); });
}
default:
LOG_WARN(tbox_logger,
"[tbox_messages] Unknown command ID for command packet: " + std::to_string(command_id));
return "";
}
}

View File

@ -0,0 +1,260 @@
#include <iomanip>
#include <memory>
#include "protocol_codec.h"
#include "tcp_client_instance.h"
std::shared_ptr<TcpClient> tcp_client_gateway;
static std::atomic<bool> g_gateway_threads_started{false};
static std::atomic<int> g_gateway_fail_count{0};
// 处理车辆登入请求
static void handle_login_request(const ReceivedPacket& packet)
{
if (packet.response_flag == 0x01)
{
LOG_INFO(tbox_logger, "[tbox_gateway] Login successful");
// ---------------------------
// 启动心跳线程0x07
// ---------------------------
bool expected = false;
if (!g_gateway_threads_started.compare_exchange_strong(expected, true))
{
// 已经启动过心跳/实时线程了,不再重复开
LOG_WARN(tbox_logger, "[tbox_gateway] Heartbeat & realtime threads already started, skip");
}
else
{
// 这里用 weak_ptr 捕获
std::weak_ptr<TcpClient> wp = tcp_client_gateway;
// 心跳线程
std::thread(
[wp]()
{
auto heartbeat_pkt = build_command_packet(0x07);
while (true)
{
int interval = ConfigManager::instance().getHeartbeatInterval();
auto client = wp.lock();
if (!client)
{
LOG_WARN(tbox_logger, "[tbox_gateway] Heartbeat thread exit: TcpClient expired");
break;
}
if (client->is_connected())
{
client->send_data(heartbeat_pkt);
LOG_INFO(tbox_logger, "[tbox_gateway] Sent heartbeat packet to Gateway");
}
else
{
LOG_WARN(tbox_logger, "[tbox_gateway] Cannot send heartbeat, not connected");
break;
}
std::this_thread::sleep_for(std::chrono::seconds(interval));
}
})
.detach();
// 实时上报线程
std::thread(
[wp]()
{
while (true)
{
auto client = wp.lock();
if (!client)
{
LOG_WARN(tbox_logger, "[tbox_gateway] Realtime thread exit: TcpClient expired");
break;
}
if (client->is_connected())
{
auto realtime_pkt = build_command_packet(0x02);
// std::ostringstream ss;
// for (uint8_t b : realtime_pkt)
// {
// ss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)b << "
// ";
// }
// LOG_INFO(tbox_logger, "[tbox_gateway] TX REALTIME HEX: " + ss.str());
client->send_data(realtime_pkt);
LOG_INFO(tbox_logger, "[tbox_gateway] Sent realtime report (0x02), length=" +
std::to_string(realtime_pkt.size()));
}
else
{
LOG_WARN(tbox_logger, "[tbox_gateway] Realtime report stopped, disconnected");
break;
}
std::this_thread::sleep_for(std::chrono::seconds(5));
}
})
.detach();
}
// 登录成功 → 立即发一次校时
auto time_sync_pkt = build_command_packet(0x08);
if (tcp_client_gateway) tcp_client_gateway->send_data(time_sync_pkt);
LOG_INFO(tbox_logger, "[tbox_gateway] Sent time sync request to Gateway");
}
else
{
std::stringstream ss;
ss << "[tbox_gateway] Login failed with response flag: 0x" << std::hex << std::uppercase << std::setw(2)
<< std::setfill('0') << static_cast<int>(packet.response_flag) << std::dec;
LOG_WARN(tbox_logger, ss.str()); // 登录失败,打印响应标志
}
}
// 处理终端校时请求
static void handle_time_sync_request(const ReceivedPacket& packet)
{
if (packet.response_flag == 0x01)
{
if (packet.data_unit.size() >= 6)
{
// 平台发来的北京时间
struct tm t_platform{};
t_platform.tm_year = packet.data_unit[0] + 100; // 年份从1900开始算
t_platform.tm_mon = packet.data_unit[1] - 1; // 月份从0开始
t_platform.tm_mday = packet.data_unit[2];
t_platform.tm_hour = packet.data_unit[3];
t_platform.tm_min = packet.data_unit[4];
t_platform.tm_sec = packet.data_unit[5];
time_t platform_utc_tt = timegm(&t_platform) - 8 * 3600; // 转成 UTC 时间
if (platform_utc_tt == -1)
{
LOG_WARN(tbox_logger, "[tbox_gateway] Invalid platform time data");
return;
}
// 获取系统 UTC 时间
auto now = std::chrono::system_clock::now();
time_t system_utc_tt = std::chrono::system_clock::to_time_t(now);
// 计算时间差(秒)
double diff_seconds = std::difftime(system_utc_tt, platform_utc_tt);
LOG_INFO(tbox_logger, "[tbox_gateway] Time diff: " + std::to_string(diff_seconds) + " seconds");
// 只在差距大于阈值时才校时,例如 5 秒
if (std::abs(diff_seconds) > 5)
{
struct timespec ts;
ts.tv_sec = platform_utc_tt; // 注意这里使用 UTC
ts.tv_nsec = 0;
if (clock_settime(CLOCK_REALTIME, &ts) == -1)
{
LOG_WARN(tbox_logger, "[tbox_gateway] Failed to set system time (need root)");
return;
}
std::stringstream ss;
ss << "[tbox_gateway] System time updated to: " << std::put_time(&t_platform, "%Y-%m-%d %H:%M:%S");
LOG_INFO(tbox_logger, ss.str());
LOG_INFO(tbox_logger, "[tbox_gateway] Time sync successful");
}
else
{
LOG_INFO(tbox_logger, "[tbox_gateway] Time difference within threshold, no sync needed");
}
}
}
else
{
std::stringstream ss;
ss << "[tbox_gateway] Time sync failed with response flag: 0x" << std::hex << std::uppercase << std::setw(2)
<< std::setfill('0') << static_cast<int>(packet.response_flag) << std::dec;
LOG_WARN(tbox_logger, ss.str()); // 校时失败,打印响应标志
}
}
static void handle_tbox_msg(const std::vector<uint8_t>& data)
{
// std::ostringstream oss;
// for (uint8_t c : data)
// {
// oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast<int>(c) << " ";
// }
// LOG_INFO(tbox_logger, "[tbox_gateway] Received from Gateway (HEX): " + oss.str());
// 处理接收到的数据
auto processed_data = process_received_packet(data);
if (processed_data.command_id == 0 || processed_data.data_unit.empty()) return; // 无效id或者数据为空直接返回
std::stringstream ss;
ss << "[tbox_gateway] Processed Packet - Command ID: 0x" << std::hex << std::uppercase << std::setw(2)
<< std::setfill('0') << static_cast<int>(processed_data.command_id) << ", Data Length: " << std::dec
<< processed_data.data_unit.size();
LOG_INFO(tbox_logger, ss.str());
// 根据命令类型处理
switch (processed_data.command_id)
{
case 0x01: handle_login_request(processed_data); break;
case 0x08: handle_time_sync_request(processed_data); break;
default:
LOG_WARN(tbox_logger, "[tbox_gateway] Unknown command ID in response packet: " +
std::to_string(processed_data.command_id));
return; // 未知命令,直接返回
}
}
static void handle_tbox_status(bool connected)
{
if (connected)
{
g_gateway_fail_count.store(0);
LOG_INFO(tbox_logger, "[tbox_gateway] TBox Gateway connected");
auto built_pkt = build_command_packet(0x01); // 车辆登入请求
LOG_INFO(tbox_logger, "[tbox_gateway] Sent login request to Gateway");
if (tcp_client_gateway) tcp_client_gateway->send_data(built_pkt);
}
else
{
g_gateway_threads_started.store(false);
int fail = g_gateway_fail_count.fetch_add(1);
int backoff = std::min(30, 1 << fail); // 1,2,4,8,16,30 秒
LOG_WARN(tbox_logger, "[tbox_gateway] disconnected, backoff " + std::to_string(backoff) + "s");
std::this_thread::sleep_for(std::chrono::seconds(backoff));
// 断线后,重新请求网关地址
if (!tcp_client_router || !tcp_client_router->is_connected())
{
init_tcp_client_tbox_router(ConfigManager::instance().getPlatformIp(),
ConfigManager::instance().getPlatformPort());
}
}
}
void init_tcp_client_tbox_gateway(const std::string& ip, int port)
{
std::this_thread::sleep_for(std::chrono::milliseconds(150));
tcp_client_gateway = std::make_shared<TcpClient>("tbox_gateway", ip, port, tbox_logger);
tcp_client_gateway->set_receive_callback(handle_tbox_msg);
tcp_client_gateway->set_status_callback(handle_tbox_status);
tcp_client_gateway->start();
}

View File

@ -0,0 +1,86 @@
#include <iomanip>
#include <memory>
#include "protocol_codec.h"
#include "tcp_client_instance.h"
std::shared_ptr<TcpClient> tcp_client_router;
static void handle_tbox_msg(const std::vector<uint8_t>& data)
{
// std::ostringstream oss;
// for (uint8_t c : data)
// {
// oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast<int>(c) << " ";
// }
// LOG_INFO(tbox_logger, "[tbox_router] Received from Router (HEX): " + oss.str());
// 处理接收到的数据
auto processed_data = process_received_packet(data);
if (processed_data.command_id != 0xC1 || processed_data.data_unit.empty())
return; // 不是网关地址应答或者数据为空,直接返回
std::stringstream ss;
ss << "[tbox_router] Processed Packet - Command ID: 0x" << std::hex << std::uppercase << std::setw(2)
<< std::setfill('0') << static_cast<int>(processed_data.command_id) << ", Data Length: " << std::dec
<< processed_data.data_unit.size();
LOG_INFO(tbox_logger, ss.str());
const auto& d = processed_data.data_unit;
uint8_t ip_type = d[6];
int port = (d[7] << 8) | d[8];
std::string ip;
if (ip_type == 0x00 && d.size() >= 13) // IPv4
{
ip = std::to_string(d[9]) + "." + std::to_string(d[10]) + "." + std::to_string(d[11]) + "." +
std::to_string(d[12]);
}
else if (ip_type == 0x01 && d.size() >= 25) // IPv6
{
std::ostringstream ip_ss;
for (int i = 0; i < 16; ++i)
{
if (i > 0 && i % 2 == 0) ip_ss << ":";
ip_ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(d[9 + i]);
}
ip = ip_ss.str();
}
else
{
return; // 数据长度不够或者 IP 类型未知,直接返回
}
LOG_INFO(tbox_logger, "[tbox_router] Gateway Address - IP: " + ip + ", Port: " + std::to_string(port));
init_tcp_client_tbox_gateway(ip, port); // 初始化 TBox Gateway 连接
}
static void handle_tbox_status(bool connected)
{
if (connected)
{
LOG_INFO(tbox_logger, "[tbox_router] TBox Router connected");
auto built_packet = build_command_packet(0xC1); // 构建网关地址请求包
LOG_INFO(tbox_logger, "[tbox_router] Sending gateway address request to Router");
if (tcp_client_router) tcp_client_router->send_data(built_packet); // 发送请求
}
else
{
LOG_WARN(tbox_logger, "[tbox_router] TBox Router disconnected");
}
}
void init_tcp_client_tbox_router(const std::string& ip, int port)
{
tcp_client_router = std::make_shared<TcpClient>("tbox_router", ip, port, tbox_logger);
tcp_client_router->set_reconnect_policy(60, 300); // 设置重连策略初始60秒最大300秒
tcp_client_router->set_receive_callback(handle_tbox_msg); // 数据接收回调
tcp_client_router->set_status_callback(handle_tbox_status); // 连接状态变化回调
tcp_client_router->start();
}

View File

@ -0,0 +1,125 @@
#include <iomanip>
#include <memory>
#include <sstream>
#include "tbox_data_manager.h"
#include "tcp_client_instance.h"
// 车辆位置信息0x05TCP Client
std::unique_ptr<TcpClient> tcp_client_vehicle_position;
// ===============================
// 数据接收回调
// ===============================
static void handle_vehicle_position_msg(const std::vector<uint8_t>& data)
{
// 将收到的原始内容转成字符串
std::string s(reinterpret_cast<const char*>(data.data()), data.size());
// LOG_INFO(tbox_logger, "[vehicle_position] Received: " + s);
// 内部缓存变量(你之后要用)
static double last_lat = 0.0;
static double last_lon = 0.0;
static double last_heading = 0.0;
static std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now();
// --- 提取定位的 INSPVAXA 数据 ---
// 收到的内容可能包含多行,我们按行拆开
std::stringstream ss(s);
std::string line;
while (std::getline(ss, line))
{
// 专门匹配 INS PVAX 行
if (line.rfind("#INSPVAXA", 0) == 0) // starts_with("#INSPVAXA")
{
// 按逗号拆分
std::vector<std::string> f;
{
std::stringstream cs(line);
std::string item;
while (std::getline(cs, item, ',')) f.push_back(item);
}
// 防止字段不够
if (f.size() > 18)
{
try
{
// NovAtel INS PVAX 格式:
// f[11] = 纬度
// f[12] = 经度
// f[17] = 航向角Yaw
last_lat = std::stod(f[11]);
last_lon = std::stod(f[12]);
last_heading = std::stod(f[17]);
if (last_heading < 0) last_heading += 360.0;
{
std::lock_guard<std::mutex> lock(vehicle_position_mutex);
// ×10^6精确到百万分之一度
current_vehicle_position.latitude = static_cast<uint32_t>(last_lat * 1'000'000);
current_vehicle_position.longitude = static_cast<uint32_t>(last_lon * 1'000'000);
// ×10精度 0.1 度)
current_vehicle_position.heading = static_cast<uint16_t>(last_heading * 10);
}
// 更新时间戳
last_update_time = std::chrono::steady_clock::now();
}
catch (...)
{
// 忽略解析失败
}
}
}
}
// --- 超时保护 ---
const int TIMEOUT_MS = 3000;
auto now = std::chrono::steady_clock::now();
auto diff_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update_time).count();
{
std::lock_guard<std::mutex> lock(vehicle_position_mutex);
current_vehicle_position.status = (diff_ms > TIMEOUT_MS ? 0x01 : 0x00);
}
}
// ===============================
// 连接状态回调
// ===============================
static void handle_vehicle_position_status(bool connected)
{
if (connected) { LOG_INFO(tbox_logger, "[tbox_vehicle_position] TCP client connected"); }
else
{
LOG_WARN(tbox_logger, "[tbox_vehicle_position] TCP client disconnected");
}
}
// ===============================
// 初始化函数(对外接口)
// ===============================
void init_tcp_client_vehicle_position(const std::string& ip, int port)
{
tcp_client_vehicle_position = std::make_unique<TcpClient>("tbox_vehicle_position", ip, port, tbox_logger);
// 设置重连策略,可按需要改
tcp_client_vehicle_position->set_reconnect_policy(5, 60);
// 设置数据接收回调
tcp_client_vehicle_position->set_receive_callback(handle_vehicle_position_msg);
// 设置连接状态回调
tcp_client_vehicle_position->set_status_callback(handle_vehicle_position_status);
// 启动客户端(开始连接)
tcp_client_vehicle_position->start();
}

View File

@ -0,0 +1,39 @@
#include "tcp_server_instance.h"
std::unique_ptr<TcpServer> tcp_server_tbox_autodata;
static void handle_tbox_autodata_msg(const std::vector<uint8_t>& data)
{
if (data.size() < 25) return;
if (data[0] != 0x23 || data[1] != 0x23) return;
uint8_t cmd = data[2];
if (cmd != 0x02) return;
uint16_t data_len = (static_cast<uint16_t>(data[22]) << 8) | data[23];
size_t offset = 24;
if (offset + data_len > data.size() - 1) return;
// 至少要有 6 字节可丢弃
if (data_len <= 6) return;
// payload 起始位置:跳过前 6 字节(时间戳)
const uint8_t* payload_begin = data.data() + offset + 6;
size_t payload_len = data_len - 6;
{
std::lock_guard<std::mutex> lock(autodata_mutex);
current_autodata.assign(payload_begin, payload_begin + payload_len);
}
}
void init_tcp_server_tbox_autodata(const std::string& ip, int port) // 初始化 TBoxAutodata TCP 服务器
{
std::string server_id = "tbox_autodata";
tcp_server_tbox_autodata = std::make_unique<TcpServer>(server_id, ip, port, tbox_logger);
tcp_server_tbox_autodata->set_receive_callback(handle_tbox_autodata_msg);
tcp_server_tbox_autodata->start();
}

View File

@ -0,0 +1,49 @@
#include "mqtt_client_instance.h"
#include "tbox_messages.h"
#include "tcp_server_instance.h"
std::unique_ptr<TcpServer> tcp_server_tbox_v2v;
static void handle_tbox_v2v_tcp_msg(const std::vector<uint8_t>& data)
{
auto pkt = process_received_packet(data);
if (pkt.command_id == 0 || pkt.response_flag != 0xFE) return;
switch (pkt.command_id)
{
case 0xD3:
case 0xD4:
case 0xD6:
{
auto topic = build_mqtt_topic_by_cmd(pkt.command_id);
if (topic.empty()) break;
if (!mqtt_client_tbox_v2v || !mqtt_client_tbox_v2v->is_connected()) break;
const size_t payload_len = pkt.data_unit.size();
LOG_INFO(v2v_logger, "[tbox_v2v][tcp->mqtt] publish, cmd=" + byte_to_hex(pkt.command_id) +
", topic=" + topic + ", payload_len=" + std::to_string(payload_len));
std::string payload_str(pkt.data_unit.begin(), pkt.data_unit.end());
mqtt_client_tbox_v2v->publish(topic, payload_str, 1);
break;
}
default:
{
LOG_WARN(v2v_logger, "[tbox_v2v][tcp->mqtt] drop, unknown cmd, cmd=" + byte_to_hex(pkt.command_id) +
", payload_len=" + std::to_string(pkt.data_unit.size()));
break;
}
}
}
void init_tcp_server_tbox_v2v(const std::string& ip, int port) // 初始化 TBoxV2V TCP 服务器
{
std::string server_id = "tbox_v2v_tcp";
tcp_server_tbox_v2v = std::make_unique<TcpServer>(server_id, ip, port, v2v_logger);
tcp_server_tbox_v2v->set_receive_callback(handle_tbox_v2v_tcp_msg);
tcp_server_tbox_v2v->start();
}

193
src/can/can_bus.cpp Normal file
View File

@ -0,0 +1,193 @@
#include "can_bus.h"
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include <cstring>
#include <thread>
CanBus::CanBus(const std::string& id, const std::string& interface_name, Logger& logger)
: id_(id), interface_name_(interface_name), logger_(logger)
{
}
CanBus::~CanBus() { stop(); }
bool CanBus::start()
{
if (running_) return false;
running_ = true;
worker_ = std::thread(
[this]()
{
int retry_interval = retry_first_;
while (running_)
{
if (!init_socket())
{
LOG_WARN(logger_,
"[" + id_ + "] CAN init failed, retry in " + std::to_string(retry_interval) + "s");
for (int i = 0; i < retry_interval && running_; ++i)
std::this_thread::sleep_for(std::chrono::seconds(1));
retry_interval = std::min(retry_interval * 2, retry_max_);
continue;
}
LOG_INFO(logger_, "[" + id_ + "] CAN bus started on " + interface_name_);
// 成功初始化后,重置重试间隔
retry_interval = retry_first_;
// 进入接收循环
receive_loop();
// 如果退出 receive_loop关闭 socket 重新尝试
if (sockfd_ >= 0)
{
::close(sockfd_);
sockfd_ = -1;
}
LOG_WARN(logger_, "[" + id_ + "] CAN bus disconnected, retrying...");
}
});
return true;
}
void CanBus::stop()
{
bool expected = true;
if (!running_.compare_exchange_strong(expected, false)) return;
if (sockfd_ >= 0)
{
::close(sockfd_);
sockfd_ = -1;
}
if (worker_.joinable()) worker_.join();
LOG_INFO(logger_, "[" + id_ + "] CAN bus stopped");
}
bool CanBus::send_frame(const can_frame& frame)
{
std::lock_guard<std::mutex> lock(send_mutex_);
if (!running_ || sockfd_ < 0) return false;
static int err_count = 0;
ssize_t n = write(sockfd_, &frame, sizeof(frame));
if (n != sizeof(frame))
{
static auto last_log_time = std::chrono::steady_clock::now();
++err_count;
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - last_log_time).count() >= 2)
{
LOG_ERROR(logger_, "[" + id_ + "] CAN write error (x" + std::to_string(err_count) + ")");
err_count = 0;
last_log_time = now;
}
return false;
}
// 发送成功则清空计数
err_count = 0;
return true;
}
bool CanBus::init_socket()
{
sockfd_ = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (sockfd_ < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] Failed to create CAN socket");
return false;
}
// 关闭回环、不要接收自己发送的帧
int loopback = 0;
setsockopt(sockfd_, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
int recv_own_msgs = 0;
setsockopt(sockfd_, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &recv_own_msgs, sizeof(recv_own_msgs));
struct ifreq ifr{};
std::strncpy(ifr.ifr_name, interface_name_.c_str(), IFNAMSIZ);
if (ioctl(sockfd_, SIOCGIFINDEX, &ifr) < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] ioctl SIOCGIFINDEX failed");
::close(sockfd_);
sockfd_ = -1;
return false;
}
struct sockaddr_can addr{};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] bind CAN socket failed");
::close(sockfd_);
sockfd_ = -1;
return false;
}
return true;
}
void CanBus::receive_loop()
{
can_frame frame{};
while (running_ && sockfd_ >= 0)
{
ssize_t n = read(sockfd_, &frame, sizeof(frame));
if (n < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] CAN read error");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// 检查黑名单
{
std::lock_guard<std::mutex> lock(blacklist_mutex_);
if (blacklist_.find(frame.can_id & (frame.can_id & CAN_EFF_FLAG ? CAN_EFF_MASK : CAN_SFF_MASK)) !=
blacklist_.end())
{
continue; // 黑名单 ID直接丢弃
}
}
if (receive_callback_) receive_callback_(frame);
}
}
// 黑名单管理
void CanBus::add_blacklist_id(uint32_t can_id)
{
std::lock_guard<std::mutex> lock(blacklist_mutex_);
blacklist_.insert(can_id);
}
void CanBus::remove_blacklist_id(uint32_t can_id)
{
std::lock_guard<std::mutex> lock(blacklist_mutex_);
blacklist_.erase(can_id);
}
void CanBus::clear_blacklist()
{
std::lock_guard<std::mutex> lock(blacklist_mutex_);
blacklist_.clear();
}

206
src/config/INIReader.cpp Executable file
View File

@ -0,0 +1,206 @@
// 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;
}

326
src/config/ini.c Executable file
View File

@ -0,0 +1,326 @@
/* 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);
}

167
src/main.cpp Normal file
View File

@ -0,0 +1,167 @@
#include "main.h"
#include <unistd.h>
#include <csignal>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
// -----------------------------------------------------------------------------
// 命令行参数解析
// -----------------------------------------------------------------------------
struct CmdOptions
{
bool show_help = false;
bool show_version = false;
bool init_config = false;
std::string config_path;
};
CmdOptions parseArgs(int argc, char* argv[])
{
CmdOptions opts;
if (argc == 1)
{
// 默认情况:使用当前目录下的 config.ini
opts.config_path = "./config.ini";
return opts;
}
for (int i = 1; i < argc; ++i)
{
std::string arg = argv[i];
if (arg == "-h" || arg == "--help")
{
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
{
std::cerr << "Unknown option: " << arg << "\n";
std::cerr << "Use -h to see available options.\n";
exit(1);
}
}
return opts;
}
// -----------------------------------------------------------------------------
// 日志初始化
// -----------------------------------------------------------------------------
Logger veh_rc_logger(common::get_executable_file_path("/log/veh_rc"));
Logger tbox_logger(common::get_executable_file_path("/log/tbox"));
Logger v2v_logger(common::get_executable_file_path("/log/v2v"));
// -----------------------------------------------------------------------------
// 主函数
// -----------------------------------------------------------------------------
int main(int argc, char* 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)
{
std::cout << "Usage: " << argv[0] << " [-v] [-h] [-c <config_path>]\n";
std::cout << "If no arguments are given, './config.ini' will be used by default.\n";
return 0;
}
if (opts.show_version)
{
std::cout << "Version: " << SOFTWARE_VERSION << "\n";
return 0;
}
if (!opts.config_path.empty())
{
if (!std::filesystem::exists(opts.config_path))
{
std::cerr << "Error: config file not found at " << opts.config_path << "\n";
return 1;
}
std::cout << "Using config file: " << opts.config_path << "\n";
ConfigManager::instance().load(opts.config_path);
}
// 启动线程
// 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());
init_tcp_server_tbox_v2v("0.0.0.0", 10005); // 建立与域控间的V2V链路
init_mqtt_client_tbox_v2v(ConfigManager::instance().getMqttIp(), ConfigManager::instance().getMqttPort(),
ConfigManager::instance().getMqttUsername(),
ConfigManager::instance().getMqttPassword()); // 连接平台V2V MQTT服务器
// init_mqtt_client_veh_rc(ConfigManager::instance().getCockpitMqttIp(),
// ConfigManager::instance().getCockpitMqttPort()); // 连接台架的 MQTT 服务器
// 阻塞
while (true) { std::this_thread::sleep_for(std::chrono::seconds(5)); }
return 0;
}

184
src/network/mqtt_client.cpp Normal file
View File

@ -0,0 +1,184 @@
#include "mqtt_client.h"
#include <chrono>
#include <thread>
MqttClient::MqttClient(const std::string &id, const std::string &server_ip, int server_port, Logger &logger,
const std::string &username, const std::string &password, const std::string &client_id,
bool clean_session, int keep_alive, int qos)
: id_(id),
server_ip_(server_ip),
server_port_(server_port),
logger_(logger),
username_(username),
password_(password),
client_id_(client_id.empty() ? id : client_id),
clean_session_(clean_session),
keep_alive_(keep_alive),
qos_(qos)
{
}
MqttClient::~MqttClient() { stop(); }
void MqttClient::start()
{
if (running_) return;
running_ = true;
worker_ = std::thread(&MqttClient::client_loop, this);
}
void MqttClient::stop()
{
bool expected = true;
if (!running_.compare_exchange_strong(expected, false)) return;
disconnect();
if (worker_.joinable()) worker_.detach();
connected_ = false;
}
bool MqttClient::is_connected() const { return connected_; }
void MqttClient::set_reconnect_policy(int first, int max)
{
reconnect_first_ = first;
reconnect_max_ = max;
}
void MqttClient::set_status_callback(StatusCallback cb) { status_callback_ = cb; }
void MqttClient::set_message_callback(MessageCallback cb) { message_callback_ = cb; }
void MqttClient::client_loop()
{
int retry_interval = reconnect_first_;
std::string address = "tcp://" + server_ip_ + ":" + std::to_string(server_port_);
client_ = std::make_shared<mqtt::async_client>(address, client_id_);
client_->set_callback(*this);
while (running_)
{
if (!try_connect())
{
connected_ = false;
if (status_callback_) status_callback_(false);
LOG_WARN(logger_, "[" + id_ + "] Connect failed, retry in " + std::to_string(retry_interval) + "s");
for (int i = 0; i < retry_interval && running_; ++i) std::this_thread::sleep_for(std::chrono::seconds(1));
if (retry_interval < reconnect_max_) retry_interval = std::min(retry_interval * 2, reconnect_max_);
continue;
}
connected_ = true;
LOG_INFO(logger_, "[" + id_ + "] Connected to " + server_ip_ + ":" + std::to_string(server_port_));
retry_interval = reconnect_first_;
if (status_callback_) status_callback_(true);
// 保持 loop 存活(阻塞等待断线)
while (running_ && connected_) std::this_thread::sleep_for(std::chrono::milliseconds(200));
if (status_callback_) status_callback_(false);
disconnect();
}
}
bool MqttClient::try_connect()
{
try
{
mqtt::connect_options connOpts;
connOpts.set_clean_session(clean_session_);
connOpts.set_keep_alive_interval(keep_alive_);
connOpts.set_connect_timeout(10);
if (!username_.empty()) connOpts.set_user_name(username_);
if (!password_.empty()) connOpts.set_password(password_);
auto tok = client_->connect(connOpts);
tok->wait();
return true;
}
catch (const mqtt::exception &e)
{
LOG_ERROR(logger_, "[" + id_ + "] MQTT connect() failed: " + std::string(e.what()));
return false;
}
}
void MqttClient::disconnect()
{
try
{
if (client_ && connected_)
{
client_->disconnect()->wait();
connected_ = false;
LOG_INFO(logger_, "[" + id_ + "] Disconnected from " + server_ip_ + ":" + std::to_string(server_port_));
}
}
catch (...)
{
LOG_WARN(logger_, "[" + id_ + "] Exception during disconnect");
}
}
bool MqttClient::publish(const std::string &topic, const std::string &payload, int qos)
{
if (qos == -1) qos = qos_;
std::lock_guard<std::mutex> lock(send_mutex_);
if (!client_ || !connected_) return false;
try
{
client_->publish(topic, payload.data(), payload.size(), qos, false)->wait_for(std::chrono::milliseconds(500));
return true;
}
catch (const mqtt::exception &e)
{
LOG_ERROR(logger_, "[" + id_ + "] publish() failed: " + std::string(e.what()));
connected_ = false;
return false;
}
}
bool MqttClient::subscribe(const std::string &topic, int qos)
{
if (qos == -1) qos = qos_;
if (!client_ || !connected_) return false;
try
{
client_->subscribe(topic, qos)->wait();
LOG_INFO(logger_, "[" + id_ + "] Subscribed to " + topic);
return true;
}
catch (const mqtt::exception &e)
{
LOG_ERROR(logger_, "[" + id_ + "] subscribe() failed: " + std::string(e.what()));
return false;
}
}
void MqttClient::connection_lost(const std::string &cause)
{
if (cause.empty())
{
LOG_WARN(logger_, "[" + id_ + "] Connection lost");
}
else
{
LOG_WARN(logger_, "[" + id_ + "] Connection lost: " + cause);
}
connected_ = false;
}
void MqttClient::message_arrived(mqtt::const_message_ptr msg)
{
if (message_callback_) message_callback_(msg->get_topic(), msg->get_payload_str());
}

245
src/network/tcp_client.cpp Normal file
View File

@ -0,0 +1,245 @@
#include "tcp_client.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <future>
#include <thread>
TcpClient::TcpClient(const std::string &id, const std::string &ip, int port, Logger &logger)
: id_(id), ip_(ip), port_(port), logger_(logger)
{
}
TcpClient::~TcpClient() { stop(); }
void TcpClient::start()
{
if (running_) return;
running_ = true;
worker_ = std::thread(&TcpClient::client_loop, this);
}
void TcpClient::stop()
{
bool expected = true;
if (!running_.compare_exchange_strong(expected, false))
{
return;
}
close_socket();
if (worker_.joinable())
{
worker_.detach();
}
connected_ = false;
}
bool TcpClient::is_connected() const { return connected_; }
void TcpClient::client_loop()
{
int retry_interval = reconnect_first_;
while (running_)
{
if (!try_connect())
{
connected_ = false;
if (status_callback_) status_callback_(false);
LOG_INFO(logger_, "[" + id_ + "] Connect failed, retry in " + std::to_string(retry_interval) + "s");
for (int i = 0; i < retry_interval && running_; ++i) std::this_thread::sleep_for(std::chrono::seconds(1));
if (retry_interval < reconnect_max_) retry_interval = std::min(retry_interval * 2, reconnect_max_);
continue;
}
connected_ = true;
if (status_callback_) status_callback_(true);
// LOG_INFO(logger_, "[" + id_ + "] Connected to " + ip_ + ":" + std::to_string(port_));
// 成功连接后,重置重连间隔
retry_interval = reconnect_first_;
handle_io();
connected_ = false;
close_socket();
if (status_callback_) status_callback_(false);
// LOG_INFO(logger_, "[" + id_ + "] Disconnected");
}
}
void TcpClient::handle_io()
{
char buffer[1024];
while (running_ && connected_)
{
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sock_fd_, &readfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100000;
int ret = select(sock_fd_ + 1, &readfds, nullptr, nullptr, &tv);
if (sock_fd_ < 0 || !running_) break;
if (ret > 0 && FD_ISSET(sock_fd_, &readfds))
{
ssize_t n = recv(sock_fd_, buffer, sizeof(buffer), 0);
if (n > 0)
{
std::vector<uint8_t> data(buffer, buffer + n);
if (receive_callback_) receive_callback_(data);
}
else
{
connected_ = false;
break;
}
}
else if (ret < 0 && errno != EINTR)
{
connected_ = false;
break;
}
}
}
bool TcpClient::try_connect()
{
close_socket();
sock_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd_ < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] socket() failed: " + std::string(strerror(errno)));
return false;
}
// 设置非阻塞
int flags = fcntl(sock_fd_, F_GETFL, 0);
if (flags < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] fcntl(F_GETFL) failed: " + std::string(strerror(errno)));
close_socket();
return false;
}
if (fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK) < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] fcntl(F_SETFL) failed: " + std::string(strerror(errno)));
close_socket();
return false;
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port_);
if (inet_pton(AF_INET, ip_.c_str(), &server_addr.sin_addr) <= 0)
{
LOG_ERROR(logger_, "[" + id_ + "] Invalid IP: " + ip_);
close_socket();
return false;
}
int ret = connect(sock_fd_, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret == 0)
{
// 立刻成功
// 恢复阻塞
fcntl(sock_fd_, F_SETFL, flags);
return true;
}
else if (ret < 0 && errno != EINPROGRESS)
{
LOG_ERROR(logger_, "[" + id_ + "] connect() failed: " + std::string(strerror(errno)));
close_socket();
return false;
}
// 等待写事件
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(sock_fd_, &writefds);
struct timeval tv;
tv.tv_sec = 3; // 超时时间3秒
tv.tv_usec = 0;
ret = select(sock_fd_ + 1, nullptr, &writefds, nullptr, &tv);
if (ret <= 0)
{
LOG_ERROR(logger_, "[" + id_ + "] connect() timeout or select() error");
close_socket();
return false;
}
// 检查连接状态
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(sock_fd_, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] getsockopt() failed: " + std::string(strerror(errno)));
close_socket();
return false;
}
if (err != 0)
{
LOG_ERROR(logger_, "[" + id_ + "] connect() failed after select: " + std::string(strerror(err)));
close_socket();
return false;
}
// 恢复阻塞
fcntl(sock_fd_, F_SETFL, flags);
return true;
}
void TcpClient::close_socket()
{
if (sock_fd_ >= 0)
{
::shutdown(sock_fd_, SHUT_RDWR); // 让 select/recv 立即返回
::close(sock_fd_);
sock_fd_ = -1;
}
}
bool TcpClient::send_data(const std::string &data)
{
std::lock_guard<std::mutex> lock(send_mutex_);
if (sock_fd_ < 0) return false;
ssize_t sent = send(sock_fd_, data.data(), data.size(), 0);
if (sent < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] send() failed: " + std::string(strerror(errno)));
return false;
}
return true;
}

186
src/network/tcp_server.cpp Normal file
View File

@ -0,0 +1,186 @@
#include "tcp_server.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
TcpServer::TcpServer(const std::string &id, const std::string &ip, int port, Logger &logger)
: id_(id), ip_(ip), port_(port), logger_(logger)
{
}
TcpServer::~TcpServer() { stop(); }
void TcpServer::start()
{
if (running_) return;
running_ = true;
worker_ = std::thread(&TcpServer::server_loop, this);
}
void TcpServer::stop()
{
running_ = false;
if (listen_fd_ >= 0)
{
::close(listen_fd_);
listen_fd_ = -1;
}
{
std::lock_guard<std::mutex> lock(clients_mutex_);
for (auto &kv : clients_)
{
::shutdown(kv.second.fd, SHUT_RDWR);
::close(kv.second.fd);
}
clients_.clear();
}
if (worker_.joinable()) worker_.detach(); // detach 避免回调里 stop 阻塞
}
void TcpServer::server_loop()
{
listen_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd_ < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] socket() failed: " + std::string(strerror(errno)));
return;
}
int opt = 1;
setsockopt(listen_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port_);
inet_pton(AF_INET, ip_.c_str(), &addr.sin_addr);
if (bind(listen_fd_, (sockaddr *)&addr, sizeof(addr)) < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] bind() failed: " + std::string(strerror(errno)));
return;
}
if (listen(listen_fd_, 10) < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] listen() failed: " + std::string(strerror(errno)));
return;
}
LOG_INFO(logger_, "[" + id_ + "] Listening on " + ip_ + ":" + std::to_string(port_));
while (running_)
{
sockaddr_in client_addr{};
socklen_t len = sizeof(client_addr);
int client_fd = accept(listen_fd_, (sockaddr *)&client_addr, &len);
if (client_fd < 0)
{
if (errno == EINTR) continue;
LOG_ERROR(logger_, "[" + id_ + "] accept() failed: " + std::string(strerror(errno)));
continue;
}
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
int client_port = ntohs(client_addr.sin_port);
ClientInfo info{client_fd, ip_str, client_port};
{
std::lock_guard<std::mutex> lock(clients_mutex_);
clients_[client_fd] = info;
}
LOG_INFO(logger_, "[" + id_ + "] Client connected: fd=" + std::to_string(client_fd) + " " + info.ip + ":" +
std::to_string(info.port));
if (status_callback_) status_callback_(client_fd, true, info.ip, info.port);
std::thread(&TcpServer::handle_client, this, info).detach();
}
}
void TcpServer::handle_client(ClientInfo client)
{
char buffer[1024];
while (running_)
{
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(client.fd, &readfds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100000; // 0.1s
int ret = select(client.fd + 1, &readfds, nullptr, nullptr, &tv);
if (ret > 0 && FD_ISSET(client.fd, &readfds))
{
ssize_t n = recv(client.fd, buffer, sizeof(buffer), 0);
if (n > 0)
{
std::vector<uint8_t> data(buffer, buffer + n);
if (receive_callback_) receive_callback_(data);
}
else
{
LOG_WARN(logger_, "[" + id_ + "] Client disconnected: fd=" + std::to_string(client.fd) + " " +
client.ip + ":" + std::to_string(client.port));
break;
}
}
else if (ret < 0 && errno != EINTR)
{
LOG_ERROR(logger_, "[" + id_ + "] recv() failed from fd=" + std::to_string(client.fd) + " " + client.ip +
": " + std::string(strerror(errno)));
break;
}
}
close_client(client.fd);
}
void TcpServer::close_client(int client_fd)
{
ClientInfo info;
{
std::lock_guard<std::mutex> lock(clients_mutex_);
auto it = clients_.find(client_fd);
if (it != clients_.end())
{
info = it->second;
clients_.erase(it);
}
}
if (client_fd >= 0) ::close(client_fd);
if (status_callback_) status_callback_(client_fd, false, info.ip, info.port);
}
bool TcpServer::send_data(int client_fd, const std::string &data)
{
std::lock_guard<std::mutex> lock(clients_mutex_);
if (clients_.count(client_fd) == 0) return false;
ssize_t sent = send(client_fd, data.data(), data.size(), 0);
return sent >= 0;
}
void TcpServer::broadcast(const std::string &data)
{
std::lock_guard<std::mutex> lock(clients_mutex_);
for (auto &kv : clients_)
{
send(kv.second.fd, data.data(), data.size(), 0);
}
}

View File

@ -0,0 +1,164 @@
#include "protocol_codec.h"
#include <chrono>
#include <nlohmann/json.hpp>
namespace ProtocolCodec
{
// 获取当前北京时间,返回 6 字节 vector {year, month, day, hour, minute, second}
std::vector<uint8_t> get_current_time_bytes()
{
using namespace std::chrono;
system_clock::time_point now = system_clock::now();
std::time_t tt = system_clock::to_time_t(now);
std::tm utc{};
gmtime_r(&tt, &utc);
// UTC + 8h
utc.tm_hour += 8;
mktime(&utc); // 自动处理跨天/月/年的进位
uint8_t year = utc.tm_year % 100;
uint8_t month = utc.tm_mon + 1;
uint8_t day = utc.tm_mday;
uint8_t hour = utc.tm_hour;
uint8_t minute = utc.tm_min;
uint8_t second = utc.tm_sec;
return {year, month, day, hour, minute, second};
}
// BCC 校验
uint8_t calculate_bcc(const std::vector<uint8_t> &data)
{
uint8_t bcc = 0;
for (uint8_t byte : data)
{
bcc ^= byte;
}
return bcc;
}
// 编码 FullPacket 为字节流(使用大端)
std::vector<uint8_t> encode_full_packet(const FullPacket &packet)
{
std::vector<uint8_t> buf;
buf.push_back(packet.start_flag1);
buf.push_back(packet.start_flag2);
buf.push_back(packet.command_id);
buf.push_back(packet.response_flag);
// VIN: 填充至17字节
std::string vin_padded = packet.vin;
vin_padded.resize(17, ' ');
buf.insert(buf.end(), vin_padded.begin(), vin_padded.end());
buf.push_back(packet.encryption_method);
// 大端写入 data_length高字节在前
buf.push_back((packet.data_length >> 8) & 0xFF);
buf.push_back(packet.data_length & 0xFF);
buf.insert(buf.end(), packet.data_unit.begin(), packet.data_unit.end());
// BCC 校验(从 command_id 到最后的 data
std::vector<uint8_t> bcc_range(buf.begin() + 2, buf.end());
uint8_t bcc = calculate_bcc(bcc_range);
buf.push_back(bcc);
return buf;
}
// 解码字节流为 FullPacket成功返回 packet否则 std::nullopt
std::optional<FullPacket> decode_full_packet(const std::vector<uint8_t> &buffer)
{
if (buffer.size() < 24)
{
// std::cout << "[decode_full_packet] Buffer too short (<24), size = " << buffer.size() << std::endl;
return std::nullopt;
}
if (!(buffer[0] == 0x23 && buffer[1] == 0x23))
{
// std::cout << "[decode_full_packet] Invalid start flag: "
// << std::hex << static_cast<int>(buffer[0]) << " "
// << static_cast<int>(buffer[1]) << std::dec << std::endl;
return std::nullopt;
}
FullPacket pkt;
pkt.start_flag1 = buffer[0];
pkt.start_flag2 = buffer[1];
pkt.command_id = buffer[2];
pkt.response_flag = buffer[3];
pkt.vin = std::string(buffer.begin() + 4, buffer.begin() + 21);
pkt.encryption_method = buffer[21];
// 大端读取 data_length高字节在前
pkt.data_length = (buffer[22] << 8) | buffer[23];
// std::cout << "data_length: " << pkt.data_length << std::endl;
// std::cout << "expected total len: " << (24 + pkt.data_length + 1)
// << ", actual: " << buffer.size() << std::endl;
size_t expected_len = 24 + pkt.data_length + 1; // header + data + checksum
if (buffer.size() < expected_len)
{
// std::cout << "[decode_full_packet] Incomplete packet, expected = " << expected_len
// << ", actual = " << buffer.size() << std::endl;
return std::nullopt;
}
pkt.data_unit.assign(buffer.begin() + 24, buffer.begin() + 24 + pkt.data_length);
uint8_t expected_bcc = buffer[24 + pkt.data_length];
std::vector<uint8_t> bcc_range(buffer.begin() + 2, buffer.begin() + 24 + pkt.data_length);
uint8_t actual_bcc = calculate_bcc(bcc_range);
if (expected_bcc != actual_bcc)
{
// std::cout << "[decode_full_packet] BCC mismatch, expected = "
// << (int)expected_bcc << ", actual = " << (int)actual_bcc << std::endl;
return std::nullopt;
}
pkt.checksum = expected_bcc;
return pkt;
}
// 构造通用应答包(根据 ret 设置应答标志,保留时间戳和流水号)
std::vector<uint8_t> make_ack_response(const FullPacket &request, bool result)
{
FullPacket reply;
reply.command_id = request.command_id;
reply.response_flag = result ? 0x01 : 0x02; // 成功 0x01失败 0x02
reply.vin = request.vin;
reply.encryption_method = request.encryption_method;
if (reply.command_id == 0x81)
{
// 保留时间戳6字节和流水号2字节总共 8 字节
if (request.data_unit.size() >= 8)
{
reply.data_unit.assign(request.data_unit.begin(), request.data_unit.begin() + 8);
}
else
{
reply.data_unit = {}; // 不足 8 字节时,清空
}
}
else
{
reply.data_unit.clear();
}
reply.data_length = reply.data_unit.size();
return ProtocolCodec::encode_full_packet(reply);
}
} // namespace ProtocolCodec

164
src/serial/serial_port.cpp Normal file
View File

@ -0,0 +1,164 @@
#include "serial_port.h"
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <chrono>
#include <cstring>
#include <iostream>
#include <thread>
SerialPort::SerialPort(const std::string& id, const std::string& device, int baudrate, Logger& logger,
int retry_interval)
: id_(id), device_(device), baudrate_(baudrate), logger_(logger), retry_interval_(retry_interval)
{
}
SerialPort::~SerialPort() { stop(); }
void SerialPort::start()
{
stop_flag_ = false;
reconnect_thread_ = std::thread(&SerialPort::reconnect_loop, this);
}
void SerialPort::stop()
{
stop_flag_ = true;
running_ = false;
if (reader_thread_.joinable()) reader_thread_.join();
if (reconnect_thread_.joinable()) reconnect_thread_.join();
close_port();
}
bool SerialPort::open_port()
{
fd_ = open(device_.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if (fd_ < 0)
{
LOG_ERROR(logger_, "[" + id_ + "] Failed to open " + device_ + ": " + strerror(errno));
return false;
}
if (!configure_port(fd_))
{
LOG_ERROR(logger_, "[" + id_ + "] Failed to configure " + device_);
close(fd_);
fd_ = -1;
return false;
}
running_ = true;
reader_thread_ = std::thread(&SerialPort::reader_loop, this);
LOG_INFO(logger_, "[" + id_ + "] Opened serial port " + device_ + " at " + std::to_string(baudrate_) + " baud");
return true;
}
void SerialPort::close_port()
{
running_ = false;
if (fd_ >= 0)
{
close(fd_);
fd_ = -1;
}
}
bool SerialPort::is_open() const { return fd_ >= 0; }
bool SerialPort::send_data(const std::vector<uint8_t>& data)
{
if (fd_ < 0) return false;
std::lock_guard<std::mutex> lock(send_mutex_);
return write(fd_, data.data(), data.size()) == (ssize_t)data.size();
}
bool SerialPort::send_data(const std::string& data)
{
return send_data(std::vector<uint8_t>(data.begin(), data.end()));
}
void SerialPort::set_receive_callback(ReceiveCallback cb) { receive_callback_ = std::move(cb); }
void SerialPort::set_receive_callback(ReceiveStringCallback cb)
{
receive_callback_ = [cb](const std::vector<uint8_t>& data) { cb(std::string(data.begin(), data.end())); };
}
void SerialPort::reader_loop()
{
std::vector<uint8_t> buffer(1024);
while (running_)
{
int n = read(fd_, buffer.data(), buffer.size());
if (n > 0)
{
if (receive_callback_) receive_callback_({buffer.begin(), buffer.begin() + n});
}
else if (n <= 0)
{
LOG_ERROR(logger_, "[" + id_ + "] Read error, closing port");
close_port();
reconnect_loop();
break;
}
}
}
void SerialPort::reconnect_loop()
{
int current_interval = retry_interval_; // 初始重连间隔
const int max_interval = 300; // 最大 5 分钟
while (!stop_flag_)
{
if (open_port())
{
// 正常打开,等 reader_loop 出错才回来
while (running_ && !stop_flag_) std::this_thread::sleep_for(std::chrono::seconds(1));
close_port();
LOG_ERROR(logger_, "[" + id_ + "] Port closed, will retry");
// 成功过一次,就把间隔重置为初始值
current_interval = retry_interval_;
}
else
{
LOG_INFO(logger_, "[" + id_ + "] Connect failed, retry in " + std::to_string(current_interval) + "s");
std::this_thread::sleep_for(std::chrono::seconds(current_interval));
// 逐渐增加重试间隔(指数翻倍,直到最大值)
current_interval = std::min(current_interval * 2, max_interval);
}
}
}
bool SerialPort::configure_port(int fd)
{
struct termios tty{};
if (tcgetattr(fd, &tty) != 0) return false;
cfsetospeed(&tty, B115200);
cfsetispeed(&tty, B115200);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
tty.c_iflag &= ~IGNBRK;
tty.c_lflag = 0;
tty.c_oflag = 0;
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_cflag |= (CLOCAL | CREAD);
tty.c_cflag &= ~(PARENB | PARODD);
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
return tcsetattr(fd, TCSANOW, &tty) == 0;
}