# Logic

> Logic 页面、它的“地址空间”栏、文件夹、命令栏、每个面板背后的逻辑扫描周期和写入出口，以及决定这个页面画出什么的三个权限。

Logic 是导航栏上的第三项。任何拥有**查看 Logic** 权限的角色都能打开它，它的标题栏写着“在地址空间之上定义变量（派生值或可写值）和动作（写入这些值的脚本）。”它是一个主从页面（见[页面是怎么用的](app-conventions)）：左边是标题为**地址空间**的一栏，右边是命令栏和详情面板。你在这里编辑的一切都随打字自动保存；整个页面上没有“保存”按钮。

## 本区域的页面 {#in-this-area}

| 页面 | 讲什么 |
| --- | --- |
| [地址空间](logic-address-space) | 七个保留根、一个地址怎么书写和规整、筛选框，以及输入一个地址的三种办法。 |
| [变量](logic-variables) | 变量编辑器：身份与地址、表达式和内部存储值两种种类、“数值”卡片、设置值。 |
| [表达式](logic-expressions) | 表达式语言、一个点位的可读面、求值器执行的那些规则、脚本编辑器。 |
| [动作](logic-actions) | 动作编辑器、脚本可以写什么、每一条校验提示、运行和启用/禁用。 |
| [触发器](logic-triggers) | 触发器编辑器和定时间隔调度。 |
| [报警](logic-alarms) | 报警编辑器、状态机、报警提示、确认和全部确认。 |
| [来自其他页面](logic-from-other-pages) | System、Server、Connector 和 Process 这几个根、点位面板，以及点名由哪个页面编辑它们的那条通知。 |

同一栏里的 **Assets** 根自成一组：[资源](logic-assets)讲 Images、Icons、Colors、Symbols 和 Connection types。

## “地址空间”栏 {#the-address-space-column}

这一栏在两个小标题下列出地址空间的那些根。**在这里定义**放的是这个页面创建和编辑的东西：**Variables**、**Alarms**、**Actions**、**Triggers** 和 **Assets**。**来自其他页面**放的是它只显示的东西：**System**、**Server**、**Connector** 和 **Process**。第一个小标题下的每一个根都带一个数量（Variables 和 Alarms 数它们的定义，Actions 和 Triggers 数它们的，Assets 数它五个类别的总和），而 Alarms 根、每个报警文件夹和每一行报警都带一个彩色圆点，颜色是它下面处于活动或等待确认状态的最高优先级；报警一被答复，圆点就清掉。

这棵树是懒加载的：一根树枝在你展开它时才载入。树里从不画实时数值；选中节点的详情面板才显示它们，每秒刷新两次。

