2026-08-27 调试完成版本

- FC 控制指令域修正:payload 14→18 字节,纵倾/横倾姿态拆分为数据1/数据2
- WebServer 广播改为待发队列模式,修复 mongoose 跨线程调用问题
- build-board.sh 增加板卡组网说明(/23 掩码方案)、pCCU 服务部署与 mission 同步
- 同步 0824 协议文档更新
This commit is contained in:
zjk
2026-08-27 21:51:36 +08:00
parent 54c32a180a
commit c8becd1f6c
13 changed files with 162 additions and 67 deletions
+22 -10
View File
@@ -43,16 +43,14 @@ void WebServer::handleEvent(struct mg_connection* c, int ev, void* ev_data) {
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n");
} else if (ev == MG_EV_WS_OPEN) {
std::lock_guard<std::mutex> lock(m_wsMutex);
// Web 线程内:先构建欢迎快照(可能涉及 DB 查询,勿持锁),再登记连接
std::string welcome;
if (m_onOpen) welcome = m_onOpen();
m_wsConnections.push_back(c);
if (m_onOpen) {
std::string welcome = m_onOpen();
if (!welcome.empty()) {
mg_ws_send(c, welcome.c_str(), welcome.size(), WEBSOCKET_OP_TEXT);
}
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) {
std::lock_guard<std::mutex> lock(m_wsMutex);
for (auto it = m_wsConnections.begin(); it != m_wsConnections.end(); ++it) {
if (*it == c) {
m_wsConnections.erase(it);
@@ -103,7 +101,7 @@ void WebServer::stop() {
m_running = false;
if (m_thread.joinable()) m_thread.join();
std::lock_guard<std::mutex> lock(m_wsMutex);
// Web 线程已退出,此处触碰连接列表安全
for (auto c : m_wsConnections) {
mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE);
}
@@ -118,15 +116,29 @@ bool WebServer::serverThreadCB(void* pParam) {
bool WebServer::serverThreadFunc() {
while (m_running) {
mg_mgr_poll(&m_mgr, 200);
// 仅在本线程(poll 间隙)发送,避免跨线程操作 mongoose 连接
flushPending();
}
return true;
}
void WebServer::broadcast(const std::string& text) {
std::lock_guard<std::mutex> lock(m_wsMutex);
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 ccu
+7 -2
View File
@@ -32,6 +32,8 @@ public:
bool isRunning() const { return m_running; }
// 向所有 WebSocket 客户端广播文本
// 线程安全:mongoose 非线程安全,本接口只把文本放入待发队列,
// 由 Web 线程在 mg_mgr_poll 返回后统一发送(禁止跨线程直接 mg_ws_send)
void broadcast(const std::string& text);
// 新客户端连接时调用,返回初始快照
@@ -44,14 +46,17 @@ private:
static bool serverThreadCB(void* pParam);
bool serverThreadFunc();
void handleEvent(struct mg_connection* c, int ev, void* ev_data);
// Web 线程内:把待发队列中的快照推给所有 WS 客户端(仅本线程触碰连接列表)
void flushPending();
struct mg_mgr m_mgr;
int m_port = 0;
std::atomic<bool> m_running{false};
std::thread m_thread;
std::mutex m_wsMutex;
std::vector<mg_connection*> m_wsConnections;
std::mutex m_pendingMutex; // 保护 m_pending
std::string m_pending; // 最新待广播快照(新快照覆盖旧快照)
std::vector<mg_connection*> m_wsConnections; // 仅 Web 线程访问
std::function<std::string()> m_onOpen;
std::function<std::string(const std::string&, const std::string&)> m_apiHandler;
};
+8 -3
View File
@@ -172,6 +172,9 @@ function hydroCard(d){
h+=row('电动阀开度', (d.electric_valve_open*0.1).toFixed(1)+'%');
h+=row('甲醇累计使用量', (d.methanol_total_use*0.1).toFixed(1)+' kg');
h+=row('甲醇溶液进料量', d.methanol_feed+' mL/min');
h+=row('氧侧生成水箱液位', (d.oxygen_side_water_level*0.1).toFixed(1)+' mm');
h+=row('氢侧生成水箱液位', (d.hydrogen_side_water_level*0.1).toFixed(1)+' mm');
h+=row('配重水箱液位', (d.ballast_water_level*0.1).toFixed(1)+' mm');
h+=row('I#FC最低单片电压', (d.fc1_min_cell_voltage*10)+' mV (位置'+d.fc1_min_cell_pos+')');
h+=row('I#FC平均单片电压', (d.fc1_avg_cell_voltage*10)+' mV');
h+=row('2#FC最低单片电压', (d.fc2_min_cell_voltage*10)+' mV (位置'+d.fc2_min_cell_pos+')');
@@ -211,7 +214,7 @@ function tankCard(d){
let h='';
h+=row('液氧罐压力', (d.tank_lo2_pressure*0.01).toFixed(2)+' MPa');
h+=row('二氧化碳压力', (d.tank_co2_pressure*0.01).toFixed(2)+' MPa');
h+=row('液氧罐液位', (d.tank_lo2_level*0.01).toFixed(2)+' mm');
h+=row('液氧罐液位', (d.tank_lo2_level*0.01).toFixed(2)+' m');
h+=row('合金供氢流量', (d.alloy_h2_flow*0.01).toFixed(2)+' L/min');
h+=row('FC供氢流量', (d.fc_h2_flow*0.01).toFixed(2)+' L/min');
h+=row('FC供氧流量', (d.fc_o2_flow*0.01).toFixed(2)+' L/min');
@@ -321,8 +324,10 @@ function fcCmdCard(d){
h+=row('模式设定', parsed(d.mode, modeText(d.mode)));
h+=row('操控指令', parsed(d.cmd, cmdText(d.cmd)));
h+=row('输出功率指令', d.outputPower+' (×0.1kW)');
h+=row('纵倾姿态', (d.pitch*0.1).toFixed(1)+'°');
h+=row('横倾姿态', (d.roll*0.1).toFixed(1)+'°');
h+=row('纵倾姿态数据1', (d.pitch1*0.1).toFixed(1)+'°');
h+=row('纵倾姿态数据2', (d.pitch2*0.1).toFixed(1)+'°');
h+=row('横倾姿态数据1', (d.roll1*0.1).toFixed(1)+'°');
h+=row('横倾姿态数据2', (d.roll2*0.1).toFixed(1)+'°');
h+=row('应急允许', parsed(d.emergencyAllow, emerAllowText(d.emergencyAllow)));
h+=row('潜深深度', d.depth+' m');
h+=row('补给/排放指令', parsed(d.supplyCmd, supplyCmdText(d.supplyCmd)));