generated from guanyuankai/bonus-edge-proxy
feat(video+battery): 完成视频服务,加入电池管理模块.
This commit is contained in:
parent
9280f52aef
commit
79b2616493
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ struct TrackedVehicle {
|
|||
|
||||
// [预留] 如果需要记录车辆经过线条的时间,可以在这里加字段
|
||||
// int64_t cross_line_timestamp = 0;
|
||||
float confidence;
|
||||
};
|
||||
|
||||
// 每一帧的数据包(用于线程间传递)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
#include "battery_service.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#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<std::mutex> 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<uint8_t> 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<std::mutex> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#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<bool> m_running;
|
||||
std::thread m_rx_thread;
|
||||
std::vector<uint8_t> m_rx_buffer;
|
||||
|
||||
// 数据缓存与线程安全
|
||||
BatteryPacket_t m_latest_data;
|
||||
std::mutex m_data_mutex;
|
||||
bool m_has_data = false;
|
||||
};
|
||||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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<TrackedVehicle>& trackedObjects) {
|
||||
std::lock_guard<std::mutex> 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 ! "
|
||||
|
|
|
|||
|
|
@ -67,6 +67,6 @@ private:
|
|||
int model_data_size_;
|
||||
|
||||
const std::vector<std::string> 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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue