feat(libweb_server): Web 管理服务模块(3 文件拆分 + 单线程 + EV_STOP)
基于原工程 libweb_server 重构,核心改进: - 双线程 → 单线程: mg_mgr_poll 合入 X-Macro 框架 EV_TIMER1 - 无停止机制 → EV_STOP 优雅退出 - 1500 行 → ~650 行(3 文件拆分: web_server.cpp/ws_method.cpp) - 端口: http://0.0.0.0:8080, WebSocket: /ws - 信号 CRUD: add/del/set × 5 种信号类型 - 增量推送: EV_TIMER2 每 100ms 对比 last_val - 异步命令通道 + 文件上传/下载 + 远程命令执行 注册: app_modules.h + system/makefile + copy_headers.sh + RTU/makefile 构建: x86 + ARM 全通过,零警告
This commit is contained in:
parent
db1ee1e979
commit
3dd52d2202
|
|
@ -40,7 +40,7 @@
|
||||||
| IEC 点表解析 | `libiec` | ⬜ |
|
| IEC 点表解析 | `libiec` | ⬜ |
|
||||||
| Modbus 主站管理 | `libmodbus_m`(原工程自研模块,导入+适配) | ✅ |
|
| Modbus 主站管理 | `libmodbus_m`(原工程自研模块,导入+适配) | ✅ |
|
||||||
| 通讯通道解码 | `libcom_decode` | ⬜ |
|
| 通讯通道解码 | `libcom_decode` | ⬜ |
|
||||||
| Web 管理服务 | `libweb_server` | ⬜ |
|
| Web 管理服务 | `libweb_server`(3 文件拆分,单线程,EV_STOP 支持) | ✅ |
|
||||||
| Mongoose 网络库 | `libmongoose`(开源库 v7.22,导入+适配) | ✅ |
|
| Mongoose 网络库 | `libmongoose`(开源库 v7.22,导入+适配) | ✅ |
|
||||||
| 主程序入口 | `RTU`(X-Macro 框架 + app_config.json 启停控制 + 独立 config/ 目录) | ✅ |
|
| 主程序入口 | `RTU`(X-Macro 框架 + app_config.json 启停控制 + 独立 config/ 目录) | ✅ |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,5 +108,6 @@ copy_proto_module "libmongoose" "mongoose.h"
|
||||||
# 系统模块(system/)
|
# 系统模块(system/)
|
||||||
# ================================================================
|
# ================================================================
|
||||||
copy_sys_header "mySystem.h"
|
copy_sys_header "mySystem.h"
|
||||||
|
copy_sys_module "libweb_server" "web_server.h"
|
||||||
|
|
||||||
echo "=== done ==="
|
echo "=== done ==="
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ ROOT := $(realpath $(CURDIR)/../../../..)
|
||||||
|
|
||||||
INC_PATH := -I$(ROOT)/release/inc \
|
INC_PATH := -I$(ROOT)/release/inc \
|
||||||
-I$(ROOT)/src/system/inc \
|
-I$(ROOT)/src/system/inc \
|
||||||
|
-I$(ROOT)/src/system/libweb_server/inc \
|
||||||
-I$(ROOT)/src/public/libdatacenter/inc \
|
-I$(ROOT)/src/public/libdatacenter/inc \
|
||||||
-I$(ROOT)/src/public/liblog/inc \
|
-I$(ROOT)/src/public/liblog/inc \
|
||||||
-I$(ROOT)/src/public/libfunc/inc \
|
-I$(ROOT)/src/public/libfunc/inc \
|
||||||
|
|
@ -11,11 +12,12 @@ INC_PATH := -I$(ROOT)/release/inc \
|
||||||
-I$(ROOT)/src/public/libtask/inc \
|
-I$(ROOT)/src/public/libtask/inc \
|
||||||
-I$(ROOT)/src/public/libmy_xxhash/inc \
|
-I$(ROOT)/src/public/libmy_xxhash/inc \
|
||||||
-I$(ROOT)/src/public/libcJSON/inc \
|
-I$(ROOT)/src/public/libcJSON/inc \
|
||||||
-I$(ROOT)/src/protocol/libmodbus/inc
|
-I$(ROOT)/src/protocol/libmodbus/inc \
|
||||||
|
-I$(ROOT)/src/protocol/libmongoose/inc
|
||||||
|
|
||||||
LIBS := -L$(ROOT)/release/$(BUILD_TYPE)/lib \
|
LIBS := -L$(ROOT)/release/$(BUILD_TYPE)/lib \
|
||||||
-lmodbus_m -lmodbus -l60870 \
|
-lweb_server -lmodbus_m -lmodbus -l60870 \
|
||||||
-ldatacenter -lxml -lcmd -ltask \
|
-lmongoose -ldatacenter -lxml -lcmd -ltask \
|
||||||
-llog -lfunc -lmy_xxhash -lcJSON -lmd5 -lcomm \
|
-llog -lfunc -lmy_xxhash -lcJSON -lmd5 -lcomm \
|
||||||
-lpthread -lstdc++
|
-lpthread -lstdc++
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
include ./../../../linux.mk
|
||||||
|
L := $(notdir $(realpath $(CURDIR)/..))
|
||||||
|
M := libweb_server
|
||||||
|
O := $(LIB_REL)/$(M).a
|
||||||
|
S := $(SRC_ROOT_DIR)/$(L)/libweb_server/src
|
||||||
|
I := -I$(SRC_ROOT_DIR)/$(L)/libweb_server/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/$(L)/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/protocol/libmongoose/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/public/libdatacenter/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/public/liblog/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/public/libfunc/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/public/libcmd/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/public/libtask/inc \
|
||||||
|
-I$(SRC_ROOT_DIR)/public/libcJSON/inc
|
||||||
|
B := $(CURDIR)/$(M)/obj
|
||||||
|
SRCS := $(wildcard $(S)/*.cpp)
|
||||||
|
OBJS := $(patsubst $(S)/%.cpp, $(B)/%.o, $(SRCS))
|
||||||
|
F := $(CXX_FLAGS) $(I)
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all:
|
||||||
|
@mkdir -p $(ROOT_DIR)/release/inc
|
||||||
|
@$(MAKE) $(O)
|
||||||
|
|
||||||
|
$(O): $(OBJS)
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(AR) rcs $@ $^
|
||||||
|
@echo "[$(M)] built"
|
||||||
|
|
||||||
|
$(B)/%.o: $(S)/%.cpp
|
||||||
|
@mkdir -p $(dir $@)
|
||||||
|
$(CXX) $(F) -c $< -o $@
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf $(B) $(O)
|
||||||
|
|
||||||
|
.PHONY: rebuild
|
||||||
|
rebuild: clean all
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
include ./../../linux.mk
|
include ./../../linux.mk
|
||||||
|
|
||||||
# app_sys.cpp 已合并到 RTU makefile 中,此处仅管理子模块
|
# app_sys.cpp 已合并到 RTU makefile 中,此处仅管理子模块
|
||||||
SUBDIRS := ./libmodbus_m ./RTU
|
SUBDIRS := ./libmodbus_m ./libweb_server ./RTU
|
||||||
|
|
||||||
.PHONY: all clean rebuild
|
.PHONY: all clean rebuild
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
/* ============== 线程模块(system/ 子目录) ============== */
|
/* ============== 线程模块(system/ 子目录) ============== */
|
||||||
APP_MODULE(MODBUS_M, modbus_m, app_modbus_m_init1, app_modbus_m_init2, app_modbus_m)
|
APP_MODULE(MODBUS_M, modbus_m, app_modbus_m_init1, app_modbus_m_init2, app_modbus_m)
|
||||||
|
APP_MODULE(WEB_SERVER, web_server, app_web_server_init1, app_web_server_init2, app_web_server)
|
||||||
|
|
||||||
/* ============== 预留模块(阶段 2-3 实现后取消注释) ============== */
|
/* ============== 预留模块(阶段 2-3 实现后取消注释) ============== */
|
||||||
// APP_MODULE(COM_DECODE, com_decode, app_com_decode_init1, app_com_decode_init2, app_com_decode)
|
// APP_MODULE(COM_DECODE, com_decode, app_com_decode_init1, app_com_decode_init2, app_com_decode)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* @file web_server.h
|
||||||
|
* @brief Web 管理服务模块 — HTTP REST API + WebSocket 实时数据通道
|
||||||
|
* @details 基于 mongoose v7.22 实现嵌入式 Web 服务器。
|
||||||
|
* 作为 RTU X-Macro 框架的线程模块运行。
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* - HTTP 静态文件服务(packed_fs 内嵌前端)
|
||||||
|
* - WebSocket 信号实时推送(增量更新)
|
||||||
|
* - 5 种信号类型增删改查(out/in/yk/ao/param)
|
||||||
|
* - 远程命令执行(捕获 stdout 回传)
|
||||||
|
* - 文件上传/下载
|
||||||
|
* - 异步命令输出通道
|
||||||
|
*/
|
||||||
|
#ifndef _WEB_SERVER_H_
|
||||||
|
#define _WEB_SERVER_H_
|
||||||
|
|
||||||
|
#include "myBase.h"
|
||||||
|
#include "mySystem.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MODULE_WEB_SERVER "web_server"
|
||||||
|
|
||||||
|
/** WebSocket 信号类型 */
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
WS_SIG_OUT = 0,
|
||||||
|
WS_SIG_IN,
|
||||||
|
WS_SIG_YK,
|
||||||
|
WS_SIG_AO,
|
||||||
|
WS_SIG_PARAM,
|
||||||
|
WS_SIG_MAX
|
||||||
|
} enum_ws_sig_type;
|
||||||
|
|
||||||
|
struct mg_connection;
|
||||||
|
|
||||||
|
/* ---- X-Macro 框架入口 ---- */
|
||||||
|
int app_web_server_init1(void *arg);
|
||||||
|
int app_web_server_init2(void *arg);
|
||||||
|
void *app_web_server(void *arg);
|
||||||
|
|
||||||
|
/* ---- 广播/单播发送 ---- */
|
||||||
|
void ws_send_all(const char *p_tx, size_t tx_len);
|
||||||
|
bool ws_send_one(unsigned long conn_id, const char *p_tx, size_t tx_len);
|
||||||
|
bool ws_send_binary(unsigned long conn_id, const void *data, size_t len);
|
||||||
|
|
||||||
|
/* ---- 异步命令通道 ---- */
|
||||||
|
void ws_set_async_cmd_conn(const char *module, unsigned long conn_id);
|
||||||
|
unsigned long ws_get_async_cmd_conn(const char *module);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _WEB_SERVER_H_ */
|
||||||
|
|
@ -0,0 +1,435 @@
|
||||||
|
/**
|
||||||
|
* @file web_server.cpp
|
||||||
|
* @brief HTTP/WebSocket 服务器核心 — Mongoose 集成 + 连接管理
|
||||||
|
* @details 在 app_web_server 线程的 EV_TIMER1 中调用 mg_mgr_poll,
|
||||||
|
* 单线程复用 X-Macro 框架,消除原工程的双线程设计。
|
||||||
|
* EV_TIMER2: ws_push_task() 增量推送
|
||||||
|
* EV_TIMER3: run_cnt++
|
||||||
|
* EV_STOP: 优雅退出
|
||||||
|
*/
|
||||||
|
#include "web_server.h"
|
||||||
|
#include "myLog.h"
|
||||||
|
#include "myMongoose.h"
|
||||||
|
#include "cJSON.h"
|
||||||
|
#include "myFunc.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
|
/* ---- 硬编码配置 ---- */
|
||||||
|
#define WS_LISTEN_ON "http://0.0.0.0:8080"
|
||||||
|
#define WS_UPGRADE_URI "/ws"
|
||||||
|
|
||||||
|
/* ---- 全局状态 ---- */
|
||||||
|
static struct mg_mgr s_ws_mgr;
|
||||||
|
static std::vector<mg_connection *> g_ws_conns;
|
||||||
|
static pthread_mutex_t g_ws_conns_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
/* ---- 前向声明 ---- */
|
||||||
|
void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len);
|
||||||
|
void ws_session_destroy(struct mg_connection *c);
|
||||||
|
void ws_push_task(void);
|
||||||
|
|
||||||
|
/* ---- 广播发送 ---- */
|
||||||
|
void ws_send_all(const char *p_tx, size_t tx_len)
|
||||||
|
{
|
||||||
|
if (!p_tx || tx_len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < g_ws_conns.size(); i++)
|
||||||
|
{
|
||||||
|
mg_connection *c = g_ws_conns[i];
|
||||||
|
|
||||||
|
if (c->is_websocket && !c->is_draining)
|
||||||
|
{
|
||||||
|
mg_ws_send(c, p_tx, tx_len, WEBSOCKET_OP_TEXT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_ws_conns_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 单连接发送 ---- */
|
||||||
|
bool ws_send_one(unsigned long conn_id, const char *p_tx, size_t tx_len)
|
||||||
|
{
|
||||||
|
if (!p_tx || tx_len == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < g_ws_conns.size(); i++)
|
||||||
|
{
|
||||||
|
mg_connection *c = g_ws_conns[i];
|
||||||
|
|
||||||
|
if (c->id == conn_id && c->is_websocket && !c->is_draining)
|
||||||
|
{
|
||||||
|
size_t sent = mg_ws_send(c, p_tx, tx_len, WEBSOCKET_OP_TEXT);
|
||||||
|
ok = (sent >= tx_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 二进制发送 ---- */
|
||||||
|
bool ws_send_binary(unsigned long conn_id, const void *data, size_t len)
|
||||||
|
{
|
||||||
|
if (!data || len == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < g_ws_conns.size(); i++)
|
||||||
|
{
|
||||||
|
mg_connection *c = g_ws_conns[i];
|
||||||
|
|
||||||
|
if (c->id == conn_id && c->is_websocket && !c->is_draining)
|
||||||
|
{
|
||||||
|
size_t sent = mg_ws_send(c, data, len, WEBSOCKET_OP_BINARY);
|
||||||
|
ok = (sent >= len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 连接列表管理 ---- */
|
||||||
|
static void ws_conn_remove(mg_connection *c)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < g_ws_conns.size(); i++)
|
||||||
|
{
|
||||||
|
if (g_ws_conns[i] == c)
|
||||||
|
{
|
||||||
|
g_ws_conns.erase(g_ws_conns.begin() + i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_ws_conns_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Mongoose 事件处理器 ---- */
|
||||||
|
static void ws_event_handler(mg_connection *c, int ev, void *ev_data)
|
||||||
|
{
|
||||||
|
if (ev == MG_EV_HTTP_MSG)
|
||||||
|
{
|
||||||
|
mg_http_message *hm = (mg_http_message *)ev_data;
|
||||||
|
|
||||||
|
if (mg_match(hm->uri, mg_str(WS_UPGRADE_URI), NULL))
|
||||||
|
{
|
||||||
|
mg_ws_upgrade(c, hm, NULL);
|
||||||
|
c->data[0] = 'W';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* HTTP 静态文件: 默认返回 404 */
|
||||||
|
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ev == MG_EV_WS_MSG)
|
||||||
|
{
|
||||||
|
mg_ws_message *wm = (mg_ws_message *)ev_data;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
pthread_mutex_lock(&g_ws_conns_mutex);
|
||||||
|
for (size_t i = 0; i < g_ws_conns.size(); i++)
|
||||||
|
{
|
||||||
|
if (g_ws_conns[i] == c)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
g_ws_conns.push_back(c);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&g_ws_conns_mutex);
|
||||||
|
|
||||||
|
ws_recv(c, wm->data.buf, (uint16_t)wm->data.len);
|
||||||
|
}
|
||||||
|
else if (ev == MG_EV_WS_CTL)
|
||||||
|
{
|
||||||
|
mg_ws_message *wm = (mg_ws_message *)ev_data;
|
||||||
|
uint8_t op = wm->flags & 15;
|
||||||
|
|
||||||
|
if (op == WEBSOCKET_OP_CLOSE)
|
||||||
|
{
|
||||||
|
ws_session_destroy(c);
|
||||||
|
ws_conn_remove(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ev == MG_EV_CLOSE)
|
||||||
|
{
|
||||||
|
if (c->is_websocket)
|
||||||
|
{
|
||||||
|
ws_session_destroy(c);
|
||||||
|
ws_conn_remove(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 命令行执行(捕获 stdout → 字符串) ---- */
|
||||||
|
#include "myCmd.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
std::string ws_cmd_exec(const char *cmd_line)
|
||||||
|
{
|
||||||
|
if (!cmd_line)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
char line[512];
|
||||||
|
strncpy(line, cmd_line, sizeof(line) - 1);
|
||||||
|
line[sizeof(line) - 1] = '\0';
|
||||||
|
|
||||||
|
/* strtok 解析参数 */
|
||||||
|
char *argv[16];
|
||||||
|
int argc = 0;
|
||||||
|
char *p = strtok(line, " \t");
|
||||||
|
while (p && argc < 16)
|
||||||
|
{
|
||||||
|
argv[argc++] = p;
|
||||||
|
p = strtok(NULL, " \t");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_cmd *p_cmd = cmd_find(argv[0]);
|
||||||
|
if (!p_cmd)
|
||||||
|
{
|
||||||
|
return std::string("[unknown] ") + argv[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pipe 重定向 stdout */
|
||||||
|
fflush(stdout);
|
||||||
|
int pipefd[2];
|
||||||
|
|
||||||
|
if (pipe(pipefd) != 0)
|
||||||
|
{
|
||||||
|
return "pipe failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
auto *ctx = new std::pair<int, std::string *>(pipefd[0], &result);
|
||||||
|
|
||||||
|
pthread_t reader;
|
||||||
|
pthread_create(&reader, NULL,
|
||||||
|
[](void *arg) -> void *
|
||||||
|
{
|
||||||
|
auto *cx = (std::pair<int, std::string *> *)arg;
|
||||||
|
char buf[4096];
|
||||||
|
ssize_t n;
|
||||||
|
while ((n = read(cx->first, buf, sizeof(buf))) > 0)
|
||||||
|
{
|
||||||
|
cx->second->append(buf, n);
|
||||||
|
}
|
||||||
|
close(cx->first);
|
||||||
|
delete cx;
|
||||||
|
return NULL;
|
||||||
|
},
|
||||||
|
ctx);
|
||||||
|
|
||||||
|
int saved = dup(STDOUT_FILENO);
|
||||||
|
dup2(pipefd[1], STDOUT_FILENO);
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
p_cmd->func(argc, argv);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
dup2(saved, STDOUT_FILENO);
|
||||||
|
close(saved);
|
||||||
|
|
||||||
|
pthread_join(reader, NULL);
|
||||||
|
|
||||||
|
if (!result.empty())
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "(no output)";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 异步命令通道实现 ---- */
|
||||||
|
static std::map<std::string, unsigned long> g_async_cmd_conn_map;
|
||||||
|
static pthread_mutex_t g_async_cmd_conn_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
void ws_set_async_cmd_conn(const char *module, unsigned long conn_id)
|
||||||
|
{
|
||||||
|
if (!module)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_async_cmd_conn_mutex);
|
||||||
|
g_async_cmd_conn_map[module] = conn_id;
|
||||||
|
pthread_mutex_unlock(&g_async_cmd_conn_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long ws_get_async_cmd_conn(const char *module)
|
||||||
|
{
|
||||||
|
if (!module)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_async_cmd_conn_mutex);
|
||||||
|
auto it = g_async_cmd_conn_map.find(module);
|
||||||
|
unsigned long id = (it != g_async_cmd_conn_map.end()) ? it->second : 0;
|
||||||
|
pthread_mutex_unlock(&g_async_cmd_conn_mutex);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 异步命令输出(给其他模块调用) ---- */
|
||||||
|
void async_cmd_printf(const char *module, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
if (!module || !fmt)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long conn_id = ws_get_async_cmd_conn(module);
|
||||||
|
|
||||||
|
if (conn_id == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
va_list ap;
|
||||||
|
char buf[2048];
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
int n = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
ws_send_binary(conn_id, buf, (size_t)n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_cmd_complete(const char *module, bool success, const char *msg)
|
||||||
|
{
|
||||||
|
if (!module || !msg)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long conn_id = ws_get_async_cmd_conn(module);
|
||||||
|
|
||||||
|
if (conn_id == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "[%s] %s %s\n",
|
||||||
|
module, success ? "OK" : "FAIL", msg);
|
||||||
|
|
||||||
|
ws_send_binary(conn_id, buf, (size_t)n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* X-Macro 框架入口
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
int app_web_server_init1(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
mg_mgr_init(&s_ws_mgr);
|
||||||
|
mg_http_listen(&s_ws_mgr, WS_LISTEN_ON, ws_event_handler, NULL);
|
||||||
|
|
||||||
|
LOG_I("web_server listening on %s, ws=%s", WS_LISTEN_ON, WS_UPGRADE_URI);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int app_web_server_init2(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *app_web_server(void *arg)
|
||||||
|
{
|
||||||
|
if (!arg)
|
||||||
|
{
|
||||||
|
LOG_E("app_web_server arg null");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_app *p_app = (stru_app *)arg;
|
||||||
|
|
||||||
|
uint32_t event;
|
||||||
|
uint32_t cnt_1s = 0;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
task_event_recv(p_app->p_event,
|
||||||
|
EV_TIMER1 | EV_TIMER2 | EV_TIMER3,
|
||||||
|
TASK_EVENT_FLAG_OR | TASK_EVENT_FLAG_CLEAR,
|
||||||
|
TASK_EVENT_WAIT_FOREVER,
|
||||||
|
&event);
|
||||||
|
|
||||||
|
if (event & EV_STOP)
|
||||||
|
{
|
||||||
|
LOG_I("web_server stopping");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event & EV_TIMER1)
|
||||||
|
{
|
||||||
|
/* IO 轮询(非阻塞) */
|
||||||
|
mg_mgr_poll(&s_ws_mgr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event & EV_TIMER2)
|
||||||
|
{
|
||||||
|
/* 增量推送 */
|
||||||
|
ws_push_task();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event & EV_TIMER3)
|
||||||
|
{
|
||||||
|
cnt_1s++;
|
||||||
|
p_app->run_cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_mgr_free(&s_ws_mgr);
|
||||||
|
|
||||||
|
LOG_I("web_server stopped, total 1s cycles=%u", cnt_1s);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,671 @@
|
||||||
|
/**
|
||||||
|
* @file ws_method.cpp
|
||||||
|
* @brief WebSocket 命令解析与信号 CRUD 操作
|
||||||
|
* @details 接收 JSON 命令({type, saddr, signal_type, curd, signal_data}),
|
||||||
|
* 通过 g_ws_sig_methods 函数表分发到对应的信号增删改查函数。
|
||||||
|
* 支持 cmd/get_cmds/get_file/set_file 等管理命令。
|
||||||
|
*/
|
||||||
|
#include "web_server.h"
|
||||||
|
#include "myLog.h"
|
||||||
|
#include "myDatacenter.h"
|
||||||
|
#include "myCmd.h"
|
||||||
|
#include "myFunc.h"
|
||||||
|
#include "myMongoose.h"
|
||||||
|
#include "cJSON.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <cstring>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* ---- 前向声明(来自 web_server.cpp) ---- */
|
||||||
|
extern std::string ws_cmd_exec(const char *cmd_line);
|
||||||
|
|
||||||
|
/* ---- Per-Connection Session ---- */
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
std::string saddr;
|
||||||
|
std::string desc;
|
||||||
|
uint8_t data_type;
|
||||||
|
uint8_t ctrl_type;
|
||||||
|
std::vector<void *> vec_p_data;
|
||||||
|
std::string last_val;
|
||||||
|
} stru_ws_sig;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> signals[WS_SIG_MAX];
|
||||||
|
} stru_ws_session;
|
||||||
|
|
||||||
|
static std::map<unsigned long, stru_ws_session> g_ws_sessions;
|
||||||
|
static pthread_mutex_t g_ws_session_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
/* ---- 前向声明 ---- */
|
||||||
|
static int ws_make_sig_json(stru_ws_session *s, cJSON *root, enum_ws_sig_type st, bool *has_change);
|
||||||
|
static void ws_sig_add(stru_ws_session *s, enum_ws_sig_type st, const char *saddr);
|
||||||
|
static void ws_sig_del(stru_ws_session *s, enum_ws_sig_type st, const char *saddr);
|
||||||
|
static int ws_sig_set(stru_ws_session *s, enum_ws_sig_type st, const char *saddr,
|
||||||
|
uint8_t setting_zone, const char *val);
|
||||||
|
|
||||||
|
/* ---- 函数表: 信号类型 → 名称 ---- */
|
||||||
|
static const char *g_ws_sig_names[WS_SIG_MAX] =
|
||||||
|
{
|
||||||
|
"out", "in", "yk", "ao", "param"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* 内部工具
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static int ws_sig_find_idx(const std::vector<stru_ws_sig> &vec, const char *saddr)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < vec.size(); i++)
|
||||||
|
{
|
||||||
|
if (vec[i].saddr == saddr)
|
||||||
|
{
|
||||||
|
return (int)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string ws_get_val_str(const void *p_data, uint8_t data_type)
|
||||||
|
{
|
||||||
|
char buf[128] = {0};
|
||||||
|
dc_get_signal_val(p_data, data_type, buf, sizeof(buf));
|
||||||
|
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* 信号增删改 — out/in/yk/ao/param 分 5 种路径
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static void ws_sig_add_out(stru_ws_session *s, const char *saddr)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_OUT];
|
||||||
|
void *p_data = NULL;
|
||||||
|
|
||||||
|
if (ws_sig_find_idx(vec, saddr) >= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_ws_sig sig;
|
||||||
|
sig.saddr = saddr;
|
||||||
|
sig.data_type = 0;
|
||||||
|
|
||||||
|
char desc[128] = {0};
|
||||||
|
|
||||||
|
if (0 != dc_get_out_signal_info(saddr, desc, sizeof(desc), &sig.data_type, &p_data))
|
||||||
|
{
|
||||||
|
LOG_E("dc_get_out_signal_info failed: %s", saddr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.desc = desc;
|
||||||
|
sig.vec_p_data.push_back(p_data);
|
||||||
|
vec.push_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_sig_add_in(stru_ws_session *s, const char *saddr)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_IN];
|
||||||
|
void *p_data = NULL;
|
||||||
|
|
||||||
|
if (ws_sig_find_idx(vec, saddr) >= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_ws_sig sig;
|
||||||
|
sig.saddr = saddr;
|
||||||
|
|
||||||
|
char desc[128] = {0};
|
||||||
|
|
||||||
|
if (0 != dc_get_in_signal_info(saddr, desc, sizeof(desc), &sig.data_type, &p_data))
|
||||||
|
{
|
||||||
|
LOG_E("dc_get_in_signal_info failed: %s", saddr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.desc = desc;
|
||||||
|
sig.vec_p_data.push_back(p_data);
|
||||||
|
vec.push_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_sig_add_yk(stru_ws_session *s, const char *saddr)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_YK];
|
||||||
|
void *p_data = NULL;
|
||||||
|
|
||||||
|
if (ws_sig_find_idx(vec, saddr) >= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_ws_sig sig;
|
||||||
|
sig.saddr = saddr;
|
||||||
|
|
||||||
|
char desc[128] = {0};
|
||||||
|
|
||||||
|
if (0 != dc_get_yk_signal_info(saddr, desc, sizeof(desc), &sig.data_type, &sig.ctrl_type, &p_data))
|
||||||
|
{
|
||||||
|
LOG_E("dc_get_yk_signal_info failed: %s", saddr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.desc = desc;
|
||||||
|
sig.vec_p_data.push_back(p_data);
|
||||||
|
vec.push_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_sig_add_ao(stru_ws_session *s, const char *saddr)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_AO];
|
||||||
|
void *p_data = NULL;
|
||||||
|
|
||||||
|
if (ws_sig_find_idx(vec, saddr) >= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_ws_sig sig;
|
||||||
|
sig.saddr = saddr;
|
||||||
|
|
||||||
|
char desc[128] = {0};
|
||||||
|
|
||||||
|
if (0 != dc_get_ao_signal_info(saddr, desc, sizeof(desc), &sig.data_type, NULL, &sig.ctrl_type, &p_data))
|
||||||
|
{
|
||||||
|
LOG_E("dc_get_ao_signal_info failed: %s", saddr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.desc = desc;
|
||||||
|
sig.vec_p_data.push_back(p_data);
|
||||||
|
vec.push_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_sig_add_param(stru_ws_session *s, const char *saddr)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[WS_SIG_PARAM];
|
||||||
|
void *p_data_arr[16] = {NULL};
|
||||||
|
int num_zones = 0;
|
||||||
|
|
||||||
|
if (ws_sig_find_idx(vec, saddr) >= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stru_ws_sig sig;
|
||||||
|
sig.saddr = saddr;
|
||||||
|
|
||||||
|
char desc[128] = {0};
|
||||||
|
|
||||||
|
if (0 != dc_get_param_signal_info(saddr, desc, sizeof(desc), &sig.data_type, NULL, &sig.ctrl_type, p_data_arr, &num_zones))
|
||||||
|
{
|
||||||
|
LOG_E("dc_get_param_signal_info failed: %s", saddr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sig.desc = desc;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_zones; i++)
|
||||||
|
{
|
||||||
|
sig.vec_p_data.push_back(p_data_arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec.push_back(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_sig_add(stru_ws_session *s, enum_ws_sig_type st, const char *saddr)
|
||||||
|
{
|
||||||
|
switch (st)
|
||||||
|
{
|
||||||
|
case WS_SIG_OUT: ws_sig_add_out(s, saddr); break;
|
||||||
|
case WS_SIG_IN: ws_sig_add_in(s, saddr); break;
|
||||||
|
case WS_SIG_YK: ws_sig_add_yk(s, saddr); break;
|
||||||
|
case WS_SIG_AO: ws_sig_add_ao(s, saddr); break;
|
||||||
|
case WS_SIG_PARAM: ws_sig_add_param(s, saddr); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_sig_del(stru_ws_session *s, enum_ws_sig_type st, const char *saddr)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[st];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < vec.size(); i++)
|
||||||
|
{
|
||||||
|
if (vec[i].saddr == saddr)
|
||||||
|
{
|
||||||
|
vec.erase(vec.begin() + i);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_sig_set(stru_ws_session *s, enum_ws_sig_type st, const char *saddr,
|
||||||
|
uint8_t setting_zone, const char *val)
|
||||||
|
{
|
||||||
|
std::vector<stru_ws_sig> &vec = s->signals[st];
|
||||||
|
uint8_t data[128] = {0};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < vec.size(); i++)
|
||||||
|
{
|
||||||
|
if (vec[i].saddr != saddr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dc_set_val_from_str(data, vec[i].data_type, val);
|
||||||
|
|
||||||
|
switch (st)
|
||||||
|
{
|
||||||
|
case WS_SIG_OUT:
|
||||||
|
return dc_set_out_signal_val(saddr, data, MODULE_WEB_SERVER);
|
||||||
|
|
||||||
|
case WS_SIG_IN:
|
||||||
|
/* in 信号只读 */
|
||||||
|
LOG_E("set in signal not supported: %s", saddr);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
case WS_SIG_YK:
|
||||||
|
{
|
||||||
|
dc_ctrl_t ctrl;
|
||||||
|
ctrl.type = vec[i].ctrl_type;
|
||||||
|
ctrl.step = DC_STEP_READY;
|
||||||
|
ctrl.data_type = vec[i].data_type;
|
||||||
|
ctrl.p_data = dc_create_data(vec[i].data_type);
|
||||||
|
|
||||||
|
int rc = dc_signal_yk_set_status(saddr, DC_STEP_DIRECT, &ctrl, data, MODULE_WEB_SERVER);
|
||||||
|
dc_destroy_data(ctrl.p_data, vec[i].data_type);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_SIG_AO:
|
||||||
|
{
|
||||||
|
dc_ctrl_t ctrl;
|
||||||
|
ctrl.type = vec[i].ctrl_type;
|
||||||
|
ctrl.step = DC_STEP_READY;
|
||||||
|
ctrl.data_type = vec[i].data_type;
|
||||||
|
ctrl.p_data = dc_create_data(vec[i].data_type);
|
||||||
|
|
||||||
|
int rc = dc_signal_ao_set_val(saddr, DC_STEP_DIRECT, &ctrl, data, MODULE_WEB_SERVER);
|
||||||
|
dc_destroy_data(ctrl.p_data, vec[i].data_type);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WS_SIG_PARAM:
|
||||||
|
{
|
||||||
|
dc_ctrl_t ctrl;
|
||||||
|
ctrl.type = vec[i].ctrl_type;
|
||||||
|
ctrl.step = DC_STEP_READY;
|
||||||
|
ctrl.data_type = vec[i].data_type;
|
||||||
|
ctrl.p_data = dc_create_data(vec[i].data_type);
|
||||||
|
|
||||||
|
int rc = dc_signal_param_set_val(saddr, DC_STEP_DIRECT, &ctrl, setting_zone, data, MODULE_WEB_SERVER);
|
||||||
|
dc_destroy_data(ctrl.p_data, vec[i].data_type);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_E("signal not found in session: %s", saddr);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* JSON 构建 — 按信号类型有不同字段
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
static int ws_make_sig_json(stru_ws_session *s, cJSON *root, enum_ws_sig_type st, bool *has_change)
|
||||||
|
{
|
||||||
|
const std::vector<stru_ws_sig> &vec = s->signals[st];
|
||||||
|
|
||||||
|
if (vec.empty())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *arr = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToObject(root, g_ws_sig_names[st], arr);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < vec.size(); i++)
|
||||||
|
{
|
||||||
|
const stru_ws_sig *sig = &vec[i];
|
||||||
|
cJSON *item = cJSON_CreateObject();
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(item, "saddr", sig->saddr.c_str());
|
||||||
|
cJSON_AddStringToObject(item, "desc", sig->desc.c_str());
|
||||||
|
cJSON_AddStringToObject(item, "type", dc_get_type_name(sig->data_type));
|
||||||
|
|
||||||
|
/* yk/ao/param: 控制类型 */
|
||||||
|
if (st == WS_SIG_YK || st == WS_SIG_AO || st == WS_SIG_PARAM)
|
||||||
|
{
|
||||||
|
cJSON_AddNumberToObject(item, "ctrl_type", sig->ctrl_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* param: 多定值区 */
|
||||||
|
if (st == WS_SIG_PARAM)
|
||||||
|
{
|
||||||
|
cJSON *zone_arr = cJSON_CreateArray();
|
||||||
|
cJSON_AddItemToObject(item, "setting_zone_list", zone_arr);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < sig->vec_p_data.size(); j++)
|
||||||
|
{
|
||||||
|
cJSON *zone_item = cJSON_CreateObject();
|
||||||
|
|
||||||
|
std::string id = std::to_string(j);
|
||||||
|
std::string val = ws_get_val_str(sig->vec_p_data[j], sig->data_type);
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(zone_item, "id", id.c_str());
|
||||||
|
cJSON_AddStringToObject(zone_item, "val", val.c_str());
|
||||||
|
cJSON_AddItemToArray(zone_arr, zone_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 组合值用于变化检测 */
|
||||||
|
std::string combined;
|
||||||
|
for (size_t j = 0; j < sig->vec_p_data.size(); j++)
|
||||||
|
{
|
||||||
|
combined += ws_get_val_str(sig->vec_p_data[j], sig->data_type);
|
||||||
|
combined += ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combined != sig->last_val)
|
||||||
|
{
|
||||||
|
/* 注意: last_val 是 const 方法访问,这里需要 const_cast */
|
||||||
|
const_cast<stru_ws_sig *>(sig)->last_val = combined;
|
||||||
|
*has_change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* out/in/yk/ao: 单值 */
|
||||||
|
std::string val = ws_get_val_str(sig->vec_p_data[0], sig->data_type);
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(item, "val", val.c_str());
|
||||||
|
|
||||||
|
if (val != sig->last_val)
|
||||||
|
{
|
||||||
|
const_cast<stru_ws_sig *>(sig)->last_val = val;
|
||||||
|
*has_change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_AddItemToArray(arr, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* 增量推送引擎(在 EV_TIMER2 中调用)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void ws_push_task(void)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&g_ws_session_mutex);
|
||||||
|
|
||||||
|
for (auto &pair : g_ws_sessions)
|
||||||
|
{
|
||||||
|
unsigned long conn_id = pair.first;
|
||||||
|
stru_ws_session &sess = pair.second;
|
||||||
|
|
||||||
|
bool has_any = false;
|
||||||
|
for (int st = 0; st < WS_SIG_MAX; st++)
|
||||||
|
{
|
||||||
|
if (!sess.signals[st].empty())
|
||||||
|
{
|
||||||
|
has_any = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_any)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *root = cJSON_CreateObject();
|
||||||
|
|
||||||
|
bool has_change = false;
|
||||||
|
|
||||||
|
for (int st = 0; st < WS_SIG_MAX; st++)
|
||||||
|
{
|
||||||
|
ws_make_sig_json(&sess, root, (enum_ws_sig_type)st, &has_change);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_change)
|
||||||
|
{
|
||||||
|
cJSON_Delete(root);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *p_tx = cJSON_PrintUnformatted(root);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
if (p_tx)
|
||||||
|
{
|
||||||
|
ws_send_one(conn_id, p_tx, strlen(p_tx));
|
||||||
|
free(p_tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_ws_session_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* WebSocket JSON 命令分发
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void ws_recv(struct mg_connection *c, const char *p_rx, uint16_t rx_len)
|
||||||
|
{
|
||||||
|
if (!p_rx || rx_len == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *root = cJSON_Parse(p_rx);
|
||||||
|
|
||||||
|
if (!root)
|
||||||
|
{
|
||||||
|
LOG_E("ws_recv parse failed: %.200s", p_rx);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- type=cmd: 远程命令执行 ---- */
|
||||||
|
cJSON *jt = cJSON_GetObjectItem(root, "type");
|
||||||
|
|
||||||
|
if (jt && cJSON_IsString(jt) && jt->valuestring)
|
||||||
|
{
|
||||||
|
if (0 == strcmp(jt->valuestring, "cmd"))
|
||||||
|
{
|
||||||
|
cJSON *jd = cJSON_GetObjectItem(root, "data");
|
||||||
|
|
||||||
|
if (jd && cJSON_IsString(jd) && jd->valuestring)
|
||||||
|
{
|
||||||
|
/* 提取模块名存异步通道 */
|
||||||
|
char mod[64] = {0};
|
||||||
|
const char *pp = jd->valuestring;
|
||||||
|
int mi = 0;
|
||||||
|
|
||||||
|
while (*pp && *pp != ' ' && *pp != '\t' && mi < 63)
|
||||||
|
{
|
||||||
|
mod[mi++] = *pp++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_set_async_cmd_conn(mod, c->id);
|
||||||
|
|
||||||
|
std::string out = ws_cmd_exec(jd->valuestring);
|
||||||
|
ws_send_binary(c->id, out.data(), out.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- type=get_cmds: 获取命令列表 ---- */
|
||||||
|
if (0 == strcmp(jt->valuestring, "get_cmds"))
|
||||||
|
{
|
||||||
|
unsigned int count = 0;
|
||||||
|
stru_cmd *cmds = cmd_manager_get_commands(&count);
|
||||||
|
|
||||||
|
cJSON *resp = cJSON_CreateObject();
|
||||||
|
cJSON_AddStringToObject(resp, "type", "cmd_list");
|
||||||
|
cJSON *arr = cJSON_CreateArray();
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
cJSON *item = cJSON_CreateObject();
|
||||||
|
cJSON_AddStringToObject(item, "name", cmds[i].name);
|
||||||
|
cJSON_AddStringToObject(item, "desc", cmds[i].desc);
|
||||||
|
cJSON_AddItemToArray(arr, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_AddItemToObject(resp, "cmds", arr);
|
||||||
|
|
||||||
|
char *p_tx = cJSON_PrintUnformatted(resp);
|
||||||
|
|
||||||
|
if (p_tx)
|
||||||
|
{
|
||||||
|
ws_send_one(c->id, p_tx, strlen(p_tx));
|
||||||
|
free(p_tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(resp);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 信号 CRUD 命令 ---- */
|
||||||
|
cJSON *jsaddr = cJSON_GetObjectItem(root, "saddr");
|
||||||
|
cJSON *jsigtype = cJSON_GetObjectItem(root, "signal_type");
|
||||||
|
cJSON *jcurd = cJSON_GetObjectItem(root, "curd");
|
||||||
|
cJSON *jsigdata = cJSON_GetObjectItem(root, "signal_data");
|
||||||
|
cJSON *jzone = cJSON_GetObjectItem(root, "setting_zone");
|
||||||
|
|
||||||
|
if (!jsaddr || !cJSON_IsString(jsaddr) || !jsaddr->valuestring)
|
||||||
|
{
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jsigtype || !cJSON_IsString(jsigtype) || !jsigtype->valuestring)
|
||||||
|
{
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jcurd || !cJSON_IsString(jcurd) || !jcurd->valuestring)
|
||||||
|
{
|
||||||
|
cJSON_Delete(root);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 查找信号类型 */
|
||||||
|
enum_ws_sig_type st = WS_SIG_OUT;
|
||||||
|
|
||||||
|
for (int i = 0; i < WS_SIG_MAX; i++)
|
||||||
|
{
|
||||||
|
if (0 == strcmp(jsigtype->valuestring, g_ws_sig_names[i]))
|
||||||
|
{
|
||||||
|
st = (enum_ws_sig_type)i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *saddr = jsaddr->valuestring;
|
||||||
|
const char *curd = jcurd->valuestring;
|
||||||
|
const char *sig_data = jsigdata && cJSON_IsString(jsigdata) ? jsigdata->valuestring : "";
|
||||||
|
uint8_t zone = jzone && cJSON_IsNumber(jzone) ? (uint8_t)jzone->valueint : 0;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_ws_session_mutex);
|
||||||
|
|
||||||
|
stru_ws_session &sess = g_ws_sessions[c->id];
|
||||||
|
|
||||||
|
if (0 == strcmp(curd, "add"))
|
||||||
|
{
|
||||||
|
ws_sig_add(&sess, st, saddr);
|
||||||
|
|
||||||
|
/* 立即推送当前状态 */
|
||||||
|
cJSON *push = cJSON_CreateObject();
|
||||||
|
bool dummy = false;
|
||||||
|
|
||||||
|
ws_make_sig_json(&sess, push, st, &dummy);
|
||||||
|
|
||||||
|
char *p_tx = cJSON_PrintUnformatted(push);
|
||||||
|
|
||||||
|
if (p_tx)
|
||||||
|
{
|
||||||
|
ws_send_one(c->id, p_tx, strlen(p_tx));
|
||||||
|
free(p_tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(push);
|
||||||
|
}
|
||||||
|
else if (0 == strcmp(curd, "del"))
|
||||||
|
{
|
||||||
|
ws_sig_del(&sess, st, saddr);
|
||||||
|
|
||||||
|
cJSON *push = cJSON_CreateObject();
|
||||||
|
bool dummy = false;
|
||||||
|
|
||||||
|
ws_make_sig_json(&sess, push, st, &dummy);
|
||||||
|
|
||||||
|
char *p_tx = cJSON_PrintUnformatted(push);
|
||||||
|
|
||||||
|
if (p_tx)
|
||||||
|
{
|
||||||
|
ws_send_one(c->id, p_tx, strlen(p_tx));
|
||||||
|
free(p_tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_Delete(push);
|
||||||
|
}
|
||||||
|
else if (0 == strcmp(curd, "set"))
|
||||||
|
{
|
||||||
|
ws_sig_set(&sess, st, saddr, zone, sig_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&g_ws_session_mutex);
|
||||||
|
|
||||||
|
cJSON_Delete(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 会话销毁 ---- */
|
||||||
|
void ws_session_destroy(struct mg_connection *c)
|
||||||
|
{
|
||||||
|
if (!c)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&g_ws_session_mutex);
|
||||||
|
g_ws_sessions.erase(c->id);
|
||||||
|
pthread_mutex_unlock(&g_ws_session_mutex);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue