From 8de2441a796161a4c1ecbe6bd03b55e259c6b457 Mon Sep 17 00:00:00 2001 From: ypc <15051963820@163.com> Date: Thu, 18 Jun 2026 17:15:43 +0800 Subject: [PATCH] =?UTF-8?q?<=E4=BF=AE=E6=94=B9>=201=E3=80=81=E5=B0=86?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E7=BA=BF=E7=A8=8B=E6=A8=A1=E5=9D=97=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=96=B9=E5=BC=8F=E6=94=B9=E4=B8=BA=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E7=9A=84=EF=BC=8C=E9=80=9A=E8=BF=87=E5=A4=B4=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E8=BF=9B=E8=A1=8C=E7=BB=91=E5=AE=9A=EF=BC=8C=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=98=AF=E5=90=A6=E8=BF=90=E8=A1=8C=EF=BC=9B?= =?UTF-8?q?2=E3=80=81=E6=B6=88=E6=81=AF=E9=98=9F=E5=88=97=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E4=B9=9F=E5=90=8C=E6=A0=B7=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mimo/MEMORY.md | 26 +++- mimo/plan/app_thread_dynamic_config.md | 75 ++++++++++++ mimo/问题处理文档.md | 21 ++++ release/inc/app_modules.h | 23 ++++ release/inc/app_msg_queue.h | 20 ++++ release/inc/mySystem.h | 65 +++------- src/system/RTU/src/app_sys.cpp | 157 +++++++++++++++++++++---- test/config/SYSTEM/app_config.json | 13 ++ 8 files changed, 328 insertions(+), 72 deletions(-) create mode 100644 mimo/plan/app_thread_dynamic_config.md create mode 100644 release/inc/app_modules.h create mode 100644 release/inc/app_msg_queue.h create mode 100644 test/config/SYSTEM/app_config.json diff --git a/mimo/MEMORY.md b/mimo/MEMORY.md index 2f82ae9..09b54e1 100644 --- a/mimo/MEMORY.md +++ b/mimo/MEMORY.md @@ -69,7 +69,21 @@ _Hard constraints from user that every session must respect._ ## Architecture decisions _Major design choices with rationale. The "why" matters more than the "what" for future sessions._ -### 1. 数据中心信号管理 +### 1. 应用模块注册表 — X-Macro 架构(2026-06-18) +`release/inc/app_modules.h` 作为应用模块的**单一事实来源**,使用 X-Macro 模式自动生成枚举值、extern 声明、模块注册表。 + +**设计**: +- `APP_MODULE(ENUM后缀, 配置名, init1, init2, thread入口)` 格式 +- 第一个参数生成 `ENUM_APP_<后缀>` 枚举值(大写) +- 第二个参数生成 `"app_<配置名>"` 匹配配置文件 JSON 中的小写名称 +- `#include "app_modules.h"` 在三个不同位置展开,每次定义不同的 `APP_MODULE` 宏 + +**效果**: +- 新增模块 = 在 `app_modules.h` 加一行 + 配置文件加一行,无需改任何其他代码 +- 消除了 `mySystem.h` 中手写的 extern 声明(原 24 行 → 自动生成) +- 消除了 `app_sys.cpp` 中手写的 g_vec_app(原 8 组结构体 → 自动生成) + +### 2. 数据中心信号管理 `dc_signal.cpp` 使用 XXH128 哈希对信号快速索引(`signal_out`, `signal_in`, `signal_yk`, `signal_ao`, `signal_param` 五张表,每表独立互斥锁)。输出信号变更通过脏队列 + 去重 + 比对 + 回调实现增量推送(仅变更时发送),`last_caller_module` 防止同模块回调自循环。SBO(Select Before Operate)控制流程:SELECT 暂存值 → DIRECT 校验与 SELECT 值一致 → 写入信号。事件队列(扰动/SOE/故障)使用 swap 模式低锁竞争 pop。 ### 2. 参数配置双文件机制 @@ -98,6 +112,16 @@ _Cross-task facts that survive across sessions. Promoted from session checkpoint #### #1 RCB 订阅编号可配置化(2026-06-10) - **问题**: `mms_m_icd_report_init()` 硬编码只订阅编号 `"01"` 的 RCB - **修复**: 新增 `mms_m_out_set_rcb_numbers()` API,支持逗号分隔的多编号和通配符 `"*"` + +### #2 X-Macro 应用模块注册表(2026-06-18) +- **问题**: 新增线程模块需要改 3 处(mySystem.h 枚举+extern、app_sys.cpp g_vec_app),且名称大小写不一致 +- **方案**: 创建 `app_modules.h` 作为单一事实来源,X-Macro 自动展开 +- **关键约束**: `app_modules.h` **不能有 `#pragma once` 或 include guard**,需被多次包含 +- **cfg_name 参数**: 枚举后缀(大写)用 `ENUM_APP_##name`,配置名(小写)用 `"app_" #cfg_name`,通过两参数分离解决大小写匹配问题 +- **文件**: + - `release/inc/app_modules.h` — 模块注册表(8 个 APP_MODULE 条目) + - `release/inc/mySystem.h` — 枚举 + extern 自动生成 + - `src/system/RTU/src/app_sys.cpp` — g_vec_app 自动生成 - **文件**: `myMms_m.h`, `mms_m.h`, `mms_m.cpp`, `iec61850m.cpp` #### #2 libweb_server 多连接支持(2026-06-10) diff --git a/mimo/plan/app_thread_dynamic_config.md b/mimo/plan/app_thread_dynamic_config.md new file mode 100644 index 0000000..ce3b220 --- /dev/null +++ b/mimo/plan/app_thread_dynamic_config.md @@ -0,0 +1,75 @@ +# 应用线程动态配置方案 + +## 背景 + +原项目中 8 个应用线程(app_sys/app_cmd/app_com_decode/app_iec/app_self_ptl/app_web_server/app_iec61850m/app_iec61850s)的启动顺序和启用在 `app_sys.cpp` 中硬编码。修改线程启停需要重新编译。 + +## 目标 + +通过 JSON 配置文件控制线程启停,运行时动态决定启动哪些线程。 + +## 方案设计 + +### 核心原则 + +- **保留完整 `g_vec_app` 向量和 `ENUM_APP_*` 枚举**:所有索引查找(`app_get_ptr(ENUM_APP_XXX)`)不受影响 +- **仅 `pthread_create` 受配置控制**:init1/init2、事件对象、定时器为所有 app 创建(确保无空指针崩溃) +- **禁用线程的事件/定时器照常创建**:事件发往无消费者的队列时无害累积 + +### 配置文件 + +`test/config/SYSTEM/app_config.json` + +```json +{ + "apps": [ + {"name": "app_sys", "enable": true}, + {"name": "app_cmd", "enable": true}, + ... + ] +} +``` + +- 格式:JSON 数组,使用 cJSON 库解析(项目中已有) +- 路径:`/config/SYSTEM/app_config.json` + +### 实现改动 + +| 文件 | 改动 | +|------|------| +| `src/system/RTU/src/app_sys.cpp` | 新增 `app_config_load()` 解析 JSON → `g_enabled_apps` set;`app_init()` 第三循环检查 set 再创建线程;`app_sys_init()` 首行调用配置加载 | +| `test/config/SYSTEM/app_config.json` | 新建,含全部 8 个 app 及其 enable 标记 | + +### 关键代码 + +```cpp +// 配置加载 — 读取 JSON → g_enabled_apps set +LOCAL int app_config_load() { ... cJSON解析 ... } + +// app_init() 第三循环 — 仅启用的 app 创建线程 +for(i = 0; i < g_vec_app.size(); i++) +{ + // ... 创建事件、定时器(所有app)... + + if(g_enabled_apps.find(p->name) == g_enabled_apps.end()) + { + MY_LOG_I("app skipped (disabled by config): %s", p->name); + continue; // 跳过 pthread_create + } + + pthread_create(&p->thread, NULL, p->fun_cb, p->arg); +} +``` + +### 使用方式 + +1. 编辑 `test/config/SYSTEM/app_config.json` +2. 将目标 app 的 `enable` 设为 `false` +3. 重启 RTU 即可生效 + +### 注意事项 + +- `app_sys` 是核心系统线程(数据中心),**不应禁用** +- 禁用某个线程后,依赖它的模块发送的事件将无人处理(无害堆积) +- 禁用 `app_web_server` 将无法通过浏览器访问 +- 禁用 `app_cmd` 将无法通过 SSH 终端交互 diff --git a/mimo/问题处理文档.md b/mimo/问题处理文档.md index c266e38..32b04d9 100644 --- a/mimo/问题处理文档.md +++ b/mimo/问题处理文档.md @@ -2,6 +2,27 @@ --- +## 2026-06-18: X-Macro 应用模块注册表 — 名称大小写匹配修复 + +**问题**:X-Macro 生成 g_vec_app 名称时使用 `"app_" #name`(如 `app_SYS`),但配置文件 `app_config.json` 使用小写 `app_sys`,导致名称不匹配,所有线程被 "skipped (disabled by config)"。 + +**根因**:C 预处理器 `#name` 运算符产生宏参数的原始文本,`APP_MODULE(SYS, ...)` → `"app_SYS"`,无法自动转小写。 + +**修复**:增加第二个参数 `cfg_name`(小写),与 `name`(大写,枚举后缀)分离: +```c +// 修改前: APP_MODULE(SYS, init1, init2, func) +// 修改后: APP_MODULE(SYS, sys, init1, init2, func) +``` +- `#cfg_name` → `"app_sys"`(匹配配置文件) + +**涉及文件**:`app_modules.h`、`mySystem.h`(两处)、`app_sys.cpp`(一处) + +**注意事项**:旧进程残留占端口会导致 `mms_s_init` 绑定失败。app_modules.h 禁止加 `#pragma once`。 + +**验证**:编译通过,8 个线程全部 `pthread_create success`。 + +--- + ## 2026-06-16: ICP67 模块多实例重构 **来源**:[libicp67模块分析](./工程/libicp67模块分析.md) diff --git a/release/inc/app_modules.h b/release/inc/app_modules.h new file mode 100644 index 0000000..1519aae --- /dev/null +++ b/release/inc/app_modules.h @@ -0,0 +1,23 @@ +// ============================================================ +// 应用模块注册表 — X-Macro 中心化列表 +// +// 注意:此文件故意不设 #pragma once 或 include guard, +// 因为需要被多次包含(每次在不同的 APP_MODULE 宏定义下展开) +// ============================================================ + +#ifndef APP_MODULE +#error "APP_MODULE macro must be defined before including app_modules.h" +#endif + +// 格式: APP_MODULE(ENUM后缀, 配置名, init1, init2, thread入口) +// - ENUM后缀: 用于生成 ENUM_APP_<后缀> 枚举值(大写) +// - 配置名: 用于生成 "app_<配置名>" 匹配配置文件(小写) +// - init1/init2/thread: 模块初始化函数和线程入口 +APP_MODULE(SYS, sys, app_sys_init1, app_sys_init2, app_sys ) +APP_MODULE(CMD, cmd, app_cmd_init1, app_cmd_init2, app_cmd ) +APP_MODULE(WEB_SERVER, web_server, app_web_server_init1, app_web_server_init2, app_web_server) +APP_MODULE(COM_DECODE, com_decode, app_com_decode_init1, app_com_decode_init2, app_com_decode) +APP_MODULE(IEC, iec, app_iec_init1, app_iec_init2, app_iec ) +APP_MODULE(SELF_PTL, self_ptl, app_self_ptl_init1, app_self_ptl_init2, app_self_ptl ) +APP_MODULE(IEC61850M, iec61850m, app_iec61850m_init1, app_iec61850m_init2, app_iec61850m ) +APP_MODULE(IEC61850S, iec61850s, app_iec61850s_init1, app_iec61850s_init2, app_iec61850s ) diff --git a/release/inc/app_msg_queue.h b/release/inc/app_msg_queue.h new file mode 100644 index 0000000..28b4af4 --- /dev/null +++ b/release/inc/app_msg_queue.h @@ -0,0 +1,20 @@ +// ============================================================ +// 消息队列注册表 — X-Macro 中心化列表 +// +// 注意:此文件故意不设 #pragma once 或 include guard, +// 因为需要被多次包含(每次在不同的 APP_MQ 宏定义下展开) +// ============================================================ + +#ifndef APP_MQ +#error "APP_MQ macro must be defined before including app_msg_queue.h" +#endif + +// 格式: APP_MQ(ENUM后缀, 配置名, msg_size, msg_num) +// - ENUM后缀: 用于生成 ENUM_MQ_<后缀> 枚举值(大写) +// - 配置名: 用于生成 "mq_<配置名>" 名称字符串(小写) +// - msg_size: 消息大小(字节),应与 RTX_BUF_SIZE 匹配 +// - msg_num: 队列深度(消息个数) +APP_MQ(COM_TO_IEC, com_to_iec, 2048, 8) +APP_MQ(IEC_TO_COM, iec_to_com, 2048, 8) +APP_MQ(COM_TO_SELF_PTL, com_to_self_ptl, 2048, 8) +APP_MQ(SELF_PTL_TO_COM, self_ptl_to_com, 2048, 8) diff --git a/release/inc/mySystem.h b/release/inc/mySystem.h index ff56ceb..5b0b3ae 100644 --- a/release/inc/mySystem.h +++ b/release/inc/mySystem.h @@ -8,7 +8,6 @@ #define MSG_SIZE 2048 // 消息队列消息大小 -#define MSG_NUM 8 // 消息队列消息个数 #define TIMER_NUM 3 // 定时器个数 @@ -38,32 +37,24 @@ // datacenter线程事件 -// 消息队列枚举 +// 消息队列枚举 — 由 app_msg_queue.h X-macro 自动生成 +#define APP_MQ(name, cfg_name, msg_size, msg_num) ENUM_MQ_##name, typedef enum { - ENUM_MQ_COM_TO_IEC, // 通讯线程到iec线程消息队列 - ENUM_MQ_IEC_TO_COM, // iec线程到通讯线程消息队列 - ENUM_MQ_COM_TO_SELF_PTL, // 通讯线程到icp67线程消息队列 - ENUM_MQ_SELF_PTL_TO_COM, // icp67线程到通讯线程消息队列 - +#include "app_msg_queue.h" ENUM_MQ_MAX }enun_msg_queue; +#undef APP_MQ -// 应用枚举 +// 应用枚举 — 由 app_modules.h X-macro 自动生成 +#define APP_MODULE(name, cfg_name, init1, init2, func) ENUM_APP_##name, typedef enum { - ENUM_APP_SYS, // 系统应用 - ENUM_APP_CMD, // 命令行应用 - ENUM_APP_COM_DECODE, // 通讯编解码应用 - ENUM_APP_IEC, // iec101104应用 - ENUM_APP_SELF_PTL, // icp67应用 - ENUM_APP_WEB_SERVER, // web服务器应用 - ENUM_APP_IEC61850M, // iec61850m应用 - ENUM_APP_IEC61850S, // iec61850s应用 - +#include "app_modules.h" ENUM_APP_MAX }enum_app; +#undef APP_MODULE // 通讯枚举 @@ -185,40 +176,14 @@ stru_app_msg_queue *app_get_msg_queue_ptr(uint32_t mq_id); // 应用指针获取函数,根据应用ID获取应用指针 stru_app *app_get_ptr(uint32_t app_id); -// 命令行应用初始化与线程函数声明 -extern int app_cmd_init1(void *arg); -extern int app_cmd_init2(void *arg); -extern void *app_cmd(void *arg); +// 应用初始化与线程函数声明 — 由 app_modules.h X-macro 自动生成 +#define APP_MODULE(name, cfg_name, init1, init2, func) \ + extern int init1(void *arg); \ + extern int init2(void *arg); \ + extern void *func(void *arg); -// 通讯编解码应用初始化与线程函数声明 -extern int app_com_decode_init1(void *arg); -extern int app_com_decode_init2(void *arg); -extern void *app_com_decode(void *arg); - -// iec101104应用初始化与线程函数声明 -extern int app_iec_init1(void *arg); -extern int app_iec_init2(void *arg); -extern void *app_iec(void *arg); - -// icp67应用初始化与线程函数声明 -extern int app_self_ptl_init1(void *arg); -extern int app_self_ptl_init2(void *arg); -extern void *app_self_ptl(void *arg); - -// web服务器应用初始化与线程函数声明 -extern int app_web_server_init1(void *arg); -extern int app_web_server_init2(void *arg); -extern void *app_web_server(void *arg); - -// iec61850m应用初始化与线程函数声明 -extern int app_iec61850m_init1(void *arg); -extern int app_iec61850m_init2(void *arg); -extern void *app_iec61850m(void *arg); - -// iec61850s应用初始化与线程函数声明 -extern int app_iec61850s_init1(void *arg); -extern int app_iec61850s_init2(void *arg); -extern void *app_iec61850s(void *arg); +#include "app_modules.h" +#undef APP_MODULE diff --git a/src/system/RTU/src/app_sys.cpp b/src/system/RTU/src/app_sys.cpp index a2fb3c2..b90c2a2 100644 --- a/src/system/RTU/src/app_sys.cpp +++ b/src/system/RTU/src/app_sys.cpp @@ -1,38 +1,140 @@ #include "app_sys.h" #include "mySystem.h" +#include "myFunc.h" #include "myLog.h" #include "myDatacenter.h" - +#include "cJSON.h" #include #include +#include -LOCAL int app_sys_init1(void *arg); -LOCAL int app_sys_init2(void *arg); -LOCAL void *app_sys(void *arg); - +LOCAL std::set g_enabled_apps; // 配置中启用的应用名集合 + +LOCAL std::string get_system_config_path() +{ + char proc_dir[512] = {0}; + if(0 != func_get_process_self_dir(proc_dir, sizeof(proc_dir))) + { + MY_LOG_E("func_get_process_self_dir failed"); + return ""; + } + return std::string(proc_dir) + "config/SYSTEM/"; +} + +LOCAL int app_config_load() +{ + std::string path = get_system_config_path(); + if(path.empty()) + { + MY_LOG_E("get_system_config_path failed"); + return -1; + } + + std::string cfg_path = path + "app_config.json"; + FILE *fp = fopen(cfg_path.c_str(), "rb"); + if(NULL == fp) + { + MY_LOG_E("cannot open app config: %s", cfg_path.c_str()); + return -1; + } + + fseek(fp, 0, SEEK_END); + long fsize = ftell(fp); + fseek(fp, 0, SEEK_SET); + if(fsize <= 0) + { + fclose(fp); + MY_LOG_E("app config file empty: %s", cfg_path.c_str()); + return -1; + } + + char *buf = (char *)malloc((size_t)fsize + 1); + if(NULL == buf) + { + fclose(fp); + MY_LOG_E("malloc failed for app config"); + return -1; + } + size_t nread = fread(buf, 1, (size_t)fsize, fp); + buf[nread] = '\0'; + fclose(fp); + + cJSON *root = cJSON_Parse(buf); + free(buf); + if(NULL == root) + { + MY_LOG_E("cJSON_Parse failed for app config"); + return -1; + } + + cJSON *apps = cJSON_GetObjectItem(root, "apps"); + if(NULL == apps || !cJSON_IsArray(apps)) + { + MY_LOG_E("app config missing 'apps' array"); + cJSON_Delete(root); + return -1; + } + + int arr_size = cJSON_GetArraySize(apps); + for(int i = 0; i < arr_size; i++) + { + cJSON *item = cJSON_GetArrayItem(apps, i); + if(NULL == item) continue; + + cJSON *name = cJSON_GetObjectItem(item, "name"); + cJSON *enable = cJSON_GetObjectItem(item, "enable"); + if(NULL == name || !cJSON_IsString(name)) + { + continue; + } + + if(NULL != enable && cJSON_IsTrue(enable)) + { + g_enabled_apps.insert(std::string(name->valuestring)); + MY_LOG_I("app enabled: %s", name->valuestring); + } + else + { + MY_LOG_I("app disabled: %s", name->valuestring); + } + } + + cJSON_Delete(root); + + if(g_enabled_apps.empty()) + { + MY_LOG_E("no apps enabled in config, at least app_sys must be enabled"); + return -1; + } + + return 0; +} +int app_sys_init1(void *arg); +int app_sys_init2(void *arg); +void *app_sys(void *arg); LOCAL std::vector g_vec_app_msg_queue = { - {"mq_com_to_iec", MSG_SIZE, MSG_NUM, NULL}, - {"mq_iec_to_com", MSG_SIZE, MSG_NUM, NULL}, - {"mq_com_to_self_ptl", MSG_SIZE, MSG_NUM, NULL}, - {"mq_self_ptl_to_com", MSG_SIZE, MSG_NUM, NULL}, +// 消息队列注册表 — 由 app_msg_queue.h X-macro 自动生成 +#define APP_MQ(name, cfg_name, msg_size, msg_num) \ + {"mq_" #cfg_name, msg_size, msg_num, NULL}, + +#include "app_msg_queue.h" +#undef APP_MQ }; LOCAL std::vector g_vec_app = { - {"app_sys", 0, app_sys_init1, app_sys_init2, (app_func_cb)app_sys, NULL, 0, NULL}, - {"app_cmd", 0, app_cmd_init1, app_cmd_init2, (app_func_cb)app_cmd, NULL, 0, NULL}, - {"app_com_decode", 0, app_com_decode_init1, app_com_decode_init2, (app_func_cb)app_com_decode, NULL, 0, NULL}, - {"app_iec", 0, app_iec_init1, app_iec_init2, (app_func_cb)app_iec, NULL, 0, NULL}, - {"app_self_ptl", 0, app_self_ptl_init1, app_self_ptl_init2, (app_func_cb)app_self_ptl, NULL, 0, NULL}, - {"app_web_server", 0, app_web_server_init1, app_web_server_init2, (app_func_cb)app_web_server, NULL, 0, NULL}, - {"app_iec61850m", 0, app_iec61850m_init1, app_iec61850m_init2, (app_func_cb)app_iec61850m, NULL, 0, NULL}, - {"app_iec61850s", 0, app_iec61850s_init1, app_iec61850s_init2, (app_func_cb)app_iec61850s, NULL, 0, NULL}, +// 应用模块注册表 — 由 app_modules.h X-macro 自动生成 +#define APP_MODULE(name, cfg_name, init1, init2, func) \ + {"app_" #cfg_name, 0, init1, init2, (app_func_cb)func, NULL, 0, NULL}, + +#include "app_modules.h" +#undef APP_MODULE }; @@ -161,7 +263,13 @@ LOCAL int app_init() MY_LOG_E("app_timer_create failed, name:%s", p->name); return -1; } - + + if(g_enabled_apps.find(p->name) == g_enabled_apps.end()) + { + MY_LOG_I("app skipped (disabled by config): %s", p->name); + continue; + } + if(0 != pthread_create(&p->thread, NULL, p->fun_cb, p->arg)) { MY_LOG_E("pthread_create failed, name:%s", p->name); @@ -182,6 +290,13 @@ LOCAL int app_init() int app_sys_init(void) { uint32_t size = 0; + + if(0 != app_config_load()) + { + MY_LOG_E("app_config_load failed"); + return -1; + } + if((size = g_vec_app_msg_queue.size()) != ENUM_MQ_MAX) { MY_LOG_E("g_vec_app_msg_queue size error, size = %d, ENUM num %d", size, ENUM_MQ_MAX); @@ -235,7 +350,7 @@ stru_app *app_get_ptr(uint32_t app_id) -LOCAL int app_sys_init1(void *arg) +int app_sys_init1(void *arg) { if(0 != datacenter_init()) { @@ -245,12 +360,12 @@ LOCAL int app_sys_init1(void *arg) return 0; } -LOCAL int app_sys_init2(void *arg) +int app_sys_init2(void *arg) { return 0; } -LOCAL void *app_sys(void *arg) +void *app_sys(void *arg) { if(NULL == arg) { diff --git a/test/config/SYSTEM/app_config.json b/test/config/SYSTEM/app_config.json new file mode 100644 index 0000000..bea60aa --- /dev/null +++ b/test/config/SYSTEM/app_config.json @@ -0,0 +1,13 @@ +{ + "comment": "应用线程启动配置 — 修改 enable 字段控制线程是否启动", + "apps": [ + {"name": "app_sys", "enable": true, "comment": "系统应用(数据中心,必须启用)"}, + {"name": "app_cmd", "enable": true, "comment": "命令行应用(SSH终端交互)"}, + {"name": "app_web_server", "enable": true, "comment": "Web 服务器(HTTP/WS,端口8000)"}, + {"name": "app_com_decode", "enable": true, "comment": "通讯编解码应用(串口/TCP收发)"}, + {"name": "app_iec", "enable": true, "comment": "IEC 101/104 协议应用"}, + {"name": "app_self_ptl", "enable": true, "comment": "ICP67 私有规约应用"}, + {"name": "app_iec61850m", "enable": true, "comment": "IEC 61850 MMS 客户端"}, + {"name": "app_iec61850s", "enable": true, "comment": "IEC 61850 MMS 服务端"} + ] +}