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

472 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/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 号下降沿 |
---
## 三、链路 (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=输入2RS: 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)` 实现非阻塞毫秒级延时