feat(libplc): 非阻塞定时器重构与健壮性增强
- 用 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)
This commit is contained in:
parent
fd34c6815f
commit
a339ed73a0
|
|
@ -8,8 +8,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <locale.h> // ANSI编码支持
|
||||
#include <unistd.h> // usleep函数实现延时
|
||||
#include <time.h> // clock_gettime 非阻塞定时器
|
||||
#include <sys/stat.h> // 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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue