> ## Documentation Index
> Fetch the complete documentation index at: https://all.mimedal.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 适配器

> 用伪代码说明适配器怎样连接动作契约、能力门控和设备驱动。

适配器是 FSP 服务中贴近具体设备的一层。它接收已经通过动作契约和能力门控的请求，调用驱动，并把设备返回值解释成上层能理解的结果。

本页用同一个加液例子说明三部分怎样协作。页面中的代码都是伪代码，用来表达流程，不限定编程语言。完整的可运行 Python 示例和测试放在 GitCode 源仓库中。

<Card title="查看 GitCode 可运行示例" icon="code" href="https://gitcode.com/mimedal/all/blob/main/examples/fsp_three_parts.py">
  查看动作契约、能力门控、适配器和模拟驱动的完整实现。
</Card>

| 部分   | 要回答的问题                   | 加液例子                                |
| ---- | ------------------------ | ----------------------------------- |
| 动作契约 | 这个动作是什么意思，输入什么，做到什么才算完成？ | 从来源容器向目标容器加液；参数单位是 mL；实际加液量误差不超过规定值 |
| 能力门控 | 这台设备支持吗？代码实现了吗？现在允许执行吗？  | 是否装有加液模块、配置是否完成、设备是否被占用             |
| 适配器  | 这台设备具体怎么做，返回的数据是什么意思？    | 将 mL 换成驱动要求的 μL，调用设备，读取实际加液量和停止状态   |

一句话说：**契约定义动作，门控决定是否放行，适配器对接设备；厂商设备接口必须包含操作、对象、系统三个维度的内容。**

```text theme={null}
加液请求
  → 按动作契约检查参数
  → 检查设备能力、配置和当前状态
  → 适配器换算参数并调用驱动
  → 适配器解释设备返回值
  → 按契约判断结果，写回操作、对象和系统三个维度
```

## 1. 动作契约：把动作的意思写清楚

动作契约不写串口地址、厂商命令或某台设备的连接配置。它说明同名操作应该遵守的共同规则。

以 `add_liquid` 为例：

| 内容    | 本例约定                                        |
| ----- | ------------------------------------------- |
| 对象    | 一个来源容器、一个目标容器                               |
| 参数    | 加液量 `volume_mL`，范围 0.05–0.8 mL，分辨率 0.001 mL |
| 执行前条件 | 来源液量足够、目标无盖且容量足够、两个容器版本都未变化                 |
| 完成条件  | 设备已停止，实际加液量已取得，误差不超过 0.005 mL               |
| 失败或未知 | 保存已确认结果；没有实际读数时不能把请求量当作结果                   |

这些数字只是教学例子，不代表真实设备规格。不同设备可以采用更严格的范围，但不能偷偷改变单位或降低完成要求。

### 契约检查伪代码

```text theme={null}
动作契约 add_liquid 版本 1：
  所需能力 = liquid_transfer
  数量范围 = 0.05 至 0.8 mL
  数量分辨率 = 0.001 mL
  完成误差 = 不超过 0.005 mL

检查动作请求：
  要求动作名称、契约版本和参数字段完整
  要求动作名称为 add_liquid
  要求契约版本为 1
  要求 volume_mL 是有限数字并符合范围和分辨率
  返回规范化后的加液量

判断完成：
  只有实际加液量存在，且与请求量的差不超过允许误差，才算完成
```

参数检查、设备绑定和结果判断都引用同一份动作契约，不能在每个适配器里另抄一份范围和误差。

本例的来源和目标由对象列表中的 `source`、`target` 角色确定，所以参数中不重复填写来源编号。若接口同时提供两处容器编号，必须检查它们一致。

## 2. 能力门控：不能只看“支持”两个字

“支持加液”至少要分开检查：

1. **硬件支持**：这台设备是否有加液能力？
2. **代码已实现**：有没有这台设备对应的 `add_liquid` 实现？
3. **配置完成**：必要连接参数、标定和验收是否完成？
4. **本次允许执行**：调用方有权限吗？设备是否空闲？容器和版本是否正确？

前两项通常来自设备定义和程序注册。配置是否完成来自部署检查。设备是否忙、容器剩余多少液体，则要在执行前确认。静态说明或 Function Skill 不能代替这些现场检查。

### 门控伪代码

```text theme={null}
检查本次调用：
  要求服务端权限检查已经通过
  要求设备声明 liquid_transfer 能力
  要求目标设备、动作名称和契约版本存在唯一实现
  要求设备配置完成、在线、空闲且不处于待核实状态
  要求来源和目标角色正确，且对象版本没有变化
  要求来源液量足够、目标容器已打开且容量足够

任一条件不满足：
  返回具体拒绝原因
  不发送任何设备命令
```

