From 323dc1ced0c9fd2ac9ddd9d5d55a5d4060e86bdf Mon Sep 17 00:00:00 2001 From: ypc <15051963820@163.com> Date: Wed, 8 Jul 2026 13:38:05 +0800 Subject: [PATCH] =?UTF-8?q?docs(plc):=20=E6=9B=B4=E6=96=B0PLC=20XML?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E8=A7=A3=E6=9E=90=E4=B8=8E?= =?UTF-8?q?=E7=94=9F=E6=88=90=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gate 类型从 3 种扩展至 9 种(OR/AND/NOT/T62P/T62D/RISING/FALLING/SR/RS) - 新增 delayMs、R/S 属性说明 - Comb 新增 id 属性(必填,后序遍历分配) - 组合链支持链级 Inputs (SR/RS 直接 Input 连接) - 数据结构扩展: PlcGate 新增 delayMs/rsSSrc/rsRSrc/combId, PlcLink 新增 dstPin - 解析/生成伪代码同步更新 - 新增 6.3 时序门示例 + 6.4 RS/SR 触发器示例 - 注意事项从 6 条扩展至 12 条 --- ...LC_XML配置文件解析与生成_QT上位机开发文档.md | 374 ++++++++++++++---- 1 file changed, 296 insertions(+), 78 deletions(-) diff --git a/mimo/工程/PLC_XML配置文件解析与生成_QT上位机开发文档.md b/mimo/工程/PLC_XML配置文件解析与生成_QT上位机开发文档.md index cef5a49..4499612 100644 --- a/mimo/工程/PLC_XML配置文件解析与生成_QT上位机开发文档.md +++ b/mimo/工程/PLC_XML配置文件解析与生成_QT上位机开发文档.md @@ -3,7 +3,7 @@ > **面向对象**: QT 上位机开发人员 > **配置文件**: `config/PLC/Reclose_logic.xml` > **文件编码**: UTF-8 -> **文档日期**: 2026-07-07 +> **文档日期**: 2026-07-08 --- @@ -103,38 +103,87 @@ QT 上位机需要实现: | 属性 | 类型 | 必填 | 说明 | |------|------|------|------| -| `type` | string | 是 | 逻辑门类型,取值见表(见下方) | -| `id` | int | 是 | 逻辑门序号,从 0 起递增,同类型门内唯一 | +| `type` | string | 是 | 逻辑门类型,取值见下表 | +| `id` | int | 是 | 同类型门内序号,从 0 起递增,同类型门内唯一 | | `x` | int | 否 | 可视化画布 X 坐标 | | `y` | int | 否 | 可视化画布 Y 坐标 | +| `delayMs` | int | 否 | **延迟毫秒数**(仅 `T62P`/`T62D` 有效),默认 1000 | +| `R` | string | 否 | **复位输入来源**(仅 `SR`/`RS` 有效),格式见下方说明 | +| `S` | string | 否 | **置位输入来源**(仅 `SR`/`RS` 有效),格式见下方说明 | #### 逻辑门类型对照表 -| `type` 字符串 | 含义 | 输入数 | 说明 | -|---------------|------|--------|------| -| `OR` | 或门 | ≥1 | 任一输入为 1 则输出 1 | -| `AND` | 与门 | ≥1 | 全部输入为 1 则输出 1 | -| `NOT` | 非门 | 1 | 输入取反输出 | +| `type` 字符串 | 含义 | 输入数 | 输出数 | 说明 | +|---------------|------|--------|--------|------| +| `OR` | 或门 | 1~10 | 1~10 | 任一输入为 1 则输出 1 | +| `AND` | 与门 | 1~10 | 1~10 | 全部输入为 1 则输出 1 | +| `NOT` | 非门 | 1 | 1~10 | 输入取反输出 | +| `T62P` | 62p 延时 | 1 | 1~10 | 输入 1 延时 `delayMs` ms 后输出 1,输入 0 立即输出 0 | +| `T62D` | 62d 延时 | 1 | 1~10 | 输入 1 立即输出 1,输入 0 延时 `delayMs` ms 后输出 0 | +| `RISING` | 上升沿 | 1 | 1~10 | 0→1 跳变时输出脉冲 | +| `FALLING` | 下降沿 | 1 | 1~10 | 1→0 跳变时输出脉冲 | +| `SR` | SR 触发器 | 2 | 1~10 | S 置位优先。其中引脚顺序为 pin0=S(置位),pin1=R(复位) | +| `RS` | RS 触发器 | 2 | 1~10 | R 复位优先。其中引脚顺序为 pin0=R(复位),pin1=S(置位) | -> **注意**:当前 XML 配置支持的逻辑门类型为 `OR`、`AND`、`NOT`。若后续扩展 `T62P`/`T62D`/`SR`/`RS`/`RISING`/`FALLING` 等时序元件,需同步更新类型对照表。 +#### T62P/T62D 延时示例 + +```xml + + +``` + +#### SR/RS 触发器的 R/S 属性 + +`R` 和 `S` 的值是**来源描述字符串**,格式为 `Comb#N` 或 `Input#N`: + +| 值格式 | 含义 | +|--------|------| +| `Input#59` | 该引脚由输入信号 `no=59` 直接驱动 | +| `Comb#0` | 该引脚由 `id=0` 的 Comb 块内的 Gate 输出驱动 | + +```xml + + + + + +``` + +> **注意**:SR/RS 的两个引脚各有独立角色(S/R),连线时每个引脚只能接一根线。QT 上位机需分别显示两个输入引脚并标注 S/R 标签。 ### 3.5 `` — 组合逻辑块(核心难点) -`` 是**可递归嵌套**的逻辑组合块,用于表达多层级的复杂逻辑。 +`` 是**可递归嵌套**的逻辑组合块,用于表达多层级的复杂逻辑。每个 `` 必须有唯一的 `id` 属性。 + +| 属性 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `id` | int | 是 | Comb 块唯一标识,从 0 起递增。自底向上分配(最内层 Comb id=0,向外递增)。用于 RS/SR 引脚引用(`R="Comb#0"`) | ```xml - + - - ... - ... - - ... + + + + + + + + + + + + + + + + + - - + + @@ -146,6 +195,7 @@ QT 上位机需要实现: 2. **嵌套子 Comb**:子 Comb 的 Gate 输出 → 连到本层 Gate 的输入端(一根线) 3. **本层 Gate**:接收上述所有输入,运算后输出到 Outputs 4. **递归终止**:最内层 Comb 没有嵌套子 Comb,只有 Inputs → Gate → Outputs +5. **Comb id 分配**:后序遍历(自底向上),最内层 id=0,向外逐层递增 **数据流示意**: @@ -155,6 +205,36 @@ QT 上位机需要实现: [子Comb Gate] ─┘ ``` +### 3.6 组合链中的链级元素 + +与之前不同,组合链现已支持在 `` 块与链级 `` 之间插入链级 ``,用于 SR/RS 触发器等需要同时接收 Comb 输出和直接 Input 信号的场景: + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +**组合链子元素顺序**:`` → ``(可选)→ `` → `` + --- ## 四、完整解析算法(C++ / Qt) @@ -169,10 +249,15 @@ QT 上位机需要实现: // 逻辑门类型枚举 enum GateType { - GATE_OR = 2, - GATE_AND = 3, - GATE_NOT = 4 - // 未来可扩展: GATE_T62P=5, GATE_T62D=6, ... + GATE_OR = 2, + GATE_AND = 3, + GATE_NOT = 4, + GATE_T62P = 5, // 62p 延时 + GATE_T62D = 6, // 62d 延时 + GATE_RISING = 7, // 上升沿 + GATE_SR = 8, // SR 触发器 + GATE_RS = 9, // RS 触发器 + GATE_FALLING = 10 // 下降沿 }; // 信号节点 @@ -184,24 +269,30 @@ struct PlcSignal { // 逻辑门 struct PlcGate { GateType type; - int id; // 同类型门内序号 - int x, y; // 画布坐标 + int id; // 同类型门内序号 + int x, y; // 画布坐标 + int delayMs; // 延时毫秒数(仅 T62P/T62D,默认 1000) + QString rsSSrc; // S 引脚来源(仅 SR/RS,如 "Input#13"、"Comb#0") + QString rsRSrc; // R 引脚来源(仅 SR/RS) + int combId; // 所在 Comb 块的 id(用于生成 R/S 引用) }; // 连线 struct PlcLink { - int srcType; // 源节点类型: 1=输入信号, 2~4=逻辑门 - int srcId; // 源节点ID - int dstType; // 目标节点类型: 0=输出信号, 2~4=逻辑门 - int dstId; // 目标节点ID + int srcType; // 源节点类型编码 + int srcId; // 源节点 ID + int dstType; // 目标节点类型编码 + int dstId; // 目标节点 ID + int dstPin; // 目标输入引脚序号(仅 SR/RS 有效:SR: 0=S/1=R, RS: 0=R/1=S; 其他门默认 -1) }; -// Comb 组合块(递归结构) +// Comb 组合块 struct PlcComb { - QVector inputs; // 本层输入信号 - QVector subCombs; // 嵌套子Comb - PlcGate gate; // 本层逻辑门 - QVector outputs; // 本层输出信号 + int id; // Comb 块标识(自底向上编号) + QVector inputs; // 本层输入信号 + QVector subCombs; // 嵌套子 Comb + PlcGate gate; // 本层逻辑门 + QVector outputs; // 本层输出信号 }; // 逻辑链 @@ -211,10 +302,10 @@ struct PlcChain { int outputsCount; // outputs 属性 bool isComb; // true=组合链, false=简单链 - QVector inputs; // (简单链) 输入信号 - PlcGate gate; // (简单链) 逻辑门 - PlcComb comb; // (组合链) 顶层Comb - QVector outputs; // 输出信号 + QVector inputs; // (简单链/组合链) 链级输入信号 + QVector topCombs; // (组合链) 顶层 Comb 列表 + PlcGate gate; // 链级逻辑门(组合链的顶层Gate,或简单链的Gate) + QVector outputs; // 输出信号 }; // 整个配置文件 @@ -235,18 +326,30 @@ struct PlcConfig { // 从 XML 字符串解析逻辑门类型 static GateType parseGateType(const QString &s) { - if (s == "OR") return GATE_OR; - if (s == "AND") return GATE_AND; - if (s == "NOT") return GATE_NOT; + if (s == "OR") return GATE_OR; + if (s == "AND") return GATE_AND; + if (s == "NOT") return GATE_NOT; + if (s == "T62P") return GATE_T62P; + if (s == "T62D") return GATE_T62D; + if (s == "RISING") return GATE_RISING; + if (s == "SR") return GATE_SR; + if (s == "RS") return GATE_RS; + if (s == "FALLING") return GATE_FALLING; return GATE_OR; // 默认 } // 从 Gate 类型得到字符串 (生成 XML 用) static QString gateTypeToString(GateType t) { switch (t) { - case GATE_OR: return "OR"; - case GATE_AND: return "AND"; - case GATE_NOT: return "NOT"; + case GATE_OR: return "OR"; + case GATE_AND: return "AND"; + case GATE_NOT: return "NOT"; + case GATE_T62P: return "T62P"; + case GATE_T62D: return "T62D"; + case GATE_RISING: return "RISING"; + case GATE_SR: return "SR"; + case GATE_RS: return "RS"; + case GATE_FALLING: return "FALLING"; } return "OR"; } @@ -263,10 +366,14 @@ static PlcSignal parseSignal(const QDomElement &el) { // 解析 元素 static PlcGate parseGate(const QDomElement &el) { PlcGate g; - g.type = parseGateType(el.attribute("type")); - g.id = el.attribute("id").toInt(); - g.x = el.attribute("x").toInt(); - g.y = el.attribute("y").toInt(); + g.type = parseGateType(el.attribute("type")); + g.id = el.attribute("id").toInt(); + g.x = el.attribute("x").toInt(); + g.y = el.attribute("y").toInt(); + g.delayMs = el.attribute("delayMs", "1000").toInt(); // 默认 1000ms + g.rsSSrc = el.attribute("S", ""); // SR/RS 的 S 来源 + g.rsRSrc = el.attribute("R", ""); // SR/RS 的 R 来源 + g.combId = -1; // 由解析过程赋值 return g; } @@ -289,6 +396,9 @@ static QVector parseSignalList(const QDomElement &parent, static bool parseComb(const QDomElement &combEl, PlcComb &comb) { if (combEl.isNull()) return false; + // 0. 读取 Comb id 属性 + comb.id = combEl.attribute("id", "0").toInt(); + // 1. 本层直接输入 comb.inputs = parseSignalList(combEl, "Inputs"); @@ -306,6 +416,7 @@ static bool parseComb(const QDomElement &combEl, PlcComb &comb) { QDomElement gateEl = combEl.firstChildElement("Gate"); if (!gateEl.isNull()) { comb.gate = parseGate(gateEl); + comb.gate.combId = comb.id; // 回填所在 Comb 的 id } // 4. 本层输出 @@ -325,22 +436,23 @@ static PlcChain parseChain(const QDomElement &chainEl) { chain.isComb = !combEl.isNull(); if (chain.isComb) { - // 组合链: 解析顶层 Comb - parseComb(combEl, chain.comb); - - // 链级顶层 Gate(在 Comb 之后) - QDomNodeList children = chainEl.childNodes(); - bool foundComb = false; - for (int i = 0; i < children.count(); i++) { - QDomElement el = children.at(i).toElement(); - if (el.tagName() == "Comb") { foundComb = true; continue; } - if (foundComb && el.tagName() == "Gate") { - chain.gate = parseGate(el); - break; - } + // 组合链: 解析所有顶层 Comb(可能有多个) + QDomElement cEl = combEl; + while (!cEl.isNull()) { + PlcComb comb; + parseComb(cEl, comb); + chain.topCombs.append(comb); + cEl = cEl.nextSiblingElement("Comb"); } - // 链级顶层 Outputs + // 链级 Inputs(Comb 之后,Gate 之前,如 RS/SR 的直接 Input 连接) + chain.inputs = parseSignalList(chainEl, "Inputs"); + + // 链级 Gate + QDomElement gEl = chainEl.firstChildElement("Gate"); + if (!gEl.isNull()) chain.gate = parseGate(gEl); + + // 链级 Outputs chain.outputs = parseSignalList(chainEl, "Outputs"); } else { // 简单链: Inputs → Gate → Outputs @@ -422,6 +534,18 @@ static QDomElement createGateEl(QDomDocument &doc, const PlcGate &gate) { el.setAttribute("id", gate.id); el.setAttribute("x", gate.x); el.setAttribute("y", gate.y); + + // T62P/T62D: 延时毫秒 + if (gate.type == GATE_T62P || gate.type == GATE_T62D) { + el.setAttribute("delayMs", gate.delayMs); + } + + // SR/RS: 输出 R/S 属性(格式 "Comb#N" 或 "Input#N") + if (gate.type == GATE_SR || gate.type == GATE_RS) { + if (!gate.rsSSrc.isEmpty()) el.setAttribute("S", gate.rsSSrc); + if (!gate.rsRSrc.isEmpty()) el.setAttribute("R", gate.rsRSrc); + } + return el; } @@ -439,6 +563,7 @@ static QDomElement createSignalListEl(QDomDocument &doc, // 递归生成 元素 static QDomElement createCombEl(QDomDocument &doc, const PlcComb &comb) { QDomElement el = doc.createElement("Comb"); + el.setAttribute("id", comb.id); // Comb id 属性 // Inputs if (!comb.inputs.isEmpty()) { @@ -469,14 +594,17 @@ static QDomElement createChainEl(QDomDocument &doc, const PlcChain &chain) { el.setAttribute("outputs", chain.outputsCount); if (chain.isComb) { - // 组合链: Comb + Gate + Outputs - el.appendChild(createCombEl(doc, chain.comb)); - // Gate(仅在 Comb 后有意义时输出) - if (chain.gate.type == GATE_OR || - chain.gate.type == GATE_AND || - chain.gate.type == GATE_NOT) { - el.appendChild(createGateEl(doc, chain.gate)); + // 组合链: 所有顶层 Comb + for (const auto &comb : chain.topCombs) { + el.appendChild(createCombEl(doc, comb)); } + // 链级 Inputs(如有直接连接,如 RS/SR 的 Input 直连) + if (!chain.inputs.isEmpty()) { + el.appendChild(createSignalListEl(doc, "Inputs", chain.inputs)); + } + // 链级 Gate + el.appendChild(createGateEl(doc, chain.gate)); + // 链级 Outputs if (!chain.outputs.isEmpty()) { el.appendChild(createSignalListEl(doc, "Outputs", chain.outputs)); } @@ -578,15 +706,15 @@ bool writeXmlFile(const PlcConfig &config, const QString &filePath, - + - + - + @@ -625,6 +753,93 @@ bool writeXmlFile(const PlcConfig &config, const QString &filePath, 链级 Gate(NOT id=1): out[1368] = NOT 外层_Gate ``` +### 6.3 时序门简单链示例 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +### 6.4 RS/SR 触发器示例 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + --- ## 七、信号编号(no)的获取 @@ -664,15 +879,18 @@ RTU 返回 out 信号数组,每条信号包含: | 编号 | 注意点 | |------|--------| -| 1 | `` 最多嵌套 **3 层**(见示例),更深层级需验证 RTU 固件支持 | +| 1 | `` 最多嵌套 **3 层**(见示例),更深层级需验证 RTU 固件支持。**每个 Comb 必须有唯一 `id` 属性**,后序遍历自底向上从 0 开始分配 | | 2 | `checksum` 必须使用 CRC-32/MPEG-2 算法,且大写十六进制格式 | | 3 | `key` 建议使用 `QDateTime::currentMSecsSinceEpoch()` 生成,保证唯一性 | | 4 | `x`/`y` 坐标可不填写(设为 0),RTU 解析时忽略这些字段 | | 5 | XML 文件编码必须为 **UTF-8**,不要使用 UTF-8 BOM | | 6 | `` 的 `gates`/`inputs`/`outputs` 属性值为统计数量,生成时必须准确填写 | -| 7 | Comb 内 Gate 的 `id` 表示**同类型逻辑门内的序号**(OR/AND/NOT 分别计数,从 0 起递增) | -| 8 | 生成 XML 后建议通过 RTU Web 界面或命令行 `plc cfg reload` 触发配置重新加载 | -| 9 | 一个 `` 可含多条 ``,但目前 RTU 将所有 Chain 合并为一个逻辑图处理 | +| 7 | **T62P/T62D** 的 `delayMs` 为可选属性,默认 1000ms。范围 0~600000 | +| 8 | **SR/RS 触发器**有两个固定输入引脚:SR 的 pin0=S(置位)/pin1=R(复位),RS 的 pin0=R(复位)/pin1=S(置位)。每个引脚只能接一根线 | +| 9 | **SR/RS 的 R/S 属性**仅在 Gate 上输出,格式为 `Comb#N` 或 `Input#N`。若 RS/SR 所在链为组合链且有直接 Input 连接,需在链级 `` 块中包含对应信号 | +| 10 | 输入信号 `no` 必须是 datacenter out 信号表中的有效序号。QT 上位机需通过 WebSocket 动态获取信号列表,不可硬编码号段 | +| 11 | Comb 内 Gate 的 `id` 表示**同类型逻辑门内的序号**(OR/AND/NOT/T62P/T62D/... 分别计数,从 0 起递增) | +| 12 | 一个 `` 可含多条 ``,目前 RTU 将所有 Chain 合并为一个逻辑图处理 | --- @@ -704,15 +922,15 @@ RTU 返回 out 信号数组,每条信号包含: - + - + - +