RTU/mimo/工程/PLC配置文件解析与生成.md

16 KiB
Raw Blame History

PLC 配置文件解析与生成文档

文件路径: config/PLC/Reclose_logic.txt
相关模块: src/system/libplc/ (PLC 逻辑引擎)
前端页面: test/web_root/js/plc_debug.js (PLC 调试页)
文档日期: 2026-07-03


一、文件格式总览

Reclose_logic.txt 是一个单行 JSON 文件(无换行、无空格),包含多个逻辑图的配置。

整体结构

{
    "<逻辑图key>": "<逻辑图数据字符串>",
    "online_cfgId": "<在线配置ID>"
}

逻辑图数据字符串格式

逻辑图数据是一个 key=value&key=value&... 格式的 URL 参数字符串:

key=840957954&name=Block Over Current(G)&desc=desc&desc_pos_x=0&desc_pos_y=0&node=[...]&link=[...]
字段 类型 说明
key string 逻辑图唯一标识(数字字符串)
name string 逻辑图名称(显示用)
desc string 描述文本
desc_pos_x int 描述文本显示坐标 X
desc_pos_y int 描述文本显示坐标 Y
node array 节点数组,格式 [{type:id:x:y}, ...]
link array 链路数组,格式 [{src_type:src_id-dest_type:dest_id}, ...]

二、节点类型定义

2.1 节点类型枚举

类型标识 节点类型 说明
0 硬件输出点 映射 dev_in 中的输出点 ID逻辑运算结果输出到硬件
1 硬件输入点 映射 dev_out 中的输入点 ID硬件输入信号作为逻辑输入
2 或门 (OR) 多输入逻辑或元件
3 与门 (AND) 多输入逻辑与元件
4 非门 (NOT) 单输入逻辑取反元件
5 62p 延时元件 输入 1 延时输出 1输入 0 立即输出 0上升沿延时
6 62d 延时元件 输入 1 立即输出 1输入 0 延时输出 0下降沿延时
7 上升沿元件 检测 0→1 跳变,输出一个脉冲
8 SR 触发器 S 优先的置位/复位触发器S=1 置位R=1 复位)
9 RS 触发器 R 优先的置位/复位触发器R=1 复位S=1 置位)
10 下降沿元件 检测 1→0 跳变,输出一个脉冲

2.2 节点编码格式

