> Language: 简体中文
> English default entry: [English](../../en/capabilities/hardware-hotplug.md)
> Translation status: current

# 外接硬件接入与热插拔架构方案

更新日期：2026-06-12

文档关系：

- 总体入口：`总体架构方案.md`
- Topic EventBus 与 Lua Agent 调度核心：`Rust与Lua事件总线智能体调度架构方案.md`
- 动态 Adapter 与外部能力接入：`Lua调用外部Agent动态Adapter架构方案.md`
- Agent 扫描与发现：`Agent扫描与发现架构方案.md`
- 配置体系：`项目配置方案.md`
- 进程级恢复与升级：`进程级停机升级架构方案.md`

## 1. 方案定位

本文定义 Eva-CLI 如何接入外接硬件，并支持设备插入、拔出、重连、驱动绑定、命令调用、数据上报和运行时恢复。

核心结论：

- 外接硬件属于受控外部能力，应通过 **HardwareAdapter** 接入 AdapterRegistry。
- 硬件发现、设备句柄、驱动协议、热插拔、权限、审计和重连由 **Rust Runtime** 托管。
- Lua Agent 不直接访问 USB、串口、蓝牙、HID、网络 socket 或操作系统设备 API。
- Lua 只能通过 `ctx.tools.invoke_agent` 调用硬件 capability，或订阅 `/hardware/**` Topic 处理设备事件。
- 热插拔事件通过 EventBus 发布，关键生命周期事件写入 Durable Event Log。
- 设备身份、连接实例和运行状态必须分离，避免拔插后状态错绑。
- 未授权设备只能被发现和记录，不能自动 claim、自动打开或暴露为可调用 capability。

该方案的目标不是把任意硬件变成通用原始 IO，而是把硬件抽象成可控、可观测、可恢复、可路由的系统能力。

## 2. 目标与非目标

### 2.1 目标

- 支持常见外接硬件接入，包括 USB HID、USB CDC/串口、BLE、蓝牙经典、局域网设备和厂商 SDK 设备。
- 支持设备热插拔、断线检测、重连、状态恢复和命令取消。
- 支持通过 manifest 声明设备匹配规则、协议、capability、权限和限制。
- 支持设备生命周期事件、数据事件和命令结果事件进入 Topic EventBus。
- 支持硬件 capability 进入 AdapterRegistry 和 AdapterRouter，与现有 Adapter 调用模型一致。
- 支持多设备同时在线，并区分物理设备身份、逻辑设备别名和连接 generation。
- 支持设备健康状态、错误归一、审计日志、指标和诊断。
- 支持可靠性分层：生命周期事件持久化，高频数据按配置采样、聚合或丢弃。

### 2.2 非目标

- 不让 Lua 直接打开设备、读写文件描述符、调用系统设备 API 或发送任意 raw bytes。
- 不做全总线无限制扫描，不主动 claim 未授权设备。
- 不默认安装内核驱动、修改系统设备权限或更改操作系统安全策略。
- 不默认把所有传感器数据完整写入 Durable Event Log。
- 不承诺设备拔出期间外部物理世界状态可恢复。
- 不把硬件协议解析下沉到 Lua 作为默认方案。
- 不把设备厂商 SDK 进程设为隐式可信边界；厂商 SDK 仍需通过 Adapter policy 受控。

## 3. 总体架构

```text
OS Device Events / Polling Fallback
                |
                v
      [HardwareDiscoveryService - Rust]
                |
                v
        [DeviceNormalizer]
                |
                v
     [Hardware Policy Precheck]
                |
                v
          [DeviceRegistry]
                |
        +-------+-------+
        |               |
        v               v
 [DriverBinding]   [DeviceStateStore]
        |
        v
 [HardwareAdapterRuntime]
        |
        v
   AdapterRegistry
        |
        v
   AdapterRouter / ctx.tools.invoke_agent
        |
        v
     Hardware Device

Device lifecycle / data / errors
        |
        v
      EventBus
        |
        v
   Scheduler -> Lua Agent
```

系统新增四类核心组件：

```text
HardwareDiscoveryService
DeviceRegistry
DriverBinding
HardwareAdapterRuntime
```

它们都属于 Rust Runtime 边界，不属于 Lua Agent，也不属于普通外部 Agent。

## 4. 核心边界

### 4.1 Rust 托管硬件边界

Rust 负责：

- 监听操作系统热插拔事件。
- 枚举设备和归一化设备描述。
- 校验硬件 manifest、policy 和设备 allowlist。
- 打开、claim、关闭设备句柄。
- 执行协议握手、读写、超时、取消和重试。
- 管理每个设备的连接 generation、任务队列和健康状态。
- 将设备事件发布到 EventBus。
- 将硬件 capability 注册到 AdapterRegistry。
- 维护审计、指标、状态存储和错误归一。

### 4.2 Lua 只表达业务意图

Lua 可以：

