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/1),ID 即为信号点号
- 对于逻辑元件(类型 2~10),ID 编码规则见下表
- 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 号下降沿 |
三、链路 (link) 规则
3.1 链路编码格式
{源节点类型:源节点ID-目标节点类型:目标节点ID}
3.2 连接约束
| 源节点类型 |
目标节点类型 |
约束 |
| 硬件输入点 (1) |
逻辑元件 (2~10) |
单输入可以连接多个目标 |
| 硬件输入点 (1) |
硬件输出点 (0) |
直接驱动(无逻辑运算) |
| 逻辑元件 (2~10) → 输出 |
逻辑元件 (2~10) → 输入 |
级联连接 |
| 逻辑元件 (2~10) → 输出 |
硬件输出点 (0) |
运算结果驱动输出 |
| 非门 (4) → 输出 |
任何 |
输出为输入信号的取反 |
| 非门 (4) → 输入 |
— |
仅接收单个输入信号(单输入元件) |
| 与/或门 (2/3) → 输入 |
— |
可接收多个输入信号(多输入元件) |
3.3 关键约束
- 非门 (4) 是单输入元件,只能有一个源节点连接到它
- 与门 (3) / 或门 (2) 是多输入元件,可以有多个源节点连接
- 62p/62d 是单输入元件
- 上升沿/下降沿是单输入元件
- SR/RS 触发器是双输入元件(SR: S=输入1, R=输入2;RS: 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、延时参数等 |
| 链路管理 |
新增/删除/修改节点间连接 |
| 模板库 |
常用逻辑图模板(自保持、互锁等) |
九、注意事项
- 文件编码: 文件为 UTF-8 编码,单行无换行
- ID 进制: 节点 ID 和链路中的 ID 均为十六进制 (
%x 格式)
- URL 编码:
name 和 desc 字段可能包含空格,在数据字符串中需进行 URL 编码(%20 等)
- 坐标系统: X 轴向右为正,Y 轴向下为正,支持负坐标
- 数量上限: 每个逻辑图最多 20 个节点 + 20 条链路 (
MAX_NODES_PER_GRAPH=20, MAX_LINKS_PER_GRAPH=20)
- 热加载: PLC 模块通过
stat() 检测文件 mtime 变化,自动重新加载
- 在线配置ID:
online_cfgId 是全局配置标识,所有逻辑图共用一个
- 逻辑图独立: 多个逻辑图之间相互独立,各自独立执行
- 拓扑排序: 后端使用 Kahn 算法确保节点按依赖顺序执行,前端如需仿真也应同样处理
- 62p/62d 延时: 使用
clock_gettime(CLOCK_MONOTONIC) 实现非阻塞毫秒级延时