RTU/mimo/工程/PLC_XML配置文件解析与生成_QT上位机开发文档.md

750 lines
24 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 配置文件XML解析与生成说明文档
> **面向对象**: QT 上位机开发人员
> **配置文件**: `config/PLC/Reclose_logic.xml`
> **文件编码**: UTF-8
> **文档日期**: 2026-07-07
---
## 一、概述
PLC 逻辑配置使用 XML 格式存储。一个 XML 文件定义一个 PLC 逻辑工程,由若干条**逻辑链Chain**组成。每条链描述一组输入信号经逻辑门运算后输出到目标信号的过程。
QT 上位机需要实现:
1. **解析(读取)**:将 XML 文件解析为内存中的逻辑图数据结构,用于画布展示和编辑
2. **生成(写入)**:将用户编辑完的逻辑图保存/导出为 XML 文件,下发到 RTU 设备
---
## 二、XML 整体结构
```
<PLCConfig name="..." key="..." checksum="...">
<Chain gates="n" inputs="m" outputs="k"> <!-- 简单链 -->
<Inputs> <Signal .../> </Inputs>
<Gate .../>
<Outputs> <Signal .../> </Outputs>
</Chain>
<Chain gates="n" inputs="m" outputs="k"> <!-- 组合链含Comb嵌套 -->
<Comb>
<Inputs> <Signal .../> </Inputs>
<Comb> ... </Comb> <!-- 嵌套子Comb -->
<Gate .../>
<Outputs> <Signal .../> </Outputs>
</Comb>
<Gate .../> <!-- 链级顶层Gate -->
<Outputs> <Signal .../> </Outputs>
</Chain>
</PLCConfig>
```
### 2.1 根元素 `<PLCConfig>`
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `name` | string | 是 | 工程名称,如 `"PLC Logic"` |
| `key` | string | 是 | 工程唯一标识,建议使用时间戳毫秒数(如 `"1783070862205"` |
| `checksum` | string | 是 | 32 位 CRC 校验值,大写十六进制字符串(如 `"3486BE04"`)。通常使用 CRC-32/MPEG-2 算法对整个 XML 内容(不含 checksum 属性本身)计算得到 |
---
## 三、元素详解
### 3.1 `<Chain>` — 逻辑链
每条 `<Chain>` 代表一组相关的逻辑运算。可独立存在,也可通过信号编号组合。
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `gates` | int | 是 | 链内逻辑门总数(含顶层 Gate 和 Comb 内部 Gate |
| `inputs` | int | 是 | 链内输入端信号总数 |
| `outputs` | int | 是 | 链内输出端信号总数 |
**两种形态**
| 形态 | 子元素 | 场景 |
|------|--------|------|
| **简单链** | `<Inputs>` + `<Gate>` + `<Outputs>` | 单层逻辑:若干输入信号经一个逻辑门运算后输出 |
| **组合链** | `<Comb>` + `<Gate>` + `<Outputs>` | 多层嵌套逻辑Comb 内部可嵌套子 Comb |
### 3.2 `<Inputs>` — 输入信号集
```xml
<Inputs>
<Signal no="59" x="-676" y="69" />
<Signal no="60" x="-674" y="183" />
</Inputs>
```
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `no` | int | 是 | **信号编号**,对应 RTU 数据中心 out 信号的序号dc_signal_out 的 `no` 字段) |
| `x` | int | 否 | 可视化画布 X 坐标像素值,解析/生成时可忽略(仅用于前端画布渲染) |
| `y` | int | 否 | 可视化画布 Y 坐标像素值,解析/生成时可忽略 |
> **关键映射关系**`Signal.no` ↔ 数据中心 `dc_signal_out` 记录的 `no` 字段。QT 上位机需通过 WebSocket 或 HTTP API 获取信号列表(`signal_type` = `"out"`),从中取得各信号的 `no` 和 `saddr`(信号地址)。
### 3.3 `<Outputs>` — 输出信号集
```xml
<Outputs>
<Signal no="1361" x="-17" y="137" />
</Outputs>
```
`<Inputs>` 结构完全相同。区别在于语义:`<Outputs>` 中的信号编号对应 **PLC 输出信号**(通常 `no >= 1361` 为 RTU 内置预留号段)。
### 3.4 `<Gate>` — 逻辑门
```xml
<Gate type="OR" id="0" x="-346" y="141" />
```
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `type` | string | 是 | 逻辑门类型,取值见表(见下方) |
| `id` | int | 是 | 逻辑门序号,从 0 起递增,同类型门内唯一 |
| `x` | int | 否 | 可视化画布 X 坐标 |
| `y` | int | 否 | 可视化画布 Y 坐标 |
#### 逻辑门类型对照表
| `type` 字符串 | 含义 | 输入数 | 说明 |
|---------------|------|--------|------|
| `OR` | 或门 | ≥1 | 任一输入为 1 则输出 1 |
| `AND` | 与门 | ≥1 | 全部输入为 1 则输出 1 |
| `NOT` | 非门 | 1 | 输入取反输出 |
> **注意**:当前 XML 配置支持的逻辑门类型为 `OR`、`AND`、`NOT`。若后续扩展 `T62P`/`T62D`/`SR`/`RS`/`RISING`/`FALLING` 等时序元件,需同步更新类型对照表。
### 3.5 `<Comb>` — 组合逻辑块(核心难点)
`<Comb>` 是**可递归嵌套**的逻辑组合块,用于表达多层级的复杂逻辑。
```xml
<Comb>
<Inputs> <!-- 本层直接输入信号 -->
<Signal no="67" x="-6" y="937" />
</Inputs>
<Comb> <!-- 嵌套子Comb可多层 -->
<Inputs> ... </Inputs>
<Comb> ... </Comb>
<Gate type="AND" id="0" .../>
<Outputs> ... </Outputs>
</Comb>
<Gate type="OR" id="1" .../> <!-- 本层逻辑门 -->
<Outputs> <!-- 本层输出信号 -->
<Signal no="1367" x="627" y="836" />
</Outputs>
</Comb>
```
**解析规则**
1. **本层 Inputs**:直接连到本层 Gate 的输入端
2. **嵌套子 Comb**:子 Comb 的 Gate 输出 → 连到本层 Gate 的输入端(一根线)
3. **本层 Gate**:接收上述所有输入,运算后输出到 Outputs
4. **递归终止**:最内层 Comb 没有嵌套子 Comb只有 Inputs → Gate → Outputs
**数据流示意**
```
[本层Inputs] ──┐
├──→ [本层 Gate] ──→ [本层 Outputs]
[子Comb Gate] ─┘
```
---
## 四、完整解析算法C++ / Qt
### 4.1 数据结构定义
```cpp
#include <QString>
#include <QVector>
#include <QPoint>
#include <QtXml/QDomDocument>
// 逻辑门类型枚举
enum GateType {
GATE_OR = 2,
GATE_AND = 3,
GATE_NOT = 4
// 未来可扩展: GATE_T62P=5, GATE_T62D=6, ...
};
// 信号节点
struct PlcSignal {
int no; // 信号编号(映射 dc_signal_out.no
int x, y; // 画布坐标
};
// 逻辑门
struct PlcGate {
GateType type;
int id; // 同类型门内序号
int x, y; // 画布坐标
};
// 连线
struct PlcLink {
int srcType; // 源节点类型: 1=输入信号, 2~4=逻辑门
int srcId; // 源节点ID
int dstType; // 目标节点类型: 0=输出信号, 2~4=逻辑门
int dstId; // 目标节点ID
};
// Comb 组合块(递归结构)
struct PlcComb {
QVector<PlcSignal> inputs; // 本层输入信号
QVector<PlcComb> subCombs; // 嵌套子Comb
PlcGate gate; // 本层逻辑门
QVector<PlcSignal> outputs; // 本层输出信号
};
// 逻辑链
struct PlcChain {
int gatesCount; // gates 属性
int inputsCount; // inputs 属性
int outputsCount; // outputs 属性
bool isComb; // true=组合链, false=简单链
QVector<PlcSignal> inputs; // (简单链) 输入信号
PlcGate gate; // (简单链) 逻辑门
PlcComb comb; // (组合链) 顶层Comb
QVector<PlcSignal> outputs; // 输出信号
};
// 整个配置文件
struct PlcConfig {
QString name;
QString key;
QString checksum;
QVector<PlcChain> chains;
};
```
### 4.2 XML 解析实现
```cpp
#include <QtXml/QDomDocument>
#include <QFile>
#include <QDebug>
// 从 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;
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";
}
return "OR";
}
// 解析 <Signal> 元素
static PlcSignal parseSignal(const QDomElement &el) {
PlcSignal s;
s.no = el.attribute("no").toInt();
s.x = el.attribute("x").toInt();
s.y = el.attribute("y").toInt();
return s;
}
// 解析 <Gate> 元素
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();
return g;
}
// 解析 <Inputs> 或 <Outputs> 元素
static QVector<PlcSignal> parseSignalList(const QDomElement &parent,
const QString &tagName) {
QVector<PlcSignal> list;
QDomElement container = parent.firstChildElement(tagName);
if (!container.isNull()) {
QDomElement sig = container.firstChildElement("Signal");
while (!sig.isNull()) {
list.append(parseSignal(sig));
sig = sig.nextSiblingElement("Signal");
}
}
return list;
}
// 递归解析 <Comb> 元素
static bool parseComb(const QDomElement &combEl, PlcComb &comb) {
if (combEl.isNull()) return false;
// 1. 本层直接输入
comb.inputs = parseSignalList(combEl, "Inputs");
// 2. 递归解析嵌套子 Comb
QDomElement subComb = combEl.firstChildElement("Comb");
while (!subComb.isNull()) {
PlcComb sub;
if (parseComb(subComb, sub)) {
comb.subCombs.append(sub);
}
subComb = subComb.nextSiblingElement("Comb");
}
// 3. 本层 Gate
QDomElement gateEl = combEl.firstChildElement("Gate");
if (!gateEl.isNull()) {
comb.gate = parseGate(gateEl);
}
// 4. 本层输出
comb.outputs = parseSignalList(combEl, "Outputs");
return true;
}
// 解析整条 <Chain>
static PlcChain parseChain(const QDomElement &chainEl) {
PlcChain chain;
chain.gatesCount = chainEl.attribute("gates").toInt();
chain.inputsCount = chainEl.attribute("inputs").toInt();
chain.outputsCount = chainEl.attribute("outputs").toInt();
// 判断是否有 Comb组合链
QDomElement combEl = chainEl.firstChildElement("Comb");
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;
}
}
// 链级顶层 Outputs
chain.outputs = parseSignalList(chainEl, "Outputs");
} else {
// 简单链: Inputs → Gate → Outputs
chain.inputs = parseSignalList(chainEl, "Inputs");
QDomElement gEl = chainEl.firstChildElement("Gate");
if (!gEl.isNull()) chain.gate = parseGate(gEl);
chain.outputs = parseSignalList(chainEl, "Outputs");
}
return chain;
}
// 主解析函数: 从文件路径加载 XML
PlcConfig parseXmlFile(const QString &filePath) {
PlcConfig config;
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Cannot open file:" << filePath;
return config;
}
QDomDocument doc;
if (!doc.setContent(&file)) {
qWarning() << "XML parse error:" << filePath;
file.close();
return config;
}
file.close();
QDomElement root = doc.documentElement();
if (root.tagName() != "PLCConfig") {
qWarning() << "Root element is not <PLCConfig>";
return config;
}
config.name = root.attribute("name");
config.key = root.attribute("key");
config.checksum = root.attribute("checksum");
QDomElement chainEl = root.firstChildElement("Chain");
while (!chainEl.isNull()) {
config.chains.append(parseChain(chainEl));
chainEl = chainEl.nextSiblingElement("Chain");
}
qDebug() << "Parsed" << config.chains.size() << "chains from" << filePath;
return config;
}
```
---
## 五、XML 生成算法C++ / Qt
### 5.1 生成实现
```cpp
#include <QtXml/QDomDocument>
#include <QFile>
#include <QtCore/QCryptographicHash>
// 计算 CRC-32/MPEG-2与 RTU 一致)
// 简化版: 实际应使用完整的 CRC32 MPEG2 查表算法
// 此处仅示意,生产环境请使用完整 CRC 实现库(如 Qt 的 qChecksum 或 zlib crc32
// 生成 <Signal> 元素
static QDomElement createSignalEl(QDomDocument &doc, const PlcSignal &sig) {
QDomElement el = doc.createElement("Signal");
el.setAttribute("no", sig.no);
el.setAttribute("x", sig.x);
el.setAttribute("y", sig.y);
return el;
}
// 生成 <Gate> 元素
static QDomElement createGateEl(QDomDocument &doc, const PlcGate &gate) {
QDomElement el = doc.createElement("Gate");
el.setAttribute("type", gateTypeToString(gate.type));
el.setAttribute("id", gate.id);
el.setAttribute("x", gate.x);
el.setAttribute("y", gate.y);
return el;
}
// 生成 <Inputs>/<Outputs> 容器
static QDomElement createSignalListEl(QDomDocument &doc,
const QString &tagName,
const QVector<PlcSignal> &signals) {
QDomElement el = doc.createElement(tagName);
for (const auto &s : signals) {
el.appendChild(createSignalEl(doc, s));
}
return el;
}
// 递归生成 <Comb> 元素
static QDomElement createCombEl(QDomDocument &doc, const PlcComb &comb) {
QDomElement el = doc.createElement("Comb");
// Inputs
if (!comb.inputs.isEmpty()) {
el.appendChild(createSignalListEl(doc, "Inputs", comb.inputs));
}
// 嵌套子 Comb
for (const auto &sub : comb.subCombs) {
el.appendChild(createCombEl(doc, sub));
}
// Gate
el.appendChild(createGateEl(doc, comb.gate));
// Outputs
if (!comb.outputs.isEmpty()) {
el.appendChild(createSignalListEl(doc, "Outputs", comb.outputs));
}
return el;
}
// 生成 <Chain> 元素
static QDomElement createChainEl(QDomDocument &doc, const PlcChain &chain) {
QDomElement el = doc.createElement("Chain");
el.setAttribute("gates", chain.gatesCount);
el.setAttribute("inputs", chain.inputsCount);
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));
}
if (!chain.outputs.isEmpty()) {
el.appendChild(createSignalListEl(doc, "Outputs", chain.outputs));
}
} else {
// 简单链: Inputs → Gate → Outputs
if (!chain.inputs.isEmpty()) {
el.appendChild(createSignalListEl(doc, "Inputs", chain.inputs));
}
el.appendChild(createGateEl(doc, chain.gate));
if (!chain.outputs.isEmpty()) {
el.appendChild(createSignalListEl(doc, "Outputs", chain.outputs));
}
}
return el;
}
// 主生成函数: 将配置写入 XML 文件
bool writeXmlFile(const PlcConfig &config, const QString &filePath,
const QString &checksumHex) {
QDomDocument doc;
// XML 声明
QDomProcessingInstruction pi =
doc.createProcessingInstruction("xml",
"version=\"1.0\" encoding=\"UTF-8\"");
doc.appendChild(pi);
// 根元素
QDomElement root = doc.createElement("PLCConfig");
root.setAttribute("name", config.name);
root.setAttribute("key", config.key);
root.setAttribute("checksum", checksumHex); // CRC32 十六进制大写
doc.appendChild(root);
// 所有 Chain
for (const auto &chain : config.chains) {
root.appendChild(createChainEl(doc, chain));
}
// 写入文件
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << "Cannot write file:" << filePath;
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
doc.save(stream, 4); // 4 空格缩进
file.close();
qDebug() << "XML saved to" << filePath;
return true;
}
```
### 5.2 checksum 计算方法
```cpp
#include <zlib.h> // 或使用 Qt + 自定义 CRC32 MPEG2 表
// CRC-32/MPEG-2 多项式: 0x04C11DB7, 初始值: 0xFFFFFFFF
// 注意: 计算时应对 XML 字符串的内容计算,**不含** checksum 属性自身
// 实战中建议调用 RTU 侧接口获取 checksum 值,或在生成 XML 后
// 通过 RTU 命令行方式让设备重新计算 checksum
```
> **建议**`key` 字段用 `QDateTime::currentMSecsSinceEpoch()` 生成时间戳毫秒字符串。`checksum` 字段可调用 RTU 侧提供的 CRC 计算接口,或在生成 XML 后由 RTU 自动校验/更新。
---
## 六、简单链与组合链对照
### 6.1 简单链示例
```xml
<!-- 一条简单链: 信号59和60做OR运算输出到1361 -->
<Chain gates="1" inputs="2" outputs="1">
<Inputs>
<Signal no="59" x="-676" y="69" />
<Signal no="60" x="-674" y="183" />
</Inputs>
<Gate type="OR" id="0" x="-346" y="141" />
<Outputs>
<Signal no="1361" x="-17" y="137" />
</Outputs>
</Chain>
```
**逻辑等效**`out[1361] = in[59] OR in[60]`
### 6.2 组合链示例
```xml
<!-- 组合链: 多层嵌套 -->
<!-- 内层1: 62,63,64,65 做 AND → 输出到 1363,1364,1365 -->
<!-- 内层2: 66 和 内层1 Gate 做 AND → 输出到 1366 -->
<!-- 外层: 67 和内层2 Gate 做 OR → 输出到 1367 -->
<!-- 链级: 外层 Gate(OR) 做 NOT → 输出到 1368 -->
<Chain gates="4" inputs="6" outputs="6">
<Comb>
<Inputs>
<Signal no="67" x="-6" y="937" />
</Inputs>
<Comb>
<Inputs>
<Signal no="66" x="-333" y="876" />
</Inputs>
<Comb>
<Inputs>
<Signal no="62" x="-669" y="413" />
<Signal no="63" x="-670" y="534" />
<Signal no="64" x="-667" y="650" />
<Signal no="65" x="-669" y="769" />
</Inputs>
<Gate type="AND" id="0" x="-346" y="595" />
<Outputs>
<Signal no="1363" x="-9" y="490" />
<Signal no="1364" x="-11" y="599" />
<Signal no="1365" x="-10" y="700" />
</Outputs>
</Comb>
<Gate type="AND" id="1" x="-42" y="829" />
<Outputs>
<Signal no="1366" x="324" y="773" />
</Outputs>
</Comb>
<Gate type="OR" id="1" x="288" y="890" />
<Outputs>
<Signal no="1367" x="627" y="836" />
</Outputs>
</Comb>
<Gate type="NOT" id="1" x="594" y="950" />
<Outputs>
<Signal no="1368" x="915" y="947" />
</Outputs>
</Chain>
```
**逻辑等效**
```
内层1 Gate(AND id=0): out[1363..1365] = in[62] AND in[63] AND in[64] AND in[65]
内层2 Gate(AND id=1): out[1366] = in[66] AND 内层1_Gate
外层 Gate(OR id=1): out[1367] = in[67] OR 内层2_Gate
链级 Gate(NOT id=1): out[1368] = NOT 外层_Gate
```
---
## 七、信号编号no的获取
QT 上位机需要通过 WebSocket 与 RTU 通信获取当前已注册的信号列表。
### 7.1 请求信号列表
```json
{
"curd": "get",
"signal_type": "out"
}
```
### 7.2 响应格式
RTU 返回 out 信号数组,每条信号包含:
| 字段 | 类型 | 说明 |
|------|------|------|
| `no` | int | 信号序号(即 XML 中的 `Signal.no` |
| `saddr` | string | 信号地址,如 `"plc.st.out.0"` |
| `desc` | string | 信号描述,如 `"plc输出0"` |
| `val` | variant | 当前值 |
| `data_type` | string | 数据类型 |
### 7.3 输出信号号段约定
- **RTU 内置 PLC 输入信号**`no` 在 **5967** 左右(实现中通过 `out` 信号表动态注册)
- **RTU 内置 PLC 输出信号**`no` 在 **13611368** 左右
- QT 上位机应**动态读取信号列表**而非硬编码号段
---
## 八、注意事项
| 编号 | 注意点 |
|------|--------|
| 1 | `<Comb>` 最多嵌套 **3 层**(见示例),更深层级需验证 RTU 固件支持 |
| 2 | `checksum` 必须使用 CRC-32/MPEG-2 算法,且大写十六进制格式 |
| 3 | `key` 建议使用 `QDateTime::currentMSecsSinceEpoch()` 生成,保证唯一性 |
| 4 | `x`/`y` 坐标可不填写(设为 0RTU 解析时忽略这些字段 |
| 5 | XML 文件编码必须为 **UTF-8**,不要使用 UTF-8 BOM |
| 6 | `<Chain>``gates`/`inputs`/`outputs` 属性值为统计数量,生成时必须准确填写 |
| 7 | Comb 内 Gate 的 `id` 表示**同类型逻辑门内的序号**OR/AND/NOT 分别计数,从 0 起递增) |
| 8 | 生成 XML 后建议通过 RTU Web 界面或命令行 `plc cfg reload` 触发配置重新加载 |
| 9 | 一个 `<PLCConfig>` 可含多条 `<Chain>`,但目前 RTU 将所有 Chain 合并为一个逻辑图处理 |
---
## 九、附录:完整 XML 示例文件
```xml
<?xml version="1.0" encoding="UTF-8"?>
<PLCConfig name="PLC Logic" key="1783070862205" checksum="3486BE04">
<!-- 链1: 简单或门 (59 OR 60 → 1361) -->
<Chain gates="1" inputs="2" outputs="1">
<Inputs>
<Signal no="59" x="-676" y="69" />
<Signal no="60" x="-674" y="183" />
</Inputs>
<Gate type="OR" id="0" x="-346" y="141" />
<Outputs>
<Signal no="1361" x="-17" y="137" />
</Outputs>
</Chain>
<!-- 链2: 简单的非门 (61 → NOT → 1362) -->
<Chain gates="1" inputs="1" outputs="1">
<Inputs>
<Signal no="61" x="-669" y="298" />
</Inputs>
<Gate type="NOT" id="0" x="-345" y="296" />
<Outputs>
<Signal no="1362" x="-10" y="294" />
</Outputs>
</Chain>
<!-- 链3: 三层嵌套组合逻辑 -->
<Chain gates="4" inputs="6" outputs="6">
<Comb>
<Inputs>
<Signal no="67" x="-6" y="937" />
</Inputs>
<Comb>
<Inputs>
<Signal no="66" x="-333" y="876" />
</Inputs>
<Comb>
<Inputs>
<Signal no="62" x="-669" y="413" />
<Signal no="63" x="-670" y="534" />
<Signal no="64" x="-667" y="650" />
<Signal no="65" x="-669" y="769" />
</Inputs>
<Gate type="AND" id="0" x="-346" y="595" />
<Outputs>
<Signal no="1363" x="-9" y="490" />
<Signal no="1364" x="-11" y="599" />
<Signal no="1365" x="-10" y="700" />
</Outputs>
</Comb>
<Gate type="AND" id="1" x="-42" y="829" />
<Outputs>
<Signal no="1366" x="324" y="773" />
</Outputs>
</Comb>
<Gate type="OR" id="1" x="288" y="890" />
<Outputs>
<Signal no="1367" x="627" y="836" />
</Outputs>
</Comb>
<Gate type="NOT" id="1" x="594" y="950" />
<Outputs>
<Signal no="1368" x="915" y="947" />
</Outputs>
</Chain>
</PLCConfig>
```
---
> **文档维护**:本文档基于 RTU 固件 [src/system/libplc/src/plc.cpp](src/system/libplc/src/plc.cpp) 中的 XML 解析逻辑编写。配置文件格式如随固件升级变更,请同步更新本文档。