build-test-deploy / ci (push) Failing after 32m37s
- 协议模块(protocol/MotorCan):12 条 CAN ID、指令帧 0x18EF2010 编码、 状态帧(转速/母线电压/温度)与支路一/二故障报警帧解码,故障位按协议 逐位映射中文描述(docs/推进电机通信协议20260321.docx) - 主程序(MOOS 应用):订阅 pCanBridge 透传 CAN_0x* 解码电机状态; 500ms 周期下发指令帧(CAN_TX_0x18EF2010,m_sSrcAux=CAN1), 可选 red_channel 冗余下发 0x18EF2011;复位位保持 reset_hold_sec - 网页(18081):按协议指令帧字段独立下发(使能命令 Byte0 bit0 / 复位命令 Byte0 bit1 / 期望转速 Byte2/3,无联动按钮); WebSocket 1Hz 推送转速/电压/温度/故障告警/链路统计 - 配置接入:missions/h100.moos 增加 pMotor 配置块(can_channel=CAN1) - 测试:pmotorTest 92 项(指令帧字节级对照协议示例、解码/故障表) - 部署:scripts/build-board.sh 增加 pMotor 产物拷贝 + pMotor.service; .gitignore 补 bin/pMotor、bin/pmotorTest - 联调验证:MOOSDB+pCanBridge+假CANET 全链路,下行帧 01 00 E8 03 与协议文档示例一致,复位位 3s 保持行为正确
145 lines
4.3 KiB
C++
145 lines
4.3 KiB
C++
#include "WebServer.h"
|
|
#include "pages/index.h"
|
|
#include <iostream>
|
|
|
|
namespace motor {
|
|
|
|
//---------------------------------------------------------
|
|
// 事件处理:HTTP 路由 + WebSocket 收发
|
|
|
|
void WebServer::handleEvent(struct mg_connection* c, int ev, void* ev_data) {
|
|
if (ev == MG_EV_HTTP_MSG) {
|
|
struct mg_http_message* hm = (struct mg_http_message*)ev_data;
|
|
|
|
// WebSocket 升级
|
|
if (mg_match(hm->uri, mg_str("/ws"), NULL)) {
|
|
mg_ws_upgrade(c, hm, NULL);
|
|
return;
|
|
}
|
|
|
|
// /api/* 接口:交给宿主程序处理,返回 JSON
|
|
std::string path(hm->uri.buf, hm->uri.len);
|
|
if (path.rfind("/api/", 0) == 0) {
|
|
std::string body;
|
|
if (m_apiHandler) {
|
|
std::string query(hm->query.buf, hm->query.len);
|
|
body = m_apiHandler(path, query);
|
|
}
|
|
if (!body.empty()) {
|
|
mg_http_reply(c, 200, "Content-Type: application/json\r\n",
|
|
"%.*s", (int)body.size(), body.c_str());
|
|
} else {
|
|
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 电机控制页面
|
|
if (path == "/" || path == "/index.html") {
|
|
mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n",
|
|
"%.*s", (int)INDEX_HTML.size(), INDEX_HTML.c_str());
|
|
return;
|
|
}
|
|
|
|
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n");
|
|
} else if (ev == MG_EV_WS_OPEN) {
|
|
// Web 线程内:先构建欢迎快照,再登记连接
|
|
std::string welcome;
|
|
if (m_onOpen) welcome = m_onOpen();
|
|
m_wsConnections.push_back(c);
|
|
if (!welcome.empty()) {
|
|
mg_ws_send(c, welcome.c_str(), welcome.size(), WEBSOCKET_OP_TEXT);
|
|
}
|
|
} else if (ev == MG_EV_CLOSE || ev == MG_EV_ERROR) {
|
|
for (auto it = m_wsConnections.begin(); it != m_wsConnections.end(); ++it) {
|
|
if (*it == c) {
|
|
m_wsConnections.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
} else if (ev == MG_EV_WS_MSG) {
|
|
// 忽略客户端消息(控制走 /api/*)
|
|
}
|
|
}
|
|
|
|
WebServer::WebServer() {
|
|
mg_mgr_init(&m_mgr);
|
|
}
|
|
|
|
WebServer::~WebServer() {
|
|
stop();
|
|
mg_mgr_free(&m_mgr);
|
|
}
|
|
|
|
void WebServer::setOnOpen(std::function<std::string()> handler) {
|
|
m_onOpen = std::move(handler);
|
|
}
|
|
|
|
void WebServer::setApiHandler(std::function<std::string(const std::string&, const std::string&)> handler) {
|
|
m_apiHandler = std::move(handler);
|
|
}
|
|
|
|
bool WebServer::start(int port) {
|
|
if (m_running) return true;
|
|
m_port = port;
|
|
|
|
std::string addr = "http://0.0.0.0:" + std::to_string(port);
|
|
mg_http_listen(&m_mgr, addr.c_str(), [](mg_connection* c, int ev, void* ev_data) {
|
|
WebServer* server = static_cast<WebServer*>(c->fn_data);
|
|
if (server) server->handleEvent(c, ev, ev_data);
|
|
}, this);
|
|
|
|
m_running = true;
|
|
m_thread = std::thread([this]() { serverThreadFunc(); });
|
|
|
|
std::cout << "pMotor web UI listening on " << addr << std::endl;
|
|
return true;
|
|
}
|
|
|
|
void WebServer::stop() {
|
|
if (!m_running) return;
|
|
m_running = false;
|
|
if (m_thread.joinable()) m_thread.join();
|
|
|
|
// Web 线程已退出,此处触碰连接列表安全
|
|
for (auto c : m_wsConnections) {
|
|
mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE);
|
|
}
|
|
m_wsConnections.clear();
|
|
}
|
|
|
|
bool WebServer::serverThreadCB(void* pParam) {
|
|
WebServer* pThis = static_cast<WebServer*>(pParam);
|
|
return pThis->serverThreadFunc();
|
|
}
|
|
|
|
bool WebServer::serverThreadFunc() {
|
|
while (m_running) {
|
|
mg_mgr_poll(&m_mgr, 200);
|
|
// 仅在本线程(poll 间隙)发送,避免跨线程操作 mongoose 连接
|
|
flushPending();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WebServer::flushPending() {
|
|
std::string text;
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_pendingMutex);
|
|
text = std::move(m_pending);
|
|
m_pending.clear();
|
|
}
|
|
if (text.empty()) return;
|
|
for (auto c : m_wsConnections) {
|
|
mg_ws_send(c, text.c_str(), text.size(), WEBSOCKET_OP_TEXT);
|
|
}
|
|
}
|
|
|
|
void WebServer::broadcast(const std::string& text) {
|
|
// 只投递到队列,实际发送在 Web 线程 flushPending() 中完成
|
|
std::lock_guard<std::mutex> lock(m_pendingMutex);
|
|
m_pending = text;
|
|
}
|
|
|
|
} // namespace motor
|