# 验证

> 配置健康页面：工作站在流程、逻辑、仪表画面以及服务器和 Broker 的列表里扫描什么、它能报出的每一条发现以及它们的措辞、点一行会打开什么、导航栏上的角标，以及为什么这里不修任何东西。

验证页面列出工作站站不住脚的地方：某个通道绑定、某个联锁、某个自动化字段、某个 Logic 表达式、脚本或条件，或者某个仪表画面组件，指向了工作站已经没有的点位、变量、流程或地址；还有写法被它的编辑器拒绝的 Logic 表达式、脚本或条件；还有某个 Process 动作，它的文书按现在的配置一次运行发不出来；还有一次运行的启动会拒绝的流程。它是导航栏底部的**验证**入口，对持有**查看验证**权限的身份打开，地址是 `/validation`。它是一个滚动页面，按整幅宽度绘制，因为每一条发现都是一句话。它是状态栏里运行时健康状态的兄弟：那一个说工作站是否在读，这一个说配置是否仍然解析得开。

它也列出工作站用来交出数据的那些列表（OPC UA 服务器的列表、Modbus 服务器的寄存器映射、MQTT Broker 的发布列表）中信号源已被删除的行：这样的行仍留在列表里，但不再提供或发布任何内容。

这一页只报告；它从不修理。点一行会打开拥有这处故障的那个编辑器，并停在持有它的那个实体上，修理就是你在那里做的编辑。

## 状态卡片 {#the-status-card}

顶部那张卡片就是这一页对配置的全部答复，也是唯一会宣布工作站干净的地方：

| 状态 | 圆点 | 标题 | 细节 |
|---|---|---|---|
| 干净 | 绿色 | 没有需要修复的地方 | 流程、逻辑、仪表画面以及工作站提供和发布的内容里的每一处引用都能解析，每个流程都能启动，每个动作都具备产出所需的东西。 |
| 有发现 | 琥珀色 | *N* 项需要修复（只有一项时写“1 项”） | 这里不做修复。点开一行会打开拥有该缺陷的编辑器，并定位到承载它的那个对象。 |

卡片上带着这一页唯一的命令，**重新扫描**（悬停提示：“立刻再检查一遍整份配置。只要有东西发生变化，这份列表也会自行刷新。”）。有扫描在进行时它是灰的。因为一次找到同样结果的扫描会让列表原封不动，所以结果报在状态栏的操作记录条上：“重新扫描完成：没有需要修复的地方。”“重新扫描完成：N 项需要修复。”，或者“重新扫描失败：*原因*”。

你很少需要重新扫描。校验器一直开着，跑在工作站上，并在它所读取的任何一个界面上出现一阵改动之后 750 毫秒自己重扫一遍：Connector 的设备、线路和点位；Process 模型；素材；Logic 的变量、动作和报警；仪表画面；标签模板；报告版式。这一页和导航栏上的角标跟着结果走。一次正好赶上 Connector 在挪进挪出设备（一次驱动刷新、一台设备正在添加）的扫描，会等 Connector 安定下来之后重做，因此一个引用绝不会仅仅因为它的设备正在途中就被判为损坏。

工作站用来交出数据的三个列表它也读：在 OPC UA 服务器、Modbus 服务器或某个 Broker 的发布卡片上添加、编辑或删除一行，或者删除某一行指向的点位、变量、报警或命令，都会同样触发重扫。

## 那些分组 {#the-groups}

发现按拥有它们的界面分组，顺序是固定的。里面什么都没有的分组不画出来，因此一台只有一处仪表画面故障的工作站只显示一个分组。

| 分组 | 发现的种类 | 标题下面那一行 |
|---|---|---|
| Process | 绑定（通道绑定、联锁、自动化）、流程和它们布防的事件条目，以及动作文书 | *N* 项。通道绑定、联锁和单元自动化指向了本工作站已经没有的点位、变量或流程，启动会拒绝的流程，以及那些按当前配置无法让一次运行产出文件的动作。 |
| Logic | 表达式变量、动作、报警 | *N* 项。表达式、动作脚本和报警条件读取了一个已经无法解析的地址，或者写法被它们的编辑器拒绝。 |
| 仪表画面 | 组件 | *N* 项。组件的属性指向了一个已经无法解析的地址。它们照常绘制，但什么都读不到。 |
| Connector | OPC UA 服务器列表、Modbus 服务器寄存器映射和 MQTT Broker 发布列表中的行 | *N* 项。信号源已被删除的 OPC UA 服务器列表、Modbus 服务器寄存器映射和 MQTT Broker 发布列表中的行。它们仍留在列表中，但不再提供或发布任何内容。 |