{类型:ID:X坐标:Y坐标}
  • 类型 (type): 整数,对应上表中的类型标识
  • ID: 十六进制整数
    • 对于硬件输入/输出点(类型 0/1ID 即为信号点号
    • 对于逻辑元件(类型 2~10ID 编码规则见下表
  • X 坐标 / Y 坐标: 有符号整数,用于可视化布局

逻辑元件 ID 编码

元件类型 ID 编码规则 示例
与门 (3) 低 8 位 = 元件序号 (0, 1, 2...) 3:0 = 第 0 号与门
或门 (2) 低 8 位 = 元件序号 2:1 = 第 1 号或门
非门 (4) 低 8 位 = 元件序号 4:0 = 第 0 号非门
62p (5) 高 24 位 = 延时毫秒 tp低 8 位 = 序号 5:0x3E800 = 延时 1000ms 的第 0 号 62p
62d (6) 高 24 位 = 延时毫秒 td低 8 位 = 序号 6:0x7D000 = 延时 2000ms 的第 0 号 62d
上升沿 (7) 低 8 位 = 元件序号 7:0 = 第 0 号上升沿
SR (8) 低 8 位 = 元件序号 8:0 = 第 0 号 SR 触发器
RS (9) 低 8 位 = 元件序号 9:0 = 第 0 号 RS 触发器
下降沿 (10) 低 8 位 = 元件序号 10:0 = 第 0 号下降沿

3.1 链路编码格式

{源节点类型:源节点ID-目标节点类型:目标节点ID}

3.2 连接约束

源节点类型 目标节点类型 约束
硬件输入点 (1) 逻辑元件 (2~10) 单输入可以连接多个目标
硬件输入点 (1) 硬件输出点 (0) 直接驱动(无逻辑运算)
逻辑元件 (2~10) → 输出 逻辑元件 (2~10) → 输入 级联连接
逻辑元件 (2~10) → 输出 硬件输出点 (0) 运算结果驱动输出
非门 (4) → 输出 任何 输出为输入信号的取反
非门 (4) → 输入 仅接收单个输入信号(单输入元件)
与/或门 (2/3) → 输入 可接收多个输入信号(多输入元件)

3.3 关键约束

  1. 非门 (4) 是单输入元件,只能有一个源节点连接到它
  2. 与门 (3) / 或门 (2) 是多输入元件,可以有多个源节点连接
  3. 62p/62d 是单输入元件
  4. 上升沿/下降沿是单输入元件
  5. SR/RS 触发器是双输入元件SR: S=输入1, R=输入2RS: R=输入1, S=输入2

四、完整示例

4.1 原始文件内容

{"840957954":"key=840957954&name=Block Over Current(G)&desc=desc&desc_pos_x=0&desc_pos_y=0&node=[{1:38:-330:-120},{0:1:130:-120},{1:36:-390:-10},{0:2:170:-10},{1:37:-400:80},{0:3:170:110},{3:0:-10:30}]&link=[{1:38-0:1},{3:0-0:2},{3:0-0:3},{1:36-3:0},{1:37-3:0}]","online_cfgId":"3486BE04"}

4.2 节点拆解

节点编码 类型 ID 坐标 含义
1:38:-330:-120 1 (输入点) 0x38 (56) (-330,-120) 硬件输入信号 56
0:1:130:-120 0 (输出点) 0x1 (1) (130,-120) 硬件输出信号 1
1:36:-390:-10 1 (输入点) 0x36 (54) (-390,-10) 硬件输入信号 54
0:2:170:-10 0 (输出点) 0x2 (2) (170,-10) 硬件输出信号 2
1:37:-400:80 1 (输入点) 0x37 (55) (-400,80) 硬件输入信号 55
0:3:170:110 0 (输出点) 0x3 (3) (170,110) 硬件输出信号 3
3:0:-10:30 3 (与门) 0 (第0号) (-10,30) 第 0 号与门

4.3 链路拆解

链路编码 目标 含义
1:38-0:1 输入点 0x38 输出点 0x1 输入 56 直驱输出 1
3:0-0:2 与门 0 号 输出点 0x2 与门结果 → 输出 2
3:0-0:3 与门 0 号 输出点 0x3 与门结果 → 输出 3
1:36-3:0 输入点 0x36 与门 0 号 输入 54 → 与门输入 1
1:37-3:0 输入点 0x37 与门 0 号 输入 55 → 与门输入 2

4.4 逻辑表达式

输出1 = 输入56
输出2 = 输入54 && 输入55
输出3 = 输入54 && 输入55

五、解析流程C 后端)

参考代码:src/system/libplc/src/plc.cpp

5.1 解析步骤

1. fopen 读取整个文件到 buffer
2. 去除所有空白字符isspace
3. 逐逻辑图解析:
   a. 查找 "key" 字段 → 提取逻辑图 key
   b. 查找 "node=[...]" → 按 {type:id:x:y} 格式解析节点
      - sscanf(token, "%d:%x:%d:%d", &type, &id, &x, &y)
   c. 查找 "link=[...]" → 按 {src_type:src_id-dest_type:dest_id} 格式解析链路
      - sscanf(token, "%d:%x-%d:%x", &src_type, &src_id, &dest_type, &dest_id)
   d. 查找 "name=..." → 提取逻辑图名称
4. 缓存解析结果(文件未变化时复用缓存)

5.2 关键数据结构

typedef struct {
    int type;       // 节点类型 (0~10)
    int id;         // 节点 ID
    int x, y;       // 可视化坐标
    int value;      // 当前信号值
    int indegree;   // 入度(拓扑排序用)
    // 延时定时器字段 (62p/62d)
    uint8_t timer_active;
    struct timespec timer_start;
    int timer_delay_ms;
    int timer_pending_output;
} LogicNode;

typedef struct {
    int src_type, src_id;    // 源节点
    int dest_type, dest_id;  // 目标节点
} LogicLink;

typedef struct {
    char key[20];                     // 逻辑图 key
    char name[50];                    // 逻辑图名称
    LogicNode nodes[MAX_NODES];       // 节点数组
    int node_count;
    LogicLink links[MAX_LINKS];       // 链路数组
    int link_count;
} LogicGraph;

5.3 执行流程

1. 拓扑排序Kahn 算法):按依赖关系排序节点
2. 按拓扑序执行逻辑元件运算:
   - 非门: value = !input
   - 与门: value = input1 && input2 && ...
   - 或门: value = input1 || input2 || ...
   - 62p:  输入1→延时tp毫秒→输出1输入0→立即输出0
   - 62d:  输入1→立即输出1输入0→延时td毫秒→输出0
   - 上升沿: last=0 && cur=1 → 脉冲1
   - 下降沿: last=1 && cur=0 → 脉冲1
   - SR: S=1→value=1; R=1→value=0 (S优先)
   - RS: R=1→value=0; S=1→value=1 (R优先)