- 订阅 `/hardware/**` Topic。
- 读取事件 payload 中的结构化设备状态或业务数据。
- 通过 `ctx.tools.invoke_agent` 调用受控硬件 capability。
- 根据硬件事件发布业务 Topic。

Lua 不可以：

- 传入任意设备路径。
- 传入任意 VID/PID 匹配规则。
- 传入任意 raw bytes。
- 打开串口、HID、BLE、socket 或厂商 SDK。
- 修改设备权限、驱动、系统配置或热插拔策略。
- 绕过 AdapterRouter 直接调用 HardwareAdapterRuntime。

### 4.3 Adapter 是硬件能力入口

硬件接入沿用现有 Adapter 定义：

```text
Adapter = manifest + capability + policy + transport + protocol + runtime state
```

硬件扩展新增：

```text
transport = hardware
```

硬件 capability 应表达业务语义，而不是底层 IO 细节：

```text
device.health
device.identify
scale.weight.read
scale.tare
card.read
printer.print
sensor.sample.read
relay.switch.set
camera.frame.capture
```

## 5. 设备类型与接入形态

### 5.1 USB HID

适合：

- 称重设备。
- 读卡器。
- 自定义 HID 控制器。
- 简单按键、旋钮、传感器。

特点：

- 通常通过 VID/PID、usage page、usage、serial 匹配。
- 数据可能以 report 形式上报。
- 权限和 claim 行为跨平台差异较大。
- 适合用设备专属 DriverBinding 解析 report。

### 5.2 USB CDC / 串口

适合：

- MCU 开发板。
- 工业控制器。
- Modbus RTU 设备。
- GPS、扫码枪、仪表。

特点：

- 通常通过串口路径、VID/PID、serial、friendly name 匹配。
- 需要配置 baud rate、data bits、stop bits、parity、flow control。
- 断线常表现为读写错误或端口消失。
- 协议 framing 必须由 Rust 驱动层处理。

### 5.3 BLE

适合：

- 低功耗传感器。
- 可穿戴设备。
- 蓝牙信标。
- 简单控制设备。

特点：

- 通过 service UUID、characteristic UUID、device address、manufacturer data 匹配。
- 需要处理扫描、连接、配对、订阅 notify、RSSI 和重连。
- 地址可能随机化，不能只依赖 MAC 地址作为稳定身份。
- 设备 identity 需要结合服务、厂商数据和绑定记录。

### 5.4 蓝牙经典

适合：

- 传统串口蓝牙设备。
- 音频、输入设备或厂商协议设备。

特点：

- 可能需要系统配对。
- discovery 和连接权限强依赖操作系统。
- 不应默认自动配对，配对流程应作为受控用户授权或外部配置。

### 5.5 局域网设备

适合：

- 网络打印机。
- 摄像头。
- IoT 网关。
- 工控网关。

特点：

- 可以通过显式 endpoint、mDNS、SSDP 或厂商发现协议发现。
- 默认不做全网段扫描。
- 网络设备仍按 `transport = hardware` 处理，因为其生命周期、数据流和控制语义属于设备域。
- 如果设备本身暴露 HTTP API，也可以用 HttpAdapter，但需要明确区分“普通 HTTP 服务”和“硬件设备运行态”。

### 5.6 厂商 SDK 设备

适合：

- 只能通过厂商动态库、守护进程或本地服务访问的设备。

特点：

- 推荐用 Rust 内置 binding 或受控 sidecar 进程封装。
- 厂商 SDK 不应让 Lua 直接调用。
- SDK 权限、进程生命周期、崩溃隔离和输出 schema 仍由 HardwareAdapterRuntime 管理。

## 6. 数据模型

### 6.1 DeviceDescriptor

`DeviceDescriptor` 表示一次枚举或热插拔事件中看到的设备描述。

```rust
pub struct DeviceDescriptor {
    pub device_uid: String,
    pub bus: DeviceBus,
    pub vendor_id: Option<u16>,
    pub product_id: Option<u16>,
    pub serial: Option<String>,
    pub manufacturer: Option<String>,
    pub product: Option<String>,
    pub os_path: Option<String>,
    pub friendly_name: Option<String>,
    pub properties: serde_json::Value,
    pub observed_at_ms: u64,
}

pub enum DeviceBus {
    UsbHid,
    Serial,
    Ble,
    BluetoothClassic,
    Network,
    VendorSdk,
}
```

字段说明：

- `device_uid`：系统归一后的稳定设备身份，优先使用 serial、硬件唯一 ID 或显式绑定记录生成。
- `bus`：设备接入总线。
- `vendor_id` / `product_id`：USB 或厂商设备标识。
- `serial`：设备序列号；如果设备不提供 serial，不能假定路径稳定。
- `os_path`：操作系统设备路径，仅 Rust 可用，不直接暴露给 Lua 作为可调用参数。
- `properties`：总线特有属性，例如 HID usage、BLE service UUID、串口参数候选。

### 6.2 LogicalDevice

`LogicalDevice` 表示系统内可被业务引用的逻辑设备。