## 一行 {#a-row}

每一行就是一条发现，而且不用打开任何东西就说清了它讲的是什么：

- 一个表示种类的图标：Process 绑定用单元图标，动作文书用打印标志，流程用书面组成标志，事件条目用旗帜标志，Logic 树里的变量、动作和报警各用自己的标志，组件用仪表画面标志。
- 实体的**名称**，随后在类型能说出名称说不出的东西时写上它的**类型**（集成商从未命名过的组件退回到它的种类，这样一行就不会读成“仪表盘 仪表盘”），再随后在有位置时写“位于*位置*”。
- 那条发现：对于指名了目标的字段，写“*字段*指向 `引用`，而它已经不存在。”，其中的引用用等宽字体排；对于运行时用不了的字段，写“*字段*：*哪里不对*。”
- 右边一个箭头：这一行会打开点什么。这一行的悬停提示和它的无障碍名称是整句话再加上点击落到哪里（“在 Process 中打开该单元”“在 Process 中打开该动作”“在 Process 中打开该流程”“在 Process 中打开该事件条目”“在编辑器中打开该组件”“在 Logic 中打开该变量”“在 Logic 中打开该动作”“在 Logic 中打开该报警”）。
- 对服务器列表中的一行，图标是 Connector 树为持有该列表的对象（OPC UA 服务器、Modbus 服务器、MQTT Broker）画的标记，点击落到哪里由悬停提示的末尾说出（“在 OPC UA 服务器中打开该行”“在 Modbus 服务器中打开该行”“在 MQTT Broker 的页面中打开该行”）。

## 这次扫描能产出的每一条发现 {#every-finding-the-scan-can-produce}

下面的措辞就是这一页自己的措辞。通道绑定存的是它信号源的键，所以它那一行写出的是那个再也解析不了的键；联锁、自动化信号源和回写量存的是操作员从来不读的标识，所以它们那一行不把标识打出来，而是说那个点位或变量已被移除。

### Process：通道绑定 {#process-channel-bindings}

类型“通道绑定”；名称：通道的名称（或者“（已移除的通道）”）；位置：“*模型* · *单元*”。打开这个模型的通道屏幕并停在该单元上。

| 字段 | 措辞 |
|---|---|
| 点位 | 指向存下来的点位键，而它已经不存在 |
| 变量 | 指向存下来的变量键，而它已经不存在 |

### Process：联锁 {#process-interlocks}

类型“联锁”；名称：单元的名称；位置：“*模型* · *单元*”。一个单元按它实际观测的东西来判：它自己的信号源，或者在它跟随模型时用模型的默认设置。打开这个模型的自动化屏幕并停在该单元上。

| 字段 | 措辞 |
|---|---|
| 信号源 | 它跟随模型，而模型的默认设置没有指定布尔信号源。 |
| 信号源 | 没有配置布尔信号源。 |
| 信号源 | 点位和变量同时被选中了。 |
| 点位 | 它设定的那个点位已被移除。 |
| 点位 | DateTime 类型的点位无法转换成布尔值。（Guid 和 ByteString 类型的点位同理） |
| 变量 | 它设定的那个变量已被移除。 |
| 变量 | 日期和时间类型的变量无法转换成布尔值。（图片、图标和颜色类型的变量同理） |

前三条只有在这个模型的停止联锁处于启用时才报出来。

### Process：自动化 {#process-automation}

类型“自动化”；名称：单元的名称；位置：“*模型* · *单元*”。只有**已启用**的自动化才被判：关掉的那种什么也不驱动，也就从列表里退场。打开这个模型的自动化屏幕并停在该单元上。

| 字段 | 措辞 |
|---|---|
| 命令 | 没有配置启动信号源。 |
| 流程 | 固定的那个流程不可用。（固定选择方式） |
| 选择器 | 一条信号源发现（见下），针对整数信号源 |
| 选择码 *n* | 它选中的流程已被移除。 |
| *启动*命令的信号源、*停止*命令的信号源等等，每个命令信号源一条 | 一条信号源发现，针对布尔信号源 |
| 元数据 | 有一条映射填的是已被移除的流程或字段。 |
| 元数据“*字段*” | 一条信号源发现（数值字段用数值，其余用布尔、整数、实数、文本或日期），或者：这个字段不支持自动生成。 |
| 回写量“就绪”、回写量“活动”、回写量“所选代码”、回写量“命令结果” | 它写入的那个点位已被移除。/ 它写入的那个点位不可写。/ 它需要一个布尔类型的点位。（就绪、活动）/ 它需要一个整数类型的点位。（所选代码、命令结果） |

