当前只有一个有效工程格式:4.0。代码直接读写本文描述的结构,不为 1.0、2.0、3.0 或早期开发草稿增加迁移分支、字段推断或双写逻辑;旧版本加载会明确失败。缺少必填字段、字段类型错误、引用不存在或领域校验失败时,加载必须失败且不得替换当前工程。
{
"formatVersion": "4.0",
"id": "project-1",
"name": "包装线",
"hmiPages": [],
"initialHmiPageId": "",
"alarmDefinitions": [],
"registerComments": [],
"controlLogics": []
}
所有顶层字段必填。空工程允许 hmiPages、alarmDefinitions、registerComments 和 controlLogics 为空,此时 initialHmiPageId 必须为空。存在页面时,initialHmiPageId 必须引用 hmiPages 中的页面 ID。
hmiPages 与 controlLogics 都是有序数组。页面顺序用于工程树显示;控制逻辑顺序是离线扫描顺序。对象 ID 稳定且唯一,名称可修改且在同类对象中唯一。
编辑器的当前页面 ID 和当前逻辑 ID 是主窗口会话状态,不进入工程 JSON。每次进入运行态时,HMI 从 initialHmiPageId 重新启动。
{
"id": "page-1",
"name": "主操作页面",
"width": 800,
"height": 400,
"controls": []
}
每个控件都要求 id、type、bounds、text、binding 和 properties。bounds 包含整数 x、y、width、height,矩形必须完整位于页面内。binding 为寄存器对象或 JSON null。
页面尺寸是当前 PC HMI 编辑器的业务尺寸,宽度必须为 320~1600,高度必须为 200~800。默认页面尺寸为 800×400;超出范围的 JSON 页面直接加载失败。
properties 保存控件外观扩展属性。当前保留以下 HMI 外观属性名:
| 属性名 | 值格式 | 说明 |
|---|---|---|
textColor |
#RRGGBB 字符串 |
控件普通文字颜色,缺失时使用控件默认颜色 |
fontSize |
6~72 的十进制数字字符串 |
控件文字字号 |
fontBold |
true 或 false 字符串 |
是否使用粗体 |
fontItalic |
true 或 false 字符串 |
是否使用斜体 |
字体和颜色属于静态显示属性,不参与 M/D 绑定、PLC 轮询和离线控制逻辑。未知扩展属性仍按字符串保留。
type |
绑定 | 额外必填字段 |
|---|---|---|
button |
M 地址或 null 草稿 |
buttonOperation、buttonEnableCondition |
indicator |
M 地址或 null 草稿 |
无 |
numericDisplay |
D 地址或 null 草稿 |
dataType |
numericInput |
D 地址或 null 草稿 |
dataType |
label |
必须为 null |
无 |
statusText |
M/D 地址或 null 草稿 |
statusText;D 模式还要求 dataType |
pageJump |
必须为 null |
targetPageId |
alarmList |
必须为 null |
无 |
type 只接受表中列出的控件类型。其他类型按严格 4.0 规则直接拒绝加载,不做兼容迁移。
数值显示和数值输入控件必须额外保存 dataType,值只能是 int16、int32、float32 或 float64:
{
"id": "temperature",
"type": "numericDisplay",
"bounds": {"x": 20, "y": 20, "width": 160, "height": 40},
"text": "温度",
"binding": {"area": "D", "index": 10},
"properties": {},
"dataType": "float32"
}
四种数值类型的 JSON 与地址规则如下:
dataType |
含义 | 占用字数 | 有效起始地址 |
|---|---|---|---|
int16 |
有符号 16 位整数 | 1 | D0~D4000 |
int32 |
有符号 32 位整数 | 2 | D0~D3999 |
float32 |
IEEE-754 单精度浮点 | 2 | D0~D3999 |
float64 |
IEEE-754 双精度浮点,界面显示为 Double (Float64) |
4 | 偶数首地址 D0~D3996 |
多字值统一按信捷字序保存:低地址放低 16 位,地址递增时依次保存更高的 16 位。数值控件和 D 状态文本之间允许相同起始地址和相同类型重复绑定,部分重叠或同起始地址不同类型会被拒绝。要求 dataType 的控件缺少该字段或使用 int64 等未知枚举时加载失败。
按钮除了自身的 M 位操作外,还可以保存一个可选的启用条件。没有条件时,buttonEnableCondition 必须为 JSON null;有条件时只能保存一个 mBit 或 dValue 条件:
{
"id": "start-button",
"type": "button",
"bounds": {"x": 20, "y": 20, "width": 120, "height": 40},
"text": "启动",
"binding": {"area": "M", "index": 10},
"properties": {},
"buttonOperation": "momentaryOn",
"buttonEnableCondition": {
"type": "mBit",
"address": {"area": "M", "index": 0},
"expected": false
}
}
mBit 使用 expected 判断 M 位 ON/OFF;dValue 还必须提供 dataType、operation 和有限数值 value。operation 只能是 equal、notEqual、lessThan、lessThanOrEqual、greaterThan 或 greaterThanOrEqual。Int16/Int32 的比较值必须是对应范围内的整数,Float32/Float64 必须是可表示的有限值。条件地址及其连续 D 字会加入真机 PLC 轮询;条件读回失败时运行画面将按钮置灰并禁止操作。
状态文本是独立只读控件。M 模式保存 OFF/ON 文本:
{
"id": "machine-state",
"type": "statusText",
"bounds": {"x": 20, "y": 80, "width": 140, "height": 40},
"text": "状态文本",
"binding": {"area": "M", "index": 0},
"properties": {},
"statusText": {
"source": "m",
"offText": "设备停止",
"onText": "设备运行"
}
}
D 模式保存数值类型和 1~16 个连续区间:
{
"id": "temperature-state",
"type": "statusText",
"bounds": {"x": 180, "y": 80, "width": 140, "height": 40},
"text": "状态文本",
"binding": {"area": "D", "index": 100},
"properties": {},
"dataType": "float64",
"statusText": {
"source": "d",
"ranges": [
{"lower": null, "upper": 30, "text": "低温"},
{"lower": 30, "upper": 80, "text": "温度正常"},
{"lower": 80, "upper": null, "text": "高温"}
]
}
}
D 区间统一按 [lower, upper) 匹配,即包含下限、不包含上限。第一条 lower 必须为 null,最后一条 upper 必须为 null,相邻区间的前一条 upper 必须等于后一条 lower,从而覆盖整个数值域且不留空档、不重叠。Int16/Int32 边界必须为整数,所有边界必须是有限数值。状态结果文本不能为空,只允许单行且最多 256 个 UTF-8 字节。编辑草稿允许 binding 为 null,进入运行态前必须绑定;运行读取失败时画面显示 --。
按钮操作字符串为 setOn、setOff、toggle 或 momentaryOn。PageJump 使用目标页面稳定 ID,不使用名称或数组位置:
{
"id": "page-jump-1",
"type": "pageJump",
"bounds": {"x": 20, "y": 20, "width": 120, "height": 40},
"text": "参数设置",
"binding": null,
"properties": {},
"targetPageId": "page-2"
}
编辑草稿允许 targetPageId 为空字符串,但进入运行态前必须配置为现存页面 ID。任何页面被 PageJump 引用时不得删除。
AlarmList 只负责显示项目级当前报警,不保存独立触发逻辑,也不直接绑定单个寄存器。编辑界面的标题输入上限为 12 个字符。多个页面放置 AlarmList 时共享同一组当前报警和确认状态。运行态始终按编辑时设置的位置和尺寸显示控件;有报警时逐行显示,没有报警时显示空状态。每页最多显示 5 条,超出时通过表头翻页按钮查看,记录不会被丢弃。标题和报警文本按单行绘制,空间不足时显示省略号。报警恢复后对应记录立即移除,不改变控件的可见性和尺寸。
{
"id": "alarm-1",
"address": {"area": "M", "index": 0},
"condition": "mOn",
"threshold": 0,
"message": "急停已按下"
}
alarmDefinitions 是项目级有序数组,报警 ID 必须唯一。触发条件只包含当前业务需要的四种:
condition |
地址区域 | 触发规则 |
|---|---|---|
mOn |
M | 位值为 ON |
mOff |
M | 位值为 OFF |
dHigh |
D | 字值大于等于 threshold |
dLow |
D | 字值小于等于 threshold |
threshold 使用有符号 16 位范围;M 报警仍保留该必填字段但不参与判断。报警文本不能为空,编辑界面的输入上限为 20 个字符。运行界面按单行绘制,超出当前消息列宽度时显示省略号。发生时间、活动状态、确认状态和清除时间属于运行会话,不进入工程 JSON。
registerComments 是工程级 M/D 地址元数据数组。每个元素必须包含有效的 address 和非空 text,同一地址只能出现一次。软元件注释只能使用单行文本,不能包含回车或换行,且不能超过 64 个 UTF-8 字节:
{
"address": {"area": "M", "index": 0},
"text": "启动按钮"
}
注释不保存寄存器当前值,也不自动把地址加入 Modbus 轮询。网络注释使用网络首行的 LadderRung.comment 保存,与行 ID、名称、连续网格和输出并列;通过任意竖线连接到上一行的支路行必须保存空字符串。同一网络只有首行注释可以非空,违反该规则的 4.0 工程直接拒绝加载,不迁移或回退。网络注释允许为空;非空时只能使用单行文本,不能包含回车或换行,且不能超过 128 个 UTF-8 字节:
{
"id": "rung-1",
"name": "启动网络",
"comment": "启动按钮接通后延时启动",
"cells": [
{"id":"cell-0","kind":"gap"}, {"id":"cell-1","kind":"gap"},
{"id":"cell-2","kind":"gap"}, {"id":"cell-3","kind":"gap"},
{"id":"cell-4","kind":"gap"}, {"id":"cell-5","kind":"gap"},
{"id":"cell-6","kind":"gap"}, {"id":"cell-7","kind":"gap"},
{"id":"cell-8","kind":"gap"}, {"id":"cell-9","kind":"gap"}
],
"output": null
}
{
"id": "logic-1",
"name": "主控制逻辑",
"enabled": true,
"rungs": []
}
enabled 决定离线执行器是否扫描该逻辑。所有启用逻辑按 controlLogics 数组顺序执行;禁用逻辑仍要求 ID、名称、行和连续网格结构合法,但允许保留未完成节点作为草稿。
控制逻辑初始可以没有行,rungs 为空不是错误。编辑器第一次添加条件、横线或输出时才创建行;删除最后一行后可以再次保存为空逻辑。没有输出的空行草稿允许保存 10 个 Gap 网格。输出行也允许暂存未完成的条件路径;运行时 Gap 会按断路处理,只有实际导通的输出槽才执行。
4.0 使用连续网格模型。每条 LadderRung 固定保存 10 个 cells,每格的 kind 是 gap、wire 或 node;条件节点直接嵌在 node 格中。行不再是互相隔离的网络容器,行间连接由控制逻辑的 verticalConnections 保存。长竖线由多个相邻连接对象组成,插入行会拆分原连接,删除行只在上下两段同列存在时合并。4.0 不把 ConditionExpression 写入工程 JSON,也不读取旧 condition 字段:
{"id":"vertical-1","upperRungId":"rung-1","lowerRungId":"rung-2","columnBoundary":2}
每行 cells 必须严格为 10 个。鼠标拖动会逐格设置横线或空白,删除横线只恢复目标格;竖线连接的行在执行器中共享列边界电源。
每个 cells 元素只描述一个固定列,例如 {"id":"cell-1","kind":"wire"}、{"id":"cell-2","kind":"gap"},或者带 node 对象的 kind":"node"。横线和空白不使用 columnSpan;每个格子固定占用一列。Gap 表示明确断路,可以保存和重新加载;执行器遇到 Gap 时该行电源在此处断开,允许作为未完成草稿或并联网络中的断路支路。
单个行前 10 列是条件区,第 11 列固定放输出指令。空白格可作为编辑草稿,输出行中的断路会让该支路不导通。左右母线、格子端子和竖线均按网格模型绘制,画布像素坐标和运行轨迹不进入 JSON;横线和断路不绑定寄存器,也不产生 M/D 轮询地址。节点配置的类型和值域如下:
{"type": "contact", "address": {"area": "M", "index": 0}, "mode": "normallyOpen"}
{"type": "edgeContact", "address": {"area": "M", "index": 0}, "mode": "rising"}
{"type": "edgeContact", "address": {"area": "M", "index": 0}, "mode": "falling"}
{"type": "compare", "address": {"area": "D", "index": 0}, "comparison": "greaterThanOrEqual", "value": 10}
{"type": "coil", "address": {"area": "M", "index": 1}, "mode": "normal"}
{"type": "move", "source": {"kind": "register", "address": {"area": "D", "index": 1}}, "destination": {"area": "D", "index": 2}}
{"type": "arithmetic", "operation": "add", "left": {"kind": "register", "address": {"area": "D", "index": 2}}, "right": {"kind": "constant", "value": 1}, "destination": {"area": "D", "index": 2}}
当前 LogicNodeConfig 只接受 contact、edgeContact、coil、compare、move 和 arithmetic。timerContact、counterContact、ton、counter 等未支持类型会使工程加载失败,不会被忽略或自动迁移。触点模式为 normallyOpen / normallyClosed,边沿模式为 rising / falling,线圈模式为 normal / set / reset;比较运算支持 equal / notEqual / lessThan / lessThanOrEqual / greaterThan / greaterThanOrEqual,算术运算使用 add / subtract。上升沿和下降沿在离线执行器及真机本地推算中产生一次扫描脉冲。
MOVE、ADD 和 SUB 只能把结果写入 D 地址,源操作数和算术左右操作数可以是有符号 16 位常量或 D 地址。指令仅在所在网络条件成立时执行;ADD/SUB 使用更宽中间类型计算,结果超出 -32768~32767 时饱和到对应边界,并在本地运行轨迹中记录 overflow: true,不会中止扫描。真机本地推算时这些结果只写临时仓库,不写 PLC。
保存顺序为领域校验、JSON 序列化、QSaveFile 原子提交。加载顺序为 JSON 语法、formatVersion、必填字段和类型、领域关系校验,全部成功后才替换当前工程。
工程保存允许未绑定的寄存器控件和未完成梯形图草稿。进入离线运行还必须通过运行校验:所有 HMI 寄存器控件完成绑定、所有 PageJump 目标有效、所有报警定义合法、所有启用逻辑中的节点已配置;Gap 仍表示断路并会让该支路不导通,不会被当成隐含横线。禁用草稿逻辑不阻止运行。左侧电源线直接接通的输出必须保存为 10 个 Wire 网格加输出槽。
当前实现只读写本文定义的严格 4.0。1.0、2.0 或其他旧工程不会自动迁移,加载时直接返回不支持的格式版本。