3. 输出链路处理:将源节点值写入硬件输出点

六、前端解析与生成JavaScript

6.1 JSON 解析

/**
 * 解析 Reclose_logic.txt 文件内容为逻辑图数组
 * @param {string} fileContent - 文件原始内容
 * @returns {{ graphs: LogicGraph[], online_cfgId: string }}
 */
function parsePLCFile(fileContent) {
    // 去除空白
    var clean = fileContent.replace(/\s/g, '');
    var data = JSON.parse(clean);
    var graphs = [];
    
    for (var key in data) {
        if (key === 'online_cfgId') continue;
        graphs.push(parseGraphData(key, data[key]));
    }
    
    return {
        graphs: graphs,
        online_cfgId: data.online_cfgId || ''
    };
}

/**
 * 解析单个逻辑图数据字符串
 * @param {string} key - 逻辑图 key
 * @param {string} dataStr - key=value&... 格式的数据字符串
 * @returns {LogicGraph}
 */
function parseGraphData(key, dataStr) {
    var params = {};
    var pairs = dataStr.split('&');
    for (var i = 0; i < pairs.length; i++) {
        var idx = pairs[i].indexOf('=');
        if (idx === -1) continue;
        params[pairs[i].substring(0, idx)] = pairs[i].substring(idx + 1);
    }
    
    var graph = {
        key: key,
        name: decodeURIComponent(params.name || ''),
        desc: decodeURIComponent(params.desc || ''),
        desc_pos_x: parseInt(params.desc_pos_x) || 0,
        desc_pos_y: parseInt(params.desc_pos_y) || 0,
        nodes: [],
        links: []
    };
    
    // 解析节点
    if (params.node) {
        var nodeMatch = params.node.match(/\[(.*)\]/);
        if (nodeMatch) {
            var tokens = nodeMatch[1].match(/\{[^}]+\}/g) || [];
            for (var j = 0; j < tokens.length; j++) {
                var inner = tokens[j].replace(/[{}]/g, '');
                var parts = inner.split(':');
                if (parts.length === 4) {
                    graph.nodes.push({
                        type: parseInt(parts[0]),
                        id: parseInt(parts[1], 16),
                        x: parseInt(parts[2]),
                        y: parseInt(parts[3])
                    });
                }
            }
        }
    }
    
    // 解析链路
    if (params.link) {
        var linkMatch = params.link.match(/\[(.*)\]/);
        if (linkMatch) {
            var tokens = linkMatch[1].match(/\{[^}]+\}/g) || [];
            for (var k = 0; k < tokens.length; k++) {
                var inner = tokens[k].replace(/[{}]/g, '');
                var linkParts = inner.split('-');
                if (linkParts.length === 2) {
                    var src = linkParts[0].split(':');
                    var dest = linkParts[1].split(':');
                    graph.links.push({
                        src_type: parseInt(src[0]),
                        src_id: parseInt(src[1], 16),
                        dest_type: parseInt(dest[0]),
                        dest_id: parseInt(dest[1], 16)
                    });
                }
            }
        }
    }
    
    return graph;
}

6.2 JSON 生成(序列化)

/**
 * 将逻辑图数组序列化为 Reclose_logic.txt 文件内容
 * @param {LogicGraph[]} graphs - 逻辑图数组
 * @param {string} onlineCfgId - 在线配置 ID
 * @returns {string} 文件内容(紧凑 JSON无换行无空格
 */
function generatePLCFile(graphs, onlineCfgId) {
    var result = {};
    
    for (var i = 0; i < graphs.length; i++) {
        var g = graphs[i];
        var params = [];
        
        params.push('key=' + g.key);
        params.push('name=' + encodeURIComponent(g.name || ''));
        params.push('desc=' + encodeURIComponent(g.desc || 'desc'));
        params.push('desc_pos_x=' + (g.desc_pos_x || 0));
        params.push('desc_pos_y=' + (g.desc_pos_y || 0));
        
        // 序列化节点
        var nodeParts = [];
        for (var j = 0; j < g.nodes.length; j++) {
            var n = g.nodes[j];
            nodeParts.push('{' + n.type + ':' + n.id.toString(16) + ':' + n.x + ':' + n.y + '}');
        }
        params.push('node=[' + nodeParts.join(',') + ']');
        
        // 序列化链路
        var linkParts = [];
        for (var k = 0; k < g.links.length; k++) {
            var l = g.links[k];
            linkParts.push('{' + l.src_type + ':' + l.src_id.toString(16)
                + '-' + l.dest_type + ':' + l.dest_id.toString(16) + '}');
        }
        params.push('link=[' + linkParts.join(',') + ']');
        
        result[g.key] = params.join('&');
    }
    
    result.online_cfgId = onlineCfgId || '';
    
    return JSON.stringify(result);  // 紧凑格式,无空格
}

