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

# 设备发现

> 发现设备并按需读取设备技能定义

## 设备发现

`fsp/discover` 只返回设备级最小元数据，不返回完整技能 Schema。

### 请求

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "discover-1",
  "method": "fsp/discover",
  "params": {}
}
```

### 响应

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "discover-1",
  "result": {
    "result_type": "complete",
    "value": {
      "devices": [
        {
          "device_id": "lab.device-01",
          "name": "示例设备",
          "description": "提供容器处理和状态读取技能。",
          "capability_revision": "rev-20260902-02",
          "available": true
        }
      ]
    },
    "diagnostics": []
  }
}
```

| 字段                              | 类型              | 是否必需 | 范围与含义                                |
| ------------------------------- | --------------- | ---- | ------------------------------------ |
| `devices`                       | `array[object]` | 是    | 当前 FSP 服务管理的设备；没有设备时返回 `[]`          |
| `devices[].device_id`           | `string`        | 是    | 长度 `1..128`；稳定设备标识                   |
| `devices[].name`                | `string`        | 是    | 长度 `1..128`；设备显示名称                   |
| `devices[].description`         | `string`        | 是    | 长度 `0..1024`；说明设备用途，供调用方判断是否继续读取技能摘要 |
| `devices[].capability_revision` | `string`        | 是    | 长度 `1..128`；当前技能定义修订号                |
| `devices[].available`           | `boolean`       | 是    | 表示设备当前是否可接收后续请求，不替代完整系统条件检查          |

稳定引用必须使用 `device_id`，不得使用显示名称定位设备。

## 渐进式披露

FSP 把技能信息分为三个读取层级：

| 层级    | 接口                                            | 返回内容                      | 使用时机      |
| ----- | --------------------------------------------- | ------------------------- | --------- |
| 设备元数据 | `fsp/discover`                                | 设备身份、简介、修订号和可用性           | 建立设备候选集合  |
| 技能摘要  | `fsp/get_capabilities`，`detail_level=summary` | 技能标识、名称、说明、请求分类、执行模式和对象摘要 | 检索与选择技能   |
| 技能详情  | `fsp/get_capabilities`，`detail_level=full`    | 所选技能的对象、控制、系统三个完整维度       | 校验并构造实际请求 |

摘要只能用于发现和筛选。调用 `write` 或 `invoke` 类技能前，调用方必须取得对应完整详情；调用 `read` 类技能时也必须按完整详情校验输入参数。

## 获取技能定义

### 请求

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "capability-1",
  "method": "fsp/get_capabilities",
  "params": {
    "device_id": "lab.device-01",
    "detail_level": "full",
    "command_ids": ["dispense"],
    "if_revision": "rev-20260902-01"
  }
}
```

| 参数             | 类型              | 是否必需 | 范围与约束                                        | 作用                    |
| -------------- | --------------- | ---- | -------------------------------------------- | --------------------- |
| `device_id`    | `string`        | 是    | 长度 `1..128`；必须来自当前服务的发现结果                    | 指定设备                  |
| `detail_level` | `string`        | 否    | `summary` 或 `full`；默认 `summary`              | 指定披露级别                |
| `command_ids`  | `array[string]` | 否    | 仅适用于 `full`；元素数量 `1..100`，元素唯一且每项长度 `1..128` | 只读取指定技能详情；省略时返回全部技能详情 |
| `if_revision`  | `string`        | 否    | 长度 `1..128`；必须是调用方已经完整缓存的同级结果修订号             | 条件读取                  |

`params` 不得携带未声明字段。`detail_level=summary` 时不得携带 `command_ids`。未知 `command_id` 必须返回错误，不得静默忽略。

## 技能摘要响应

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "capability-summary-1",
  "result": {
    "result_type": "complete",
    "value": {
      "device": {
        "device_id": "lab.device-01",
        "name": "示例设备",
        "description": "提供容器处理和状态读取技能。"
      },
      "revision": "rev-20260902-02",
      "detail_level": "summary",
      "ttl_ms": 300000,
      "cache_scope": "service",
      "skills": [
        {
          "command_id": "read_device_state",
          "name": "读取设备状态",
          "description": "读取设备当前状态和观测时间。",
          "request_class": "read",
          "execution_mode": "sync",
          "object_summary": "设备整体状态"
        },
        {
          "command_id": "dispense",
          "name": "加液",
          "description": "向满足条件的目标容器加入指定体积液体。",
          "request_class": "invoke",
          "execution_mode": "async",
          "object_summary": "已声明兼容类型的目标容器"
        }
      ]
    },
    "diagnostics": []
  }
}
```

摘要中的每项技能必须足以支持检索，但不得省略 `request_class`。调用方据此判断后续需要采用的安全校验级别。