```rust
pub struct LogicalDevice {
    pub logical_id: String,
    pub device_uid: String,
    pub alias: Option<String>,
    pub adapter_id: String,
    pub capabilities: Vec<String>,
    pub policy_tags: Vec<String>,
    pub bound_manifest_id: String,
}
```

字段说明：

- `logical_id`：业务稳定引用，例如 `scale.main`、`printer.frontdesk`。
- `device_uid`：物理设备身份。
- `alias`：人工配置别名。
- `adapter_id`：注册到 AdapterRegistry 的 provider ID。
- `bound_manifest_id`：绑定该设备的 hardware manifest。

### 6.3 DeviceConnection

`DeviceConnection` 表示一次具体连接实例。

```rust
pub struct DeviceConnection {
    pub connection_id: String,
    pub device_uid: String,
    pub logical_id: Option<String>,
    pub generation: u64,
    pub status: DeviceStatus,
    pub opened_at_ms: Option<u64>,
    pub last_seen_ms: u64,
    pub last_error: Option<HardwareError>,
}

pub enum DeviceStatus {
    Discovered,
    Authorizing,
    Claiming,
    Handshaking,
    Online,
    Degraded,
    Offline,
    Removed,
    Disabled,
    Unauthorized,
}
```

`generation` 是热插拔正确性的关键字段。每次设备重新连接并被打开时，必须生成新的 connection generation。业务层可以保持 `logical_id` 不变，但 Rust 必须用 `generation` 区分旧连接和新连接。

### 6.4 HardwareInvokeRequest

硬件调用复用 `AgentInvokeRequest`，硬件相关参数放在 `payload` 中。

```json
{
  "request_id": "req-001",
  "capability": "scale.tare",
  "provider": "hardware.scale.main",
  "payload": {
    "logical_id": "scale.main",
    "expected_generation": 42,
    "args": {}
  },
  "timeout_ms": 5000
}
```

约束：

- `provider` 必须指向已注册的 HardwareAdapter。
- `logical_id` 只能引用已授权设备。
- `expected_generation` 可选；存在时用于防止命令发往重连后的错误连接实例。
- `args` 必须符合 capability schema。
- Lua 不得传入 `os_path` 或底层句柄。

### 6.5 HardwareInvokeResponse

硬件响应复用 `AgentInvokeResponse`，输出保持结构化。

```json
{
  "request_id": "req-001",
  "provider": "hardware.scale.main",
  "capability": "scale.tare",
  "status": "completed",
  "output": {
    "logical_id": "scale.main",
    "generation": 42,
    "result": "ok"
  },
  "error": null
}
```

## 7. Hardware Adapter Manifest

硬件 Adapter manifest 推荐放在：

```text
config/adapters/hardware/*.yaml
```

也可以兼容放在：

```text
config/adapters/*.yaml
```

示例：

```yaml
id: hardware.scale.usb-main
name: Main USB Scale
version: 1.0.0
enabled: true
transport: hardware

hardware:
  bus: usb_hid
  match:
    vendor_id: "0x1234"
    product_id: "0x5678"
    serial: "SCALE-001"
    usage_page: "0xff00"
  identity:
    logical_id: scale.main
    alias: frontdesk-scale
    uid_strategy: serial
  protocol:
    name: scale_v1
    framing: hid_report
    handshake_timeout_ms: 3000
  hotplug:
    debounce_ms: 500
    reconnect_ttl_ms: 30000
    stale_generation_policy: reject_commands

capabilities:
  - device.health
  - scale.weight.read
  - scale.tare

permissions:
  raw_io: false
  claim_exclusive: true
  network: false
  pairing: false

limits:
  timeout_ms: 5000
  max_concurrency: 1
  command_queue_capacity: 64
  max_event_bytes: 65536

events:
  lifecycle_persist: true
  data_mode: latest
  data_sample_interval_ms: 200

routing:
  priority: 100
  default_for:
    - scale.weight.read
```

字段说明：

- `transport`：硬件设备固定为 `hardware`。
- `hardware.bus`：设备总线类型。
- `hardware.match`：设备匹配规则，只能由 manifest 声明。
- `hardware.identity`：逻辑设备身份和稳定 UID 策略。
- `hardware.protocol`：设备协议和 framing。
- `hardware.hotplug`：热插拔 debounce、重连 TTL 和旧 generation 处理策略。
- `permissions.raw_io`：是否允许原始 IO capability。默认必须为 `false`。
- `permissions.claim_exclusive`：是否独占 claim 设备。
- `limits.command_queue_capacity`：单设备命令队列上限。
- `events.data_mode`：高频数据处理策略。

## 8. 设备发现与授权

### 8.1 发现来源

硬件发现来源包括：

```text
OS hotplug notification
startup enumeration
manual rescan command
polling fallback
network discovery provider
vendor SDK device list
```

跨平台建议：

