# 工具，按范围列出

> 代理能调用的每一个工具都做什么，按声明它的那个能力开关分组，另有不需要开关的帮助工具。

工具名称是稳定的 snake_case 标识符。错误是结构化的、能照着办的：一次失败的调用会返回哪里出了错，外加一句提示，点名哪个工具或哪一步能把它修好，这样代理会自己纠正，而不是靠猜。服务器会指示每一个代理，在回答关于应用怎么用的问题之前，先用 `help_search` 和 `help_read` 读帮助；“备份（管理）”这个范围开着的时候，还会指示代理在改动配置之前先做一次配置备份。代理实际看得到下面哪几份清单，由[每个范围开放什么](agent-areas)决定，另外还有不需要任何范围的帮助工具。

## 帮助 {#help}

这两个帮助工具读的就是这份文档，也就是安装包里带的那一份：和[帮助面板](app-window#contextual-help)打开的是同一批文章，是为工作站上装的这个版本写的。只要总开关开着，不管各个范围的开关怎么设，它们都随 endpoint 一起提供，因为这些文字是公开的文档，不会透露工作站的任何东西。运行时停止期间，两个工具照样能用。

| 工具 | 作用 |
| --- | --- |
| `help_search` | 按一个问题里的词查找文章，每篇返回 slug、标题、摘要以及那些词命中的小节，最匹配的排在最前（默认 10 篇，最多 50 篇）。不区分大小写，也忽略重音符号。不给查询词时，列出全部文章。 |
| `help_read` | 按 slug 把一整篇文章读成纯文本：标题、摘要、小节列表和正文，每个小节单占一行标题，表格的每一行占一行文字，另外附上它所链接的那些文章的 slug。 |

两个工具默认都用工作站的界面语言来读，所以回答里点名的页面、按钮和字段就是屏幕上的那些。任何一个都可以用 `culture` 指定安装的帮助里带着的另一种语言；没带的语言会被拒绝，并附上带着的语言清单，也不会有别的语言顶替它来回答。帮助缺失或读不出来的安装，每一次调用都回答原因，而不是文章。

## Connector：读取 {#connector--read}

| 工具 | 作用 |
| --- | --- |
| `connector_browse` | 列出 Connector 页面画出的那棵树以及实时健康状态：添加到工作站的每一个驱动都在，下面什么都没有的也在，而带着设备、串行线路或 Broker 的驱动总是已添加的；Modbus RTU 按驱动、串行线路、设备、点位；MQTT 按驱动、Broker（带会话状态）、设备、点位；其余每个驱动按驱动、设备、点位，每台设备的文件夹标在旁边。Generic serial and TCP 像 Modbus RTU 那样列出它的串行线路，像其他驱动那样列出它的网络设备，并标出每台设备的传输方式。SCPI instruments 也以同样的方式列出它的串行线路和其他设备。File 设备带有它的文件夹、文件名、读取方式（单个文件或收件文件夹）和格式。Database 设备带有它的数据库类型（`databaseKind`）。每台设备带有 `error`，用的是它的页面显示的那同一句话，语言是工作站的语言：错误属于它的串行线路或 Broker 时，是指向那里的一句话；否则是设备自己的错误（连不上、连接被拒绝、驱动发出警告）；再否则是读取被拒绝的点位的汇总，每个点位的原因是 `tag_read` 的 `readFailure`。设备没有错误时它是 `null`；对于设备没有连接的点位，`tag_read` 给出的那句话指向的就是这里。它会亮出那些点位别名（`Driver.Folders….Device.Tag`），也就是 `tag_read`、`tag_write` 和 Logic 表达式所用的那些。Connector 停着的时候，别名是 null。 |
| `tag_read` | 按别名读一个点位的实时数值：`valueRaw` 是设备自己的值，没有经过任何环节；`value` 是走完转换链所有环节（包括小数位环节）之后的工程值，设备报上来 2.718281828459 的 Double，在小数位为 2 时读作 2.72；另外还有质量、两个时间戳，以及 `substitution`，它说明这个值是不是一个配置好的替代值，而不是设备报上来的东西：`None`（一次测量，或者根本没有值）、`NaNReplaced`（读数是 NaN，声明的替代值顶了它的位置）、`LastGoodHeld`（读取失败了，最后一次落地的读数被保持着，带着那次读数的原始值和时间戳）或者 `ReadFailureReplaced`（读取失败了，呈现的是声明的替代值）。替代值算作一个值，所以只要不出越限故障，质量就保持良好，但它从来不是一次测量。`communicating` 说明设备是否还在应答：最后一次读取失败了，或者还什么都没读到时，它是 `false`。一次读数超过点位的轮询间隔再加一小段余量仍没有新的读数跟上时，算作一次失败的读取：数值、质量和 `substitution` 都按失败的读取来回答（默认没有数值、质量不良；点位要求时则是保持的读数或声明的数字），`communicating` 为 `false`，时间戳标明最后一次到达的读数。超出点位限值的数值不是连接故障：质量变为不良，而 `communicating` 仍然是 `true`。`readFailure` 说明一次读取为什么没有落地，用的是点位面板显示的那同一句话，语言是工作站的语言：点位自己的读取被拒绝时，是设备或驱动给出的原因；点位所在的设备没有连接时，是指向该设备的一句话。点位正常读取时它是 `null`；一次没落地的读取还没有记录在案的原因时，它也是 `null`。 |
| `modbus_server_status` | 报告工作站自己的 [Modbus 服务器](connector-modbus-server)：是否打开（`enabled`）、`port`、`status`（Off、Listening、PortInUse、在 `failure` 里带着系统原话的 Failed，或 RuntimeStopped）、此刻已连接的客户端（同时最多 `maximumClients` 个），以及寄存器映射里的每一行：它的 `registerClass`、从 0 计数的 `register` 和 `lastRegister`、类型与排布、`access`（ReadOnly 或 ReadAndWrite）、信号源（点位或变量，按地址和 id），以及客户端此刻在那里读到的 `value` 和 `good`，或者让客户端收到异常 04 的 `gap`（NoSource、SourceRemoved、NoGoodValue、NotRepresentable）。运行时停止期间仍可使用。 |
| `station_broker_status` | 报告工作站自己的 [MQTT Broker](connector-mqtt-broker)，即设备向其发布消息的那个 Broker，而不是工作站所读取的 Broker：是否打开（`enabled`）、`port`、`status`（Off、Listening、PortInUse、在 `failure` 中附上系统原话的 Failed，或 RuntimeStopped）、`authentication`（Anonymous 或 UsernamePassword，附带 `username` 和 `hasPassword`，从不包含密码）、现在连接的设备（同时最多 `maximumClients` 台），以及 `thisStationEntry`，即工作站借以读取它的 MQTT 驱动 Broker“本工作站”。运行时停止时也能使用。 |
| `mqtt_publish_list` | 列出工作站向它读取的每个 MQTT Broker 发布的内容，也就是每个 Broker 的[发布卡片](driver-mqtt#the-publish-card)：代理的 `publishTopicPrefix` 以及它是否在发布，每一行带信号源（按地址；信号源被删除时 `removed` 为真）、`topic` 和消息（`value` 或 `alarm`），两种消息在 `payloads` 中说明。运行时停止期间仍然可用。 `access` 为 `ReadAndWrite` 的行在它的 `writeTopic`（主题后加 `/set`）上接受值，写入它的信号源，并以 `MQTT <Broker 名称>` 记入诊断日志；`payloads.write` 说明这个主题接受什么。每个 Broker 都给出它的 `publishFormat`；Sparkplug B Broker 还加上 `sparkplug`（它的组、工作站的边缘节点 ID 和它的设备），它的每一行都说明作为哪个设备和指标发布，`payloads.sparkplug` 说明 Sparkplug B 的发出方式（[以 Sparkplug B 发布](driver-mqtt#publishing-in-sparkplug-b)）。 |
| `remote_access_devices` | 报告谁在通过网络打开工作站的画面：此刻从另一台电脑或平板连接的浏览器（地址、浏览器和系统、页面、用户、从何时起，以及每一个是哪台已配置设备），和已配置的设备各自如何打开（起始画面、锁定、以谁的身份登录、多少分钟后返回、仅查看，以及固定用户或起始画面变了时它为什么改用别的方式打开）。它从不带密钥或密钥的哈希。运行时停止时照样可用。 |
| `server_status` | 报告内置 [OPC UA 服务器](connector-opcua-server)的健康状况：是否打开（`enabled`）、是否在运行、运行时长、endpoint URL、已连接的客户端会话，以及已配置地址空间的规模；`available` 说明 Connector 运行时是否在读（服务器关着或停着时它照样在读），`serverError` 说明服务器上一次启动为什么失败。`list` 是服务器所提供的内容，也是它提供的全部内容：每个点位、Logic 变量、报警或设备命令一行，每行带有 `access` 和配置给客户端的 `node`；列表为空时 `servesNothing` 为 true。 |

## Connector：配置 {#connector--configure}

| 工具 | 作用 |
| --- | --- |
| `driver_capabilities` | 列出应用能服务的每一个驱动，连同一台设备所需的连接字段、点位寻址模型（自由文本地址和/或定型字段）、是否支持设备发现及其参数、时序取值范围和支持的数值类型，还有 `defaultPort`，也就是一台没有指定端口的设备所连的那个端口。`added` 表示这个驱动是否已添加到工作站，也就是它是否画在 Connector 的树里；用 `device_upsert`、`serial_line_upsert` 或 `mqtt_broker_upsert` 新建设备、串行线路或 Broker，会把它的驱动添加进来。AVEVA PI 和 HTTP 连的是设备所用传输方式的端口，因此还会给出 `defaultPortByTransport`：默认的 HTTPS 是 443，不加密的连接是 80。Generic serial and TCP 另外给出 `genericSerialTcp`：它的传输方式、字节如何输入、消息如何结束、校验和、点位的取值方式、过滤条件和取值位置、数值如何读写，以及两个完整示例。Omron 增加了 `omron`：两种协议各自的设备字段、地址写法和数据类型，FINS 的传输方式和取值范围，写入和设备发现。SCPI instruments 增加了 `scpi`：它的传输方式、本计算机是否有 VISA（`visa.installed`）、结束符、写入确认、初始化命令、查询和写入命令的写法与应答的读法、设备发现，以及两个完整示例。NI-DAQmx 增加了 `niDaqmx`：每种测量的示例通道、单位、字段和允许的数据类型与访问方式。设备需要产品无法随附之物的驱动会增加 `prerequisite`（Fluke NetDAQ：Visual C++ 2005 运行库；OPC DA：OPC Core Components，即 OpcEnum 以及 OPC DA 的 proxy 和 stub；NI-DAQmx：National Instruments 的 NI-DAQmx；SCPI：仅其 VISA 设备需要的 64 位 VISA，即 NI-VISA 或 Keysight IO Libraries Suite，附两个页面；Database：ODBC 设备所需的 64 位 ODBC 驱动或数据源，按设备判断（`perDevice` 为 true），因此 `installed` 保持为 null，缺少什么由设备自己的错误说明）：本计算机是否有它（`installed`，针对已添加到工作站的驱动）、官方下载页面、文件、安装方法、已知的易错之处，以及之后会发生什么。SNMP 增加了 `snmp`：各版本及其接受的凭据、SNMPv3 的安全级别和协议、按值类型划分的数据类型、驱动按名称认识的每个标准对象及其 OID、读取与写入的行为、设备发现，以及两个完整示例。File 增加了 `file`：简单路径、文件夹及只能在其中操作的限制、文件名的各种形式、单个文件或收件文件夹、各格式及其地址语法、点位读取什么及其类型、文本设置、读取与写入的行为、设备发现，以及三个完整示例。Database 增加了 `database`：每种数据库及其字段和自己的端口、加密、点位的两种读取方式和两种写入方式、值只能以 `@value` 或 `@now` 进入语句的规则、超时、关于数据库用户的建议、设备发现，以及四个完整示例。这是代理在 `device_upsert` 或 `tag_upsert` 之前要读的那张地图。 |
| `serial_line_list` | 列出每一条已保存的串行线路，已禁用的和空的也在内，带它稳定的 id 和所属驱动；Modbus RTU 线路还带传输模式以及配置的、自动的和实际生效的时序值。 |
| `serial_line_upsert` | 创建一条串行线路，或者按稳定 id 更新一条。`driver` 指明它所属的驱动，`ModbusRtu`（默认）、`GenericSerialTcp`、`Scpi` 或 `Omron`，创建时确定；通用驱动、SCPI 驱动或 Omron 驱动的线路拒绝 `mode`、`responseTimeoutMs` 和 `minimumTransactionIntervalMs`。名称和启用状态任何时候都能改；最小事务间隔在线路启用时也能改；串口、波特率、串行格式、传输模式和响应超时只能在已保存的那条线路禁用着的时候改。 |
| `serial_line_delete` | 按稳定 id 删除一条线路。里面还有设备的线路会被拒绝，除非这次调用明确授权把每一台子设备和它们的点位一起删掉。 |
| `mqtt_broker_list` | 列出每一个已保存的 MQTT Broker，已禁用的和空的也算在内，带着稳定 id、应答地址、传输方式、用户名、客户端 ID、保活间隔、主题前缀和设备数量。它会说明是否存有密码，但从不返回密码本身。还会显示发布格式（`publishFormat`）和 Sparkplug B 组（`sparkplugGroupId`）。 |
| `mqtt_broker_upsert` | 创建一个 MQTT Broker，或按稳定 id 更新一个：名称、启用状态、主机（整条 `mqtt://` 或 `mqtts://` 地址会被拆开）、端口、TLS、用户名、密码、客户端 ID、保活间隔，以及它的发布卡片建议主题时所用的前缀（`publishTopicPrefix`，空字符串恢复默认值）。只改传入的参数；Broker 只有在有主机时才能启用，别的 Broker 已在用的客户端 ID 会被拒绝。保存对连接的修改，会让 Broker 下所有设备共用的会话重新连接。`publishFormat`（`Json` 或 `SparkplugB`）选择发布卡片的发出方式，`sparkplugGroupId` 选择工作站边缘节点的 Sparkplug B 组（空字符串恢复为 `GanterLab`；含有 `/`、`+` 或 `#` 的组会被拒绝）。 |
| `mqtt_broker_delete` | 按稳定 id 删除一个 Broker。里面还有设备的 Broker 会被拒绝，除非这次调用明确授权把每一台子设备和它们的点位一起删掉。 |
| `mqtt_publish_upsert` | 在 Broker 的[发布卡片](driver-mqtt#the-publish-card)上创建一行（`brokerId` 和 `source`：点位写 `Connector.` 加别名，变量写 `Variables.` 加路径，报警写 `Alarms.` 加路径），或按 `id` 编辑一行。`topic` 可选：省略时，这一行使用按信号源名称在 Broker 前缀下建议的主题，空字符串会重新取用它。一个 Broker 同一个信号源只发布一次，同一个主题也只发布一次；主题不能含 + 或 #，也不能以 $ 开头。一行永远不会换到另一个 Broker。 `access` 为默认的 `ReadOnly` 或 `ReadAndWrite`；只有写入型驱动上有写访问权限的点位或内部存储值变量才接受 `ReadAndWrite`，这时工作站会写入发布到这一行主题后加 `/set` 的内容（[回写](driver-mqtt#writing-back)）。一行的主题不能是另一行的主题后加 `/set`。在 Sparkplug B Broker 上，这一行作为指标发布（`row.sparkplug`），`ReadAndWrite` 接受 Sparkplug B 命令发给它的值。 |
| `mqtt_publish_delete` | 按 `id` 删除发布卡片的一行。工作站不再发布它；点位、变量或报警本身不受影响。 |
| `device_upsert` | 按“驱动 + 名称”创建或更新一台设备（驱动在创建之后不可变）。只有传进来的参数会变；连接字段随驱动而异；把一台设备移进文件夹会改写指向它旧点位地址的每一处引用。Generic serial and TCP 的设备接受 `transport` 以及消息和校验和卡片上的字段，`serialLineId` 只接受本驱动的线路；线路上的设备拒绝主机、端口和文件夹。Omron 设备接受 `omronProtocol`（`Fins` 或 `NjNx`），FINS 下还接受 FINS 卡片上的那些 `fins*` 字段；按名称读取的设备拒绝端口。SCPI 设备接受 `transport`（`Tcp`、`Visa`，或带本驱动线路 `serialLineId` 的 `SerialLine`）、`visaResource`、`terminator`、`responseTimeoutMs`、`writeCheck` 和 `setupCommands`。NI-DAQmx 设备接受 `daqmxDevice`，即 NI MAX 为它起的名称。SNMP 设备接受 `snmpVersion`、`snmpCommunity`（SNMPv1、SNMPv2c）、`username`、`password`、`snmpSecurityLevel`、`snmpAuthProtocol`、`snmpPrivacyProtocol` 和 `snmpPrivacyPassword`（SNMPv3）、`responseTimeoutMs` 和 `snmpRetries`；只返回每个秘密是否已设置，从不返回秘密本身。File 设备接受 `fileFolder`、`fileName`、`fileMode`（`SingleFile` 或 `Inbox`）、`fileFormat`、`fileEncoding`、`fileSeparator`、`fileDecimal`、`fileHeaderRow` 和 `fileWrites`，写入打开时还有 `fileHeaderText` 和 `fileLineEnd`；它返回 `file.fileStatus`，即运行中的驱动读取的文件以及从中读出的内容。Database 设备接受 `databaseKind`，服务器接受 `host`、`port` 和 `databaseName`（SQL Server 还接受 `databaseInstance`、`databaseWindowsSignIn` 和 `databaseEncryption`），SQLite 接受 `databaseFile`，ODBC 接受 `databaseOdbc`，以及 `username` 和 `password`；它返回 `database`，即地址以及设备是否仍在等待其用户，从不返回密码。0 到 65535 之外的端口会被拒绝；0 表示驱动所连的那个端口。这次改动会立刻被调和进正在跑的服务器。 |
| `device_delete` | 删除一台设备和它的全部点位；立刻被调和进正在跑的服务器。 |
| `tag_upsert` | 在一台设备上创建或更新一个点位。寻址随驱动而异（自由文本地址和/或定型字段；Generic serial and TCP 则是取值方式、请求、过滤条件、取值位置和写入字段；SCPI instruments 则是作为 `address` 的查询、`valueField` 和 `writeTemplate`；NI-DAQmx 则是作为 `address` 的物理通道和其测量的 `daqmx*` 字段；SNMP 则是作为 `address` 的 OID，用数字或标准名称；File 则是作为 `address` 的列或路径、`fileSource`、`fileRow` 及其字段、`fileWriteMode` 和 `fileLineTemplate`，自由文本的值使用通用驱动的 `value*` 字段，新点位为只读；Database 则是作为 `address` 的值列、`databaseSource`、`databaseTable`、`databaseRow` 及其字段或 `databaseQuery`，点位写入时还有 `databaseWriteMode` 和 `databaseCommand`，新点位同样为只读）；应答里会回显拼好的总线地址，以及给 `tag_read` 用的那个别名。也包含那些无效读数环节：NaN 处理方式只针对数值点位，读取失败时的呈现方式则针对工作站读取的每一个点位，其中只有 `Replace` 限于数值点位和布尔点位。已经存下的 `Replace` 如果点位的新类型容不下，就改存成默认值：类型不再是数值时，NaN 处理方式变成 `PassThrough`；类型是 String、DateTime、Guid 或 ByteString 时，读取失败时的呈现方式变成 `ShowError`。 |
| `tag_delete` | 删除一个点位；立刻被调和。 |
| `server_start` | 打开内置 OPC UA 服务器并启动它，和它那一行上的启用一样：它提供它的列表，并在 `server_stop` 之前跨重启一直保持打开。已经在跑时它什么都不做；Connector 本身没在跑时，先把它启动起来，把当前配置落地。 |
| `server_stop` | 关闭服务器并优雅地停掉它，和禁用一样：OPC UA 客户端被断开，直到 `server_start` 再把它打开之前什么都不发布。Connector 照样在读，点位读取、Logic 和记录照常进行。 |
| `server_restart` | 服务器开着时，只把它先停再起；Connector 全程都在读。关着的服务器会被拒绝，因为打开它的是 `server_start`。设备和点位的改动用 `connector_refresh` 生效。 |
| `server_list_upsert` | 在 OPC UA 服务器的[列表](connector-opcua-served-nodes)里新建一行，或按 `id` 更新一行。`source` 指定点位（`Connector.` 加上别名）、变量（`Variables.` 加上它的路径）、报警（`Alarms.` 加上它的路径）或设备命令（设备地址、一个点、命令名称和 `()`）；一个信号源在列表中只能出现一次。`access` 为 ReadOnly（每一新行的默认值）或 ReadAndWrite，后者只有有写访问权限的点位或内部存储值变量接受；此时客户端的写入经过 Logic 写入漏斗，并连同客户端会话记入诊断日志。应答带回这一行及其 `node`。 |
| `server_list_delete` | 按 `id` 删除列表中的一行。客户端再也找不到它的节点；它所提供的信号源不受影响。 |
| `modbus_server_configure` | 打开或关闭 Modbus 服务器（`enabled`），或者像它的页面那样把它移到另一个 `port`，1 到 65535。Web 服务器、代理访问或 OPC UA 服务器所设的端口，以及别的程序占着的端口，都会被拒绝；Windows 防火墙从不被修改。打开后，任何能到达该端口的机器都能读取映射提供的内容。返回整个服务器，和 `modbus_server_status` 一样。 |
| `station_broker_configure` | 像它的页面一样，打开或关闭工作站自己的 MQTT Broker（`enabled`），把它移到另一个 `port`，并修改它的 `authentication`、`username` 和 `password`，顺序为：端口、认证，然后打开或关闭。Web 服务器、代理访问、OPC UA 服务器或 Modbus 服务器所设的端口，以及被另一个程序占用的端口，都会被拒绝；Windows 防火墙从不被修改。打开后，它会把 Broker“本工作站”添加到 MQTT 驱动。和 `station_broker_status` 一样，返回整个 Broker。 |
| `modbus_map_upsert` | 在 Modbus 服务器的寄存器映射里新建一行，或按 `id` 更新一行。`source` 按地址指定点位（`Connector.` 加上 `connector_browse` 列出的别名）变量（`Variables.` 加上它的路径）或报警（`Alarms.` 加上它的路径，始终为 ReadOnly，提供 `alarmValue` 所指定的状态：Active、Acked 或 State）；`registerClass`（默认 HoldingRegister）、`register`（从 0 开始）、`type`（默认 Float，线圈或离散输入里为 Boolean）、String 的 `length`、`mostSignificantByte` 和 `mostSignificantWord` 沿用 Modbus 点位的字段和规则。`access` 为 ReadOnly（每一新行的默认值）或 ReadAndWrite，后者只有保持寄存器或线圈接受，而且信号源必须接受写入（有写访问权限的点位、内部存储值变量）。新行需要 `register` 和 `source`；映射拒绝的行会得到原因和提示。 |
| `modbus_map_delete` | 按 `id` 删除寄存器映射里的一行。从此读取它那些寄存器的客户端会收到异常 02；它所提供的点位或变量不受影响。 |
| `remote_access_device_update` | 像远程访问页面上设备那一行一样，修改已配置设备的名称、起始画面（按 id 或名称指定的仪表画面，留空则清除，这也会解除锁定）、锁定、无人操作的分钟数（0 表示从不）和仅查看。设备以谁的身份登录不在这里修改：那需要一位拥有管理本地用户的人。设备在工作站的屏幕上配置和忘记，从不通过工具。回答的是 `remote_access_devices` 所列出的那个设备。 |

## Connector：操作 {#connector--operate}

| 工具 | 作用 |
| --- | --- |
| `connector_refresh` | 把正在跑的服务器和已配置的那些设备增量调和（新增、移除、重建），不必整个重启；这是应用改动更轻的那条路。 |
| `device_discover` | 跑一个驱动的设备发现扫描，返回候选项及其点位，什么都不存下来。只有协议支持真扫描的驱动才参与；已经存在的候选项会被标出来。SNMP 扫描遍历带 `snmpCommunity` 的 `target`，或者已配置的设备 `snmpDevice`，SNMPv3 代理用后者扫描。File 扫描读取已配置的设备 `fileDevice`，或者为扫描输入的 `fileFolder`、`fileName`、`fileMode` 和 `fileFormat`。Database 扫描读取已配置的设备 `databaseDevice`，以及可选的 `databaseTables`（表名或带 `*` 的模式）。驱动的扫描范围卡片会拒绝的 `target`，或者扫描需要却没给的 `target`，都会被拒绝，并说明它应当是什么。在值、表或对象的上限处停止的 Database、File 或 SNMP 扫描会在 `cut` 中说明这一点，并给出要传什么才能看到其余部分；完整的列表没有 `cut`。 |

## Logic：读取 {#logic--read}

| 工具 | 作用 |
| --- | --- |
| `logic_browse` | 浏览带根的 Logic 地址空间，也就是每一条表达式所读的那棵点号树：System、Server、Connector、Variables、Alarms、Process 和 Assets。操作员建的文件夹是真正的地址层级；子点位带着它们的实时数值和质量，而 Connector 下的点位还带着 `substitution` 和 `communicating`，和 `logic_read` 报告的一样。 |
| `logic_read` | 按点号地址读一个点位（例如 `Variables.Setpoint`），返回实时数值和质量。Connector 下的点位还带着 `substitution` 和 `communicating`，和 `tag_read` 报告的一样，替代值、已经不再应答的设备的读数和实时测量就靠它们区分；其他点位两者都不带。停止更新的读数在那里读作一次失败的读取。 |

## Logic：配置 {#logic--configure}

upsert 是部分更新的（没传的参数保持原值），而每一份合并之后的草稿都要过 [Logic](logic) 编辑器所用的那同一套校验；无效的草稿会被驳回，什么都不会做一半。

| 工具 | 作用 |
| --- | --- |
| `variable_upsert` | 创建或更新一个变量：表达式（一条在地址空间之上书写的只读 JavaScript 表达式）或者内部存储值（一个可写、可选掉电保持的值）。名称是键，规整之后也是地址；folder 参数把它归档，并改写指向旧地址的每一处引用。 |
| `variable_delete` | 按路径删除一个变量，它保留下来的值也一起删。绑在它上面的通道会失去信号源。 |
| `action_upsert` | 创建或更新一个动作：一段 JavaScript 语句块，恰好有两种写入写法，内部存储值变量赋值和工程值点位写入。只做静态校验，保存时绝不执行。 |
| `action_delete` | 按名称删除一个动作。绑在它上面的触发器会空转。 |
| `trigger_upsert` | 创建或更新一个定时间隔触发器：每 `intervalMs`（最小 100）在动作通道上发出绑定的那个动作；上一次运行还没了结时到点的节拍会被跳过。动作绑定是松散的，按名称绑。 |
| `trigger_delete` | 删除一个触发器；绑定的那个动作不受影响。 |
| `alarm_upsert` | 创建或更新一条报警：一个布尔条件表达式、优先级（严重/高/中/低）、上升和下降延时、要不要确认，以及宣示方式。保存改动过的条件或时序会把正在跑的状态机重置。 |
| `alarm_delete` | 按路径删除一条报警；正在进行的宣示会先被清掉。 |
| `logic_folder_upsert` | 在某一节（Variables、Alarms、Actions、Triggers；最多三层）里创建、重命名或者重新归档一个用于组织的文件夹。对 Variables 和 Alarms 来说文件夹路径是地址的一部分，所以重命名和搬动会改写它下面的每一条表达式、每一段脚本、每一个条件和每一个仪表画面绑定。 |
| `logic_folder_delete` | 删除一个文件夹。它装着的东西一样都不会被毁掉：里面的项和子文件夹重新归到上一层，引用被改写；在去处撞名时这次删除被拒绝。 |

## Logic：操作 {#logic--operate}

这些全都搭那些页面所搭的同一道逻辑写入出口，并以来源 **MCP** 记进日志。一次写入离开 Logic 之后进的是设备自己的下发队列，而等回音的是这次工具调用本身，所以返回来的是这个值最后怎么样了，而不只是它已经发出去了。

| 工具 | 作用 |
| --- | --- |
| `variable_write` | 往一个内部存储值变量里写一个值（强制转换成它的输出类型，掉电保持时会被保留）。表达式变量是算出来的，拒绝写入。 |
| `tag_write` | 往一个可写点位里写一个工程值：倒着走完这个点位的转换链，强制转换成设备的类型，再写到设备上。应答里带着写入完成后点位呈现的东西：数值、质量、`substitution` 和 `communicating`，和 `tag_read` 报告的一样，所以接受了写入、却已不再应答读取的设备，不会被当成仍在应答的设备。只读的点位和只读的驱动会被拒绝，并说明原因。 |
| `action_run` | 在动作通道上跑一个动作，返回它的结果和用时。已禁用的动作根本不跑就失败。 |
| `alarm_ack` | 确认一条报警，也就是那次操作员响应；没有待处理项时它什么都不做。 |
| `alarm_ack_all` | 把每一条在等确认的报警都确认掉，作为一个逻辑队列项，每一次状态变化都记进日志。 |

## 日志：读取 {#logs--read}

| 工具 | 作用 |
| --- | --- |
| `logs_read` | 读程序范围内的实时日志流（就是[事件](events)控制台上的那一条），可按最低严重程度（调试/信息/警告/错误，默认“信息”）、区域（App/Server/Connector/Logic/Process/Dashboard/Agent/Licensing）、组件和一段上下文子串筛选；返回最新的那些匹配项（默认 100 条，最多 500 条）。代理的动作记在 Agent 区域下面。 |
| `event_history_read` | 读那份能挺过重启的常驻运行事件时间线：报警状态变化、事件条目状态变化和联锁状态变化。可按种类（Alarm/Occurrence/Interlock）、一段闭区间的 UTC 时段和一个级别（报警优先级、事件条目严重程度或联锁状态变化）筛选；最新的在前，默认 100 条，最多 500 条。严格只读：什么都清不掉、也删不掉任何一条日志条目。 |

## Process：读取 {#process--read}

| 工具 | 作用 |
| --- | --- |
| `process_models` | 完整列出那些 [Process](process) 模型：通道、共享分组、带解析好的接线和运行状态的单元、流程、操作面板、配方、判定、事件条目，外加全局的报告模板库。这是代理在启动任何东西之前要读的那张地图。 |
| `process_runs` | 列出那些运行记录，最新的在前：id、名称、状态、结论、跑的是什么、谁启动的（Station/User/Automation/Agent）、时间和循环进度。可以只要活动中的运行，可以按名称或任何元数据值（序列号、批次等）的一部分搜索，也可以限量（默认 50 条）。 |
| `process_run` | 按 id 读一次运行完整的冻结档案：快照、元数据、带采样计数的已判判据、事件条目的发生记录和事件、实际执行的配方写入、备注，以及冻结下来的报告和标签存档件及其打印尝试。可选地算出数据记录的行数和时间范围。 |
| `process_samples` | 读取一次运行记录下的采样点，运行中或结束后都可以。采样点不超过 500 个的时间段按记录原样返回：每个采样点一个点，每个通道一个值。更长的时间段汇总后返回：把它的时长等分成 500 份，每份带着每个通道的最小值、最大值和平均值，所以峰值会保留在最大值里。回答会说明是哪一种，汇总时还说明每份多长、每份汇集了多少个采样点。`fromUtc` 和 `toUtc` 可以缩小时间段，足够窄的时间段按记录原样返回；`channels` 只读它点名的通道。 |
| `unit_status` | 报告一个单元的实时状态：它在不在运行（连同那次活动的运行），以及每个通道背后当前的工程值，只读不记录。 |

## Process：配置 {#process--configure}

| 工具 | 作用 |
| --- | --- |
| `process_view_configure` | 恰好配置一个模型的一块操作面板：画在它旁边的图标，以及它出不出现在 View 页面上。Process 这个聚合里别的东西都不能通过 MCP 写；其余部分在 Process 页面上编辑。 |

## Process：操作 {#process--operate}

由代理启动、结束或加备注的那些运行，把**代理**记为执行者，绝不会记成碰巧在试验台上登录着的那位操作员。

| 工具 | 作用 |
| --- | --- |
| `process_snapshot` | 在流程之外，对一个单元采一行数据记录。每一次调用都在[历史](histories)里造出一次新的、已经完成的即时运行，不带判定（结论为 None）。 |
| `process_evaluate` | 采一行数据记录，并用一个判定去判它；造出一次已完成的即时运行，并返回它的结论。 |
| `process_recipe_apply` | 通过 Process 的写入出口和信号源锁，把一份即时配方对一个单元应用一次。它给设备下命令；不造出运行记录（那些写入会记进日志）。时序配方会被拒绝。 |
| `process_start` | 在一个单元上启动一次流程运行，用一个 JSON 对象填这个流程提示的元数据字段。传入的每个值要么被记录下来，要么什么都不启动：流程不提示的字段、给流水号字段的值（应用会在启动时拼出它），或者写了两次的字段，都会拒绝这次启动；必填元数据缺失或无效、单元已经在运行，或者共享通道的锁冲突，也一样。返回新的运行 id。 |
| `process_stop` | 停掉一次正在跑的运行：收尾段、冻结的历史快照、算出来的结论。 |
| `process_hold` | 把一次运行的时序曲线暂停在最后一个设定值上；数据记录继续记。 |
| `process_resume` | 让一次已暂停运行的时序曲线继续。 |
| `process_abort` | 把一次运行从“运行中”或“已暂停”中止掉：收尾段照跑，快照照写，状态变成“已中止”，结论为不合格。 |
| `process_comment` | 往一次运行的文件里加一条带时间戳的备注：运行还活动时是一条时间线记录，结束之后是一条事后记录。 |
| `process_report` | 为一次已结束的运行生成（或重新生成）PDF 报告，并返回它的文件路径。从全局库里点名一份模板会盖过这个流程配置的那一份；两者都没有时，采用全章节的默认版式。运行还活动时被拒绝。 |
| `process_run_delete` | 永久删除一条已记录的运行：它在历史索引里的指针和那个按运行分的数据库文件。运行还活动时被拒绝；配置不受影响。 |

## 仪表画面：配置 {#dashboard--configure}

这些工具读取和返回的角色名，在任何一台工作站上都是英文：`Not signed in`、`Viewer`、`Operator`、`Maintainer`、`Developer`，不管操作员在屏幕上读到的是哪些字。`Admin` 不能列进去：它始终看得到每一张仪表画面。管理员改过名的角色，就用他输入的那个名字。

| 工具 | 作用 |
| --- | --- |
| `dashboard_list` | 列出那些仪表画面（或者点名一个模型时，列出那个模型的画面）：网格尺寸、文件夹、图标、启动标志、按角色的可见性，以及每一个组件连同它的位置、层次和属性 JSON，外加每一张已保存的连线网络。它还返回组件种类的目录，带每一种的默认尺寸和默认属性包。 |
| `dashboard_upsert` | 按名称创建或更新一张仪表画面：网格尺寸（会让某个组件无处安放的缩小会被驳回）、View 页面可视化栏里的文件夹和图标、启动标志、全屏时状态栏的可见性，以及按角色的可见性。点名一个模型时，改的就是那个模型的画面。打开着的仪表画面页面会实时反映这次改动。 |
| `dashboard_delete` | 按名称删除一张仪表画面和它的组件；点名一个模型时，删的是那个模型的画面。 |
| `component_upsert` | 按种类添加一个组件（除非另有说明，放在第一个空闲格上，用这个种类的默认尺寸），或者按 id 更新一个。摆放必须落在网格里面；允许重叠，由 z 层次决定绘制顺序；属性 JSON 就是这个种类那个带类型的属性包。点名一个模型时，改的是那个模型的画面，所以在那里造出来的画面可以在这里排版。 |
| `component_delete` | 按 id 移除一个组件；点名一个模型时，改的是那个模型的画面。 |
| `connection_upsert` | 用一张带类型的连线网络把两个图元连接点连起来，从图元目录里解析出类型、角色和方向，和画布编辑器做的一模一样。两端解析出来的类型不同时会失败，除非明确允许一条未定义类型的连线；一个已经带着连线的锚点会被驳回。点名一个模型时，改的是那个模型的画面。 |
| `connection_delete` | 按 id 和期望的修订号移除一张连线网络，这样一次过期的删除会被驳回，而不是把一次更新的编辑盖掉；点名一个模型时，改的是那个模型的画面。 |

## 界面：自动操作 {#ui--automate}

这些工具驾驭的是真正的应用窗口；它们的存在，是为了让代理能核实面向操作员的那个界面，而不是核实它的一个仿制品。交互是下发给活着的那个外壳的，所以正确的做法是每做一个手势就跟一次 `ui_screenshot` 去看效果，而含糊的匹配会以候选列表的形式返回，不会猜着点下去。

| 工具 | 作用 |
| --- | --- |
| `ui_state` | 报告外壳的状态：当前页面、可用的页面、主题、窗口尺寸和可见性，以及这个实例对外提供界面所用的那个本地回环地址。这是做别的事情之前那次定位调用。 |
| `ui_navigate` | 按页面的稳定名称激活外壳的某个页面，这个名称在每台工作站上都是英文：`View`、`Process`、`Logic`、`Connector`、`Users`、`Validation`、`Events`、`Agent`、`Settings`、`Account`。导航栏在工作站语言中显示的标签也同样接受。 |
| `ui_screenshot` | 把窗口按屏幕上显示的样子抓成一个 PNG 文件，并返回它的绝对路径；窗口收在托盘里时先把它还原。它也可以只抓一个具名的画面区域（一个唯一且可见的 `data-doc-region` 区域），而一个缺失、隐藏或含糊的区域会失败并列出可见的那些区域，不会悄悄退回去抓整个窗口。 |
| `ui_scroll` | 把当前页面的主滚动区滚到顶、滚到底或者按视口翻页，并返回偏移量，这样一个长页面能被扫一遍。 |
| `ui_set_theme` | 应用工作站的主题（跟随系统、浅色或深色）：也就是工作站窗口作画所用、每个还没有选过自己主题的浏览器都跟随的那个主题；已经选过自己主题的浏览器保留自己的。它和在工作站窗口里用主题按键做的选择一样会被保存下来，所以规矩的代理做完之后会把原来的值恢复回去。 |
| `ui_resize` | 按设备无关单位调整还原状态下窗口的大小，会被夹到窗口的最小值；用来审视紧凑布局很有用。 |
| `ui_click` | 点击文字匹配得上的那个可见按钮、超链接、勾选框或开关；勾选框和开关按它们的字段标签找到，结果会报告落在什么状态。含糊时返回那些候选项，而不是随便挑一个。 |
| `ui_set_text` | 往一个按字段标签认出来的文本框里写一个值，并像打字那样把绑定提交掉。 |
| `ui_select` | 按可见文字选中一项：用斜杠分隔的一条路径会走一棵树，沿途展开；单独一段也能匹配当前页面上的列表项、标签页、下拉框条目和表格行。 |

## 备份：管理 {#backups--manage}

| 工具 | 作用 |
| --- | --- |
| `backup_create` | 在线给配置数据库拍一份快照到备份文件夹里，可以附一条说明。已记录的运行住在配置之外，备份和恢复都碰不到它们。 |
| `backup_list` | 列出那些配置备份（文件名、创建时间、大小、说明），外加任何已经排定的恢复。 |

这里刻意**没有删除备份的工具**：保留多久只由操作员在[备份](settings-backups)页面上说了算。

## 备份：恢复 {#backups--restore}

| 工具 | 作用 |
| --- | --- |
| `backup_restore` | 排定一份备份，在**下一次**应用启动时替换掉当前配置。正在跑的应用里什么都不变，这个工具也绝不会去重启它；被替换掉的那份配置会先自动拍一份快照，而在重启之前操作员随时可以从“设置”里取消这次待执行的恢复。有记录正在进行时被拒绝。 |

## 设置：管理 {#settings--manage}

| 工具 | 作用 |
| --- | --- |
| `settings_read` | 读工作站的显示和生命周期偏好：日期/时间/数字格式、小数位数、主题、关闭时收进托盘和崩溃报告这两个标志。 |
| `localization_set` | 更新那些显示偏好（部分更新：没传的参数保持原值）。只影响显示：存储、驱动 I/O 和记录始终使用固定区域性格式。 |
| `asset_list` | 列出那些用户素材（图像、图标、颜色），带 id、值和路径。 |
| `asset_import` | 从一个本地文件路径把一张 PNG/JPG 导入应用自有的素材库，并返回新的素材 id。字节是复制过去的，所以源文件之后可以移走。 |
| `symbol_validate` | 校验内联的图元内容（原始 SVG 或者一个 v2 包），什么都不存下来：把那次导入原样空跑一遍，返回错误、净化器的警告，以及认出了哪些东西的摘要。这个工具的说明里带着完整的[可移植图元契约](ganter-symbols)。 |
| `symbol_import` | 把内联的图元内容作为一个新的用户图元素材导入，并返回它的 id 和 `user:<guid>` 引用。它总是造一个新素材；撞名时自动加后缀。 |

## 反馈：起草 {#reports--draft}

| 工具 | 作用 |
| --- | --- |
| `report_draft` | 在工作站的窗口里打开[反馈表单](account-feedback#a-report-drafted-by-an-agent)，表单里已经填好代理写的内容：`kind`（`DriverRequest`、`Bug` 或 `Suggestion`）、`summary`（一行，最多 120 个字符；超长的会被拒绝，不会被截断）和 `details`（可选）。和标题栏的反馈按钮一样，它先截取窗口，再把截图勾选好附在反馈里。它马上答复表单已经打开、什么都没有发出。 |

这个工具从不发送。工作站前的人读过反馈，愿意的话改一改，再按“发送”；结果显示在工作站的状态栏里，不会回给代理。这个范围开着的时候，服务器会告诉每个代理：当用户需要 Ganter Lab 没有的驱动、某次调用失败得像是程序缺陷、或者用户提出改进要求时，主动提议写一条反馈；`driver_capabilities`、`device_upsert` 和 `device_discover` 的说明也这么说。

它带着一个 `reason` 拒绝，这时什么都不打开，也什么都不截取：

| `reason` | 何时 |
| --- | --- |
| `not_permitted` | 工作站上登录的角色没有“向 Ganter 发送反馈”。 |
| `no_station_window` | 工作站没有自己的窗口可以打开表单：只把页面提供给浏览器的主机。 |
| `no_credential` | 会话无法发送：演示配置（登录与否都一样），或者登录了工作站的 Ganter 账户却不带凭据。 |
| `form_busy` | 反馈表单正在工作站上打开着，或者某条反馈正在截取或发送。 |
| `draft_in_progress` | 表单里有一条某人正在写的反馈。反过来，代理先前那份没人改过的草稿会被替换。 |

没有登录 Ganter 账户的工作站不会被拒绝：表单照样打开，并说明反馈不带账户发出，所以 Ganter 无法回复。

和每一个会改动东西的工具一样，运行时停止期间它答 `Runtime stopped`。
