init: kunlang tbox base project
This commit is contained in:
commit
592f33ecf8
19
.clang-format
Normal file
19
.clang-format
Normal 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
21
.gitignore
vendored
Normal 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
108
CMakeLists.txt
Normal 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
|
||||
)
|
||||
34
cmake/toolchain-aarch64-musl.cmake
Normal file
34
cmake/toolchain-aarch64-musl.cmake
Normal 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)
|
||||
6
include/business/instance/can_bus_instance.h
Normal file
6
include/business/instance/can_bus_instance.h
Normal 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);
|
||||
12
include/business/instance/mqtt_client_instance.h
Normal file
12
include/business/instance/mqtt_client_instance.h
Normal 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);
|
||||
7
include/business/instance/serial_instance.h
Normal file
7
include/business/instance/serial_instance.h
Normal 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;
|
||||
13
include/business/instance/tcp_client_instance.h
Normal file
13
include/business/instance/tcp_client_instance.h
Normal 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);
|
||||
11
include/business/instance/tcp_server_instance.h
Normal file
11
include/business/instance/tcp_server_instance.h
Normal 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 服务器
|
||||
194
include/business/remote_ctrl/rc_data_manager.h
Normal file
194
include/business/remote_ctrl/rc_data_manager.h
Normal 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; // 档位,0:N档,1:D档,2:R档,3:P档
|
||||
int speed = 0; // 车辆速度,单位:km/h
|
||||
int horn = 0; // 喇叭,0:关,1:开
|
||||
int turnLight = 0; // 转向灯,0:关,1:左转,2:右转
|
||||
int load = 0; // 重载信号,0:轻载,1:重载
|
||||
int voltage = 0; // 高压上下电,0:下电,1:上电
|
||||
int positionLight = 0; // 示廓灯,0:关,1:开
|
||||
int warning = 0; // 双闪,0:关,1:开
|
||||
int fogLight = 0; // 雾灯,0:关,1:开
|
||||
int headlamp = 0; // 前大灯,0:关,1:近光灯,2:远光灯
|
||||
int tailLight = 0; // 后大灯,0:关,1:近光灯,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;
|
||||
749
include/business/tbox/tbox_data_manager.h
Normal file
749
include/business/tbox/tbox_data_manager.h
Normal 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();
|
||||
26
include/business/tbox/tbox_messages.h
Normal file
26
include/business/tbox/tbox_messages.h
Normal 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
56
include/can/can_bus.h
Normal 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
142
include/common/logger.h
Normal 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
80
include/common/utils.h
Normal 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
120
include/config/INIReader.h
Executable 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
240
include/config/config.h
Normal 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
189
include/config/ini.h
Executable 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
19
include/main.h
Normal 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;
|
||||
67
include/network/mqtt_client.h
Normal file
67
include/network/mqtt_client.h
Normal 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_;
|
||||
};
|
||||
71
include/network/tcp_client.h
Normal file
71
include/network/tcp_client.h
Normal 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; // 最大间隔(秒)
|
||||
};
|
||||
64
include/network/tcp_server.h
Normal file
64
include/network/tcp_server.h
Normal 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_;
|
||||
};
|
||||
24
include/protocol/protocol_codec.h
Normal file
24
include/protocol/protocol_codec.h
Normal 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
|
||||
26
include/protocol/protocol_struct.h
Normal file
26
include/protocol/protocol_struct.h
Normal 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 校验值
|
||||
};
|
||||
53
include/serial/serial_port.h
Normal file
53
include/serial/serial_port.h
Normal 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_;
|
||||
};
|
||||
54
src/business/remote_ctrl/can_bus_rc_ctrl.cpp
Normal file
54
src/business/remote_ctrl/can_bus_rc_ctrl.cpp
Normal 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(); // 启动发送线程
|
||||
}
|
||||
347
src/business/remote_ctrl/mqtt_client_rc_ctrl.cpp
Normal file
347
src/business/remote_ctrl/mqtt_client_rc_ctrl.cpp
Normal 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(); // 启动客户端
|
||||
}
|
||||
7
src/business/remote_ctrl/rc_data_manager.cpp
Normal file
7
src/business/remote_ctrl/rc_data_manager.cpp
Normal 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 线程安全
|
||||
139
src/business/remote_ctrl/serial_AT.cpp
Normal file
139
src/business/remote_ctrl/serial_AT.cpp
Normal 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();
|
||||
}
|
||||
130
src/business/tbox/mqtt_client_tbox_v2v.cpp
Normal file
130
src/business/tbox/mqtt_client_tbox_v2v.cpp
Normal 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(); // 启动客户端
|
||||
}
|
||||
796
src/business/tbox/tbox_data_manager.cpp
Normal file
796
src/business/tbox/tbox_data_manager.cpp
Normal 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);
|
||||
// 0:关,1:近光灯,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 1000–1029 未枚举的全部 → 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;
|
||||
}
|
||||
16
src/business/tbox/tbox_messages_decode.cpp
Normal file
16
src/business/tbox/tbox_messages_decode.cpp
Normal 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};
|
||||
}
|
||||
132
src/business/tbox/tbox_messages_encode.cpp
Normal file
132
src/business/tbox/tbox_messages_encode.cpp
Normal 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 "";
|
||||
}
|
||||
}
|
||||
260
src/business/tbox/tcp_client_tbox_gateway.cpp
Normal file
260
src/business/tbox/tcp_client_tbox_gateway.cpp
Normal 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();
|
||||
}
|
||||
86
src/business/tbox/tcp_client_tbox_router.cpp
Normal file
86
src/business/tbox/tcp_client_tbox_router.cpp
Normal 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();
|
||||
}
|
||||
125
src/business/tbox/tcp_client_tbox_veh_position.cpp
Normal file
125
src/business/tbox/tcp_client_tbox_veh_position.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include "tbox_data_manager.h"
|
||||
#include "tcp_client_instance.h"
|
||||
|
||||
// 车辆位置信息(0x05)TCP 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();
|
||||
}
|
||||
39
src/business/tbox/tcp_server_tbox_autodata.cpp
Normal file
39
src/business/tbox/tcp_server_tbox_autodata.cpp
Normal 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();
|
||||
}
|
||||
49
src/business/tbox/tcp_server_tbox_v2v.cpp
Normal file
49
src/business/tbox/tcp_server_tbox_v2v.cpp
Normal 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
193
src/can/can_bus.cpp
Normal 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
206
src/config/INIReader.cpp
Executable 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion, 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 §ion) 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 §ion) 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 §ion, const string &name) const
|
||||
{
|
||||
string key = MakeKey(section, name);
|
||||
return _values.count(key);
|
||||
}
|
||||
|
||||
string INIReader::MakeKey(const string §ion, 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
326
src/config/ini.c
Executable 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
167
src/main.cpp
Normal 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
184
src/network/mqtt_client.cpp
Normal 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
245
src/network/tcp_client.cpp
Normal 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
186
src/network/tcp_server.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
164
src/protocol/protocol_codec.cpp
Normal file
164
src/protocol/protocol_codec.cpp
Normal 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
164
src/serial/serial_port.cpp
Normal 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user