From a339ed73a0197325f298d1e55afc6510ed14026d Mon Sep 17 00:00:00 2001 From: jgp <123456@163.com> Date: Fri, 3 Jul 2026 10:51:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(libplc):=20=E9=9D=9E=E9=98=BB=E5=A1=9E?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E5=99=A8=E9=87=8D=E6=9E=84=E4=B8=8E=E5=81=A5?= =?UTF-8?q?=E5=A3=AE=E6=80=A7=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用 clock_gettime 非阻塞定时器替换 usleep 延时(62p/62d门) - 新增 LogicNode 定时器字段:timer_active/timer_start/timer_delay_ms/timer_pending_output - 新增全局 PLC 状态缓存,支持配置文件变更检测(stat+mtime) - 新增拓扑排序入度字段 indegree,Kahn算法实现拓扑执行 - 全面改进错误日志:fprintf(stderr) -> MY_LOG_E 统一日志 - 增强解析容错:节点/链路解析失败时记录详细错误信息 - 空文件、格式错误、无有效逻辑图等异常场景完善处理 - 新增 get_elapsed_ms() / plc_set_config_path() / plc_get_config_path() - app_plc_init1 动态路径初始化(兼容 func_get_process_self_dir) --- src/system/libplc/src/plc.cpp | 276 +++++++++++++++++++++++++++++----- 1 file changed, 241 insertions(+), 35 deletions(-) diff --git a/src/system/libplc/src/plc.cpp b/src/system/libplc/src/plc.cpp index 9c73fd1..da56f8e 100644 --- a/src/system/libplc/src/plc.cpp +++ b/src/system/libplc/src/plc.cpp @@ -8,8 +8,8 @@ #include #include #include -#include // ANSI编码支持 -#include // usleep函数实现延时 +#include // clock_gettime 非阻塞定时器 +#include // stat() 文件变化检测 #define MAX_OUT_NODES 20 // 最大输出节点数 @@ -68,6 +68,13 @@ typedef struct { int y; int value; int last_input; + // 非阻塞延时定时器字段(62p/62d门使用) + uint8_t timer_active; // 1=定时器计时中 + struct timespec timer_start; // 定时器启动时刻 + int timer_delay_ms; // 延时毫秒数 + int timer_pending_output; // 定时器到期后的输出值,-1=无待定输出 + // 拓扑排序字段 + int indegree; // 入度(依赖的前驱节点数) } LogicNode; typedef struct { @@ -86,6 +93,12 @@ typedef struct { int link_count; } LogicGraph; +// ==================== 全局PLC状态 ==================== +LOCAL LogicGraph g_cached_graphs[MAX_LOGIC_GRAPHS]; // 缓存的逻辑图 +LOCAL int g_cached_graph_count = 0; // 缓存的逻辑图数量 +LOCAL time_t g_last_file_mtime = 0; // 配置文件最后修改时间 +LOCAL char g_plc_config_path[256] = "/mnt/RTU/config/PLC/Reclose_logic.txt"; // 可配置路径 + // ==================== 硬件点操作函数 ==================== static HWPoint* create_hw_point(int id, int value) { HWPoint *point = (HWPoint*)malloc(sizeof(HWPoint)); @@ -97,6 +110,15 @@ static HWPoint* create_hw_point(int id, int value) { return point; } +// 计算从 start 到当前时刻经过的毫秒数 +static long get_elapsed_ms(struct timespec *start) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + long sec_diff = now.tv_sec - start->tv_sec; + long nsec_diff = now.tv_nsec - start->tv_nsec; + return sec_diff * 1000 + nsec_diff / 1000000; +} + static void destroy_hw_list(HWPoint *head) { HWPoint *tmp; while (head) { @@ -208,13 +230,18 @@ static LogicNode* find_node(LogicGraph *graph, int type, int id) { int parse_reclose_logic(const char *file_path, LogicGraph graphs[], int *graph_count) { FILE *fp = fopen(file_path, "rb"); if (!fp) { - fprintf(stderr, "错误:无法打开文件 %s\n", file_path); + MY_LOG_E("无法打开PLC配置文件: %s", file_path); return -1; } *graph_count = 0; char buffer[4096]; size_t bytes_read = fread(buffer, 1, sizeof(buffer) - 1, fp); + if (bytes_read <= 0) { + MY_LOG_E("PLC配置文件为空或读取失败: %s", file_path); + fclose(fp); + return -1; + } buffer[bytes_read] = '\0'; fclose(fp); @@ -229,6 +256,7 @@ int parse_reclose_logic(const char *file_path, LogicGraph graphs[], int *graph_c *write_ptr = '\0'; char *ptr = buffer; + int graph_index = 0; while (ptr && *graph_count < MAX_LOGIC_GRAPHS) { while (*ptr && *ptr != '"') ptr++; if (!*ptr) break; @@ -259,6 +287,7 @@ int parse_reclose_logic(const char *file_path, LogicGraph graphs[], int *graph_c graph_data[data_len] = '\0'; ptr++; + graph_index++; if (strstr(graph_data, "node=") && strstr(graph_data, "link=")) { LogicGraph *cur_graph = &graphs[*graph_count]; memset(cur_graph, 0, sizeof(LogicGraph)); @@ -301,17 +330,25 @@ int parse_reclose_logic(const char *file_path, LogicGraph graphs[], int *graph_c node_start++; *node_end = '\0'; char *token = strtok(node_start, "{},"); + int node_index = 0; while (token && cur_graph->node_count < MAX_NODES_PER_GRAPH) { + node_index++; if (strlen(token) > 0) { LogicNode node; + memset(&node, 0, sizeof(node)); + node.timer_pending_output = -1; // 初始无待定输出 if (sscanf(token, "%d:%x:%d:%d", &node.type, &node.id, &node.x, &node.y) == 4) { - node.value = 0; cur_graph->nodes[cur_graph->node_count++] = node; + } else { + MY_LOG_E("逻辑图[%s]节点解析失败,第%d个节点数据: %s", + key, node_index, token); } } token = strtok(NULL, "{},"); } + } else { + MY_LOG_E("逻辑图[%s]的node参数格式错误,缺少'['或']'", key); } } else if (strcmp(param_key, "link") == 0) { char *link_start = strchr(value, '['); @@ -320,25 +357,40 @@ int parse_reclose_logic(const char *file_path, LogicGraph graphs[], int *graph_c link_start++; *link_end = '\0'; char *token = strtok(link_start, "{},"); + int link_index = 0; while (token && cur_graph->link_count < MAX_LINKS_PER_GRAPH) { + link_index++; if (strlen(token) > 0) { LogicLink link; if (sscanf(token, "%d:%x-%d:%x", &link.src_type, &link.src_id, &link.dest_type, &link.dest_id) == 4) { cur_graph->links[cur_graph->link_count++] = link; + } else { + MY_LOG_E("逻辑图[%s]链路解析失败,第%d条链路数据: %s", + key, link_index, token); } } token = strtok(NULL, "{},"); } + } else { + MY_LOG_E("逻辑图[%s]的link参数格式错误,缺少'['或']'", key); } } data_ptr = value_end; if (*data_ptr == '&') data_ptr++; } (*graph_count)++; + } else { + MY_LOG_E("逻辑图[%d] (key=%s) 缺少 node= 或 link= 参数,已跳过", + graph_index, key); } } + + if (*graph_count == 0) { + MY_LOG_E("PLC配置文件解析失败:未找到任何有效的逻辑图"); + return -1; + } return 0; } @@ -395,14 +447,29 @@ static void execute_62p_gate(LogicNode *node, int input_value) { int tp = (node->id >> 8) & 0xFFFFFF; int component_id = node->id & 0xFF; if (input_value == 1) { - printf(" 62p延时元件 (ID=%d, 序号=%d):输入=1,开始延时%d毫秒\n", - node->id, component_id, tp); - usleep(tp * 1000); - node->value = 1; - printf(" 62p延时元件 (ID=%d, 序号=%d):延时结束,输出=1\n", - node->id, component_id); + if (!node->timer_active) { + // 启动延时定时器,延时 tp 毫秒后输出 1 + printf(" 62p延时元件 (ID=%d, 序号=%d):输入=1,启动非阻塞延时%d毫秒\n", + node->id, component_id, tp); + clock_gettime(CLOCK_MONOTONIC, &node->timer_start); + node->timer_delay_ms = tp; + node->timer_pending_output = 1; + node->timer_active = 1; + // 保持当前输出值不变,等待定时器到期 + } else if (get_elapsed_ms(&node->timer_start) >= node->timer_delay_ms) { + // 定时器到期,输出 1 + node->value = 1; + node->timer_active = 0; + node->timer_pending_output = -1; + printf(" 62p延时元件 (ID=%d, 序号=%d):延时结束,输出=1\n", + node->id, component_id); + } + // 否则仍在延时中,保持当前值 } else { + // 输入为 0,立即输出 0 并取消定时器 node->value = 0; + node->timer_active = 0; + node->timer_pending_output = -1; } } @@ -410,16 +477,31 @@ static void execute_62d_gate(LogicNode *node, int input_value) { int td = (node->id >> 8) & 0xFFFFFF; int component_id = node->id & 0xFF; if (input_value == 1) { + // 输入为 1,立即输出 1 并取消定时器 node->value = 1; + node->timer_active = 0; + node->timer_pending_output = -1; printf(" 62d延时元件 (ID=%d, 序号=%d):输入=1,立即输出=1\n", node->id, component_id); } else { - printf(" 62d延时元件 (ID=%d, 序号=%d):输入=0,开始延时%d毫秒\n", - node->id, component_id, td); - usleep(td * 1000); - node->value = 0; - printf(" 62d延时元件 (ID=%d, 序号=%d):延时结束,输出=0\n", - node->id, component_id); + if (!node->timer_active) { + // 启动延时定时器,延时 td 毫秒后输出 0 + printf(" 62d延时元件 (ID=%d, 序号=%d):输入=0,启动非阻塞延时%d毫秒\n", + node->id, component_id, td); + clock_gettime(CLOCK_MONOTONIC, &node->timer_start); + node->timer_delay_ms = td; + node->timer_pending_output = 0; + node->timer_active = 1; + // 保持当前输出值不变,等待定时器到期 + } else if (get_elapsed_ms(&node->timer_start) >= node->timer_delay_ms) { + // 定时器到期,输出 0 + node->value = 0; + node->timer_active = 0; + node->timer_pending_output = -1; + printf(" 62d延时元件 (ID=%d, 序号=%d):延时结束,输出=0\n", + node->id, component_id); + } + // 否则仍在延时中,保持当前值 } } @@ -475,6 +557,70 @@ static void execute_rs_latch(LogicNode *node, int *input_values, int input_count } } +// ==================== 拓扑排序 ==================== +// 对逻辑图的节点进行拓扑排序,确保节点按依赖顺序执行 +// 返回值:排好序的节点索引数组,由调用者通过 sorted_indices 传出 +// 返回实际能排序的节点数(可能小于 node_count 如果有环) +static int topological_sort(LogicGraph *graph, int *sorted_indices) { + if (!graph || !sorted_indices) return 0; + int n = graph->node_count; + if (n == 0) return 0; + + // 步骤1:计算每个节点的入度(从非硬件输入点的源节点连接过来的链路数) + for (int i = 0; i < n; i++) { + graph->nodes[i].indegree = 0; + } + for (int i = 0; i < graph->link_count; i++) { + LogicLink *link = &graph->links[i]; + // 找到目标节点 + LogicNode *dest = find_node(graph, link->dest_type, link->dest_id); + if (dest) { + // 找到源节点(如果不是硬件输入点,则增加目标节点的入度) + LogicNode *src = find_node(graph, link->src_type, link->src_id); + if (src && src->type != NODE_TYPE_HW_INPUT) { + dest->indegree++; + } + } + } + + // 步骤2:Kahn算法 + // 将所有入度为0的节点入队(硬件输入点入度始终为0) + int queue[MAX_NODES_PER_GRAPH]; + int q_head = 0, q_tail = 0; + for (int i = 0; i < n; i++) { + if (graph->nodes[i].indegree == 0) { + queue[q_tail++] = i; + } + } + + int sorted_count = 0; + while (q_head < q_tail) { + int idx = queue[q_head++]; + sorted_indices[sorted_count++] = idx; + + // 找到当前节点的所有后继节点,减少它们的入度 + for (int i = 0; i < graph->link_count; i++) { + LogicLink *link = &graph->links[i]; + LogicNode *src = find_node(graph, link->src_type, link->src_id); + if (src == &graph->nodes[idx]) { + LogicNode *dest = find_node(graph, link->dest_type, link->dest_id); + if (dest) { + dest->indegree--; + if (dest->indegree == 0) { + queue[q_tail++] = (dest - graph->nodes); + } + } + } + } + } + + if (sorted_count < n) { + MY_LOG_E("逻辑图[%s]存在环路依赖,仅排序了 %d/%d 个节点", + graph->key, sorted_count, n); + } + return sorted_count; +} + // ==================== 逻辑图执行 ==================== int execute_logic_graph(LogicGraph *graph, HardwareManager *hw) { if (!graph || !hw) return -1; @@ -508,10 +654,12 @@ int execute_logic_graph(LogicGraph *graph, HardwareManager *hw) { type_name, node->type, node->id, node->value); } - // 步骤2:执行逻辑元件运算 - printf("\n【逻辑元件运算】\n"); - for (int i = 0; i < graph->node_count; i++) { - LogicNode *node = &graph->nodes[i]; + // 步骤2:按拓扑顺序执行逻辑元件运算 + printf("\n【逻辑元件运算】(拓扑排序执行)\n"); + int sorted_indices[MAX_NODES_PER_GRAPH]; + int sorted_count = topological_sort(graph, sorted_indices); + for (int si = 0; si < sorted_count; si++) { + LogicNode *node = &graph->nodes[sorted_indices[si]]; int input_values[10]; int input_count; switch (node->type) { @@ -611,38 +759,71 @@ int execute_logic_graph(LogicGraph *graph, HardwareManager *hw) { // ==================== PLC 逻辑入口 ==================== int PLC_Logic(void) { - 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 -1; - } - std::string file_path = std::string(proc_dir) + "config/PLC/Reclose_logic.txt"; + // 检查配置文件是否变化,仅在文件修改时重新解析 + struct stat file_stat; + if (stat(g_plc_config_path, &file_stat) != 0) { + MY_LOG_E("无法获取PLC配置文件状态: %s", g_plc_config_path); + // 如果之前有缓存,继续使用缓存执行 + if (g_cached_graph_count == 0) return 2; + // fall through to use cached graphs + } else { + if (file_stat.st_mtime == g_last_file_mtime && g_cached_graph_count > 0) { + // 文件未变化,直接使用缓存的逻辑图执行 + HardwareManager hw; + init_hardware_manager(&hw); + extract_hw_points_from_graphs(g_cached_graphs, g_cached_graph_count, &hw, 0); - LogicGraph graphs[MAX_LOGIC_GRAPHS]; + for (int i = 0; i < g_cached_graph_count; i++) { + execute_logic_graph(&g_cached_graphs[i], &hw); + } + + // 同步输出到全局数组 + HWPoint *curr = hw.output_head; + while (curr) { + if (curr->id < MAX_OUT_NODES) { + g_plc_st_out[curr->id] = curr->value; + } + curr = curr->next; + } + destroy_hardware_manager(&hw); + return 0; + } + g_last_file_mtime = file_stat.st_mtime; + } + + // 文件已变化或首次加载,重新解析 int graph_count = 0; - - if (parse_reclose_logic(file_path.c_str(), graphs, &graph_count) != 0) { - return 2; + if (parse_reclose_logic(g_plc_config_path, g_cached_graphs, &graph_count) != 0) { + // 解析失败但之前有缓存,继续使用缓存 + if (g_cached_graph_count > 0) { + MY_LOG_E("PLC配置文件重新解析失败,继续使用上次缓存(%d个逻辑图)", + g_cached_graph_count); + graph_count = g_cached_graph_count; // 使用旧缓存 + } else { + return 2; + } + } else { + g_cached_graph_count = graph_count; } HardwareManager hw; init_hardware_manager(&hw); - extract_hw_points_from_graphs(graphs, graph_count, &hw, 0); + extract_hw_points_from_graphs(g_cached_graphs, graph_count, &hw, 0); #ifndef DEBUG printf("==================== 文件解析结果 ====================\n"); + printf("配置文件: %s\n", g_plc_config_path); printf("共解析到 %d 个逻辑图\n", graph_count); for (int i = 0; i < graph_count; i++) { printf(" 逻辑图%d:key=%s, name=%s, 节点数=%d, 链路数=%d\n", - i + 1, graphs[i].key, graphs[i].name, - graphs[i].node_count, graphs[i].link_count); + i + 1, g_cached_graphs[i].key, g_cached_graphs[i].name, + g_cached_graphs[i].node_count, g_cached_graphs[i].link_count); } print_hardware_manager(&hw); for (int i = 0; i < graph_count; i++) { - execute_logic_graph(&graphs[i], &hw); + execute_logic_graph(&g_cached_graphs[i], &hw); } printf("\n==================== 最终硬件输出状态 ====================\n"); @@ -659,6 +840,24 @@ int PLC_Logic(void) { return 0; } +// ==================== 动态配置路径 ==================== +// 设置PLC配置文件路径(支持运行时动态修改) +void plc_set_config_path(const char *path) { + if (path && path[0] != '\0') { + strncpy(g_plc_config_path, path, sizeof(g_plc_config_path) - 1); + g_plc_config_path[sizeof(g_plc_config_path) - 1] = '\0'; + // 路径变更后重置缓存,强制下次重新加载 + g_last_file_mtime = 0; + g_cached_graph_count = 0; + MY_LOG_I("PLC配置路径已更新: %s", g_plc_config_path); + } +} + +// 获取当前PLC配置文件路径 +const char* plc_get_config_path(void) { + return g_plc_config_path; +} + // ==================== 模块初始化与线程 ==================== int app_plc_init1(void *arg) { @@ -668,6 +867,13 @@ int app_plc_init1(void *arg) return -1; } + // 动态设置PLC配置文件路径 + char proc_dir[512] = {0}; + if (0 == func_get_process_self_dir(proc_dir, sizeof(proc_dir))) { + std::string plc_path = std::string(proc_dir) + "config/PLC/Reclose_logic.txt"; + plc_set_config_path(plc_path.c_str()); + } + int ret = 0; ret |= dc_signal_out("plc.run_cnt", "plc线程计数", DATA_TYPE_U32, &p_app->run_cnt);