# 工程 JSON 1.0 格式说明 ## 格式策略 当前开发阶段只有一个有效工程格式:`1.0`。代码直接读写本文描述的结构,不为早期开发草稿增加同版本默认值、字段推断、迁移分支或双写逻辑。缺少必填字段、字段类型错误、引用不存在或领域校验失败时,加载必须失败且不得替换当前工程。 ## 顶层结构 ```json { "formatVersion": "1.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` 草稿 | 无 | | `numericInput` | D 地址或 `null` 草稿 | 无 | | `label` | 必须为 `null` | 无 | | `pageJump` | 必须为 `null` | `targetPageId` | | `alarmList` | 必须为 `null` | 无 | | `progressBar` | D 地址或 `null` 草稿 | `minimumValue`、`maximumValue`、`showValue` | 按钮操作字符串为 `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 条时通过表头翻页按钮查看,记录不会被丢弃。标题和报警文本按单行绘制,空间不足时显示省略号。报警恢复后对应记录立即移除,最后一条报警恢复后控件再次隐藏。 ProgressBar 使用绑定 D 地址的有符号 16 位字值,并将其限制在 `minimumValue` 到 `maximumValue` 的范围后映射为百分比。`minimumValue` 和 `maximumValue` 必须是有符号 16 位整数, 且最小值严格小于最大值;`showValue` 为布尔值,决定是否在进度条上显示百分比。ProgressBar 是只读控件,不产生 PLC 写请求。 ## 报警定义 ```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": "启动按钮接通后延时启动", "condition": null, "output": null } ``` ## 控制逻辑 ```json { "id": "logic-1", "name": "主控制逻辑", "enabled": true, "rungs": [] } ``` `enabled` 决定离线执行器是否扫描该逻辑。所有启用逻辑按 `controlLogics` 数组顺序执行;禁用逻辑仍要求 ID、名称、网络和表达式结构合法,但允许保留未完成节点作为草稿。 网络、`Node / Wire / Series / Parallel` 条件表达式、触点、边沿、T/C 触点、比较、线圈和输出指令字段沿用当前结构化梯形图模型。横线保存为恒真的 `Wire` 叶子: ```json {"id": "wire-1", "kind": "wire", "columnSpan": 2} ``` `columnSpan` 是必填整数,范围为 `1~10`。它表示横线占用的条件区逻辑网格列数,不是像素长度。单个网络前 10 列是条件区,第 11 列固定放输出指令。竖线不保存为独立对象,而是由 `Parallel` 表达式的支路边界派生。画布像素坐标、自由连线和运行轨迹是 UI 或运行会话投影,不进入 JSON;横线不绑定寄存器,也不产生 M/D 轮询地址。节点配置的类型和值域如下: ```json {"type": "edgeContact", "address": {"area": "M", "index": 0}, "mode": "rising"} {"type": "edgeContact", "address": {"area": "M", "index": 0}, "mode": "falling"} {"type": "timerContact", "timer": {"index": 0}, "mode": "normallyOpen"} {"type": "ton", "timer": {"index": 0}, "presetMs": 1000} {"type": "counterContact", "counter": {"index": 0}, "mode": "normallyOpen"} {"type": "counter", "counter": {"index": 0}, "mode": "up", "currentValueAddress": {"area": "D", "index": 10}, "preset": {"kind": "constant", "value": 10}, "resetAddress": {"area": "M", "index": 0}} {"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}} ``` T/C 资源地址范围均为 `0~4000`,TON `presetMs` 范围为 `1~86400000`。C 仅标识离线计数器实例,计数当前值必须绑定 D 地址,复位必须绑定 M 地址,PV 可为有符号 16 位常量或 D 地址。CTU/CTD 按条件上升沿分别递增/递减;CTU 复位将 CV 置 0,CTD 复位将 CV 装载 PV,CV 始终限制在 `0~32767`,C 触点读取完成状态。上升沿和下降沿是一次扫描脉冲;TON 是非保持接通延时,输入断开时 `ET/Q` 立即清零。T/C 不参与 Modbus,也不写入 M/D 仓库。 MOVE、ADD 和 SUB 只能把结果写入 D 地址,源操作数和算术左右操作数可以是有符号 16 位常量或 D 地址。指令仅在所在网络条件成立时执行;ADD/SUB 使用更宽中间类型计算,结果超出 `-32768~32767` 时饱和到对应边界,并在离线运行轨迹中记录 `overflow: true`,不会中止扫描。 ## 校验与读写 保存顺序为领域校验、JSON 序列化、`QSaveFile` 原子提交。加载顺序为 JSON 语法、`formatVersion`、必填字段和类型、领域关系校验,全部成功后才替换当前工程。 工程保存允许未绑定的寄存器控件和未完成梯形图草稿。进入离线运行还必须通过运行校验:所有 HMI 寄存器控件完成绑定、所有 PageJump 目标有效、所有报警定义合法、所有启用逻辑包含输出指令;禁用草稿逻辑不阻止运行。只有输出而没有条件的网络表示左侧电源线直接接通,运行时按恒真条件执行。