一条信号源发现是下列之一：“没有配置信号源。”“点位和变量同时被选中了。”“它读取的那个点位已被移除。”“它读取的那个点位不可读。”（该点位被禁用或者只写）“*类型*类型的点位在这里不兼容。”“它读取的那个变量已被移除。”“*类型*类型的变量在这里不兼容。”兼容性跟着字段的角色走：布尔信号源需要布尔类型的点位或变量；整数信号源需要整数类型；数值信号源需要整数或实数类型；元数据信号源接受布尔、整数、实数、文本或日期中的任何一种类型。

### Process：流程 {#process-procedures}

类型“流程”；名称：流程的名称；位置：模型的名称。这里的判断走的是一次运行启动时走的同一道关口，因此这份列表说跑得起来的流程，就是台架会接受的流程。打开这个流程自己那一页。

| 字段 | 措辞 |
|---|---|
| 组成 | 判定要在记录下来的数据行上做。这个流程带了一条判定，却没有数据记录。 |
| 组成 | 事件条目‘*名称*’监视记录下来的采样点。这个流程把它布防了，却没有数据记录。 |
| 组成 | 没有可执行的内容：这个流程既没有引用数据记录，也没有引用配方。 |
| 组成 | 即时方式需要一个即时配方，或者不用配方。时序曲线没有单一的时刻。 |
| 组成 | 受控方式需要一个即时配方，或者不用配方。设定值在启动时写下，安全状态在停止时写下。 |
| 组成 | 时序方式需要一个时长。请设定一个。 |
| 组成 | 时序方式需要一个时序配方，或者不用配方。即时的一组设定值没有时间轴。 |
| 组成 | 配方曲线（*N* ms）比时间窗口（*M* ms）长。 |
| 组成 | 事件条目‘*名称*’监视‘Channels.*通道*’，而这个流程的数据记录不记它。 |
| 组成 | 关于一次完成的运行对产量贡献了什么的拒绝，或者关于某个配方值到不了它瞄准的那个通道的拒绝，用的是拥有它的那个编辑器的措辞。 |

### Process：事件条目 {#process-occurrences}

类型“事件条目”；名称：事件条目的名称；位置：模型的名称。只读取被某个流程布防的事件条目，而且不论多少个流程布防它，都只读一次，因为条件只有一个，修复也只有一处。打开这个事件条目自己那一页。

| 字段 | 措辞 |
|---|---|
| 条件 | 条件自己的拒绝，用的是事件条目编辑器的措辞：本模型没有的通道引用、读不通的条件、返回的不是布尔值的条件，或者两个通道落到同一个名字上。它同样是用英文写的。 |

一个自身站得住、却依然判不了的条件，因为这个流程的数据记录把它盯着的那个通道漏掉了，那就算在那个流程头上：数据记录是流程自己的选择。

### Process：动作文书 {#process-action-paperwork}

类型“动作”；名称：动作的名称；位置：模型的名称。这里的判断和动作自己那一页画出“需要复核”提示时用的是同一个，因此在那里被认为就绪的动作，在这里也是就绪的。打开这个动作自己那一页。

| 字段 | 措辞 |
|---|---|
| 命令 | 请添加一条命令：这个动作目前不产生任何输出。 |
| 命令 *n* · 开始时的标签、命令 *n* · 完成时的报告等等 | 关于那条命令的第一条说明，后面还有更多时再加上“（这条命令上还有 *k* 处）”。带好几条说明的一条命令仍然是一次修理，在同一页上完成，因此它只占一行。 |

一条命令可能带的说明，按修理它们的顺序排列：