- Windows：SetupAPI、WM_DEVICECHANGE、WinUSB/HID、COM port 枚举、Bluetooth API。
- Linux：udev、hidraw、tty、BlueZ、Avahi。
- macOS：IOKit、CoreBluetooth、Bonjour。

具体平台 API 属于实现细节，架构上必须统一归一为 `DeviceDescriptor`。

### 8.2 发现不等于授权

设备发现分为三层：

```text
observed
  -> matched
  -> authorized
```

- `observed`：系统看到设备，但没有匹配到 manifest 或 policy。
- `matched`：设备匹配到 manifest，但还未通过 policy。
- `authorized`：设备通过 manifest、policy 和运行时限制，可以 claim 并注册 capability。

未授权设备只允许发布受限诊断事件：

```text
/hardware/device/discovered
```

该事件不得包含敏感路径、系统权限细节或可被 Lua 用来直接打开设备的信息。

### 8.3 匹配规则

匹配规则按确定性从高到低：

```text
explicit logical binding
  -> serial / stable hardware id
  -> VID/PID + interface + usage/service UUID
  -> vendor/product + friendly name
  -> bus-specific fallback
```

如果多个 manifest 匹配同一设备：

- 默认标记为 `match_ambiguous`。
- 不自动 claim。
- 发布 `/hardware/device/error`。
- 需要通过配置消除歧义。

如果一个 manifest 匹配多个设备：

- 如果 manifest 声明 `multi_instance: true`，按规则生成多个 logical device。
- 否则标记为 `multiple_devices_matched`，不自动选择。

### 8.4 设备身份策略

设备身份必须区分：

```text
physical identity: device_uid
logical identity: logical_id
connection identity: connection_id + generation
```

不能只使用操作系统路径作为 `device_uid`，因为路径可能在重启、拔插或端口变化后改变。

允许的 `uid_strategy`：

```text
serial
vendor_serial
binding_record
ble_identity
network_endpoint
manifest_singleton
```

`manifest_singleton` 只适合单设备、强人工配置场景，不能作为默认策略。

## 9. 热插拔状态机

### 9.1 状态图

```text
Discovered
  -> Authorizing
  -> Claiming
  -> Handshaking
  -> Online
  -> Degraded
  -> Offline
  -> Removed

Discovered
  -> Unauthorized

Online
  -> Degraded
  -> Offline
  -> Removed

Offline
  -> Claiming
  -> Handshaking
  -> Online

Any
  -> Disabled
```

状态含义：

- `Discovered`：设备被观察到。
- `Authorizing`：正在执行 manifest 和 policy 校验。
- `Claiming`：正在打开设备并申请访问权。
- `Handshaking`：正在执行协议握手和能力确认。
- `Online`：设备可调用。
- `Degraded`：设备仍存在，但部分能力不可用或错误率较高。
- `Offline`：设备暂不可用，但仍处于重连 TTL。
- `Removed`：设备已移除，连接上下文释放。
- `Disabled`：被配置或 policy 禁用。
- `Unauthorized`：未授权，不可 claim。

### 9.2 插入语义

设备插入或启动枚举发现设备后：

```text
observe descriptor
  -> debounce
  -> normalize descriptor
  -> match manifest
  -> policy precheck
  -> claim/open
  -> protocol handshake
  -> create DeviceConnection generation
  -> register HardwareAdapter provider
  -> emit /hardware/device/online
```

`/hardware/device/online` 表示设备已经通过权限校验、句柄已打开、协议握手已完成，并且 capability 已注册或可被注册。

### 9.3 拔出语义

设备拔出可能通过两类信号识别：

- 操作系统 remove notification。
- 读写错误、心跳失败、BLE disconnect、socket close。

拔出后：

```text
mark connection Offline
  -> cancel or fail pending commands
  -> unregister or mark adapter unhealthy
  -> close handle
  -> emit /hardware/device/offline
  -> wait reconnect_ttl
  -> emit /hardware/device/removed if not reconnected
```

在 `reconnect_ttl_ms` 内重连的设备可以恢复同一 `logical_id`，但必须使用新的 `generation`。

### 9.4 重连语义

重连判断基于 `device_uid` 和 manifest binding。重连后：

- `logical_id` 可以保持不变。
- `connection_id` 必须变化。
- `generation` 必须递增。
- 旧 generation 的 pending command 必须失败、取消或显式迁移。

默认策略：

```text
stale_generation_policy = reject_commands
```

即旧连接上的命令不能自动落到新连接，除非 capability 明确声明可重放且有 idempotency key。

### 9.5 Debounce 与抖动处理

热插拔事件可能重复、乱序或延迟。HardwareDiscoveryService 必须支持：

- 按 `device_uid` 和 `os_path` 合并短时间重复事件。
- 延迟 claim，等待设备节点稳定。
- 对 remove + add 抖动执行 reconnect 合并。
- 对同一设备并发插拔事件串行化处理。

`debounce_ms` 是设备级策略，默认不应为 0。

## 10. Topic 事件契约

### 10.1 Topic 命名

推荐硬件 Topic：

