# 工程 JSON 2.0 格式说明 ## 格式策略 当前只有一个有效工程格式:`2.0`。代码直接读写本文描述的结构,不为 `1.0` 或早期开发草稿增加迁移分支、字段推断或双写逻辑;旧版本加载会明确失败。缺少必填字段、字段类型错误、引用不存在或领域校验失败时,加载必须失败且不得替换当前工程。 ## 顶层结构 ```json { "formatVersion": "2.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` 重新启动。 ## HMI 页面与控件 ```json { "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` | | `indicator` | M 地址或 `null` 草稿 | 无 | | `numericDisplay` | D 地址或 `null` 草稿 | `dataType` | | `numericInput` | D 地址或 `null` 草稿 | `dataType` | | `label` | 必须为 `null` | 无 | | `pageJump` | 必须为 `null` | `targetPageId` | | `alarmList` | 必须为 `null` | 无 | `type` 只接受表中列出的控件类型。其他类型按严格 `2.0` 规则直接拒绝加载,不做兼容迁移。 数值显示和数值输入控件必须额外保存 `dataType`,值只能是 `int16` 或 `float32`: ```json { "id": "temperature", "type": "numericDisplay", "bounds": {"x": 20, "y": 20, "width": 160, "height": 40}, "text": "温度", "binding": {"area": "D", "index": 10}, "properties": {}, "dataType": "float32" } ``` `int16` 只占起始 D 一个字,沿用现有有符号 16 位逻辑。`float32` 使用 IEEE-754 单精度,占用起始 D 和下一个 D,低地址保存低字、高地址保存高字;起始地址只能是 `D0~D3999`。数值控件之间允许相同起始地址和相同类型重复绑定,部分重叠或类型冲突会被拒绝。工程格式固定为严格 `2.0`,数值控件缺少 `dataType` 时加载失败。 按钮操作字符串为 `setOn`、`setOff`、`toggle` 或 `momentaryOn`。PageJump 使用目标页面稳定 ID,不使用名称或数组位置: ```json { "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 条。超过 5 条时通过表头翻页按钮查看,记录不会被丢弃。标题和报警文本按单行绘制,空间不足时显示省略号。报警恢复后对应记录立即移除,最后一条报警恢复后控件再次隐藏。 ## 报警定义 ```json { "id": "alarm-1", "address": {"area": "M", "index": 0}, "condition": "mOn", "threshold": 0, "message": "急停已按下" } ``` `alarmDefinitions` 是项目级有序数组,报警 ID 必须唯一。触发条件只包含当前业务需要的三种: | `condition` | 地址区域 | 触发规则 | | --- | --- | --- | | `mOn` | M | 位值为 ON | | `dHigh` | D | 字值大于等于 `threshold` | | `dLow` | D | 字值小于等于 `threshold` | `threshold` 使用有符号 16 位范围;M 报警仍保留该必填字段但不参与判断。报警文本不能为空,编辑界面的输入上限为 20 个字符。运行界面按单行绘制,超出当前消息列宽度时显示省略号。发生时间、活动状态、确认状态和清除时间属于运行会话,不进入工程 JSON。 ## 地址注释与网络注释 `registerComments` 是工程级 M/D 地址元数据数组。每个元素必须包含有效的 `address` 和非空 `text`,同一地址只能出现一次。软元件注释只能使用单行文本,不能包含回车或换行,且不能超过 64 个 UTF-8 字节: ```json { "address": {"area": "M", "index": 0}, "text": "启动按钮" } ``` 注释不保存寄存器当前值,也不自动把地址加入 Modbus 轮询。网络注释属于 `LadderRung`,与行 ID、名称、连续网格和输出并列保存。网络注释允许为空;非空时只能使用单行文本,不能包含回车或换行,且不能超过 128 个 UTF-8 字节: ```json { "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 } ``` ## 控制逻辑 ```json { "id": "logic-1", "name": "主控制逻辑", "enabled": true, "rungs": [] } ``` `enabled` 决定离线执行器是否扫描该逻辑。所有启用逻辑按 `controlLogics` 数组顺序执行;禁用逻辑仍要求 ID、名称、行和连续网格结构合法,但允许保留未完成节点作为草稿。 控制逻辑初始可以没有行,`rungs` 为空不是错误。编辑器第一次添加条件、横线或输出时才创建行;删除最后一行后可以再次保存为空逻辑。没有输出的空行草稿允许保存 10 个 `Gap` 网格。输出行也允许暂存未完成的条件路径;运行时 `Gap` 会按断路处理,只有实际导通的输出槽才执行。 2.0 使用连续网格模型。每条 `LadderRung` 固定保存 10 个 `cells`,每格的 `kind` 是 `gap`、`wire` 或 `node`;条件节点直接嵌在 `node` 格中。行不再是互相隔离的网络容器,行间连接由控制逻辑的 `verticalConnections` 保存。长竖线由多个相邻连接对象组成,插入行会拆分原连接,删除行只在上下两段同列存在时合并。2.0 不再把 `ConditionExpression` 写入工程 JSON,也不读取旧 `condition` 字段: ```json {"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 轮询地址。节点配置的类型和值域如下: ```json {"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` 网格加输出槽。 当前实现只读写本文定义的严格 `2.0`。`1.0` 或其他旧工程不会自动迁移,加载时直接返回不支持的格式版本。