## 技能详情响应

对象、控制和系统是同一项技能的三个说明维度，不是三组独立能力或三套设备接口。

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "capability-1",
  "result": {
    "result_type": "complete",
    "value": {
      "device": {
        "device_id": "lab.device-01",
        "name": "示例设备",
        "description": "提供容器处理和状态读取技能。"
      },
      "revision": "rev-20260902-02",
      "detail_level": "full",
      "ttl_ms": 300000,
      "cache_scope": "service",
      "skills": [
        {
          "command_id": "dispense",
          "name": "加液",
          "description": "向满足条件的目标容器加入指定体积液体。",
          "request_class": "invoke",
          "execution_mode": "async",
          "object": {
            "role": "target_container",
            "description": "承接液体并保存执行后样品状态的目标容器。",
            "container_types": [
              {
                "container_type_id": "container.vial.example",
                "name": "示例进样瓶",
                "properties": {
                  "shape": "cylindrical",
                  "height_mm": 75,
                  "outer_diameter_mm": 22.4,
                  "material": "glass",
                  "nominal_volume_mL": 2.0,
                  "max_working_volume_mL": 1.8
                },
                "required_states": {
                  "container_status": ["无盖"],
                  "sample_status": ["空", "液体"]
                }
              }
            ]
          },
          "control": {
            "preconditions": [
              "目标容器存在且类型兼容",
              "目标容器处于无盖状态"
            ],
            "parameters": [
              {
                "name": "target_container_id",
                "type": "string",
                "required": true,
                "description": "目标容器实例标识",
                "constraints": ["length 1..128"]
              },
              {
                "name": "source_container_id",
                "type": "string",
                "required": true,
                "description": "来源容器实例标识",
                "constraints": ["length 1..128"]
              },
              {
                "name": "volume",
                "type": "object",
                "required": true,
                "description": "加液体积",
                "children": [
                  {"name": "value", "type": "number", "required": true, "exclusive_minimum": 0, "maximum": 1.0},
                  {"name": "unit", "type": "enum", "required": true, "allowed_values": ["mL"]}
                ]
              }
            ],
            "input_schema": {
              "type": "object",
              "properties": {
                "target_container_id": {"type": "string", "minLength": 1, "maxLength": 128},
                "source_container_id": {"type": "string", "minLength": 1, "maxLength": 128},
                "volume": {
                  "type": "object",
                  "properties": {
                    "value": {"type": "number", "exclusiveMinimum": 0, "maximum": 1.0},
                    "unit": {"type": "string", "enum": ["mL"]}
                  },
                  "required": ["value", "unit"],
                  "additionalProperties": false
                }
              },
              "required": ["target_container_id", "source_container_id", "volume"],
              "additionalProperties": false
            },
            "constraints": [
              "volume.value <= target.max_working_volume - target.sample_volume"
            ],
            "effects": {
              "sample_status": {"空": "液体", "液体": "液体"}
            },
            "returns": [
              {"name": "task_id", "type": "string", "required": true},
              {"name": "status", "type": "enum", "required": true, "allowed_values": ["queued", "running", "completed", "failed", "cancelled"]}
            ],
            "output_schema": {
              "type": "object",
              "properties": {
                "task_id": {"type": "string", "minLength": 1, "maxLength": 128},
                "status": {"type": "string", "enum": ["queued", "running", "completed", "failed", "cancelled"]}
              },
              "required": ["task_id", "status"],
              "additionalProperties": false
            }
          },
          "system": {
            "required_device_states": ["idle"],
            "forbidden_device_states": ["fault", "maintenance", "offline"],
            "safety_fences": [
              "目标容器剩余容量必须大于或等于加液体积"
            ],
            "result_verification": [
              "任务必须进入 completed 状态",
              "返回实际加液体积和目标容器执行后状态",
              "提交对象状态时必须产生新的状态修订号"
            ]
          }
        }
      ]
    },
    "diagnostics": []
  }
}
```

示例中的数值只用于解释字段结构，不构成所有设备的通用限制。设备技能必须给出自身真实的范围、枚举、单位和跨字段约束。

## 响应字段

| 字段             | 类型              | 是否必需 | 范围与含义                      |
| -------------- | --------------- | ---- | -------------------------- |
| `device`       | `object`        | 是    | 设备标识、名称和总体说明               |
| `revision`     | `string`        | 是    | 长度 `1..128`；本次技能定义修订号      |
| `detail_level` | `string`        | 是    | 与请求一致，为 `summary` 或 `full` |
| `ttl_ms`       | `integer`       | 是    | `0..86400000`；`0` 表示不可缓存   |
| `cache_scope`  | `string`        | 是    | `none` 或 `service`         |
| `skills`       | `array[object]` | 是    | 当前披露范围内的技能；无匹配项时返回 `[]`    |
| `diagnostics`  | `array[object]` | 是    | 非阻断提示；没有内容时返回 `[]`         |

### 摘要条目

| 字段               | 类型       | 是否必需 | 范围与约束                                           |
| ---------------- | -------- | ---- | ----------------------------------------------- |
| `command_id`     | `string` | 是    | 长度 `1..128`；仅允许 ASCII 字母、数字、`_`、`-`、`.`；同一设备内唯一 |
| `name`           | `string` | 是    | 长度 `1..128`                                     |
| `description`    | `string` | 是    | 长度 `1..1024`；同时说明技能用途和适用时机                      |
| `request_class`  | `string` | 是    | `read`、`write` 或 `invoke`                       |
| `execution_mode` | `string` | 是    | `sync` 或 `async`                                |
| `object_summary` | `string` | 是    | 长度 `0..512`；概括作用对象或读取范围                         |

### 完整条目

完整技能在摘要字段基础上必须增加以下三个维度：

| 维度        | 必需内容                                                          |
| --------- | ------------------------------------------------------------- |
| `object`  | 对象角色、容器类型、形状、尺寸、材质、容积和允许状态；未知物理值使用 `null`                     |
| `control` | 前置条件、参数展开定义、`input_schema`、跨字段约束、状态变化、返回展开定义和 `output_schema` |
| `system`  | 必需设备状态、禁止状态、安全围栏和结果验证规则                                       |

`control.parameters` 必须与 `control.input_schema` 表达同一组参数；`control.returns` 必须与 `control.output_schema` 表达同一组输出。两个 Schema 必须符合 JSON Schema 2020-12。Schema 不能表达的跨字段、对象状态和物理约束必须写入 `preconditions`、`constraints` 或 `safety_fences`。

## 分类约束

| 请求分类     | 完整技能必须声明                        | 执行要求                              |
| -------- | ------------------------------- | --------------------------------- |
| `read`   | 读取范围、输入参数、输出 Schema、观测时间和新鲜度语义  | 不得改变设备或对象状态；设备在线且数据源可用时通常允许调用     |
| `write`  | 可写字段、目标值范围、预期修订号、写入后验证规则        | 写前校验字段与修订号；写后回读或取得等价确认；验证失败不得报告成功 |
| `invoke` | 对象条件、设备状态、全部前置条件、安全围栏、状态变化和结果验证 | 请求进入设备前必须完成强制检查；完成后必须用实际状态或证据验证结果 |

## 条件读取

`if_revision` 只适用于调用方已经完整缓存的相同披露级别和相同 `command_ids` 集合。修订号相同时，服务端返回：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "capability-1",
  "result": {
    "result_type": "not_modified",
    "value": {
      "revision": "rev-20260902-02",
      "detail_level": "full",
      "ttl_ms": 300000,
      "cache_scope": "service"
    },
    "diagnostics": []
  }
}
```