```text
/hardware/device/discovered
/hardware/device/matched
/hardware/device/authorized
/hardware/device/online
/hardware/device/degraded
/hardware/device/offline
/hardware/device/removed
/hardware/device/disabled
/hardware/device/error
/hardware/device/data
/hardware/command/accepted
/hardware/command/completed
/hardware/command/failed
/hardware/command/cancelled
```

Topic 仍使用现有分段 matcher 规则，不允许用字符串前缀匹配替代。

### 10.2 DeviceLifecycleEvent

```json
{
  "topic": "/hardware/device/online",
  "payload": {
    "device_uid": "usb:1234:5678:SCALE-001",
    "logical_id": "scale.main",
    "adapter_id": "hardware.scale.main",
    "bus": "usb_hid",
    "generation": 42,
    "status": "online",
    "capabilities": [
      "device.health",
      "scale.weight.read",
      "scale.tare"
    ],
    "observed_at_ms": 1781234567000
  },
  "meta": {
    "correlation_id": "corr-001",
    "causation_id": "hotplug-001"
  }
}
```

生命周期事件可以被 Lua Agent 订阅，用于业务初始化、UI 提示、任务恢复或降级。

### 10.3 DeviceDataEvent

```json
{
  "topic": "/hardware/device/data",
  "payload": {
    "device_uid": "usb:1234:5678:SCALE-001",
    "logical_id": "scale.main",
    "adapter_id": "hardware.scale.main",
    "generation": 42,
    "capability": "scale.weight.stream",
    "schema": "scale.weight.v1",
    "data": {
      "weight": 12.34,
      "unit": "kg",
      "stable": true
    },
    "sampled_at_ms": 1781234567123
  }
}
```

高频数据事件必须遵守 `events.data_mode`：

```text
none       不发布数据事件
latest     只保留最新值
sampled    按采样间隔发布
all        全量发布，需显式开启
aggregate  聚合后发布
```

默认建议为 `latest` 或 `sampled`，不建议默认 `all`。

### 10.4 HardwareCommandEvent

命令事件用于异步硬件调用：

```json
{
  "topic": "/hardware/command/completed",
  "payload": {
    "command_id": "cmd-001",
    "request_id": "req-001",
    "device_uid": "usb:1234:5678:SCALE-001",
    "logical_id": "scale.main",
    "adapter_id": "hardware.scale.main",
    "generation": 42,
    "capability": "scale.tare",
    "status": "completed",
    "output": {
      "result": "ok"
    }
  }
}
```

命令事件应带 `command_id`、`request_id`、`generation` 和 `capability`，用于审计、去重和故障定位。

## 11. Capability 设计

### 11.1 命名原则

硬件 capability 使用业务域命名，不暴露底层总线细节：

```text
device.health
device.identify
device.reset
scale.weight.read
scale.weight.stream
scale.tare
card.read
barcode.scan.read
printer.print
relay.switch.set
sensor.sample.read
camera.frame.capture
```

不推荐作为默认业务 capability：

```text
usb.write
serial.send
hid.report.write
ble.characteristic.write
raw.bytes.send
```

这些原始 IO 能力只能作为受限诊断能力存在，并且必须由 policy 显式开启。

### 11.2 输入输出 Schema

每个硬件 capability 必须有输入输出 schema：

```yaml
capability: scale.tare
input_schema:
  type: object
  additionalProperties: false
  properties:
    logical_id:
      type: string
    expected_generation:
      type: integer
  required:
    - logical_id
output_schema:
  type: object
  additionalProperties: false
  properties:
    result:
      type: string
      enum:
        - ok
    generation:
      type: integer
  required:
    - result
    - generation
```

Schema 校验由 Rust 执行。Lua 只接收校验后的结构化结果。

### 11.3 同步与异步调用

短命令适合同步调用：

```lua
local result = ctx.tools.invoke_agent({
  capability = "scale.weight.read",
  provider = "hardware.scale.main",
  payload = {
    logical_id = "scale.main"
  },
  timeout_ms = 3000
})
```

长任务或持续流适合 Topic 事件：

```text
/adapter/invoke
  -> HardwareAdapterRuntime
  -> /hardware/command/accepted
  -> /hardware/device/data
  -> /hardware/command/completed
```

流式数据不应长期占用同步调用。持续传感器、扫码、刷卡、相机帧等应优先使用事件订阅。

## 12. DeviceRegistry

DeviceRegistry 是硬件运行态索引。

```rust
pub struct DeviceRegistry {
    pub descriptors: HashMap<DeviceUid, DeviceDescriptor>,
    pub logical_devices: HashMap<LogicalDeviceId, LogicalDevice>,
    pub connections: HashMap<ConnectionId, DeviceConnection>,
    pub adapter_index: HashMap<AdapterId, LogicalDeviceId>,
    pub capability_index: HashMap<String, Vec<AdapterId>>,
}
```

职责：

