# 配方

> 一次运行写入的那些设定值：开始时套用的一组即时值，或者一条时序的阶跃、斜坡和保持曲线，两者都可以带上每一次结束的运行都会写的收尾块。

一份**配方**写这个模型可写的那些通道。**即时**配方是一组在运行开始时套用一次的设定值；**时序**配方是一条按运行自己的计时驱动的、由阶跃、斜坡和保持段组成的有序曲线。两者都可以以一个**收尾**块结束，也就是运行无论怎么结束都会写下的那个安全状态。每一次写入都走那唯一一个记入日志的[写入出口](logic#writing-values)，并按实际执行的情况记进运行记录文件。配方住在 Process > 模型 > 某个模型 > 设定 > **配方**下面，需要**编辑配方与判定**权限。

## 配方文件夹 {#the-recipes-folder}

选中**配方**那一行会打开文件夹页面（“运行记录开始时流程套用的设定值，经同一个出口写入，并按实际执行的情况记录下来。”），一份一张卡片，按名称排；点一张卡片就打开它，那个叉则把它删掉。一份都没有时，页面写着“还没有配方。添加一个。”

| 命令 | 做什么 | 什么时候变灰（情形） | 什么时候不画出来（角色） |
| --- | --- | --- | --- |
| **新建配方**（选中文件夹时在详情栏上；右键**添加配方**） | 添一个 `配方 <n>`：即时，失败时暂停，没有设定值。它被加进所属的分组，在那里突出显示并滚动到可见处；用它的卡片打开它。 | 任何一个单元上有运行，或者运行时已停止。 | 没有编辑配方与判定时。 |

## 配方的页面 {#the-recipes-page}

标题栏带着名称、那一行“运行记录开始时流程套用的设定值，经同一个出口写入，并按实际执行的情况记录下来。”，以及一枚写着类型的小药丸。每一个字段都自动保存，每一次编辑都会重画曲线预览。

### 定义 {#definition}

| 字段 | 是什么 | 取值 / 默认 | 效果 |
| --- | --- | --- | --- |
| **名称** | 这份配方在树里、在一个流程的配方选择框里，以及在一张操作面板的套用配方选择框里叫什么。 | 自由文本；创建时是 `配方 <n>`。 | 给那一行重命名。 |
| **种类** | 这份配方的形态。 | **即时**（默认）：一组在开始时套用一次的设定值。**时序**：一条按自己的计时走的、有序的阶跃 / 斜坡 / 保持曲线。 | 在下面两种形态之间切换页面。已经填好的设定值和曲线段都保留下来；只画当前类型所读的那一种形态。留着不用的那一块既不套用也不占用：一次运行只写并只锁住当前类型所写的东西。一个流程只接受类型与它的方式相合的配方。 |
| **失败时**（只有时序才有） | 一次写入失败、或者它需要的值读不出来时，这条曲线怎么办。 | **暂停**（默认）、**继续**、**中止**；见时序形态下面那张表。 | 这是配方的规则；每一段都可以覆盖它。 |
| **附注** | 给读这份配方的人看的自由文本。 | 多行；默认为空。 | 别无他用。 |

### 那些值 {#values}

设定值和目标值以文本形式输入，套用时按不变文化解析成通道的类型：

| 通道类型 | 接受的字面量 |
| --- | --- |
| 布尔值 | `true`、`false`、`1`、`0` |
| 整数 | 一个整数，`.` 绝不用作分隔符 |
| 小数 | 以 `.` 作小数分隔符的数字（`12.5`、`1e-3`）；在用逗号写小数的工作站上，字段也接受 `12,5`，并存为 `12.5` |
| 文本 | 任意文本，照原样 |
| 日期和时间 | 一个可往返的日期和时间（`2026-09-02T14:30:00+00:00`） |

不合通道类型的字面量绝不会被写出去：那次写入在运行记录文件里记为失败，并交给失败规则处理。这一页上每一张通道行的表格，添行时只提供同一张表格里其他行还没有占用的可写通道，因此一条通道在一组里只被写一次、在一段里也只被写一次；每一条可写通道都被占用之后，添行的按钮变灰，写着“每一条可写通道在这里都已经有设定值了。”（或者“……在这一段里都已经有目标值了。”），而一条可写通道都没有时则写“这个模型还没有可写的通道。”

## 一份即时配方 {#an-instant-recipe}

两张互不相干的卡片，各自的标题上都有一个**设定值**按钮，按它就在第一条空着的可写通道上添一行，值为 `0`：

| 卡片 | 它写什么 |
| --- | --- |
| **设定值（开始时套用）** | 在开始时写一次，写在这次运行记录任何东西之前。 |
| **收尾（安全状态）** | “运行记录结束时一律写入：完成、停止或中止都算。” |

两张表格的列是一样的：

| 列 | 是什么 | 取值 |
| --- | --- | --- |
| **通道** | 被写的那条可写通道，写作 `Ch<number> · <name>`。 | 这张表格里还没有被占用的只写通道和读写通道。 |
| **数值** | 被写下去的那个字面量。 | 见“那些值”。 |
| 行上的动作 | **删除**问“删除设定值”：那条通道的名称，“这会把这个设定值从配方里删掉。” | |

## 一份时序配方 {#a-temporal-recipe}

时序形态先画那条曲线，然后是那些曲线段，旁边是曲线段里被点中的那一段，最后是收尾块。

### 时序曲线 {#temporal-profile}

这张图就是配方本身：每一条被那些曲线段作为目标的数值通道一条系列，标签写作 `<name> [<unit>]`，画成那些曲线段拼出来的分段曲线（阶跃是跳上去然后保持，斜坡是从带进它的那个值倾斜过去，保持段或者没有目标值的段则维持不变），横轴是累计的秒数。落在非数值通道上的目标值不画出来。这张图带着每一张图都有的工具栏，也可以在它自己的窗口里打开（见[图表](view-charts)）；流程页面在它的套用那一段下面画的是同一条曲线。

### 曲线段 {#segments}

这个列表的标题是**曲线段**，它的**曲线段**按钮添一个 60000 ms 的阶跃段，没有目标值，并把它选中。点一行就在**曲线段目标值**那一栏里打开它；什么都没选中时，那一栏写着“选中一个曲线段，就能在一个地方编辑它的全部内容，包括它要驱动到的目标值。”

| 列 | 是什么 | 取值 / 默认 |
| --- | --- | --- |
| **种类** | 这一段在曲线上摆出的形状；每一个选项名字旁边都带着那个形状。 | **阶跃**（默认）、**斜坡**、**保持**。 |
| **时长（ms）** | 配方在这一段里停留多久才走到下一段。 | 一个毫秒数；创建时是 60000。这个字段不接受负数：输入会被拒绝，字段下面的附注会写出来，之前的值回来。 |
| **时间计数** | 这一段什么时候开始计它自己的时间：等现场设备收下那些设定值之后，还是把它们交出去的那一刻。 | **等待抵达设备** · **发送后继续**。阶跃段生下来就是等待的，斜坡段生下来就是发送的；改这一段的**种类**会把新种类出生时的值放回去，此后挑的是什么就是什么。保持段什么都不写，所以它这一格是空的，也没有什么可挑。 |
| **失败时** | 这里出现失败时，是跟随配方的规则，还是跟随这一段自己的某一种。 | **（随配方）**（默认：继承）、**暂停**、**继续**、**中止**。 |
| **目标值** | 这一段要驱动到什么，写在一行里：`<channel> = <value>, …`。保持段什么都不驱动，因此它读起来是空的。 | 只读；在那一栏里编辑。 |
| 行上的动作 | **删除**问“删除曲线段”：`第 <n> 段 · <类型>`，“这会把这一段及其目标值从配方里删掉。” | |

| 段的类型 | 它做什么 |
| --- | --- |
| **阶跃** | 在段开始时写下它的目标值，然后停留这段时长（阶跃段可以停留 0 ms）。 |
| **斜坡** | 在这段时长里，把每一条目标通道从它最后被命令的值（还没有被命令过时，则从它当前的值）线性地驱动到目标值。斜坡是把通道从一个读数带到另一个读数，所以它的目标选择器只提供存数值的通道：文本通道或者日期时间通道留在列表里但选不了，并写明阶跃只往它写一次。 |
| **保持** | 在这段时长里维持最后被命令的那些值；它什么都不写，也不带目标值。 |

那一栏的标题是 `第 <n> 段 · <类型>`，它把同样那些字段连同一句图例重复一遍，并在每一个下面写出当前值的效果：

| 字段 | 图例 | 效果 |
| --- | --- | --- |
| **种类** | 每个选项旁边的那个形状。 | |
| **时长（ms）** | “配方在这一段里停留多久才走到下一段。” | “执行 1 分 30 秒。”（0 或更小时写“完全没有时长”）。 |
| **时间计数** | “本段何时开始计自己的时间。” | “设定值抵达设备后才开始计时；被拒绝或超时会触发失败规则。”或者“发送并开始计时；设备的回应只作记录。” |
| **失败时** | “这里出现失败时，是按配方说的那样处理，还是按这一段单独要求的另一种方式处理。” | “跟随配方：暂停。”或者“这一段覆盖配方的做法：中止。” |

时间计数是这一段自己的属性，不属于它上面的任何东西：没有一个管整份配方的字段，因为各段计法不同的曲线是一条正当的曲线，而一个管全局的开关只会是第二个说同一件事的地方。它也不是一项失败设置。它决定的是那个时钟；失败该怎么办是下面的**失败时**规则管的。保持段上这个字段根本不画出来。

它们下面是**目标值**，装着这一段的那些目标值，那一栏的标题上有一个**目标值**按钮，按它就在这一段还没有驱动的第一条可写通道上添一行，值为 `0`。在保持段上，这张表格被换成一行“保持段维持曲线已经达到的值，因此它自己不驱动任何目标值。”，按钮变灰，写着“保持段维持已经达到的值。要在这里驱动目标值，请更改段的类型。”；保持段从更早的类型带下来的目标值仍然存着，而运行、曲线预览和执行清单都一样地忽略它们：

| 列 | 是什么 |
| --- | --- |
| **通道** | 被驱动的那条可写通道，只在这一段其他目标值还没有占用的通道里提供。 |
| **目标值** | 被写下去（阶跃）或者被达到（斜坡）的那个字面量。 |
| 行上的动作 | **删除**问“删除目标值”：那条通道的名称，“这会把这个目标值从这一段里删掉。” |

### 失败时 {#on-failure}

一次写入被拒绝、或者它需要的值读不出来时，失败规则对时序配方生效：

| 规则 | 这次运行怎么办 |
| --- | --- |
| **暂停** | 把曲线冻结在最后那个设定值上，等操作员继续或者中止。数据记录照样记录。 |
| **继续** | 把这次失败按实际执行的情况记下来，然后接着走；这条失败的通道在这一段余下的时间里被跳过。 |
| **中止** | 写下收尾块，并把这次运行以中止结束。 |

这条规则答的是配方**知道**的失败，而配方知道什么，取决于它有没有留下来听那个答复。它在任何东西发出去之前就看见的问题，两种方式下都算它知道；现场设备的一次拒绝或者一阵沉默，只有在这一段等过的时候才算它知道：

| 配方学到什么，什么时候学到 | 等待抵达设备 | 发送后继续 |
| --- | --- | --- |
| 发送之前：通道没有绑定、值和通道的类型不符、点位不在运行时里、另一次运行占着这个点位、设备已经知道是断开的 | 规则生效。 | 规则照样生效：配方在放手之前就知道了。 |
| 现场设备在线路上拒绝了这个值 | 规则生效。 | 这里不算失败：按实际执行记下来并带上原因，也记进日志。 |
| 现场设备没有按时答复 | 规则生效。 | 这里不算失败：记下来。 |
| 一个在发送之前就被同一目标上更新的写入赶超掉的设定值 | 不算失败。 | 不算失败。 |

一个被替换掉的设定值在两种方式下都绝不算失败：那个目标上更新的值才是现场设备被告知的，也才是算数的那个。斜坡本来就是这样工作的，每一个节拍都替换掉前一个还没有离开设备队列的节拍。

### 收尾（安全状态） {#finalization-safe-state}

和即时配方上那张卡片一样，“运行记录结束时一律写入：完成、停止或中止都算。”，带着它自己的**设定值**按钮和表格。

## 一份配方在哪里被用到 {#where-a-recipe-is-used}

| 界面 | 它拿这份配方做什么 |
| --- | --- |
| 一个流程的**套用**那一段 | 选择框提供这个模型的每一份配方；类型和这个流程的方式不合的配方列作“`<name>`（不兼容）”，处于禁用状态，理由写在悬停提示里。即时或受控流程接受一份即时配方或者不要配方；时序流程接受一份时序配方或者不要配方，而且它的曲线必须装得进那个时间窗口。那一段的文字说清这个选择写什么：“开始时写入 4 个设定值。”“开始时写入 4 个设定值，收尾时再写入 2 个。”“用 2 分 30 秒 走完 3 个曲线段，并在收尾时写入 2 个设定值。”，或者“不写入任何设定值。”见[流程](process-procedures)。 |
| 一张操作面板的**单条命令**卡片 | **套用配方**在任何运行之外，通过那个记入日志的出口、在一把短命的锁下面，把一份即时配方的设定值写一次；不创建运行记录文件，日志就是那份记录。只提供即时配方。见[操作面板](process-panels#the-card-catalogue)。 |

## 一次运行拿它做什么 {#what-a-run-does-with-it}

启动时，这次运行在它整个持续期间取得对配方所写的每一个信号源的所有权，因此一次配方要写另一次运行占着的信号源的启动会被指名拒绝，而在这次运行占着那些信号源期间，来自别处（一个仪表画面字段、一个脚本、一个 OPC UA 客户端）的每一次写入都会被拒绝。一组即时值在开始时写一次；一条时序曲线按这次运行的计时一段一段走，而一个没有配方的时序流程照样拿到那个计时，好让这个时间窗口里的每一个成员一起开始、一起重复、一起结束。暂停冻结这个计时，继续把它放开；暂停期间数据记录照样记录。每一次写入都以这次运行为来源记入日志，并按实际执行的情况记进运行记录文件，因此以后读的人看到的是当时到底写了什么、什么时候写的。

一个曲线段、那组即时值和收尾块，都是**一口气把自己的每一个设定值都交出去**，交出去之后各自进各自设备的队列。所以一个四条通道分在四台设备上的工位，付的是最慢那台设备的一次代价，而不是四台之和，而 Logic 自己一台也不等。

这一段接下来做什么，由**时间计数**决定。在**等待抵达设备**下面，这一段自己的时钟要等每一个设定值都被各自的现场设备收下才开始走：在那期间，单元在[操作](process-operate#the-operate-page)那面墙上读作“正在等待设定值抵达设备”，这一段的时长还没有开始往下走，数据记录全程照记。在**发送后继续**下面，时钟在设定值交出去的那一刻就开始走，之后回来的那些答复只作记录，再无别的。

启动时的那份即时配方和收尾块永远是等的，没有字段也没有得挑：一次运行不能在它的起始状态成真之前就开始，也不能在它的安全状态成真之前就被报为已经关掉。它们的拒绝按实际执行记下来，并且一如既往，绝不会中止这次运行。

收尾块在这次运行**结束**时一律写下：完成、停止或中止都算。应用本身在运行中途被关掉时不写它：那次运行并没有结束，它会在下一次启动时以暂停状态、在它持久保存下来的那一段和那个循环上继续。

## 删除配方 {#remove-recipe}

**删除配方**在选中这份配方时立在详情栏的危险组里，也在它的右键菜单里，还是文件夹页面卡片上那个叉。它先问一次（“删除配方”，“这会把这个配方从模型里删掉。”）。每一个套用过它的流程都把它的配方引用清空，变成一次不带配方的运行。它需要编辑配方与判定，在工程锁定和运行时停止之下变灰。

## 一份配方不做的事 {#what-a-recipe-does-not-do}

- 它什么也不读、什么也不判：限值是[判定](process-evaluations)的事。
- 它不写任何按单元的值。每一个单元在它自己绑定的信号源上收到的是同样那些字面量。
- 它绝不重试一次被拒绝的写入；失败规则就是全部答案。
- 一份时序配方不能作为单条命令触发；只有流程才跑曲线。
- 曲线预览只画数值通道；一个布尔值或文本目标值照写，但从不画出来。
