commit 592f33ecf8ef3b607879f1fc2f974654d0af8009 Author: cxh Date: Thu Dec 11 09:08:35 2025 +0800 init: kunlang tbox base project diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d43ef70 --- /dev/null +++ b/.clang-format @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4eb979c --- /dev/null +++ b/.gitignore @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a9d581f --- /dev/null +++ b/CMakeLists.txt @@ -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 +) diff --git a/cmake/toolchain-aarch64-musl.cmake b/cmake/toolchain-aarch64-musl.cmake new file mode 100644 index 0000000..cad05e4 --- /dev/null +++ b/cmake/toolchain-aarch64-musl.cmake @@ -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) diff --git a/include/business/instance/can_bus_instance.h b/include/business/instance/can_bus_instance.h new file mode 100644 index 0000000..821857b --- /dev/null +++ b/include/business/instance/can_bus_instance.h @@ -0,0 +1,6 @@ +#include "can_bus.h" +#include "main.h" + +extern std::unique_ptr can_bus_rc_ctrl; // 远控 CAN 总线实例 + +void init_can_bus_rc_ctrl(const std::string &interface_name); diff --git a/include/business/instance/mqtt_client_instance.h b/include/business/instance/mqtt_client_instance.h new file mode 100644 index 0000000..b6d0d17 --- /dev/null +++ b/include/business/instance/mqtt_client_instance.h @@ -0,0 +1,12 @@ +#pragma once + +#include "main.h" +#include "mqtt_client.h" + +extern std::unique_ptr 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); diff --git a/include/business/instance/serial_instance.h b/include/business/instance/serial_instance.h new file mode 100644 index 0000000..95a4a6d --- /dev/null +++ b/include/business/instance/serial_instance.h @@ -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; \ No newline at end of file diff --git a/include/business/instance/tcp_client_instance.h b/include/business/instance/tcp_client_instance.h new file mode 100644 index 0000000..8c13e86 --- /dev/null +++ b/include/business/instance/tcp_client_instance.h @@ -0,0 +1,13 @@ +#pragma once + +#include "main.h" +#include "tbox_messages.h" +#include "tcp_client.h" + +extern std::shared_ptr tcp_client_router; +extern std::shared_ptr tcp_client_gateway; +extern std::unique_ptr 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); \ No newline at end of file diff --git a/include/business/instance/tcp_server_instance.h b/include/business/instance/tcp_server_instance.h new file mode 100644 index 0000000..c167380 --- /dev/null +++ b/include/business/instance/tcp_server_instance.h @@ -0,0 +1,11 @@ +#pragma once + +#include "main.h" +#include "tbox_messages.h" +#include "tcp_server.h" + +extern std::unique_ptr tcp_server_tbox_v2v; +extern std::unique_ptr 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 服务器 diff --git a/include/business/remote_ctrl/rc_data_manager.h b/include/business/remote_ctrl/rc_data_manager.h new file mode 100644 index 0000000..aaf7b8e --- /dev/null +++ b/include/business/remote_ctrl/rc_data_manager.h @@ -0,0 +1,194 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +// 远程控车结构体 +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::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 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; diff --git a/include/business/tbox/tbox_data_manager.h b/include/business/tbox/tbox_data_manager.h new file mode 100644 index 0000000..307534e --- /dev/null +++ b/include/business/tbox/tbox_data_manager.h @@ -0,0 +1,749 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#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 encode() const + { + std::vector 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 encode() const + { + std::vector 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 motors; // 驱动电机列表 + + VehicleMotors() { motors.resize(2); } // 默认支持 2 台电机 + + // 编码整个驱动电机信息体 (0x02) + std::vector encode() const + { + std::vector result; + result.push_back(0x02); // 类型标识 + result.push_back(static_cast(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 encode() const + { + std::vector 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 encode() const + { + std::vector 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 battery_fault_codes; // 电池故障代码列表 (4*N1) + + uint8_t n2; // 驱动电机故障总数 + std::vector motor_fault_codes; // 驱动电机故障代码列表 (4*N2) + + uint8_t n3; // 发动机故障总数 + std::vector engine_fault_codes; // 发动机故障代码列表 (4*N3) + + uint8_t n4; // 其他故障总数 + std::vector other_fault_codes; // 其他故障代码列表 (4*N4) + + uint8_t n5; // 普通故障总数 + std::vector 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 encode() const + { + std::vector 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 items; + + // 新故障 or 刷新已有故障 + void add_or_update(uint8_t ecu, uint8_t level, uint32_t code) + { + std::lock_guard 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 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 snapshot() const + { + std::lock_guard 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 cells; // 单体电池电压列表 + + StorageSubsystem() + : subsystem_id(0xFF), voltage(0xFFFF), current(0xFFFF), total_cells(0), start_cell_index(0xFFFF), frame_cells(0) + { + cells.clear(); // 空容器,防止脏数据 + } + + std::vector encode() const + { + std::vector 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 encode() const + { + std::vector 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 probes; // 探针列表 + + StorageTempSubsystem() : subsystem_id(0xFF), probe_count(0) + { + probes.clear(); // 空容器,防止脏数据 + } + + std::vector encode() const + { + std::vector 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 encode() const + { + std::vector 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 encode() const + { + std::vector 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 encode() const + { + std::vector 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 tires; // 轮胎列表 + + TireData() : tire_count(0) { tires.clear(); } + + std::vector encode() const + { + std::vector 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 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 encode() const + { + std::vector 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 encode() const + { + std::vector 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 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 buildTboxRealtimePayload(); diff --git a/include/business/tbox/tbox_messages.h b/include/business/tbox/tbox_messages.h new file mode 100644 index 0000000..1d6e0dc --- /dev/null +++ b/include/business/tbox/tbox_messages.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#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 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(v); + return oss.str(); +} + +// 信息上报使用 +std::string build_command_packet(uint8_t command_id, std::span payload = {}); // 构建命令包 + +ReceivedPacket process_received_packet(const std::vector& data); // 解析收到的报文 diff --git a/include/can/can_bus.h b/include/can/can_bus.h new file mode 100644 index 0000000..30788a8 --- /dev/null +++ b/include/can/can_bus.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "logger.h" + +class CanBus +{ + public: + using ReceiveCallback = std::function; + + 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 running_{false}; + std::thread worker_; + std::mutex send_mutex_; + ReceiveCallback receive_callback_; + + std::set blacklist_; + std::mutex blacklist_mutex_; + + // 自动重试策略 + int retry_first_ = 2; // 初次失败间隔秒 + int retry_max_ = 30; // 最大间隔秒 +}; diff --git a/include/common/logger.h b/include/common/logger.h new file mode 100644 index 0000000..6c9abad --- /dev/null +++ b/include/common/logger.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include +#include +#include +#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(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) diff --git a/include/common/utils.h b/include/common/utils.h new file mode 100644 index 0000000..fa53b54 --- /dev/null +++ b/include/common/utils.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace common +{ + +// 获取当前北京时间字符串 (精确到毫秒) +inline std::string get_current_time_string() +{ + using namespace std::chrono; + auto now = system_clock::now(); + auto ms = duration_cast(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(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(steady_clock::now().time_since_epoch()).count(); +} + +} // namespace common diff --git a/include/config/INIReader.h b/include/config/INIReader.h new file mode 100755 index 0000000..0581ac0 --- /dev/null +++ b/include/config/INIReader.h @@ -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 +#include +#include +#include +#include + +// 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 Sections() const; + + // Return a newly-allocated vector of keys in the given section, in alphabetical order. + INI_API std::vector 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 _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 diff --git a/include/config/config.h b/include/config/config.h new file mode 100644 index 0000000..2e91349 --- /dev/null +++ b/include/config/config.h @@ -0,0 +1,240 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "INIReader.h" + +// ------------------------ +// 线程安全配置管理单例类 +// ------------------------ +class ConfigManager +{ + public: + // 获取单例 + static ConfigManager& instance() + { + static ConfigManager inst; + return inst; + } + + // 加载 INI 文件 + bool load(const std::string& path) + { + std::lock_guard lock(mtx); + file_path = path; + + auto r = std::make_unique(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 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>> 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 reader; + std::string file_path; + std::map> 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 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 lock(mtx); + sections[section][key] = value; + } +}; diff --git a/include/config/ini.h b/include/config/ini.h new file mode 100755 index 0000000..07aa7f4 --- /dev/null +++ b/include/config/ini.h @@ -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 + +/* 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 */ diff --git a/include/main.h b/include/main.h new file mode 100644 index 0000000..d8e4471 --- /dev/null +++ b/include/main.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +#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; diff --git a/include/network/mqtt_client.h b/include/network/mqtt_client.h new file mode 100644 index 0000000..b029bfe --- /dev/null +++ b/include/network/mqtt_client.h @@ -0,0 +1,67 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "mqtt/async_client.h" + +class MqttClient : public virtual mqtt::callback +{ + public: + using StatusCallback = std::function; + using MessageCallback = std::function; + + 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 running_{false}; + std::atomic connected_{false}; + std::thread worker_; + + int reconnect_first_ = 5; + int reconnect_max_ = 60; + + std::shared_ptr client_; + std::mutex send_mutex_; + + StatusCallback status_callback_; + MessageCallback message_callback_; +}; diff --git a/include/network/tcp_client.h b/include/network/tcp_client.h new file mode 100644 index 0000000..3e0165e --- /dev/null +++ b/include/network/tcp_client.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "logger.h" + +class TcpClient +{ + public: + using ReceiveCallback = std::function &)>; + using ReceiveStringCallback = std::function; + using StatusCallback = std::function; + + 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 &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 running_{false}; + std::atomic 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; // 最大间隔(秒) +}; diff --git a/include/network/tcp_server.h b/include/network/tcp_server.h new file mode 100644 index 0000000..58e4b5e --- /dev/null +++ b/include/network/tcp_server.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" + +class TcpServer +{ + public: + struct ClientInfo + { + int fd; + std::string ip; + int port; + }; + + using ReceiveCallback = std::function &)>; + using ReceiveStringCallback = std::function; + using StatusCallback = std::function; + + 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 &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 running_{false}; + std::thread worker_; + + std::mutex clients_mutex_; + std::map clients_; + + ReceiveCallback receive_callback_; + StatusCallback status_callback_; + Logger &logger_; +}; diff --git a/include/protocol/protocol_codec.h b/include/protocol/protocol_codec.h new file mode 100644 index 0000000..4df5b2b --- /dev/null +++ b/include/protocol/protocol_codec.h @@ -0,0 +1,24 @@ +#pragma once + +#include "protocol_struct.h" +#include +#include +#include + +namespace ProtocolCodec +{ + // 获取当前北京时间,返回 6 字节 vector {year, month, day, hour, minute, second} + std::vector get_current_time_bytes(); + + // BCC 校验 + uint8_t calculate_bcc(const std::vector &data); + + // 编码 FullPacket 为字节流(使用大端) + std::vector encode_full_packet(const FullPacket &packet); + + // 解码字节流为 FullPacket(成功返回 packet,否则 std::nullopt) + std::optional decode_full_packet(const std::vector &buffer); + + std::vector make_ack_response(const FullPacket &request, bool result); + +} // namespace ProtocolCodec diff --git a/include/protocol/protocol_struct.h b/include/protocol/protocol_struct.h new file mode 100644 index 0000000..17d38c5 --- /dev/null +++ b/include/protocol/protocol_struct.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 通用数据包结构(协议层) +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 data_unit; // 数据单元(二进制) + uint8_t checksum; // BCC 校验值 +}; diff --git a/include/serial/serial_port.h b/include/serial/serial_port.h new file mode 100644 index 0000000..511e20e --- /dev/null +++ b/include/serial/serial_port.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "logger.h" + +class SerialPort +{ + public: + using ReceiveCallback = std::function&)>; + using ReceiveStringCallback = std::function; + + 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& 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 running_{false}; + std::atomic stop_flag_{false}; + std::thread reader_thread_; + std::thread reconnect_thread_; + std::mutex send_mutex_; + + ReceiveCallback receive_callback_; + Logger& logger_; + int retry_interval_; +}; diff --git a/src/business/remote_ctrl/can_bus_rc_ctrl.cpp b/src/business/remote_ctrl/can_bus_rc_ctrl.cpp new file mode 100644 index 0000000..be7ce48 --- /dev/null +++ b/src/business/remote_ctrl/can_bus_rc_ctrl.cpp @@ -0,0 +1,54 @@ +#include "can_bus_instance.h" +#include "rc_data_manager.h" +#include "tbox_data_manager.h" + +std::unique_ptr 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 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(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(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("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(); // 启动发送线程 +} \ No newline at end of file diff --git a/src/business/remote_ctrl/mqtt_client_rc_ctrl.cpp b/src/business/remote_ctrl/mqtt_client_rc_ctrl.cpp new file mode 100644 index 0000000..8b3d99d --- /dev/null +++ b/src/business/remote_ctrl/mqtt_client_rc_ctrl.cpp @@ -0,0 +1,347 @@ +#include +#include + +#include "mqtt_client_instance.h" +#include "nlohmann/json.hpp" +#include "rc_data_manager.h" + +std::unique_ptr 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 watchdogEnabled{false}; +std::atomic emergencyActive{false}; +static std::chrono::steady_clock::time_point lastBrakeTime = std::chrono::steady_clock::now(); + +std::atomic 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(now - lastBrakeTime).count() > 1) + { + if (!emergencyActive) + { + std::lock_guard 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 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 lock(veh_report_mutex); + report_snapshot = veh_report_data; + } + + // 再写 veh_rc_data(只锁 rc) + { + std::lock_guard 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(); + int value = j.at("value").get(); + + // 查找命令类型 + 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 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 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 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 lock(veh_rc_mutex); + value = std::clamp(value, 0, 65535); + veh_rc_data.throttlePercent = static_cast((value * 250) / 65535); + break; + } + case CommandType::Brake: + { + std::lock_guard lock(veh_rc_mutex); + value = std::clamp(value, 0, 65535); + veh_rc_data.brakePercent = static_cast((value * 250) / 65535); + lastBrakeTime = std::chrono::steady_clock::now(); // 刷新心跳 + break; + } + case CommandType::DriveMode: + { + std::lock_guard 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 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(wheel)) * RAW_MAX / WHEEL_MAX; + + // 四舍五入成 uint16_t + uint16_t raw = static_cast(std::lround(raw_f)); + + // 防御性夹一下,避免因为浮点误差溢出去 + if (raw > static_cast(RAW_MAX)) raw = static_cast(RAW_MAX); + + veh_rc_data.steeringAngle = raw; + + break; + } + case CommandType::Horn: + { + std::lock_guard lock(veh_rc_mutex); + veh_rc_data.horn = value; + break; + } + case CommandType::TurnLight: + { + std::lock_guard 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 lock(veh_rc_mutex); + veh_rc_data.eStop = value; + break; + } + case CommandType::Load: + { + std::lock_guard lock(veh_rc_mutex); + veh_rc_data.overload = (value) ? 0 : 1; + break; + } + case CommandType::Voltage: + { + std::lock_guard lock(veh_rc_mutex); + veh_rc_data.highVoltage = value ? 0 : 1; + break; + } + case CommandType::PositionLight: + { + std::lock_guard lock(veh_rc_mutex); + veh_rc_data.outlineLights = value; + break; + } + case CommandType::Warning: + { + std::lock_guard lock(veh_rc_mutex); + veh_rc_data.hazardLights = value; + break; + } + case CommandType::FogLight: + { + std::lock_guard lock(veh_rc_mutex); + veh_rc_data.fogLamp = value; + break; + } + case CommandType::Headlamp: + { + std::lock_guard 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 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 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 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("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(); // 启动客户端 +} diff --git a/src/business/remote_ctrl/rc_data_manager.cpp b/src/business/remote_ctrl/rc_data_manager.cpp new file mode 100644 index 0000000..188e0a1 --- /dev/null +++ b/src/business/remote_ctrl/rc_data_manager.cpp @@ -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 线程安全 diff --git a/src/business/remote_ctrl/serial_AT.cpp b/src/business/remote_ctrl/serial_AT.cpp new file mode 100644 index 0000000..5c5ce69 --- /dev/null +++ b/src/business/remote_ctrl/serial_AT.cpp @@ -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 serial_at; +static std::thread serial_at_sender; + +static std::mutex at_tasks_mutex; // 保护 at_tasks 访问的互斥锁 + +static std::vector 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 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(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(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 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("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(); +} \ No newline at end of file diff --git a/src/business/tbox/mqtt_client_tbox_v2v.cpp b/src/business/tbox/mqtt_client_tbox_v2v.cpp new file mode 100644 index 0000000..d6eb48a --- /dev/null +++ b/src/business/tbox/mqtt_client_tbox_v2v.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include "mqtt_client_instance.h" +#include "nlohmann/json.hpp" +#include "tbox_messages.h" +#include "tcp_server_instance.h" + +std::unique_ptr 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 payload{reinterpret_cast(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("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(); // 启动客户端 +} diff --git a/src/business/tbox/tbox_data_manager.cpp b/src/business/tbox/tbox_data_manager.cpp new file mode 100644 index 0000000..9a500d0 --- /dev/null +++ b/src/business/tbox/tbox_data_manager.cpp @@ -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 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 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 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 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 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 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 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 lock(veh_report_mutex); + + veh_report_data.load = (can_load == 0) ? 0 : 1; // 0:轻载,1:重载 + veh_report_data.power = static_cast(can_soc); // 电量百分比 + } + + { + std::lock_guard 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 lock(drive_brake_mutex); + + current_drive_brake_data.accel_opening = + (can_accel_pedal <= 100) ? static_cast(can_accel_pedal) * 10 : 0xFFFE; + + current_drive_brake_data.brake_opening = + (can_brake_pedal <= 100) ? static_cast(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 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 lock(veh_report_mutex); + + veh_report_data.speed = static_cast(can_speed / 10); // 转换为 km/h + veh_report_data.mileage = static_cast(can_mileage / 10); // 转换为 km + } + + { + std::lock_guard 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 lock(drive_brake_mutex); + + current_drive_brake_data.speed = can_speed; + } + + { + std::lock_guard 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((raw + 32768u) / 100u); + + { + std::lock_guard 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 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 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 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 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 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 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 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 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 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 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 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(frame_no - 1) * 3; + + { + std::lock_guard 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(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((frame_no - 1) / 2) * 9; + } + else + { + // 偶数帧:frame_no = 2,4,6,... → k = frame_no/2 + // sum = 9*k - 3 + base = static_cast(frame_no / 2) * 9 - 3; + } + + { + std::lock_guard 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(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(real_kwh + 0.5f); // 四舍五入取整 + } + + { + std::lock_guard 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 +inline void append_encoded(std::vector& out, std::mutex& mtx, const T& obj) +{ + std::lock_guard 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& out, std::mutex& mtx, const std::vector& v) +{ + std::lock_guard lk(mtx); + + out.reserve(out.size() + v.size()); // 动态扩容 + out.insert(out.end(), v.begin(), v.end()); +} + +std::vector buildTboxRealtimePayload() +{ + std::vector 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 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; +} diff --git a/src/business/tbox/tbox_messages_decode.cpp b/src/business/tbox/tbox_messages_decode.cpp new file mode 100644 index 0000000..ac4e414 --- /dev/null +++ b/src/business/tbox/tbox_messages_decode.cpp @@ -0,0 +1,16 @@ +#include "tbox_messages.h" + +// 接收报文处理 +ReceivedPacket process_received_packet(const std::vector& 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}; +} diff --git a/src/business/tbox/tbox_messages_encode.cpp b/src/business/tbox/tbox_messages_encode.cpp new file mode 100644 index 0000000..2b96263 --- /dev/null +++ b/src/business/tbox/tbox_messages_encode.cpp @@ -0,0 +1,132 @@ +#include "tbox_messages.h" + +template +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& 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& 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& /*du*/) {}); +} + +static std::string build_time_sync_request() +{ + return build_packet(0x08, [](std::vector& /*du*/) {}); +} + +// 实时信息上报 +static std::string build_realtime_msg_request() +{ + return build_packet(0x02, + [](std::vector& 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 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& 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 ""; + } +} diff --git a/src/business/tbox/tcp_client_tbox_gateway.cpp b/src/business/tbox/tcp_client_tbox_gateway.cpp new file mode 100644 index 0000000..d270e90 --- /dev/null +++ b/src/business/tbox/tcp_client_tbox_gateway.cpp @@ -0,0 +1,260 @@ +#include +#include + +#include "protocol_codec.h" +#include "tcp_client_instance.h" + +std::shared_ptr tcp_client_gateway; + +static std::atomic g_gateway_threads_started{false}; +static std::atomic 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 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(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(packet.response_flag) << std::dec; + LOG_WARN(tbox_logger, ss.str()); // 校时失败,打印响应标志 + } +} + +static void handle_tbox_msg(const std::vector& data) +{ + // std::ostringstream oss; + // for (uint8_t c : data) + // { + // oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast(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(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("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(); +} diff --git a/src/business/tbox/tcp_client_tbox_router.cpp b/src/business/tbox/tcp_client_tbox_router.cpp new file mode 100644 index 0000000..7bbdd1e --- /dev/null +++ b/src/business/tbox/tcp_client_tbox_router.cpp @@ -0,0 +1,86 @@ +#include +#include + +#include "protocol_codec.h" +#include "tcp_client_instance.h" + +std::shared_ptr tcp_client_router; + +static void handle_tbox_msg(const std::vector& data) +{ + // std::ostringstream oss; + // for (uint8_t c : data) + // { + // oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast(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(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(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("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(); +} diff --git a/src/business/tbox/tcp_client_tbox_veh_position.cpp b/src/business/tbox/tcp_client_tbox_veh_position.cpp new file mode 100644 index 0000000..ad8ebd5 --- /dev/null +++ b/src/business/tbox/tcp_client_tbox_veh_position.cpp @@ -0,0 +1,125 @@ +#include +#include +#include + +#include "tbox_data_manager.h" +#include "tcp_client_instance.h" + +// 车辆位置信息(0x05)TCP Client +std::unique_ptr tcp_client_vehicle_position; + +// =============================== +// 数据接收回调 +// =============================== +static void handle_vehicle_position_msg(const std::vector& data) +{ + // 将收到的原始内容转成字符串 + std::string s(reinterpret_cast(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 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 lock(vehicle_position_mutex); + + // ×10^6(精确到百万分之一度) + current_vehicle_position.latitude = static_cast(last_lat * 1'000'000); + + current_vehicle_position.longitude = static_cast(last_lon * 1'000'000); + + // ×10(精度 0.1 度) + current_vehicle_position.heading = static_cast(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(now - last_update_time).count(); + + { + std::lock_guard 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("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(); +} diff --git a/src/business/tbox/tcp_server_tbox_autodata.cpp b/src/business/tbox/tcp_server_tbox_autodata.cpp new file mode 100644 index 0000000..7b2a7fa --- /dev/null +++ b/src/business/tbox/tcp_server_tbox_autodata.cpp @@ -0,0 +1,39 @@ +#include "tcp_server_instance.h" + +std::unique_ptr tcp_server_tbox_autodata; + +static void handle_tbox_autodata_msg(const std::vector& 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(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 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(server_id, ip, port, tbox_logger); + tcp_server_tbox_autodata->set_receive_callback(handle_tbox_autodata_msg); + tcp_server_tbox_autodata->start(); +} diff --git a/src/business/tbox/tcp_server_tbox_v2v.cpp b/src/business/tbox/tcp_server_tbox_v2v.cpp new file mode 100644 index 0000000..9af5ff0 --- /dev/null +++ b/src/business/tbox/tcp_server_tbox_v2v.cpp @@ -0,0 +1,49 @@ +#include "mqtt_client_instance.h" +#include "tbox_messages.h" +#include "tcp_server_instance.h" + +std::unique_ptr tcp_server_tbox_v2v; + +static void handle_tbox_v2v_tcp_msg(const std::vector& 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(server_id, ip, port, v2v_logger); + tcp_server_tbox_v2v->set_receive_callback(handle_tbox_v2v_tcp_msg); + tcp_server_tbox_v2v->start(); +} \ No newline at end of file diff --git a/src/can/can_bus.cpp b/src/can/can_bus.cpp new file mode 100644 index 0000000..46d6c09 --- /dev/null +++ b/src/can/can_bus.cpp @@ -0,0 +1,193 @@ +#include "can_bus.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 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(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 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 lock(blacklist_mutex_); + blacklist_.insert(can_id); +} + +void CanBus::remove_blacklist_id(uint32_t can_id) +{ + std::lock_guard lock(blacklist_mutex_); + blacklist_.erase(can_id); +} + +void CanBus::clear_blacklist() +{ + std::lock_guard lock(blacklist_mutex_); + blacklist_.clear(); +} diff --git a/src/config/INIReader.cpp b/src/config/INIReader.cpp new file mode 100755 index 0000000..667449a --- /dev/null +++ b/src/config/INIReader.cpp @@ -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 +#include +#include +#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(::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 INIReader::Sections() const +{ + std::set sectionSet; + for (std::map::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(sectionSet.begin(), sectionSet.end()); +} + +std::vector INIReader::Keys(const string §ion) const +{ + std::vector keys; + string keyPrefix = MakeKey(section, ""); + for (std::map::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::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(::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(user); + string key = MakeKey(section, name); + if (reader->_values[key].size() > 0) + reader->_values[key] += "\n"; + reader->_values[key] += value ? value : ""; + return 1; +} diff --git a/src/config/ini.c b/src/config/ini.c new file mode 100755 index 0000000..ba758fa --- /dev/null +++ b/src/config/ini.c @@ -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 +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#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); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..43f7b98 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,167 @@ +#include "main.h" + +#include + +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// 命令行参数解析 +// ----------------------------------------------------------------------------- +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 ]\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; +} diff --git a/src/network/mqtt_client.cpp b/src/network/mqtt_client.cpp new file mode 100644 index 0000000..84ed876 --- /dev/null +++ b/src/network/mqtt_client.cpp @@ -0,0 +1,184 @@ +#include "mqtt_client.h" + +#include +#include + +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(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 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()); +} diff --git a/src/network/tcp_client.cpp b/src/network/tcp_client.cpp new file mode 100644 index 0000000..018fb81 --- /dev/null +++ b/src/network/tcp_client.cpp @@ -0,0 +1,245 @@ +#include "tcp_client.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 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 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; +} diff --git a/src/network/tcp_server.cpp b/src/network/tcp_server.cpp new file mode 100644 index 0000000..ab050b3 --- /dev/null +++ b/src/network/tcp_server.cpp @@ -0,0 +1,186 @@ +#include "tcp_server.h" + +#include +#include +#include +#include +#include +#include + +#include + +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 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 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 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 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 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 lock(clients_mutex_); + for (auto &kv : clients_) + { + send(kv.second.fd, data.data(), data.size(), 0); + } +} diff --git a/src/protocol/protocol_codec.cpp b/src/protocol/protocol_codec.cpp new file mode 100644 index 0000000..3df7aa8 --- /dev/null +++ b/src/protocol/protocol_codec.cpp @@ -0,0 +1,164 @@ +#include "protocol_codec.h" + +#include +#include + +namespace ProtocolCodec +{ + +// 获取当前北京时间,返回 6 字节 vector {year, month, day, hour, minute, second} +std::vector 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 &data) +{ + uint8_t bcc = 0; + for (uint8_t byte : data) + { + bcc ^= byte; + } + return bcc; +} + +// 编码 FullPacket 为字节流(使用大端) +std::vector encode_full_packet(const FullPacket &packet) +{ + std::vector 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 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 decode_full_packet(const std::vector &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(buffer[0]) << " " + // << static_cast(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 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 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 diff --git a/src/serial/serial_port.cpp b/src/serial/serial_port.cpp new file mode 100644 index 0000000..b1e3914 --- /dev/null +++ b/src/serial/serial_port.cpp @@ -0,0 +1,164 @@ +#include "serial_port.h" + +#include +#include +#include + +#include +#include +#include +#include + +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& data) +{ + if (fd_ < 0) return false; + + std::lock_guard 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(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& data) { cb(std::string(data.begin(), data.end())); }; +} + +void SerialPort::reader_loop() +{ + std::vector 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; +}