- 记录观察到的设备。
- 记录逻辑设备绑定。
- 记录当前连接 generation 和状态。
- 提供 adapter_id 到 logical_id 的反向索引。
- 为 AdapterRegistry 提供硬件 provider 注册材料。
- 为 CLI inspect 和诊断提供状态视图。

DeviceRegistry 不是长期业务数据库。长期业务状态仍应进入业务 State Store。

## 13. DriverBinding

DriverBinding 是设备协议适配层。

```rust
#[async_trait::async_trait]
pub trait HardwareDriver: Send + Sync {
    fn id(&self) -> &str;
    fn supported_capabilities(&self) -> Vec<String>;

    async fn open(
        &self,
        descriptor: DeviceDescriptor,
        config: HardwareConfig,
    ) -> Result<Box<dyn HardwareSession>, HardwareError>;
}

#[async_trait::async_trait]
pub trait HardwareSession: Send + Sync {
    async fn handshake(&mut self) -> Result<DeviceHandshake, HardwareError>;
    async fn invoke(&mut self, req: HardwareCommand) -> Result<HardwareCommandResult, HardwareError>;
    async fn cancel(&mut self, command_id: &str) -> Result<(), HardwareError>;
    async fn close(&mut self) -> Result<(), HardwareError>;
}
```

DriverBinding 可以是：

- Rust 内置驱动。
- 受控 sidecar 进程。
- 厂商 SDK binding。
- 协议组合驱动，例如 serial + Modbus。

DriverBinding 不应由 Lua 热更新。若需要业务层可变换协议字段，应通过 manifest 或受控 Lua capability 做数据映射，不应把底层设备 IO 权限交给 Lua。

## 14. HardwareAdapterRuntime

HardwareAdapterRuntime 实现现有 `AgentAdapter` 接口。

```rust
pub struct HardwareAdapterRuntime {
    pub adapter_id: String,
    pub logical_id: String,
    pub device_uid: String,
    pub generation: u64,
    pub driver: Arc<dyn HardwareDriver>,
    pub command_queue: DeviceCommandQueue,
    pub health: AdapterHealth,
}
```

职责：

- 暴露硬件 capability。
- 接收 AdapterRouter 发来的 `AgentInvokeRequest`。
- 校验 request payload、logical_id 和 expected_generation。
- 将调用转为 `HardwareCommand`。
- 管理单设备并发和命令队列。
- 将结果转为 `AgentInvokeResponse`。
- 将异步状态发布到 `/hardware/**` Topic。
- 在设备 offline 时返回结构化错误。

HardwareAdapterRuntime 的健康状态应映射到 AdapterHealth：

```text
Online    -> Healthy
Degraded  -> Degraded
Offline   -> Unhealthy
Removed   -> Unhealthy
Disabled  -> Unhealthy
```

## 15. 命令队列与并发

每个在线设备应有独立命令队列：

```text
AdapterRouter
  -> HardwareAdapterRuntime
  -> DeviceCommandQueue
  -> HardwareSession
```

队列策略：

- 默认单设备串行执行命令。
- 只有驱动声明支持并发时才允许并行。
- 队列满时返回 `ConcurrencyLimited` 或 `DeviceQueueFull`。
- 设备 offline 时队列中的命令必须取消、失败或按 policy 等待重连。
- 命令必须带 timeout。

命令分类：

```text
read       可重试，通常无副作用
write      可能有副作用，默认不可自动重试
control    设备状态改变，必须审计
stream     长生命周期，必须支持取消
diagnostic 受限诊断能力
```

## 16. 错误模型

硬件错误应归一到结构化类型。

```rust
pub struct HardwareError {
    pub kind: HardwareErrorKind,
    pub message: String,
    pub device_uid: Option<String>,
    pub logical_id: Option<String>,
    pub generation: Option<u64>,
    pub provider_code: Option<String>,
    pub retryable: bool,
}

pub enum HardwareErrorKind {
    DeviceNotFound,
    DeviceUnauthorized,
    DeviceAmbiguous,
    DeviceBusy,
    DeviceDisconnected,
    DeviceDegraded,
    OpenFailed,
    ClaimFailed,
    HandshakeFailed,
    ProtocolError,
    Timeout,
    Cancelled,
    QueueFull,
    StaleGeneration,
    PermissionDenied,
    OutputSchemaInvalid,
    DriverError,
}
```

映射到 AdapterError 时：

```text
DeviceNotFound       -> NotFound
DeviceUnauthorized   -> PermissionDenied
DeviceDisconnected   -> Unhealthy
Timeout              -> Timeout
Cancelled            -> Cancelled
QueueFull            -> ConcurrencyLimited
ProtocolError        -> ProtocolError
DriverError          -> ProviderError
```

错误消息可以给 Lua 和用户界面展示，但不得包含敏感系统路径、密钥、完整厂商 SDK dump 或未经截断的大量二进制数据。

## 17. 可靠性与持久化

### 17.1 生命周期事件

以下事件建议写入 Durable Event Log：

