diff --git a/CMakeLists.txt b/CMakeLists.txt index 66049fb..a5631c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,8 @@ add_library(vehicle_road_lib STATIC src/mysqlManager/AlarmDao.cpp src/mysqlManager/DeviceIdentificationDao.cpp src/mysqlManager/ResourceFileDao.cpp + #电池管理模块 + src/batteryService/battery_service.cc ) target_include_directories(vehicle_road_lib PUBLIC diff --git a/config/config.json b/config/config.json index 74e8d57..9f83ade 100644 --- a/config/config.json +++ b/config/config.json @@ -20,12 +20,12 @@ "enable": true, "line": { "p1": { - "x": 0.39809998869895935, - "y": 0.8816999793052673 + "x": 0.2556999921798706, + "y": 0.848800003528595 }, "p2": { - "x": 0.8337000012397766, - "y": 0.5864999890327454 + "x": 0.6531999707221985, + "y": 0.8278999924659729 } }, "name": "Main_Gate_Line" diff --git a/docker-compose.yml b/docker-compose.yml index 4579c26..12962c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: - /dev/ttyS7:/dev/ttyS7 - /dev/ttyS9:/dev/ttyS9 - /dev/snd:/dev/snd + - /dev/ttyS4:/dev/ttyS4 # --- VPU/NPU/RGA/GPU 硬件访问 --- - /dev/mpp_service:/dev/mpp_service diff --git a/src/DTOs/common_types.hpp b/src/DTOs/common_types.hpp index 87aa230..374a48f 100644 --- a/src/DTOs/common_types.hpp +++ b/src/DTOs/common_types.hpp @@ -34,6 +34,7 @@ struct TrackedVehicle { // [预留] 如果需要记录车辆经过线条的时间,可以在这里加字段 // int64_t cross_line_timestamp = 0; + float confidence; }; // 每一帧的数据包(用于线程间传递) diff --git a/src/batteryService/battery_service.cc b/src/batteryService/battery_service.cc new file mode 100644 index 0000000..fa3215f --- /dev/null +++ b/src/batteryService/battery_service.cc @@ -0,0 +1,208 @@ +#include "battery_service.h" + +#include +#include +#include +#include + +#include +#include + +#include "spdlog/spdlog.h" + +// 协议常量 +#define FRAME_HEAD 0xA5 +#define CMD_REPORT 0x01 +#define CMD_SET_MODE 0x02 +#define CMD_SET_ALARM 0x03 +#define CMD_ACK 0xAA + +BatteryService::BatteryService(const std::string& port, int baud_rate) + : m_port_name(port), m_baud_rate(baud_rate), m_running(false) { + // 初始化数据结构为0 + std::memset(&m_latest_data, 0, sizeof(m_latest_data)); +} + +BatteryService::~BatteryService() { + stop(); +} + +bool BatteryService::start() { + if (!openPort()) { + return false; + } + m_running = true; + m_rx_thread = std::thread(&BatteryService::receiveLoop, this); + spdlog::info("BatteryService started on port {}", m_port_name); + return true; +} + +void BatteryService::stop() { + m_running = false; + if (m_rx_thread.joinable()) { + m_rx_thread.join(); + } + if (m_fd >= 0) { + close(m_fd); + m_fd = -1; + } + spdlog::info("BatteryService stopped."); +} + +bool BatteryService::openPort() { + m_fd = open(m_port_name.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); + if (m_fd < 0) { + spdlog::error("Failed to open serial port: {}", m_port_name); + return false; + } + + fcntl(m_fd, F_SETFL, 0); // 恢复阻塞模式 + + struct termios options; + tcgetattr(m_fd, &options); + + speed_t speed = (m_baud_rate == 115200) ? B115200 : B9600; + cfsetispeed(&options, speed); + cfsetospeed(&options, speed); + + options.c_cflag |= (CLOCAL | CREAD); + options.c_cflag &= ~PARENB; + options.c_cflag &= ~CSTOPB; + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; + options.c_cflag &= ~CRTSCTS; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag &= ~(IXON | IXOFF | IXANY); + options.c_iflag &= ~(ICRNL | INLCR); + options.c_oflag &= ~OPOST; + + tcsetattr(m_fd, TCSANOW, &options); + tcflush(m_fd, TCIOFLUSH); + return true; +} + +int BatteryService::writeData(const uint8_t* data, size_t len) { + if (m_fd < 0) + return -1; + return write(m_fd, data, len); +} + +void BatteryService::setMode(uint8_t mode) { + sendPacket(CMD_SET_MODE, &mode, 1); + spdlog::info("BatteryService: Set Mode to {}", mode); +} + +void BatteryService::setAlarm(bool on) { + uint8_t val = on ? 1 : 0; + sendPacket(CMD_SET_ALARM, &val, 1); + spdlog::info("BatteryService: Set Alarm to {}", val); +} + +nlohmann::json BatteryService::getStatusJson() { + std::lock_guard lock(m_data_mutex); + if (!m_has_data) { + return {{"status", "waiting_for_data"}}; + } + + return {{"battery_cap", m_latest_data.battery_cap}, + {"time_to_full_min", m_latest_data.time_to_full}, + {"time_to_empty_min", m_latest_data.time_to_empty}, + {"status_code", m_latest_data.battery_status}, + {"temperature_c", (float)m_latest_data.temperature / 10.0f}, + {"system_mode", m_latest_data.system_mode}, + {"alarm_flag", m_latest_data.alarm_flag}}; +} + +void BatteryService::sendPacket(uint8_t cmd, const uint8_t* payload, uint8_t len) { + std::vector packet; + packet.push_back(FRAME_HEAD); + packet.push_back(cmd); + packet.push_back(len); + if (len > 0 && payload) { + packet.insert(packet.end(), payload, payload + len); + } + uint32_t crc = calculateCRC32(packet.data(), packet.size()); + packet.push_back((crc >> 0) & 0xFF); + packet.push_back((crc >> 8) & 0xFF); + packet.push_back((crc >> 16) & 0xFF); + packet.push_back((crc >> 24) & 0xFF); + + writeData(packet.data(), packet.size()); +} + +uint32_t BatteryService::calculateCRC32(const uint8_t* data, size_t len) { + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < len; i++) { + uint32_t byte = data[i]; + crc = crc ^ byte; + for (int j = 0; j < 8; j++) { + if (crc & 1) + crc = (crc >> 1) ^ 0xEDB88320; + else + crc = crc >> 1; + } + } + return crc ^ 0xFFFFFFFF; +} + +void BatteryService::processFrame(const uint8_t* frame, size_t totalLen) { + uint8_t cmd = frame[1]; + const uint8_t* payload = &frame[3]; + + if (cmd == CMD_REPORT) { + if (totalLen >= sizeof(BatteryPacket_t) + 7) { + std::lock_guard lock(m_data_mutex); + memcpy(&m_latest_data, payload, sizeof(BatteryPacket_t)); + m_has_data = true; + + // 可选:在这里记录日志,或者只在Debug时记录,避免刷屏 + // spdlog::debug("Battery Update: {}%", m_latest_data.battery_cap); + + uint8_t ack[] = "OK"; + sendPacket(CMD_ACK, ack, 2); + } + } else if (cmd == CMD_ACK) { + spdlog::debug("BatteryService: Received ACK from MCU"); + } +} + +void BatteryService::receiveLoop() { + uint8_t buf[128]; + while (m_running) { + if (m_fd < 0) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + continue; + } + + int n = read(m_fd, buf, sizeof(buf)); + if (n > 0) { + m_rx_buffer.insert(m_rx_buffer.end(), buf, buf + n); + while (m_rx_buffer.size() >= 7) { + if (m_rx_buffer[0] != FRAME_HEAD) { + m_rx_buffer.erase(m_rx_buffer.begin()); + continue; + } + uint8_t dataLen = m_rx_buffer[2]; + size_t frameLen = 3 + dataLen + 4; + if (m_rx_buffer.size() < frameLen) + break; + + uint32_t receivedCRC = 0; + receivedCRC |= m_rx_buffer[frameLen - 4] << 0; + receivedCRC |= m_rx_buffer[frameLen - 3] << 8; + receivedCRC |= m_rx_buffer[frameLen - 2] << 16; + receivedCRC |= m_rx_buffer[frameLen - 1] << 24; + + if (calculateCRC32(m_rx_buffer.data(), frameLen - 4) == receivedCRC) { + processFrame(m_rx_buffer.data(), frameLen); + m_rx_buffer.erase(m_rx_buffer.begin(), m_rx_buffer.begin() + frameLen); + } else { + spdlog::warn("BatteryService: CRC Error"); + m_rx_buffer.erase(m_rx_buffer.begin()); + } + } + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } +} diff --git a/src/batteryService/battery_service.h b/src/batteryService/battery_service.h new file mode 100644 index 0000000..f980448 --- /dev/null +++ b/src/batteryService/battery_service.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" + +// 定义数据结构 (保持字节对齐) +#pragma pack(push, 1) +struct BatteryPacket_t { + uint8_t battery_cap; // 1. 电量百分比 + uint16_t time_to_full; // 2. 预测充满时间 + uint16_t time_to_empty; // 3. 预测放电时间 + uint8_t battery_status; // 4. 电池状态 + int16_t temperature; // 5. 温度 + uint8_t system_mode; // 6. 模式 + uint8_t alarm_flag; // 7. 报警标志 +}; +#pragma pack(pop) + +class BatteryService { +public: + BatteryService(const std::string& port, int baud_rate); + ~BatteryService(); + + // 初始化串口并启动接收线程 + bool start(); + // 停止线程并关闭串口 + void stop(); + + // API 调用的控制接口 + void setMode(uint8_t mode); + void setAlarm(bool on); + + // 获取当前缓存的电池数据 (JSON格式) + nlohmann::json getStatusJson(); + +private: + // 内部串口操作 + bool openPort(); + void receiveLoop(); + int writeData(const uint8_t* data, size_t len); + void sendPacket(uint8_t cmd, const uint8_t* payload, uint8_t len); + uint32_t calculateCRC32(const uint8_t* data, size_t len); + void processFrame(const uint8_t* frame, size_t totalLen); + + // 成员变量 + std::string m_port_name; + int m_baud_rate; + int m_fd = -1; + + std::atomic m_running; + std::thread m_rx_thread; + std::vector m_rx_buffer; + + // 数据缓存与线程安全 + BatteryPacket_t m_latest_data; + std::mutex m_data_mutex; + bool m_has_data = false; +}; diff --git a/src/main.cpp b/src/main.cpp index 5f7ecc7..8852785 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,11 +74,10 @@ void poll_system_metrics(boost::asio::steady_timer& timer, SystemMonitor::System int main(int argc, char* argv[]) { // TODO: [GYK] DEV#1: 将 URL 放入 config.json 中读取 - // std::string cam_rtsp_input = "rtsp://admin:123456@192.168.1.57:554/stream0"; - std::string cam_rtsp_input = "../data/test_car2.mp4"; + std::string cam_rtsp_input = "rtsp://admin:123456@192.168.1.57:554/stream0"; + // std::string cam_rtsp_input = "../data/test_car2.mp4"; // std::string cam_rtsp_input = - // "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1901"; - + // "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301"; std::string algorithm_rtsp_output = "rtsp://127.0.0.1:8554/processed"; const std::string config_path = "/app/config/config.json"; if (!ConfigManager::getInstance().load(config_path)) { diff --git a/src/videoService/video_pipeline.cpp b/src/videoService/video_pipeline.cpp index a1818c9..98c7ee3 100644 --- a/src/videoService/video_pipeline.cpp +++ b/src/videoService/video_pipeline.cpp @@ -198,6 +198,7 @@ void VideoPipeline::processCrossing(const TrackedVehicle& vehicle, const cv::Mat std::string fileName = fmt::format("{}_id{}.jpg", timeStr, vehicle.id); std::string fullPath = fmt::format("{}/{}", saveDir, fileName); + std::string dbPath = fmt::format("{}/{}", "/captures", fileName); // === 2. 安全截图 (内存操作,无需加数据库锁) === cv::Rect safeBox = vehicle.box; @@ -258,7 +259,7 @@ void VideoPipeline::processCrossing(const TrackedVehicle& vehicle, const cv::Mat bool fileSaved = fileDao.SaveFile("tb_device_identification_data", // source_table dataId, // business_id - fullPath, // file_path + dbPath, // file_path fileName, // source_file_name FileType::ORIGINAL // file_type ); @@ -360,7 +361,7 @@ void VideoPipeline::updateTracker(const FrameData& frameData) { newTrack.missing_frames = 0; newTrack.ev_score = current_is_ev; newTrack.last_class_id = det.class_id; - + newTrack.confidence = det.confidence; tracks_[newTrack.id] = newTrack; } } @@ -382,51 +383,43 @@ void VideoPipeline::updateTracker(const FrameData& frameData) { void VideoPipeline::drawOverlay(cv::Mat& frame, const std::vector& trackedObjects) { std::lock_guard lock(config_mtx_); - // 1. 画绊线 (保持不变) if (tripwire_config_.enabled) { cv::line(frame, tripwire_p1_pixel_, tripwire_p2_pixel_, cv::Scalar(0, 255, 255), 2); cv::putText(frame, tripwire_config_.name, tripwire_p1_pixel_, cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 255), 2); } - // 2. 画车辆 (已修改) for (const auto& trk : trackedObjects) { if (trk.missing_frames > 0) continue; - // 设置颜色:EV为绿色,燃油车为红色 cv::Scalar color = (trk.last_class_id == 1) ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255); - // 绘制车辆边框 cv::rectangle(frame, trk.box, color, 2); - // === [修改开始] 文字居中显示 EV / FUEL === + std::stringstream ss; + ss << trk.label << " " << std::fixed << std::setprecision(2) << trk.confidence; + std::string text = ss.str(); - // 1. 确定显示的文本 - // std::string text = (trk.last_class_id == 1) ? "EV" : "FUEL"; + int fontFace = cv::FONT_HERSHEY_SIMPLEX; + double fontScale = 0.6; + int thickness = 1; + int baseline = 0; - // 2. 设置字体参数 - // int fontFace = cv::FONT_HERSHEY_SIMPLEX; - // double fontScale = 0.8; // 稍微调大一点以便在框内看清 - // int thickness = 2; - // int baseline = 0; + cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, &baseline); - // 3. 计算文字本身的宽高 - // cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, &baseline); + cv::Point textOrg(trk.box.x, trk.box.y - 5); - // 4. 计算检测框的中心点 - // cv::Point boxCenter(trk.box.x + trk.box.width / 2, trk.box.y + trk.box.height / 2); + if (textOrg.y < textSize.height) { + textOrg.y = trk.box.y + textSize.height + 5; + } - // 5. 计算文字绘制的起始点 (Origin) - // 使得文字的中心与框的中心对齐 - // X轴: 框中心X - 文字宽度的一半 - // Y轴: 框中心Y + 文字高度的一半 (注意OpenCV原点在左上角,文字基线在下方) - // cv::Point textOrg(boxCenter.x - textSize.width / 2, boxCenter.y + textSize.height / 2); + cv::rectangle(frame, textOrg + cv::Point(0, baseline), + textOrg + cv::Point(textSize.width, -textSize.height), color, cv::FILLED); - // 6. 绘制文字 - // cv::putText(frame, text, textOrg, fontFace, fontScale, color, thickness); - - // === [修改结束] === + cv::Scalar textColor = + (trk.last_class_id == 1) ? cv::Scalar(0, 0, 0) : cv::Scalar(255, 255, 255); + cv::putText(frame, text, textOrg, fontFace, fontScale, textColor, thickness); } // 调试信息 (保持不变) @@ -450,7 +443,7 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl, boo int width = cap.get(cv::CAP_PROP_FRAME_WIDTH); int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT); - const double TARGET_FPS = 30.0; + const double TARGET_FPS = 20.0; std::stringstream pipeline; pipeline << "appsrc ! " diff --git a/src/yoloDetector/yolo_detector.hpp b/src/yoloDetector/yolo_detector.hpp index b39b1ad..55103e6 100644 --- a/src/yoloDetector/yolo_detector.hpp +++ b/src/yoloDetector/yolo_detector.hpp @@ -67,6 +67,6 @@ private: int model_data_size_; const std::vector CLASS_NAMES = {"fuel_car", "new_energy_car"}; - const float obj_thresh_ = 0.25f; + const float obj_thresh_ = 0.60f; const float nms_thresh_ = 0.45f; };