Compare commits

...

2 Commits

11 changed files with 354 additions and 42 deletions

View File

@ -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

View File

@ -20,12 +20,12 @@
"enable": true,
"line": {
"p1": {
"x": 0.39809998869895935,
"y": 0.8816999793052673
"x": 0.3831999897956848,
"y": 0.7179999947547913
},
"p2": {
"x": 0.8337000012397766,
"y": 0.5864999890327454
"x": 0.6578999757766724,
"y": 0.6560999751091003
}
},
"name": "Main_Gate_Line"

View File

@ -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

View File

@ -34,6 +34,7 @@ struct TrackedVehicle {
// [预留] 如果需要记录车辆经过线条的时间,可以在这里加字段
// int64_t cross_line_timestamp = 0;
float confidence;
};
// 每一帧的数据包(用于线程间传递)

View File

@ -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));
}
}
}

View File

@ -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;
};

View File

@ -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)) {
@ -94,6 +93,15 @@ int main(int argc, char* argv[]) {
std::string db_database = config.getDbName();
MysqlManager::GetInstance()->Initialize(db_host, db_user, db_pwd, db_database);
std::string uart_port = "/dev/ttyS4";
BatteryService battery_service(uart_port, 115200);
if (!battery_service.start()) {
spdlog::warn(
"Battery Service failed to start on {}, proceeding without battery monitoring.",
uart_port);
}
try {
spdlog::set_level(spdlog::level::from_str(config.getLogLevel()));
spdlog::info("Edge Proxy starting up...");
@ -177,7 +185,7 @@ int main(int argc, char* argv[]) {
device_manager.load_and_start(config.getDevicesConfigPath());
WebServer web_server(monitor, device_manager, live_data_cache, alarm_service,
config.getWebServerPort());
battery_service, config.getWebServerPort());
web_server.set_shutdown_handler([&]() {
spdlog::warn("Received shutdown command from Web API. Shutting down.");
g_io_context.stop();
@ -207,7 +215,10 @@ int main(int argc, char* argv[]) {
spdlog::info("[Shutdown] D. Disconnecting from MQTT broker...");
mqtt_client.disconnect();
spdlog::info("[Shutdown] E. Stopping main event loop...");
spdlog::info("[Shutdown] F. Stopping Battery Service...");
battery_service.stop();
spdlog::info("[Shutdown] G. Stopping main event loop...");
g_io_context.stop();
});

View File

@ -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 ! "

View File

@ -9,12 +9,14 @@
#include "spdlog/spdlog.h"
WebServer::WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager,
LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port)
LiveDataCache& liveDataCache, AlarmService& alarm_service,
BatteryService& battery_service, uint16_t port)
: crow::Crow<crow::CORSHandler>(),
m_monitor(monitor),
m_device_manager(deviceManager),
m_live_data_cache(liveDataCache),
m_alarm_service(alarm_service),
m_battery_service(battery_service),
m_port(port) {
auto& cors = this->get_middleware<crow::CORSHandler>();
cors.global()
@ -121,4 +123,32 @@ void WebServer::setup_routes() {
return crow::response(400, response_json.dump());
}
});
CROW_ROUTE((*this), "/api/v1/battery/status").methods("GET"_method)([&]() {
auto json_data = m_battery_service.getStatusJson();
json response = {{"code", 200}, {"status", "success"}, {"data", json_data}};
return crow::response(200, response.dump());
});
// [新增] 控制电池指令
CROW_ROUTE((*this), "/api/v1/battery/control")
.methods("POST"_method)([&](const crow::request& req) {
auto json_body = crow::json::load(req.body);
if (!json_body)
return crow::response(400, "Invalid JSON");
if (json_body.has("mode")) {
// mode: 1 (Work), 2 (Sleep)
int mode = json_body["mode"].i();
m_battery_service.setMode(static_cast<uint8_t>(mode));
}
if (json_body.has("alarm")) {
// alarm: true/false
bool alarm = json_body["alarm"].b();
m_battery_service.setAlarm(alarm);
}
return crow::response(200, R"({"code": 200, "message": "Command sent"})");
});
}

View File

@ -4,6 +4,7 @@
#include <thread>
#include "alarm/alarm_service.h"
#include "batteryService/battery_service.h"
#include "crow.h"
#include "crow/middlewares/cors.h"
#include "dataCache/live_data_cache.h"
@ -14,7 +15,8 @@
class WebServer : public crow::Crow<crow::CORSHandler> {
public:
WebServer(SystemMonitor::SystemMonitor& monitor, DeviceManager& deviceManager,
LiveDataCache& liveDataCache, AlarmService& alarm_service, uint16_t port = 8080);
LiveDataCache& liveDataCache, AlarmService& alarm_service,
BatteryService& battery_service, uint16_t port = 8080);
~WebServer();
WebServer(const WebServer&) = delete;
@ -38,7 +40,7 @@ private:
DeviceManager& m_device_manager;
LiveDataCache& m_live_data_cache;
AlarmService& m_alarm_service;
BatteryService& m_battery_service;
uint16_t m_port;
std::thread m_thread;

View File

@ -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;
};