```text
/hardware/device/authorized
/hardware/device/online
/hardware/device/degraded
/hardware/device/offline
/hardware/device/removed
/hardware/device/error
/hardware/command/completed
/hardware/command/failed
/hardware/command/cancelled
```

这些事件用于系统恢复、审计、诊断和业务补偿。

### 17.2 高频数据

高频数据不应默认全量持久化。配置策略：

```text
none       不持久化
latest     State Store 只保留最新值
sampled    按间隔写入
aggregate  聚合后写入
all        全量写入，需显式开启
```

如果选择 `all`，必须配置：

- 最大事件大小。
- 最大写入频率。
- retention。
- backpressure 策略。
- 磁盘上限。

### 17.3 崩溃恢复

Runtime 崩溃重启后：

- 重新执行 startup enumeration。
- 根据 DeviceRegistry snapshot 恢复 logical binding。
- 对在线设备创建新 generation。
- 对旧 generation 未完成命令标记为 unknown、failed 或 cancelled。
- 重放 Durable Event Log 时不能重复执行有副作用硬件命令。

硬件命令属于外部物理副作用，不能仅靠 EventBus replay 自动重做。只有声明为幂等且带 idempotency key 的命令可以自动重试。

### 17.4 双活与升级

进程级 blue-green 或双活切流时，硬件设备必须避免双进程同时 claim。

策略：

- 使用 per-device lease。
- 同一 logical_id 同一时间只能由一个 Runtime generation 持有写能力。
- Candidate Runtime 可以观察设备，但不能 claim 独占设备，除非进入切流阶段。
- 切流前旧 Runtime draining，停止接收新硬件命令。
- 切流后新 Runtime 重新 claim 设备并生成新 connection generation。

对不支持共享打开的设备，必须采用严格单 owner 模式。

## 18. 安全模型

### 18.1 设备 Allowlist

硬件设备必须经过 allowlist：

```text
global hardware policy
  -> adapter manifest match
  -> device binding record
  -> session/user policy
  -> request-level constraints
```

权限只能收紧，不能放宽。

### 18.2 Raw IO 限制

默认禁止：

- 任意 USB control transfer。
- 任意 HID report write。
- 任意串口 bytes write。
- 任意 BLE characteristic write。
- 任意厂商 SDK passthrough。

如果必须提供 raw IO，必须满足：

- 只允许诊断模式。
- 只允许特定 provider。
- 输入大小受限。
- 输出截断。
- 强审计。
- 默认不可由普通 Lua Agent 调用。

### 18.3 配对与系统权限

蓝牙配对、驱动安装、udev 规则、Windows 驱动权限、macOS 隐私授权等属于系统权限操作。

架构约束：

- 默认不自动执行。
- 不通过 Lua 触发。
- 必须由显式用户授权或部署配置完成。
- 授权状态只作为 discovery health 或诊断信息呈现。

### 18.4 数据敏感性

硬件数据可能包含敏感信息：

- 身份卡号。
- 条码内容。
- 摄像头图像。
- 位置信息。
- 生物特征。
- 工业设备状态。

HardwareAdapter manifest 必须允许声明数据分类：

```yaml
data_classification:
  - pii
  - credential_like
  - image
  - location
```

审计和日志必须支持脱敏、截断和禁止持久化。

## 19. 可观测性

硬件链路必须带以下字段：

```text
event_id
request_id
command_id
topic
capability
adapter_id
logical_id
device_uid
bus
generation
driver_id
correlation_id
causation_id
latency_ms
queue_depth
status
error_kind
retryable
```

指标建议：

```text
hardware_devices_observed_total
hardware_devices_online
hardware_device_reconnect_total
hardware_device_disconnect_total
hardware_command_total
hardware_command_failed_total
hardware_command_latency_ms
hardware_command_queue_depth
hardware_data_events_total
hardware_data_dropped_total
hardware_driver_error_total
```

诊断视图应支持：

- 查询当前在线设备。
- 查询某个 logical_id 的 generation 历史。
- 查询某个 device_uid 的最近错误。
- 查询硬件 capability 到 adapter 的路由。
- 查询设备命令队列深度和 inflight 命令。
- 查询未授权或匹配冲突设备。

## 20. 配置策略

### 20.1 全局硬件策略

建议在 `config/policies/hardware.yaml` 中配置全局策略：

```yaml
hardware_policy:
  enabled: true
  discovery:
    startup_enumeration: true
    hotplug_watch: true
    polling_fallback_ms: 5000
    network_discovery: false
  defaults:
    debounce_ms: 500
    reconnect_ttl_ms: 30000
    command_timeout_ms: 5000
    max_event_bytes: 65536
    raw_io: false
  allowed_buses:
    - usb_hid
    - serial
    - ble
  denied_buses:
    - vendor_sdk
  logging:
    redact_os_path: true
    redact_payload_fields:
      - card_number
      - image
```

### 20.2 设备级配置

设备级配置放在 hardware Adapter manifest：