首次读取、缓存不完整或筛选条件变化时不得发送 `if_revision`。`if_revision` 不匹配不是错误，服务端必须返回当前结果。

## 错误响应

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "capability-1",
  "error": {
    "code": -32004,
    "message": "技能不存在",
    "data": {
      "diagnostics": [
        {
          "code": "command_not_found",
          "path": "params.command_ids[0]",
          "message": "目标设备未声明该技能"
        }
      ]
    }
  }
}
```

| 诊断代码                     | 触发条件                       | 服务端行为          |
| ------------------------ | -------------------------- | -------------- |
| `invalid_request`        | 缺少必需参数、类型错误、未知参数或参数组合无效    | 返回协议错误，不返回技能数据 |
| `device_not_found`       | 当前服务没有对应 `device_id`       | 返回协议错误         |
| `command_not_found`      | `command_ids` 包含设备未声明的技能   | 返回协议错误，不忽略未知项  |
| `invalid_revision`       | `if_revision` 格式错误或缓存条件不完整 | 返回协议错误         |
| `capability_unavailable` | 暂时无法生成一致的技能定义              | 返回协议错误，不返回部分详情 |

错误响应不得包含其他设备的技能、状态或内部实现信息。

## 修订规则

以下变化必须产生新的能力修订号：

* 新增、删除或修改技能；
* 修改技能的对象、控制或系统任一维度；
* 修改请求分类、参数、返回值、前置条件、安全围栏或验证规则；
* 修改会影响调用方行为的适配映射。

设备当前忙闲状态和任务进度不改变能力修订号。

## 事实地位

完整 FSP 技能定义是设备接口事实来源。由其生成的设备技能说明、函数定义或其他模型可读内容不得改变对象属性、控制参数、请求分类、强制约束、系统安全围栏和结果验证规则。