| 控件 | 作用 |
| --- | --- |
| 筛选… | 把树收窄到名称里含有这段文字的那些行，不区分大小写。只要有任何后代匹配，文件夹就留下来，并展开到它的那些匹配项；Assets 根收窄到名称匹配的那些类别，而只有它自己的名称匹配时就整个留下；名称自己匹配的地址空间根仍然列着，展开时才载入它的树枝，而 Actions 和 Triggers 两节只列出匹配的行。 |
| 新建…（栏底） | 打开创建菜单：**新建变量**、**新建动作**、**新建触发器**、**新建报警**，然后是**新建文件夹**，每一节（Variables、Alarms、Actions、Triggers）各一个去处。在这里造出来的一切都落在它所属那一节的根上，取 `Variable1`、`Action1`、`Trigger1`、`Alarm1` 或 `Folder1` 系列里第一个空闲的名字。新的定义不会被打开（见[新建与复制](#creating-and-copying)）；新的文件夹会在它树里的那一行上打开名称字段。只对拥有**配置 Logic** 的角色画出来；工作站被锁住时它带着原因置灰。 |
| 全部展开 / 全部折叠 | 展开每一根树枝（每一层在展开时载入），或者把一切收回到那些根。 |
| 右键 | 在一节的根上、你自己的某个文件夹上、一行定义上，或者 Images、Icons、Colors、Symbols 那一行上：选中这一行并打开它的菜单（见下）。在来自其他页面的节点上不会打开任何菜单。 |

右键菜单随行和角色而带上：**新建变量 / 新建动作 / 新建触发器 / 新建报警**（建到你点的那个文件夹或根里）、**新建文件夹**（已经嵌套三层的文件夹上置灰并说明理由）、**运行**（动作行）、**确认**（报警行）、**全部确认**（Alarms 根）、**启用**或**禁用**（动作、触发器和报警）、**移动到**（一个子菜单，列出这一节的根以及这一行可以归进去的每一个文件夹）、**复制**、与命令栏同名的删除（**删除变量**等），在文件夹上还有**重命名文件夹**和**删除文件夹**。在 Images、Icons、Colors 和 Symbols 那几行上，它带的是那一行的命令栏：**导入图像…**、**新建用户图标**、**新建颜色**、**新建用户图元**、**导入图元…**和**复制对话指令**。没有“配置 Logic”的角色只拿到拥有“操作 Logic”时的运行、确认、全部确认，以及 Symbols 行上的复制对话指令；一样都没有的行不打开菜单。

在你选中什么之前，详情区是空的：“选中一个节点即可查看它：变量、动作、触发器和报警会打开各自的编辑器。用“新建”创建一个。”

## 命令栏 {#the-command-bar}

命令栏画的是选中节点的那些命令。角色永远不允许的根本不画出来；此刻这个情况所拒绝的仍然画着并置灰，提示文字里写着原因：工程操作期间是“有单元在运行时配置是锁住的。”，工作站以只读方式运行时是“运行时已停止”。

| 选中的东西 | 前导的一组 | 末尾（危险）的一组 |
| --- | --- | --- |
| 你自己的一个文件夹 | 新建变量 / 动作 / 触发器 / 报警（看这一节装的是哪一种）、新建文件夹（到了嵌套上限时置灰并写“这个文件夹已经到了文件夹能嵌套的最深一层。”）、重命名文件夹 | 删除文件夹 |
| 一个变量 | 复制 | 删除变量 |
| 一个动作 | 运行、启用或禁用、复制 | 删除动作 |
| 一个触发器 | 启用或禁用、复制 | 删除触发器 |
| 一个报警 | 确认、启用或禁用、复制 | 删除报警 |
| Alarms 根 | 全部确认 | |
| Assets 的 Images | 导入图像… | |
| Assets 的 Icons | 新建用户图标 | |
| Assets 的 Colors | 新建颜色 | |
| Assets 的 Symbols | 新建用户图元、导入图元…、复制对话指令 | |
| 一个 System、Server、Connector 或 Process 节点 | [“由别处编辑”的通知](logic-from-other-pages#the-edited-elsewhere-notice)和它的“打开”按钮 | |

**复制**把选中的那份定义复制到它所属那一节的根上，源名字空着时就用源的名字，否则依次是 `Name (2)`、`Name (3)`，副本不会被打开，而是在这一根的表格里突出显示（见[新建与复制](#creating-and-copying)）；复制出来的报警从“正常”开始，复制出来的触发器没有排程，复制出来的动作没有运行记录。**启用**和**禁用**通过和任何字段一样的自动保存翻转这份定义的“已启用”标志。每一个**删除**都先问一句（“要删除‘X’吗？…… 此操作无法撤销。”），并在操作记录条里报告“已删除。”；删掉一个变量或一个报警，会让每一处点过它地址的表达式、脚本、条件和仪表画面绑定悬空，那些由[验证](validation)列出来。删除之后，界面转到被删除者的上一级：这份定义所在的文件夹，或它那一节的根，该节点在树中被选中并展开，其表格列出剩下的内容；**删除文件夹**也同样转到上一层文件夹或该节的根。

## 新建与复制 {#creating-and-copying}

新建从不打开新建出来的东西，复制也是一种新建。无论是从栏里的**新建…**、一节的根或一个文件夹的右键菜单，还是从打开的文件夹的命令栏里用**新建变量**、**新建动作**、**新建触发器**和**新建报警**，都会按这个系列里第一个空闲的名字造出这一行，并把画面带到它上级的页面：它归入的那个文件夹，或者这一节的根。树选中这个上级并展开它，页面上的表格把新的一行突出显示并滚动到可见处，操作记录条写“已创建‘Variable1’。打开它即可编辑。”从文件夹自己的命令栏里新建，画面留在这个文件夹上。**复制**在它那一节的根上同样处理，操作记录条写“已复制为‘Setpoint (2)’。打开副本即可编辑。”在**筛选…**框里打的筛选会在到达时清空，这样上级和新的一行才在树里，才能显示为选中并展开。

一节的根或你自己的某个文件夹的页面是一张表格，列出它所含的东西，有**名称**、**地址**和**数值**，每一行末尾有一个**打开**，打开这份定义的方式和树里它那一行完全一样（在 Alarms 根上，拥有**操作 Logic** 的角色在那里还会看到**确认**）。输出类型为图标、图片或颜色的变量，会在它的数值单元格里画出它的图标、图片或色块，小到能放进一行（颜色还会同时显示它的十六进制值）；单元格从不写出图标的源码。按一行就选中它，选中的这一行就是突出显示的那一行：突出显示一直保留，直到你选了别的行，或者把栏带到别处。新的文件夹是例外：它在树里自己的那一行上打开名称字段。

## 文件夹 {#folders}

这四节中的每一节都可以用文件夹来组织。一个文件夹属于一节，而且只装那一节的东西；它最多嵌套三层。文件夹只是组织方式：它没有权限、不参与求值、也没有状态。

对 **Variables** 和 **Alarms** 来说，文件夹链参与构成地址：`Forno` 里的一个变量读作 `Variables.Forno.Setpoint`，它 `Zona1` 子文件夹里的那个读作 `Variables.Forno.Zona1.Setpoint`。一节里的一层就是一个由它的文件夹和它的定义共用的单段命名空间：两个文件夹可以各有一个 `Setpoint`，但同一层上归档的东西不能规整成同一段，编辑器会在你打字时就拒绝这样的名字，并点名那个已经被占用的地址。**Actions** 和 **Triggers** 是按身份被引用的，所以它们的文件夹只组织这棵树，它们的面板显示的是一行位置，而不是一个地址。

| 命令 | 会发生什么 |
| --- | --- |
| 新建文件夹 | 创建一个名叫 `FolderN` 的空文件夹，并在树的那一行上打开它的名称字段；把字段留空或者不改，就保留那个名字。 |
| 重命名文件夹 | 在那一行上打开名称字段。这个文件夹下面的每一个地址都跟着挪，每一处引用都被改写，记录条会说有多少处（“文件夹已重命名，更新了 3 处引用。”）。 |
| 移动到 | 把一个文件夹或一份定义归到同一节的另一个文件夹下面，或者退回这一节的根上；地址和引用都跟着走。列表只提供合法的去处：绝不会是这个文件夹自己、它自己的某个子文件夹、另一节，也不会是会让它的子文件夹越过嵌套上限的那一层。 |
| 删除文件夹 | 先问一句，并说明什么会挪走：“它的 3 项内容会挪到 Variables；什么都不会被删掉。指向这些挪走地址的引用都会被更新。”里面的东西和子文件夹都被重新归到上一层；两项在去处会撞名时，这次移除被拒绝。 |

## 逻辑扫描周期与队列 {#the-sweep-and-the-queue}

运行时跑在两条通道上，两条都绝不会等现场设备。**扫描周期**每 100 毫秒在一条顺序队列上跑：它先给每一个表达式变量求值，再给每一个已启用的报警条件求值，就是这个顺序，所以一个条件读到的是本次扫描周期的变量值；从别处来的内部存储值写入、点位写入和报警确认也由它捎着。**动作通道**跑的是动作脚本，一次一个，按被要求的先后跑。另有一次同样每 100 毫秒的扫查，把到点的[触发器](logic-triggers)投到动作通道上。

正因为两条通道互不相干，一次扫描周期可能发生在一段脚本中间，所以同一段脚本里的两次读取可能看到不同的周期；换来的是，一段停下来等设备的脚本只拖住排在它后面的那些动作，绝不拖表达式、报警或者操作员。各次写入不为它争抢：顺序是在贴着线路的地方守住的，一台设备一条队列（见下文）。消费者（仪表画面、通道、这个页面）读的是已发布的状态；这个页面上没有任何东西会手工触发一次求值。

## 写入数值 {#writing-values}

不管是谁发起的，每一次写入都走同一个出口：一个脚本里的赋值或者 `Write(...)`、一个内部存储值变量的**设置值**字段、一个仪表画面控件、一份 Process 配方、代理、一个写公开节点的外部 OPC UA 客户端，或者一个写入[Modbus 服务器](connector-modbus-server)映射里允许写入那一行的 Modbus 客户端。这个出口会拒绝不是有限数、或者转换不成目标类型的值，会拒绝一个正在被运行中的配方或者被联锁占住的信号源（并点名占着的那一方），把这次写入按它的来源（操作员、MCP、那次运行）记成“从旧值到新值”，并把它应用下去。出口断定的那些事，它一件都不用去问谁：值、目标、锁，以及设备究竟连没连上。它放行的一次点位写入，随后被交给 Connector 里那台设备自己的下发队列，欠这台设备的那些值在那里按到达的先后一个一个发出去，而别的设备是并行伺候的；一个在那里被同一点位上更新的写入赶超掉的值永远离不开队列，现场设备被告知的是更新的那个。谁要的写入就由谁去等答复，等在 Logic 之外、等在它自己的代码里：仪表画面组件、[点位页面](connector-tags#the-live-value-card)上的手工写入、代理工具、外部 OPC UA 客户端、Modbus 客户端、一份正在跑的配方，以及只在它写了 `WriteAndWait` 时的脚本。来源是动作的写入是个例外：它只进[详细捕获](events-console#detailed-capture)，绝不进事件控制台，因为一个每次扫描周期都写的脚本会产出成千上万个值，会把工作站的要闻埋掉。控制台从动作那里留下的，是它的失败。点位写入接收的是工程值，并把这个点位的[转换链](connector-value-pipeline)反过来走一遍，得到设备要的那个原始值。细节在[变量](logic-variables#set-value)和[动作](logic-actions#what-an-action-may-write)里。

## 权限与锁定 {#permissions-and-locks}

| 权限 | 在这里允许什么 |
| --- | --- |
| 查看 Logic | 打开这个页面并读它上面的一切，实时数值也在内。 |
| 配置 Logic | 创建、编辑、重命名、搬动、复制、启用、禁用和删除变量、动作、触发器、报警和文件夹，以及打理素材库。 |
| 操作 Logic | 写一个内部存储值、运行一个动作、确认报警，在这里、在仪表画面上和在事件页面上都算。 |

只有“查看 Logic”的角色把整个页面读一遍：那些编辑器不可用，上面有一句说明（“你的角色可以查看 Logic，但不能配置它（变量、动作、触发器、报警和资源）。”），任何配置类或操作类的命令都不画出来。有“操作”而没有“配置”的角色，仍然留着运行、确认、全部确认和设置值。有一个 Process 单元占住工作站时，页面显示工程锁定的通知，每一个配置类命令和编辑字段都置灰；运行、确认和设置值仍然可用。运行时已停止时，配置类和操作类都以“运行时已停止”被拒绝；树和面板仍然读得了，但不带实时数据。权限位的完整目录在[权限](users-permissions)里。

## 这个页面不做什么 {#what-the-page-does-not-do}

没有保存，也没有撤销：一次按键就是一次已经落盘的改动（记录条写“已保存。”），而一次被拒绝的保存会把字段重新载入成工作站里的内容。代理通过它自己的工具创建、修改或删除的定义，以及另一个窗口改动的定义，会自己出现在开着的栏里。开着一个这样被删除的定义的编辑器会关闭，记录条写出它的名字，并说明它是在编辑器之外被删除的。报警历史、事件控制台和仪表画面各在自己的页面上。