- 请至少选择一个完成结论。（完成时的命令一个结论都没有勾选）
- 请选择一份报告。/ 选中的报告已不存在。
- 请选择一个标签模板。/ 模板已被清除或删除；已保存的映射仍然保留。/ 选中的全局模板已不存在。
- 选中的模板还没有打印机指令；请在运行记录走到这条命令之前写入或导入。
- 无法读取模板标记：*错误*
- `{{marker}}` 需要一个信号源。/ `{{marker}}` 被映射了不止一次。
- `{{marker}}`：请选择一个信号源 / 这个信号源在开始时还取不到 / 请从这个模型的元数据目录里选一个字段 / 请从这个模型的某个判定里选一条判据 / 请明确指定一个从 0 到 *n* 的循环序号 / 请去掉这个日期时间信号源上的小数位 / 请对这个数值信号源使用默认格式 / 小数位必须在 0 到 9 之间 / 请去掉这个文本信号源上的数值或日期格式
- `{{marker}}` 已从当前模板中移除；请删掉它保存的映射。

### Logic {#logic}

没有位置：Logic 的那几种都在顶层。每一个解析不开的地址一行，地址按表达式书写它的样子写出。只扫描表达式变量；内部存储值变量没有表达式可读。当一段文本的每个地址都能解析时，还会像它的编辑器那样评判这段文本，编辑器拒绝的文本占一行，措辞就是编辑器自己的那句消息：保存为 `!Variables.Start` 的报警得到“‘Variables.Start’是点位本身，而不是它的值：作为条件时它始终被视为真。请用 Variables.Start.ValueToBool() 读取它的值。”这次评判不包括编辑器用当前值做的试算，所以一行不会随着读数时有时无；空文本不列出：那是还没写的定义。