权限结果必须来自服务端检查，不能直接相信客户端传来的布尔值。对象状态应来自服务端持有的当前记录，调用方只提供预期版本。正式系统还要检查状态读取时间、设备版本及其他安全限制。

能力门控只判断能不能执行，不发送设备命令。检查失败时，适配器不应被调用。

## 3. 适配器：只负责这台设备怎么做

同一个加液动作，不同设备可能使用不同单位、命令和返回格式。这些差异放在适配器和驱动中，不要求调用方了解。

| 适配器做的事               | 不该放进适配器的事        |
| -------------------- | ---------------- |
| 按已确认的规则换算单位、坐标或参数    | 重新解释实验目标         |
| 调用这台设备对应的驱动方法        | 找不到实现时自动换另一台设备   |
| 解释 ACK、完成状态、测量值和设备错误 | 看到 ACK 就认定样品处理成功 |
| 把有依据的结果交回上层          | 猜测实际加液量或修改公共动作定义 |

驱动负责具体通信。适配器可以调用串口、HTTP 或厂商 SDK 驱动，但不会因为调用方式不同，就改变 `add_liquid` 的含义。

### 适配器伪代码

```text theme={null}
执行加液：
  将请求量从 mL 转成驱动需要的 μL
  调用绑定到当前设备的加液驱动
  检查设备是否已经停止
  读取驱动报告的实际加液量
  如果停止状态或实际量无法确认，返回结果未知
  将实际量转回 mL 并交给动作契约判断
```

适配器返回的是驱动报告的量，不是请求量。只有 ACK、没有停止确认或实际量时，不能返回成功。真实设备可以继续查询同一任务，但不能因为回执丢失就重新发出加液命令。

## 4. 三部分怎样接起来

设备绑定使用“设备编号、动作名称、契约版本”作为查找条件。找不到就拒绝，不使用默认适配器。

```text theme={null}
执行一次请求：
  用动作契约检查并规范化参数
  根据设备编号、动作名称、契约版本查找唯一适配器
  执行能力、配置、权限、资源和对象状态门控
  占用设备资源
  调用适配器

  如果驱动调用后无法确认结果：
    标记设备需要人工或查询任务核实
    把受影响但无法确认的对象字段标为未知
    返回 unknown，禁止直接重试动作

  如果取得实际结果：
    按动作契约判断 succeeded 或 failed
    保存实际量和完成证据

  最后释放设备资源
```

适配器交回的是供上层使用的**结果片段**，不是完整 FSP 响应：

* 操作状态放入 `operation.status`。
* 实际加液量放入 `operation.actual_parameters.measured`，并附测量来源、时间和任务等证据信息。
* 容器状态变化放入 `object.changes`。实际转移量不能自动当成两个容器的当前液量；仍需按操作要求取得容器状态。
* 设备状态、资源占用和任务信息放入 `system`。

如果动作后没有重新读取容器液量，就要把对应字段标为未知。再次执行前必须补充新的容器读数和版本。资源占用状态和设备实际运动状态也必须分开表达。

## 可运行源码与测试

完整示例只使用 Python 标准库，模拟驱动会把 0.5 mL 请求执行为 0.498 mL，并验证实际量仍在本例允许误差内。它还覆盖权限拒绝、能力缺失、设备忙、版本冲突、容量不足、只有 ACK、动作后回执丢失以及结果超出误差等情况。

* [GitCode：完整示例源码](https://gitcode.com/mimedal/all/blob/main/examples/fsp_three_parts.py)
* [GitCode：示例自动测试](https://gitcode.com/mimedal/all/blob/main/tests/test_fsp_three_parts.py)

在源仓库根目录运行：

```bash theme={null}
python3 examples/fsp_three_parts.py
python3 -m unittest tests/test_fsp_three_parts.py -v
```

所有结果都是模拟结果，不能用于确认真实实验完成。

## 哪些事情还要由外层服务处理

这个示例只解释三部分的分工。它没有实现并发锁、幂等记录、任务持久化、重启恢复、状态有效期检查或完整响应组装，不能直接作为控制真实设备的网关。

正式服务要在取得相关资源后再次检查条件，再调用适配器；受理任务、记录幂等键和资源占用应按[任务状态与异常恢复](/docs/specification/fsp/lifecycle)处理。动作结果判断见[操作](/docs/specification/device-integration/operations)，完整请求和响应见[设备接口说明](/docs/specification/device-integration/overview)。
