472 lines
16 KiB
Markdown
472 lines
16 KiB
Markdown
# 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 文件**(无换行、无空格),包含多个逻辑图的配置。
|
||
|
||
### 整体结构
|
||
|
||
```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 关键约束
|
||
|
||
1. **非门 (4) 是单输入元件**,只能有一个源节点连接到它
|
||
2. **与门 (3) / 或门 (2) 是多输入元件**,可以有多个源节点连接
|
||
3. **62p/62d 是单输入元件**
|
||
4. **上升沿/下降沿是单输入元件**
|
||
5. **SR/RS 触发器是双输入元件**(SR: S=输入1, R=输入2;RS: R=输入1, S=输入2)
|
||
|
||
---
|
||
|
||
## 四、完整示例
|
||
|
||
### 4.1 原始文件内容
|
||
|
||
```json
|
||
{"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](src/system/libplc/src/plc.cpp#L230)
|
||
|
||
### 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 关键数据结构
|
||
|
||
```c
|
||
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 解析
|
||
|
||
```javascript
|
||
/**
|
||
* 解析 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 生成(序列化)
|
||
|
||
```javascript
|
||
/**
|
||
* 将逻辑图数组序列化为 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 节点类型名称映射
|
||
|
||
```javascript
|
||
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](test/web_root/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 编码**: `name` 和 `desc` 字段可能包含空格,在数据字符串中需进行 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)` 实现非阻塞毫秒级延时
|