| 类型 | 字段 | 措辞 |
|---|---|---|
| 表达式变量 | 表达式 | 指向 `address`，而它已经不存在 |
| 动作 | 脚本 | 指向 `address`，而它已经不存在 |
| 报警 | 条件 | 指向 `address`，而它已经不存在 |
| 表达式变量 | 表达式 | 编辑器拒绝这段文本时的那句消息 ([校验拒绝什么](logic-expressions#what-validation-refuses)) |
| 动作 | 脚本 | 编辑器拒绝这段文本时的那句消息 ([校验拒绝什么](logic-actions#what-validation-refuses)) |
| 报警 | 条件 | 编辑器拒绝这段文本时的那句消息 ([校验拒绝什么](logic-expressions#what-validation-refuses)) |

### 仪表画面 {#dashboards}

类型：编辑器给这个组件的种类起的名字（仪表盘、折线图、数值…）；名称：为这个组件输入的名称，没有就用它的标题，再没有就用编辑器推导出来的；位置：仪表画面的名称。每一个地址字段一行，因此一个组件的数值和颜色读了两个已移除的点位，就是两次修理。

| 字段 | 措辞 |
|---|---|
| 地址、颜色点位、第 2 个系列 › 地址，以及该种类的每一个其他地址字段；每个字段都用编辑器属性面板给它的标签来称呼（嵌套的列表按它的路径书写） | 指向 `address`，而它已经不存在 |

只读取地址字段，而且只读能解析成带根、至少有一段的地址的那种值：一个写着“Process.Press 1.Temperature”的文本组件是文字，不是引用，永远不会被报出来。同一个字段里的同一个地址算一条发现。定义在 Process 模型上的画面会跳过：它们通过一个单元占位符来绑定通道，在某个单元打开那张画面之前，没有任何点位答得上那些地址。

### Connector {#connector}

OPC UA 服务器的列表、Modbus 服务器的寄存器映射或 MQTT Broker 的发布列表中的一行，按身份持有它的信号源（点位、变量、报警，在 OPC UA 服务器上还有设备的命令）：改名永远不会让它断开，删除则让这一行留在原处，并标明它失去了什么。这样的行会列在这里，正是它自己的表格标为缺失的那一行。工作站仍然拥有、只是不提供的信号源（被禁用的点位、被禁用设备的点位、没有寄存器能承载其类型的变量）不会列出：是什么把它挡在外面，这一行自己的字段会说。这一行保存的是操作员从不阅读的身份，所以它说出消失的是哪一类信号源，而不是把身份显示出来。

| 类型 | 名称 | 位置 | 字段 | 措辞 |
|---|---|---|---|---|
| 提供的节点 | 点位缺失、变量缺失、报警缺失或命令缺失 | OPC UA 服务器 | 信号源 | 它提供的那个点位已被移除。/ 它提供的那个变量已被移除。/ 它提供的那个报警已被移除。/ 它提供的那个命令已被移除。 |
| 寄存器映射行 | 映射读出的这一行的位置（寄存器类别和寄存器） | Modbus 服务器 | 信号源 | 它提供的那个点位已被移除。/ 它提供的那个变量已被移除。/ 它提供的那个报警已被移除。 |
| 发布的主题 | 这一行的主题 | Broker 的名称 | 信号源 | 它发布的那个点位已被移除。/ 它发布的那个变量已被移除。/ 它发布的那个报警已被移除。 |

这个页面不删除也不改动任何东西，每个服务器对这一行照旧行事：OPC UA 服务器不为它提供任何内容，Modbus 服务器对读取其寄存器的客户端以异常 04 回应，Broker 保留它在该主题上保存的最后一条消息。修复在这一行的卡片上进行：另选一个信号源，或者删除这一行。

## 点一行会打开什么 {#what-a-row-opens}

| 种类 | 打开什么 | 落在哪里 |
|---|---|---|
| 组件 | 处于编辑模式的仪表画面（`/d/{dashboard}?edit=true&focus={component}`） | 该组件被选中、它的属性也显示出来，地址就在那里重新输入。编辑需要编辑仪表画面权限；没有它，仪表画面以查看方式打开。 |
| 通道绑定 | Process 工作区（`/process?focus={unit}&screen=channels`） | 这个模型的通道屏幕，该单元那一行被选中，它上面的每一层文件夹都展开。 |
| 联锁、自动化 | Process 工作区（`/process?focus={unit}&screen=automation`） | 这个模型的自动化屏幕，该单元那一行被选中。 |
| 动作文书 | Process 工作区（`/process?focus={action}&screen=action`） | 那一栏里这个动作自己的页面。 |
| 流程 | Process 工作区（`/process?focus={procedure}&screen=procedure`） | 那一栏里这个流程自己的页面。 |
| 事件条目 | Process 工作区（`/process?focus={occurrence}&screen=occurrence`） | 那一栏里这个事件条目自己的页面。 |
| 表达式变量、动作、报警 | Logic 页面（`/logic?focus={id}`） | 那个实体在它的编辑器里被选中，它在地址空间里所属的那一节展开。 |
| 提供的节点、寄存器映射行、发布的主题 | Connector（`/connector?focus={row}`） | 树中选中 OPC UA 服务器、Modbus 服务器或该 Broker，这一行在它的提供的节点、寄存器映射或发布卡片中被选中，字段在表格下方展开，在那里另选信号源或删除这一行。 |

打开只是把你送到故障上；目标页面随后对这次编辑施加它自己的权限（Process 屏幕上是管理 Process，Logic 里是配置 Logic，Connector 里是配置 Connector）。页面到达时已经解析不开的实体（这期间被删掉了）会被忽略，页面就按它平常的样子打开。

## 导航栏上的角标 {#the-rail-badge}

导航栏上的验证入口戴着一枚警示色的小药丸，写着待修复的项数，超过九十九写“99+”，配置干净时则不戴。这个数字是校验器自己的，由这一页所跟随的那同一次重扫在后台保持最新，因此你在任何页面上编辑时它都会跟着动，不需要打开这一页。打开这一页以及在这一页上重扫也会更新这个数字，因此小药丸显示的数字永远和列表一致。

## 这一页永远不做的事 {#what-the-page-never-does}

- 它什么也不修：不重新绑定，不删除，不重命名。每一行都是一次“打开”；行上没有别的动作，也没有批量动作。
- 它从不评判运行时：一台不通信的设备是状态栏里的健康状态，不是这里的一条发现。指向一个存在但读数质量不良的点位的引用，是解析得开的。
- 它从不报出被禁用的自动化、定义在模型上的画面、看起来像地址的自由文字、内部存储值变量，或者同一条动作命令的第二条及以后的说明（数字会说还有多少条在那一页上等着）。
- 它从不读取没有任何流程布防的事件条目，也从不把同一个已布防的事件条目列两次，不论有多少个流程布防它：一个条件就是一处修复。
- 它从不在 Connector 处于半途状态时扫描；它会等，然后只量一次。
- 在每一块屏幕上它都是同一份列表，工作站的窗口和浏览器一样，对每一个可以打开它的身份也一样：权限把的是这一页的关，不是每一行的关。
- 运行时停止期间，这一页和每一页一样让位给暂停提示；角标保留它最后的数字。
- 它从不删除服务器列表中的一行，也从不改变服务器对它的处理，也从不列出工作站仍然拥有、只是不提供的信号源（被禁用的点位、没有寄存器能承载其类型的变量）：那由这一行自己的卡片来说。