```yaml
hardware:
  bus: serial
  match:
    vendor_id: "0x2341"
    product_id: "0x0043"
    serial: "A123"
  serial:
    baud_rate: 115200
    data_bits: 8
    stop_bits: 1
    parity: none
    flow_control: none
  protocol:
    name: modbus_rtu
    unit_id: 1
  identity:
    logical_id: sensor.line1
    uid_strategy: serial
```

### 20.3 Agent 权限配置

Agent 调用硬件仍通过 Adapter capability 授权：

```yaml
permissions:
  tools:
    - invoke_agent
  adapters:
    capabilities:
      - device.health
      - scale.weight.read
      - scale.tare
    providers:
      - hardware.scale.main
  emit:
    - /business/scale/**
```

Agent 订阅硬件事件通过 Topic route 控制：

```yaml
subscriptions:
  - /hardware/device/online
  - /hardware/device/offline
  - /hardware/device/data
```

## 21. 与现有模块的关系

### 21.1 与 AgentDiscoveryService

AgentDiscoveryService 继续负责项目配置、外部 Agent、MCP、Skill 和 Lua capability 的发现。

硬件发现可以作为独立服务运行，也可以作为 Discovery 的专门 source：

```text
discovery/sources/hardware_devices.rs
```

区别是硬件 discovery 具有持续 watch 语义，不只是启动扫描。

### 21.2 与 AdapterRegistry

HardwareAdapterRuntime 注册到 AdapterRegistry：

```text
adapter_id = hardware.<domain>.<logical_id>
transport = hardware
capabilities = manifest.capabilities
```

AdapterRouter 无需理解 USB、串口或 BLE，只需要根据 capability、provider、health 和 policy 选择 Adapter。

### 21.3 与 EventBus

硬件事件通过 EventBus 发布。EventBus 不直接访问设备，也不承担协议解析。

可恢复 EventBus 对硬件生命周期事件提供 accepted 后可恢复语义，但不能保证物理设备副作用可回滚。

### 21.4 与 Scheduler / Lua Agent

Scheduler 按 Topic 将硬件事件投递给 Lua Agent。

Lua Agent 可根据业务需要：

- 将设备上线映射为业务可用状态。
- 将设备数据转为业务事件。
- 在设备下线时暂停任务。
- 调用硬件 capability 执行业务命令。

Lua Agent 不持有设备连接，不缓存底层句柄。

### 21.5 与进程级升级恢复

硬件 owner 必须跟随 Runtime generation。

升级时：

- 旧 Runtime 对硬件 Adapter 进入 draining。
- 新 Runtime 观察配置和设备状态。
- 切流点发生后，新 Runtime claim 设备。
- 新连接产生新 generation。
- 旧 generation 命令不得自动落到新 generation。

## 22. 设计校验口径

硬件接入设计必须满足：

- 未授权设备不会被自动 claim。
- Lua 不能直接访问设备路径、句柄或 raw IO。
- 同一 logical_id 的每次重连都有新的 generation。
- 设备拔出会取消、失败或隔离 pending command。
- AdapterRegistry 能看到硬件 provider 的健康状态。
- Scheduler 能通过 Topic 将硬件事件投递给 Agent。
- 高频数据不会默认无限写入 Durable Event Log。
- 双活或升级时不会两个 Runtime 同时写同一独占设备。
- 错误可结构化映射到 AdapterError。
- 审计链路能关联 request、command、device、generation 和 capability。

## 23. 风险与应对

| 风险 | 应对 |
| --- | --- |
| 设备路径不稳定导致错绑 | 使用 `device_uid`、binding record 和 `generation`，不以路径作为唯一身份 |
| 热插拔事件重复或乱序 | 使用 debounce、事件合并和 per-device 串行状态机 |
| 未授权设备被误调用 | discovered / matched / authorized 分层，未授权不注册 capability |
| 高频数据压垮 EventBus | `data_mode`、采样、聚合、限速和 backpressure |
| 拔出后命令落到新设备 | `expected_generation` 和 `stale_generation_policy` |
| 双活 Runtime 同时 claim 设备 | per-device lease 和 Runtime generation owner |
| Lua 绕过权限发送 raw bytes | 默认禁止 raw IO，所有命令 schema 化 |
| 厂商 SDK 崩溃影响主进程 | sidecar 隔离或 Rust binding 错误边界 |
| 日志泄露硬件敏感数据 | data classification、字段脱敏和输出截断 |

## 24. 总结

Eva-CLI 的外接硬件接入应落在 **Rust 托管硬件边界 + HardwareDiscoveryService + DeviceRegistry + DriverBinding + HardwareAdapterRuntime + AdapterRegistry + Topic EventBus** 的组合上。

Rust 负责发现、授权、claim、协议、热插拔、可靠性和审计；Lua 负责业务响应、事件编排和受控 capability 调用。这样硬件设备可以像其他外部能力一样进入统一 Adapter 体系，同时保留热插拔、设备身份、物理副作用和高频数据这些硬件领域必须单独处理的约束。