6.3 节点类型名称映射

var NODE_TYPE_NAMES = {
    0:  '硬件输出点',
    1:  '硬件输入点',
    2:  '或门 (OR)',
    3:  '与门 (AND)',
    4:  '非门 (NOT)',
    5:  '62p 延时元件',
    6:  '62d 延时元件',
    7:  '上升沿元件',
    8:  'SR 触发器',
    9:  'RS 触发器',
    10: '下降沿元件'
};

var NODE_TYPE_ICONS = {
    0: '📤', 1: '📥', 2: '🔶', 3: '🔷', 4: '🔺',
    5: '⏱️', 6: '⏱️', 7: '⬆️', 8: '🔒', 9: '🔒', 10: '⬇️'
};

function getNodeTypeName(type) {
    return NODE_TYPE_NAMES[type] || '未知(' + type + ')';
}

function getNodeTypeIcon(type) {
    return NODE_TYPE_ICONS[type] || '❓';
}

七、节点类型配置表

7.1 硬件输入/输出点配置(类型 0/1

配置来源 字段 说明
逻辑图文件 node[{0:id:x:y}] 定义输出点的 ID 和画布坐标
逻辑图文件 node[{1:id:x:y}] 定义输入点的 ID 和画布坐标
自点表配置 config/SELF_PTL/ 输入点的实际硬件映射 (dev_out[id])
自点表配置 config/SELF_PTL/ 输出点的实际硬件映射 (dev_in[id])
数据中心 dc_signal_out() PLC 模块注册的输出信号 (plc.st.out.N)

7.2 逻辑元件配置(类型 2~10

元件 关联配置 ID 含义
与门 (3) logic_limit[idx=3] ID 低 8 位 = 序号
或门 (2) logic_limit[idx=2] ID 低 8 位 = 序号
非门 (4) logic_limit[idx=4] ID 低 8 位 = 序号
62p (5) 高 24 位 = tp 延迟 ms
62d (6) 高 24 位 = td 延迟 ms
上升沿 (7) ID 低 8 位 = 序号
下降沿 (10) ID 低 8 位 = 序号
SR 触发器 (8) ID 低 8 位 = 序号
RS 触发器 (9) ID 低 8 位 = 序号

八、与现有前端的对接

8.1 现有 PLC 调试页plc_debug.js

当前 plc_debug.js 实现了:

  • 指示灯绑定 out 信号(>0 亮 / ≤0 灭)
  • 按键绑定 out 信号(点击翻转 0↔1
  • 增量刷新(不重建 DOM
  • 勾选批量删除

不涉及:逻辑图文件的解析/生成/编辑。

8.2 后续可扩展方向

功能 说明
逻辑图编辑器 可视化编辑节点和链路,拖拽连线
逻辑图导入/导出 支持上传/下载 Reclose_logic.txt
逻辑仿真 前端实时计算逻辑表达式结果
节点属性编辑 修改节点类型、ID、延时参数等
链路管理 新增/删除/修改节点间连接
模板库 常用逻辑图模板(自保持、互锁等)

九、注意事项

  1. 文件编码: 文件为 UTF-8 编码,单行无换行
  2. ID 进制: 节点 ID 和链路中的 ID 均为十六进制 (%x 格式)
  3. URL 编码: namedesc 字段可能包含空格,在数据字符串中需进行 URL 编码(%20 等)
  4. 坐标系统: X 轴向右为正Y 轴向下为正,支持负坐标
  5. 数量上限: 每个逻辑图最多 20 个节点 + 20 条链路 (MAX_NODES_PER_GRAPH=20, MAX_LINKS_PER_GRAPH=20)
  6. 热加载: PLC 模块通过 stat() 检测文件 mtime 变化,自动重新加载
  7. 在线配置ID: online_cfgId 是全局配置标识,所有逻辑图共用一个
  8. 逻辑图独立: 多个逻辑图之间相互独立,各自独立执行
  9. 拓扑排序: 后端使用 Kahn 算法确保节点按依赖顺序执行,前端如需仿真也应同样处理
  10. 62p/62d 延时: 使用 clock_gettime(CLOCK_MONOTONIC) 实现非阻塞毫秒级延时