# 沧溟 - 机枢
**Repository Path**: fangyu_fy/drivers
## Basic Information
- **Project Name**: 沧溟 - 机枢
- **Description**: 本契约面向通用设备驱动(实验仪器、工控设备、医疗设备、分析仪等),但非操作系统内核式的驱动。本契约注重设备驱动的能力:一次有始有终、有序、有时长、可能多阶段的动作,其典型过程如:"连接 → 初始化 → 设参数 → 启动 → 等待 → 读数 → 停止",时序由设备自身的物理过程决定。
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: lukan/dev/driver
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-09-21
- **Last Updated**: 2026-09-29
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Cangming.Core.Driver
设备驱动能力契约库 —— 定义所有硬件驱动的公共类型、接口与能力声明。
本库是**不可变契约**:所有硬件驱动必须遵循此契约实现,而契约一经确定便只可扩展、不可修改;库内只含接口、枚举、记录、纯虚基类与值对象,不含业务逻辑,且**不引用本模块之外的任何类型**,因此它可以被单独编译、单独发布、单独理解。
---
## 1. 定位
### 1.1. 所面向的驱动形态
本契约面向**通用设备驱动**(实验仪器、工控设备、医疗设备、分析仪等),而非操作系统内核式的驱动,二者的能力形状根本不同:内核驱动的能力单位是一次调用(读、写、ioctl)且时序由内核调度,而通用设备驱动的能力单位是**一次有始有终、有序、有时长、可能多阶段的动作**,其典型过程为"连接 → 初始化 → 设参数 → 启动 → 等待 → 读数 → 停止",时序由设备自身的物理过程决定。
由此推出本契约的第一条结论:**驱动能力的表达,最终都是一步一步的操作,即"步骤"。** 步骤不是编排叠加在能力之上,它就是能力本身的形状;因此凡属"有步骤"这一事实的内容进入契约,而"如何求值、如何展开成执行序"则留给执行方。
### 1.2. 步骤与能力是两个正交的轴
由于"怎么做"与"这类动作的语义是什么"是为不同问题服务的两件事,且二者的变化频率并不一致,因此契约把它们建模为两个轴而非同一事物的两种写法。
| 轴 | 回答的问题 | 载体 | 变化频率 |
|---|---|---|---|
| **步骤**(实例与序列) | 这个动作**怎么做**:有哪些子步骤、什么顺序、并行还是串行、多久 | `Steps/` 下的步骤契约 | 中 |
| **能力**(类型与模式) | 这类动作的**语义**:参数形状、量纲、效果、保证、条件 | `Capabilities/` 下的能力描述符 | 低 |
二者由引用关系连接:`StepDefinition.Capability` 指向同一驱动能力清单中已声明的能力,因此驱动作者主要编写步骤("我有哪些动作、怎么走"),而能力模型提供类型系统与语义(宿主据以组态、校验、仿真)。
### 1.3. 契约准入判据
这是本模块最重要的一条规矩,其用途是把"实现细节"挡在契约之外,判据本身只有一问:
> **契约条目测试:换一台物理设备、没有任何执行器,它还能被实现吗?**
判据的两个方向各有其后果:答案是"能"的概念属于设备固有属性,应当进入契约;答案是"不能"的概念则是**先做了实现选择才存在**的东西,应当留在实现方。据此,本契约曾经包含的命令队列(`IDriverCommand`、`IDriverCommandQueue`、`CommandPriority`、`CommandProgress`)被整体删除——由于"命令"只有在已经决定以队列实现之后才存在,把它写进契约等于替所有实现方预先规定"你必须有队列、必须按优先级降序、必须失败即中止",这会让直连直发、自行串行化或采用无锁环形缓冲的作者被迫适配一个不属于他的模式。
队列中唯一真正属于契约的语义是"设备出错后继续执行可能损坏硬件",这条语义并未随队列消失,而是以 `StepFailurePolicy` 的声明形式留在了步骤层。
### 1.4. 身份约定
由于驱动种类与运行实例具有不同的生命周期与键类型,且设备地址与二者的混用极易引发错误,因此身份被拆为三个各自单一的概念。
| 概念 | 含义 | 使用者 |
|---|---|---|
| `IDriver.DriverId` | **驱动种类**标识,与 `IDriverBuilder.DriverId`、`DriverInfo.DriverId` 同源 | 注册、构建、配置序列化 |
| `IDriver.InstanceId` | **本次运行的实例**标识,由 `BaseDriver` 生成 | 日志、结果、异常中串联"是哪一次运行" |
| `DeviceCode` | 设备地址的强类型值对象 | 一切与设备编码相关的参数 |
历史上曾存在的 `IUnique`、`IVariety`、`IAlias` 三接口已删除:`IUnique.CreateInstance()` 是实体入库语义且以 `void` 自变异身份,`IVariety.VarietyId` 与 `DriverId` 是同一概念的两个名字,`IAlias.Alias` 与 `DeviceCode` 职责重叠且无归属,因此三者的信息已由上述三个概念完整承接。
---
## 2. 架构
### 2.1. 从声明到调用的四层分离
由于"设备有什么""这个地址有什么""这次调用做了什么""它承诺了什么"分别服务于不同的使用场景且变化频率不同,因此契约把它们分为四层,并规定下层不得渗入上层的关注点。
```mermaid
flowchart TB
subgraph 型号级声明
M["CapabilityManifest
结构版本 + 拓扑 + 能力集 + 数据通道 + 设备级保证"]
D["CapabilityDescriptor
五段骨架"]
M --> D
end
subgraph 地址级视图
V["CapabilityView
某地址上作用域相符的可用能力"]
end
subgraph 调用级
Q["InvokeRequest
能力 + 绝对地址 + 参数值"]
K["CapabilityInvoker
三层校验后交由执行体"]
R["InvokeResult
终态 + 落地程度 + 故障 + 校验结果"]
end
M -->|按作用域模式筛选| V
V -->|选定能力| Q
Q --> K
K --> R
```
**贯穿四层的第一原则是:`CapabilityDescriptor` 不含任何参数值、不含具体地址、不含运行状态**,因为三者分别属于配置、实例视图与观察模型。这条约束是"能力集可从声明推导而非手工维护"能否成立的分水岭——只要有一个字段是实例相关的,清单便无法在型号级共享,也就永远无法从实现反向生成。
### 2.2. 能力描述符的五段骨架
由于一项能力要被宿主独立地检查与执行,它就必须能回答五个各自独立的问题,因此描述符由五段构成,且五段之间存在依赖顺序而非并列关系。
```mermaid
flowchart TB
CD["CapabilityDescriptor"]
CD --> S1["1. 身份
Id + Version + Owner"]
CD --> S2["2. 作用域
ScopePattern(相对路径)"]
CD --> S3["3. 参数与结果
ParameterSchema + ResultShape"]
CD --> S4["4. 效果与保证
EffectDeclaration + CapabilityPromise"]
CD --> S5["5. 条件
StaticCondition + RuntimePredicate"]
S1 -->|决定能否被引用| S2
S2 -->|决定能否被定位| S3
S3 -->|决定能否被组态与仿真| S4
S4 -->|决定能否被安全重试| S5
```
### 2.3. 能力位由推导产生而非手工维护
由于同一事实若在两处赋值就必然存在漂移空间,因此本契约不保留任何手工维护的能力位,而是把"设备支持什么"拆为两个各自唯一的来源。
```mermaid
flowchart LR
A["IDriver.Manifest
可执行能力"] --> C["DriverCapabilityProfile
只读,无可写状态"]
B["实例实际实现的接口
观察与容错能力"] --> C
C --> D1["SupportsGeneralControls 等
由能力标识命名空间首段投影"]
C --> D2["SupportsRealTimeStatusQuery 等
由接口实现判定"]
```
原 `DriverCapabilities`(六个 `bool`)的整体移除正是因为它是"三处真相"的根源:接口继承、`BaseDriver` 默认值、驱动覆盖三处各自声明同一件事,一旦不一致便无从判定何者为准。
---
## 3. 类型目录
### 3.1. 核心抽象
#### 3.1.1. IDriver(必须实现)
```csharp
public interface IDriver : IDriverSupportRealTimeStatusQuery,
IDriverSupportRealTimeDataCollection
{
string DriverId { get; } // 驱动种类标识(序列化键,发布后不可改)
string Name { get; set; } // 驱动名称
string InstanceId { get; } // 本实例唯一标识(基类生成)
DeviceModel DeviceModel { get; } // 设备型号
bool IsMultiDevice { get; } // 是否为多设备驱动
string Manufacturer { get; } // 设备制造商名称
CapabilityManifest? Manifest { get; } // 能力清单(能力的唯一声明来源)
DriverCapabilityProfile CapabilityProfile { get; } // 由清单与接口实现推导的能力概况
DriverOperationMode CurrentMode { get; set; } // 当前操作模式
}
```
可观察能力(状态查询与数据采集)由本接口直接继承,因此任何驱动至少具备这两项;其余能力由驱动按需追加实现对应接口,并在 `Manifest` 中以能力描述符声明其可执行能力。`Manifest` 取 null 表示驱动尚未提供能力声明,此时其能力支持仅由所实现的接口确定,故调用方应先查询 `CapabilityProfile` 而非假定能力存在。
#### 3.1.2. BaseDriver(推荐继承)
```csharp
public abstract class BaseDriver : IDriver
{
protected BaseDriver(); // 生成 InstanceId
// 子类必须实现
public abstract string DriverId { get; }
public abstract string Name { get; set; }
public abstract DeviceModel DeviceModel { get; }
public abstract string Manufacturer { get; }
public abstract event EventHandler? StatusUpdated;
public abstract event EventHandler? DataUpdated;
// 基类默认值,子类按需覆盖
public virtual bool IsMultiDevice => true;
public virtual CapabilityManifest? Manifest => null; // 未提供清单时能力支持仅由接口确定
public DriverCapabilityProfile CapabilityProfile { get; } // 惰性推导,只读
public virtual DriverOperationMode CurrentMode { get; set; } = DriverOperationMode.Experiment;
}
```
事件订阅由驱动按需自行完成(opt-in),即基类不在构造器中把事件订阅回自身;能力概况之所以惰性推导而非在构造器中计算,是因为 C# 的派生类属性初始化器在基类构造器之后执行,构造期读取虚属性 `Manifest` 只会得到 null。
#### 3.1.3. 设备编码与型号
| 类型 | 说明 |
|------|------|
| `DeviceCode`(readonly record struct) | 设备编码值对象:`Code`。所有设备编码参数一律使用它以避免与裸字符串混淆,且**不提供隐式转 `string`**,因为隐式转换会让本类型要消除的混淆重新出现 |
| `DeviceModel`(class) | 设备型号,支持级联子型号:`Model`、`SubModels`、`Unknown`(未知型号,字符串表示为空)、`Parse`、`Build`、`ToString`(以只读的 `Separator` 连接);`Parse` 与 `Build` 拒绝空型号段 |
#### 3.1.4. 状态与信息
| 类型 | 说明 |
|------|------|
| `DriverStatus`(struct) | 实时状态,6 个 bool:`IsOnline`、`IsReady`、`IsBusy`、`IsPaused`、`IsError`、`IsPlaceable` |
| `DriverInfo`(record) | 驱动元信息(required init):`DriverId`、`DriverName`、`DriverAlias`、`Manufacturer` |
| `DeviceInfo`(record) | 设备信息(required init):`DriverId`、`DeviceCode`、`DeviceAlias` |
图标与显示名等纯 UI 装饰不入契约,因为其迭代节奏与技术契约不同步,故一律由消费侧组合扩展。
#### 3.1.5. 操作与结果
| 类型 | 说明 |
|------|------|
| `DriverOperationMode` | Experiment=1、Debug=2、Calibration=3、Maintenance=4、Simulation=5 |
| `InvokeOutcome` | 调用终态:Completed=0、Rejected=1、Faulted=2、Cancelled=3、TimedOut=4、Unsupported=5,**只含终态**而进行态属会话模型 |
| `DriverTaskResult` | 任务结果的最小形态:`State` + `Message`;工厂 `Success` / `Failed` / `Unimplemented` / `Rejected` / `TimedOut` / `From(InvokeResult)` |
| `InvokeResult` | 完整调用结果:`SessionId`、`CapabilityId`、`Scope`、`Outcome`、`Message`、`Measurements`、`AppliedEffect`、`Fault`、`Validation`;工厂 `Success` / `Rejected` / `Failed` |
**拒绝、故障与未支持三者必须分诊**,因为其处置路径互不相同:执行前校验未通过(`Rejected`)应引导调用方修正参数或等待状态,执行时故障(`Faulted`)应告警停机,而设备不具备该能力(`Unsupported`)应降级或跳过。
#### 3.1.6. 能力概况与数据
| 类型 | 说明 |
|------|------|
| `DriverCapabilityProfile` | 能力概况,全部取值由推导产生而无可写状态,另提供 `DeclaredFamilies` 与逐族查询 `Supports(DriverCapabilityFamily)` |
| `DriverCapabilityFamily` | 能力族:GeneralControls / DeviceSettings / OutputFeedbacks / FaultTolerance / RealTimeStatusQuery / RealTimeDataCollection |
| `IDriverData` | 驱动数据:`Timestamp`、`DeviceCode`、`Quality`——只承载一次采集结果的通用要素 |
| `IScalarDriverData : IDriverData` | 标量数据:追加 `IReadOnlyDictionary Values`,其键为数据通道名 |
| `IDeviceModelChannelInfo` | 型号数据通道信息:`DeviceModel` + `DataChannels`,使消费方无需接触驱动实例即可查得该型号产出哪些量及其单位与量程 |
| `DataQuality` | Good=0 / Degraded=1 / Invalid=2 |
| `IDriverSetting` | 驱动设置:`DriverId`、`DeviceCode { get; set; }` |
`IScalarDriverData` 的键虽为自由字符串而无量纲信息,但其物理含义由能力清单中同名的数据通道声明给出;由于该约定依赖命名一致而无法由类型强制,消费方在解释数值时应以通道声明为准,而非猜测键的含义。
### 3.2. 步骤(Steps/)
步骤是驱动能力的最小表达单位,而本命名空间只做声明与自持执行,不含任何流程引擎类型——顺序、并行与重复的求值及展开属于执行方,因而不进契约。
#### 3.2.1. 标识与定义者
| 类型 | 说明 |
|------|------|
| `StepId`(readonly record struct) | 步骤标识,驱动内唯一,构成配置与流程的序列化键,一经发布不可更改 |
| `StepOwner`(record)+ `StepOwnerKind` | 定义者:`Kind`(Standard / Vendor / Team)+ `Name`,用于同名步骤的归属与优先级 |
#### 3.2.2. 时序声明
| 类型 | 说明 |
|------|------|
| `StepOrdering` | `Sequential` / `Parallel` / `Barrier`,描述动作的物理结构而**不求值** |
| `StepFailurePolicy` | `AbortSequence`(默认)/ `Continue` / `SkipSameCapability`,属设备安全语义 |
| `Repeat` | 重复轮次,1 表示不重复而 0 表示由设备或调用方决定 |
| `Duration` / `Timeout` | 预计时长与超时,均为声明值,供执行方预排 |
此处须区分**声明**与**执行状态**:`StepOrdering` 与 `Repeat` 描述"这个动作的物理结构"因而属设备属性,而"当前跑到第几轮"是执行方的进行态,后者由 `StepExecutionState` 作为观测数据承载。
#### 3.2.3. 参数与调用
| 类型 | 说明 |
|------|------|
| `StepParameterBinding`(record) | `Key` + `Value`:步骤声明的参数默认值 |
| `StepRequest`(record) | 一次调用的输入:`DeviceCode` + `Parameters`,**每次只绑一台设备**,多设备由调用方为每个地址各发一次请求 |
| `StepContext`(record) | 步骤体可读的全部外部信息:`ExecutionId`、`DeviceCode`、`Driver`、`Mode`、`Status`(**执行开始时的快照**)、`Parameters`、`Attempt` |
参数覆盖规则为:**调用方实参 > 步骤默认值 > 能力约束**。
#### 3.2.4. 执行与结果
| 类型 | 说明 |
|------|------|
| `StepBody`(delegate) | `ValueTask (StepContext, CancellationToken)`,是**自持抽象**,故实现步骤无需引入任何流程引擎依赖 |
| `StepResult`(record) | `Outcome` + `Message` + `Applied`;工厂 `Success` / `Faulted` / `Cancelled` |
| `StepOutcome` | `Completed` / `Faulted` / `Cancelled` / `Stopped`,只描述终态 |
| `StepExecutionState`(record) | 执行进度观测:`Path`、`Current`、`Total`;派生 `CurrentStep`、`Percent` |
`Applied` 之所以取三态(true 已生效、false 未生效、null 无状态改动),是因为非原子步骤失败时它是判定"是否需要回滚或复位"的唯一依据,故不得省略。
`StepResult` 只承载终局判定,测量值不在此列:其形状(量纲、单位、量程)由能力清单的数据通道声明,其取值由步骤声明的结果获取方式(见 §3.2.5 的 `ResultChannel`)交付。返回值上曾有的同名成员与数据通道构成同一信息的两处真源,因而移出且不得回填;步骤执行体只回答"本步骤是否完成"这一位信息,调用方据终局判定与结果获取方式各自取法。
#### 3.2.5. 定义与实现
| 类型 | 说明 |
|------|------|
| `StepDefinition`(record) | 纯声明:`Id`、`Name`、`Description`、`Owner`、`Version`、`SubSteps`、`Ordering`、`FailurePolicy`、`Repeat`、`Duration`、`Timeout`、`Parameters`、`Produces`、`Capability`、`Kind`、`ResultChannel` |
| `StepKind` | `Atom`(0)/ `Action`(1),宿主流程结构的类别标注;空表示由执行方按 `SubSteps` 推导 |
| `StepResultChannel` | `Query`(0)/ `Event`(1)/ `Sink`(2),结果获取方式;空表示尚未声明 |
| `BaseDriverStep`(abstract) | 实现"定义 + 执行体"的最小样板,提供 `Body` 与 `ToDefinition()`,且**不继承任何流程引擎类型** |
| `IDriverStep` | 步骤最小契约:`Id` + `Body` |
| `IDriverStepManifest` | 驱动步骤清单:`DriverId` + `GetSteps()` |
| `IDriverStepVerifier` | 前置校验的声明位:`VerifyAsync(StepVerificationContext, CancellationToken)`;**实现即声明"本步骤会在执行前自检"**,不实现的步骤行为不变 |
| `StepVerification`(record)+ `StepVerificationKind` | 校验结果:`Kind`(四态)+ `Message`;工厂 `Passed` / `WithWarning` / `ExistingRisk` / `Failed` |
| `StepVerificationPhase` | `Compile`(0)/ `PreExecute`(1),只覆盖执行方实际暴露的两处调用入口 |
| `StepVerificationContext`(record) | 校验上下文:与 `StepContext` 同形但**不含执行关联标识**,并追加 `Phase` |
步骤是原子的还是复合的由驱动决定:需要分层就在 `SubSteps` 中声明子步骤,不需要则留空。
`Kind` 与 `ResultChannel` 所服务的两个前提各不相同:前者回答"宿主应把这一步当作可展开的编组还是不可分解的叶子",使宿主能对叶节点做运行范围短路而对编组节点先编译展开,取值空缺即由 `SubSteps` 是否为空推导;后者回答"结果从哪里取",因为结果不经步骤执行体返回给宿主,调用方只能依据本声明决定取法,为空时便只剩"能否继续下一步"这一位信息。两者均为可选声明,不改变既有成员的形状与语义,驱动仅在推导结果不合本设备语义、或结果确有交付通道时才显式声明。
#### 3.2.6. 前置校验
执行方在编译阶段的第一步就是校验一个组步骤能否展开,返回失败即中止编译、返回带风险或警告的结果则记日志后继续,且在每轮迭代都要重新执行因为环境会变;由于"这一步现在能不能开始"完全由设备事实决定(电源未上、未使能、模式不符、限位已触发、参数越界),本命名空间以可选接口 `IDriverStepVerifier` 承载它,使这类判断回到步骤侧声明,流程因而得以在**编译期一次性报出整份流程的问题**,而不必跑到那一步才失败。**实现该接口即声明"本步骤会在执行前自检"**,不实现的步骤零改动,故不取基类虚方法——那会给每个步骤都塞一份恒定放行的空实现,把"是否校验"这一信息抹平。校验只能是只读判断,不得触发设备动作或修改驱动状态,因为它会被重复调用;实现方还须只出现本契约的类型,不得引用任何外部流程引擎类型(与 `StepBody` 的自持约束同源)。
判定取四态:`Passed` 表示可以开始,`WithWarning` 与 `ExistingRisk` 表示仍可继续但须记日志,`Failed` 表示应中止编译或运行;后三者的区别在于调用方的处置不同,故判定之外必须附一条原因,工厂对空原因一律拒收。阶段取两值:`Compile` 对应编译期校验,`PreExecute` 对应步骤即将运行之前的校验;之所以不对齐执行方的四阶段枚举,是因为其管理器只对外暴露这两处入口,为其余阶段各留一个当前无人调用的钩子属超前设计。
```mermaid
flowchart LR
A["流程编译之前"] -->|StepVerificationPhase.Compile| V["IDriverStepVerifier.VerifyAsync"]
B["步骤即将运行之前"] -->|StepVerificationPhase.PreExecute| V
V --> K{"StepVerification.Kind"}
K -->|Passed| G1["开始执行"]
K -->|WithWarning / ExistingRisk| G2["记日志后继续"]
K -->|Failed| G3["中止编译或运行"]
```
### 3.3. 执行入口与校验(Capabilities/Invocation/)
| 类型 | 说明 |
|------|------|
| `InvokeRequest`(record) | 一次调用的完整输入:`CapabilityId` + `Scope`(绝对地址)+ `Parameters` |
| `ParameterValue`(record) | 参数值及其来源:`Name` + `Value` + `IsDefault`;**来源必须可辨**,否则无法正确判定必填参数是否真正缺失 |
| `CapabilityInvoker` | 把"先校验、后执行"固定为唯一路径:`Validate` 为纯校验,`InvokeAsync` 校验通过才交由执行体动作 |
| `InvocationExecutor`(delegate) | 能力执行体,由驱动提供,使"校验与编排"同"动作实现"分离 |
| `ValidationResult`(record) | 校验结果:`IsValid` + 逐条 `Failures`,可 `ToMessage()`、`Has(kind)` 或 `ToException()` |
| `ValidationFailure`(record) | 单条失败:`Kind`(范畴)+ `Reason`(理由)+ `Subject` + `Message` |
| `ValidationFailureKind` | Parameter(应修正参数)/ Precondition(应修正调用条件)/ Environment(应等待状态改变) |
| `ValidationFailureReason` | 具体理由:缺必填、未声明、类型不符、越界、超长、模式不符、依赖不满足、缺前置能力、互斥冲突、作用域不符、运行时条件不成立、能力未声明 |
| `ParameterValidator` | **参数校验的唯一实现**:调用路径与配置路径皆委托于它,以免"能设进去的值"与"能被调用的值"各自为政 |
| `AppliedEffect` / `AppliedEffectLevel` | 副作用落地程度:None / Partial / Full,**三态**,非原子能力失败时决定回滚或复位 |
校验分三层且互不短路,以便一次性给出全部原因:参数层核对必填、未声明参数、类型相容、数值上下界、字符串长度与模式、元素个数及参数间依赖;前置条件层核对所需能力是否已声明且版本相符、是否与已声明能力互斥;环境层核对能力作用域是否适用于该地址、运行时谓词在当前状态下是否成立。
范畴与理由之所以分为两维,是因为二者的用途不同:范畴回答"这类问题该由谁解决",而理由回答"具体哪里不合规",上层据前者决定是提示用户、调整调用顺序还是等待状态,据后者决定提示什么内容。
拒绝之所以不通过异常表达,是因为校验失败属可预期的业务结果,故由调用入口译为 `Rejected` 终态并保留逐条失败项,使调用方得以按失败性质分流而不必解析消息文本;需要异常路径者可用 `ValidationResult.ToException()`。
### 3.4. 能力模型(Capabilities/)
| 组 | 类型 |
|------|------|
| 根与清单 | `CapabilityDescriptor`(五段骨架)、`CapabilityManifest`、`CapabilitySet`(型号级)、`CapabilityView`(地址级)、`DriverCapabilityFamily`、`DriverCapabilityProfile` |
| 身份 | `CapabilityId`(命名空间化分段,构成序列化键)、`CapabilityOwner` / `CapabilityOwnerKind` |
| 作用域 | `ScopeKind`、`ScopePattern`(相对路径,不含设备编码)、`ScopeIndexPattern`、`ScopeAddress`(含设备编码的绝对地址)、`ScopeAddressSegment` |
| 参数 | `ParameterTypeKind`(封闭八元)、`ParameterSchema`、`ParameterConstraint`、`ParamDependency` / `ParamDependencyKind`、`Dimension`、`Unit`、`ResultShape` |
| 效果 | `EffectKind`(纯读取至不可逆五级)、`Atomicity`、`SideEffectScope`、`EffectDeclaration` |
| 保证 | `CapabilityPromise`、`CancellationMode`、`TimeoutDeclaration` / `TimeoutKind`、`DevicePromiseDeclaration`、`ConcurrencyLimit`、`ResponseClass` |
| 条件 | `StaticCondition`、`RuntimePredicate`、`StatusField`、`ComparisonOperator`、`CapabilityReference` |
| 拓扑与数据 | `TopologyDeclaration`、`TopologyNode`、`DataChannelDeclaration`、`SamplingSemantics` |
| 一致性 | `CapabilityConsistency`、`ConsistencyReport`、`ConsistencyIssue` / `ConsistencyIssueKind` |
**参数类型集是封闭的八元**(Bool / Int / Double / String / Enum / Quantity / Duration / Array),因为通用组态、通用界面、仿真、录制回放与模板继承五件事全部建立在"宿主能理解参数形状"之上,一旦允许驱动方扩展自定义类型,这五件事会同时崩塌。其中 `Quantity` 带量纲与单位,且其量纲与数据通道的量纲来自**同一套自建定义**,否则"同一个 `double` 是微升还是毫克"无从判断,通用数据管道与单位换算亦无从建立;由于借用外部数学模块会破坏模块自治,故量纲与单位在契约内自建为封闭集合。
**参数依赖限定为三种固定形式**(Requires / Implies / Excludes),因为若以任意表达式树表达,可判定性、可穷举性与可验证性会一并下降,而宿主真正需要的是"启动前枚举整个流程的能力齐备性"。
**声明与实现的一致性由机器双向保证**:`CapabilityConsistency.Check` 同时核对"声明了却未实现"与"实现了却未声明"两个方向,因为单向检查只解决一半问题——若只检查前者,便会漏掉后者这类不报错却使能力永久不可见的隐蔽情形。校验依据是能力族的受限集合,其中可执行能力族由能力标识的命名空间首段投影而来,观察与容错等能力族由驱动实际实现的接口确定,因此既不需要额外元数据,也不可能被手工绕过。该检查既可用于注册期拒绝不合规的驱动,也可由契约测试直接断言驱动合规。
该检查有两处已证实的边界,二者都宁可放弃判定也不产生误报:其一,输出反馈族因契约中并无承载它的接口而无从核对其真假,故不列入检查;其二,一个能力族可对应多个可接受接口,因为控制能力已拆为四个细粒度接口,若只认组合接口则按实际硬件只实现细粒度接口的驱动会被误判为"声明了却未实现",而那恰是本契约推荐的实现方式。
### 3.5. 能力接口(Supports/)
| 接口 | 提供能力 | 关键成员 |
|------|---------|---------|
| `IDriverSupportConnectivity` | 连接管理 | `ConnectAsync`、`DisconnectAsync`、`InitializeAsync` |
| `IDriverSupportLifecycleControl` | 运行生命周期 | `StartAsync`、`PauseAsync`、`ResumeAsync`、`StopAsync` |
| `IDriverSupportPowerControl` | 电源管理 | `PowerOnAsync`、`PowerOffAsync`、`RestartAsync` |
| `IDriverSupportSelfTest` | 自检 | `SelfTestAsync` |
| `IDriverSupportGeneralControls` | 通用控制(上述四者的组合) | 不含自有方法,仅为"整体声明支持通用控制"这一粗粒度用途而保留 |
| `IDriverSupportDeviceSettings` | 参数配置 | `Setting`、`UpdateSettingsAsync`、`SettingChanged` 事件 |
| `IDriverSupportFaultTolerance` | 容错恢复 | `OnErrorOccurred` 事件 |
| `IDriverSupportRealTimeStatusQuery` | 状态查询 | `StatusUpdated` 事件 |
| `IDriverSupportRealTimeDataCollection` | 数据采集 | `DataUpdated` 事件 |
通用控制之所以拆为四个接口,是因为设备常见"能启动但不能断电"这类不对称配置,而 11 个方法打成一个接口会迫使实现方要么全实现、要么全不实现,使能力声明退化为"我实现了这个接口";拆散之后实现方按实际硬件只实现相应子接口,能力表达因此与硬件配置一致。
`DataChangedEventArgs`(`EventArgs`)同时携带 `OldData`(首次赋值时为 default)与 `NewData`,因为订阅方既需要据新值更新自身状态,也可能需要据旧值判断变化方向。
### 3.6. 错误(Error/)
| 类型 | 说明 |
|------|------|
| `DriverErrorCategory` | Unknown=0、Transport=1、Protocol=2、Device=3、Safety=4、Resource=5、Configuration=6、NotSupported=7 |
| `Recoverability` | None=0 / Retry=1 / Reconnect=2 / Manual=3,按处置代价由轻到重排列 |
| `DriverException : Exception` | 契约自持而不依赖外部错误框架:`Category` + `Number`(厂商私有编号)+ `Recoverability` + `CapabilityId` + `Scope` + `Message` + `InnerException`;另提供 `Transport` / `Protocol` / `Device` / `Safety` / `Resource` 分类工厂与 `At(...)` 归位方法 |
错误必须可分类而非一个裸数字,因为"设备没有这个功能"与"传输链路断了"需要不同的处置路径;而分类之外还须给出**可恢复方式**,因为上层真正要决定的是"该重试、该重连、该停机还是该换耗材"。
分类工厂的默认搭配各有其由:链路错误取可重连而非可重试,因为链路一旦异常则原动作通常已不完整,直接重试会在半途状态上叠加动作;协议错误取可重试,因为其多由偶发报文损坏或设备忙导致而链路仍完好;设备侧错误、安全联锁与资源不足均取须人工处理,因为三者涉及机械卡滞、物理风险或现场资源,在自动重试下都不会自行改善。
当错误源于某次具体调用时,`CapabilityId` 与 `Scope` 指明出事的能力与地址,从而使故障得以归位到具体通道,而非停留在整台设备这一粒度。
### 3.7. 清单与服务契约
| 类型 | 说明 |
|------|------|
| `IDriverManifest` | 驱动清单:`DriverInfo` + `DriverBuilder` |
| `IDriverBuilder` | 驱动构建器:`DriverId`、`Build(IDriverSetting) → IDriver`,失败时抛 `DriverException` |
| `IDriverSettingManifest` | 设置清单:`DriverId` + `DriverSettingBuilder` |
| `IDriverSettingBuilder` | 设置构建器:`DriverId { get; set; }`、`Build(DeviceCode) → IDriverSetting` |
| `IDeviceRegistry` | 种类目录(静态,键为 `DriverId`):`RegisterAsync`、`UnregisterAsync`、`Get`、`GetAll`,继承 `IDriverInfoService` 与 `IDriverBuildService` |
| `IDriverInfoService` | 信息查询:`GetDriverInfo`、`GetAllDriverInfos` |
| `IDriverBuildService` | 构建:`BuildDriver(IDriverSetting)` |
| `IDriverService` | 实例服务(运行时,键为 `DeviceCode`):`GetDriver`、`GetDriverByAlias`、`GetOrBuildDriverAsync`、`RegisterDriverAsync`、`UnregisterDriverAsync`,继承 `IDriverSettingService` |
| `IDriverSettingService` | 设置:`BuildDriverSetting(driverId, DeviceCode)` |
契约分两层建模,因为驱动种类的发现注册与设备实例的运行时管理具有不同的生命周期与键类型:种类层在启动阶段随插件发现而注册,以 `DriverId` 为键,回答"系统里有哪几类设备、如何构建",且**不持有任何运行时实例**;实例层在配置阶段构建绑定,以 `DeviceCode` 为键,持有运行时实例。两层各自的实现方负责维护层内一致性,即清单是实例构建的唯一来源,而登记的实例必须能追溯到其种类。
多设备驱动的绑定语义与单设备相反:由于一个实例可同时覆盖多个物理设备,`RegisterDriverAsync` 允许把同一实例绑定到多个 `DeviceCode`,且同一实例对同一编码的重复注册为幂等操作,因此调用方无需先行判重即可安全调用;而 `GetDriver` 反查覆盖该编码的实例,未注册时返回 null。
---
## 4. 驱动开发指南
### 4.1. 最小实现
```csharp
public class MyDeviceDriver : BaseDriver, IDriverSupportConnectivity
{
public override string DriverId => "MyDevice";
public override string Name { get; set; } = "我的设备";
public override string Manufacturer => "ACME Corp";
public override DeviceModel DeviceModel => new DeviceModel("MyDevice-1000");
public override event EventHandler? StatusUpdated;
public override event EventHandler? DataUpdated;
// 声明可执行能力;能力概况由基类据此与接口实现推导,无需手工维护能力位
public override CapabilityManifest? Manifest { get; } = new()
{
DriverId = "MyDevice",
Topology = TopologyDeclaration.SingleDevice(),
Capabilities = CapabilitySet.Of(new CapabilityDescriptor
{
Id = new CapabilityId("control.connect"),
Owner = new CapabilityOwner(CapabilityOwnerKind.Vendor, "ACME Corp"),
Scope = ScopePattern.ForDevice(),
}),
};
// 可选能力按实际硬件只实现相应的细粒度接口,无需为凑齐接口而给出空实现
public async Task ConnectAsync(CancellationToken ct = default) { /* ... */ }
public async Task DisconnectAsync(CancellationToken ct = default) { /* ... */ }
public async Task InitializeAsync(CancellationToken ct = default) { /* ... */ }
}
```
需要启停但不具备电源管理能力的设备只实现 `IDriverSupportLifecycleControl` 即可:由于未实现 `IDriverSupportPowerControl`,上层无法也不应调用断电动作,其"未支持"由能力清单的缺失如实表达。
### 4.2. 实现一个步骤
```csharp
public sealed class ReadTemperatureStep : BaseDriverStep
{
public override StepId Id => new("sensor.readTemperature");
public override string Name => "读取温度";
public override StepOwner Owner => new(StepOwnerKind.Vendor, "ACME");
public override CapabilityId? Capability => new("sensor.read");
public override IReadOnlyList Produces => ["temperature"];
public override IReadOnlyList Parameters => [new("unit", "C")];
public override TimeSpan? Duration => TimeSpan.FromMilliseconds(200);
public override StepBody Body => async (ctx, ct) =>
{
// ctx.DeviceCode / ctx.Driver / ctx.Mode / ctx.Status / ctx.Parameters 均可用
// 测量值不经返回值承载:形状由能力清单的数据通道声明,取值由本步骤声明的结果获取方式交付
var value = 25.6;
return StepResult.Success($"ok, {value} C");
};
}
```
### 4.3. 实现一个复合步骤
```csharp
public sealed class AutoCalibrateStep : BaseDriverStep
{
public override StepId Id => new("calibrate.auto");
public override string Name => "自动标定";
public override StepOwner Owner => new(StepOwnerKind.Vendor, "ACME");
public override StepOrdering Ordering => StepOrdering.Sequential;
public override StepFailurePolicy FailurePolicy => StepFailurePolicy.AbortSequence;
public override IReadOnlyList SubSteps =>
[
new() { Id = new("calibrate.z"), Name = "Z 轴归零", Owner = new(StepOwnerKind.Vendor, "ACME") },
new() { Id = new("calibrate.span"), Name = "量程校准", Owner = new(StepOwnerKind.Vendor, "ACME"),
Repeat = 3, Duration = TimeSpan.FromSeconds(2) },
new() { Id = new("calibrate.verify"), Name = "校验", Owner = new(StepOwnerKind.Vendor, "ACME") },
];
public override StepBody Body => (ctx, ct) => ValueTask.FromResult(StepResult.Success());
}
```
### 4.4. 发起一次调用
```csharp
var invoker = new CapabilityInvoker(async (request, ct) =>
{
// 校验已由入口完成,此处只负责动作本身
return InvokeResult.Success("session-1", request.CapabilityId,
measurements: new Dictionary { ["temperature"] = 25.6 });
});
var request = new InvokeRequest
{
CapabilityId = new CapabilityId("sensor.read"),
Scope = ScopeAddress.For(new DeviceCode("DEV-01")),
Parameters = [new ParameterValue("channel", "temperature")],
};
var result = await invoker.InvokeAsync(manifest, request, "session-1", driverStatus);
if (!result.IsSuccess && result.Outcome == InvokeOutcome.Rejected)
{
// 据失败性质分流:参数范畴应修正参数,环境范畴应等待状态改变
foreach (var failure in result.Validation!.Failures)
{
Console.WriteLine($"{failure.Kind} / {failure.Subject}:{failure.Reason}");
}
}
```
### 4.5. 数据上报
```csharp
public class MyDeviceData : IDriverData
{
public DateTime Timestamp { get; set; }
public string DeviceCode { get; set; } = string.Empty;
public DataQuality Quality { get; set; }
public double Temperature { get; set; }
}
DataUpdated?.Invoke(this, new MyDeviceData
{
Timestamp = DateTime.UtcNow,
DeviceCode = "MY-DEV-01",
Quality = DataQuality.Good,
Temperature = 25.6,
});
```
### 4.6. 注册到系统
```csharp
// 1) 种类层:注册清单并构建实例
await registry.RegisterAsync(new MyDeviceManifest());
var manifest = registry.Get("MyDevice");
var driver = registry.BuildDriver(setting);
// 2) 实例层:绑定设备编码
var deviceCode = new DeviceCode("MY-DEV-01");
await service.RegisterDriverAsync(deviceCode, driver); // 重复注册幂等
var instance = service.GetDriver(deviceCode); // 反查实例
await service.UnregisterDriverAsync(deviceCode);
```
---
## 5. 操作模式
由于同一动作在不同上下文下的合法性与风险并不相同,因此 `DriverOperationMode` 决定驱动行为上下文,同一命令在不同模式下可执行不同行为。
| 模式 | 值 | 用途 | 典型行为 |
|------|---|------|---------|
| `Experiment` | 1 | 全自动实验 | 严格校验、不可跳过步骤 |
| `Debug` | 2 | 手动调试 | 单步执行、参数覆盖、可跳过安全检查 |
| `Calibration` | 3 | 示教标定 | 反复试错、保存与恢复参数 |
| `Maintenance` | 4 | 维护诊断 | 自检、固件升级、错误历史查看 |
| `Simulation` | 5 | 虚拟运行 | 无硬件操作、模拟数据、时间加速 |
---
## 6. 契约测试
契约库的价值不在接口数量,而在**消费方行为可预期**;由于未被验证的契约只是一份文档,而文档必然与代码漂移,因此本模块自带契约测试套件(位于仓库根的 `tests/Cangming.Core.Driver.Tests`),把契约语义钉成可执行断言。
| 测试组 | 钉住的语义 |
|--------|-----------|
| `ModuleBoundaryTests` | **模块自治**:公开类型全在本命名空间内、公开 API 不夹带模块外类型、程序集不依赖非 BCL、项目文件零引用、不存在已删除的临时概念、公开属性不暴露可变集合 |
| `StepContractTests` | **步骤是能力的表达单位**:声明与实现分离、时序声明可脱离执行器成立、失败即中止为默认、终态不含进行态、`Applied` 三态如实上报 |
| `DriverIdentityTests` | **身份三分离**:`DriverId`(种类,稳定)、`InstanceId`(实例,唯一且只读)、`DeviceCode`(地址,强类型无隐式转换);能力概况由清单与接口实现推导 |
| `ErrorAndResultTests` | **错误可分类且信息不丢**:分类、编号、消息与内部异常完整承载;拒绝、故障与未支持三者分诊;设置变更事件同时携带新旧值 |
| `DeviceModelTests` | 级联型号的构建、解析与值语义,且解析拒绝空型号段而不静默接受 |
| `CapabilityDescriptorShapeTests` | **五段形状**:八元参数类型集、三种依赖形式、约束不开放驱动扩展位、模式不含设备编码、每个单位只归属一个量纲 |
| `CapabilityEffectPromiseConditionTests` | **效果、保证与条件**:不可逆与消耗资源的能力一律不得自动重试、幂等与可重试相互独立、运行时谓词只读观察模型既有字段 |
| `CapabilityManifestAndInvocationTests` | **清单、视图与调用结果**:结构版本属清单级、视图按地址作用域筛选而不返回全部能力、失败结果须能如实给出部分生效的程度 |
| `CapabilityModelIntentTests` | **落地台账与执行入口**:以正向断言记录每一项设计决策,且校验三层覆盖必填、未声明、类型、越界、依赖、前置与环境;校验不通过时不触碰设备 |
| `DiagnosticsAndConsistencyTests` | **诊断可恢复性、参数校验复用与声明实现一致性**:分类工厂给出与处置相符的可恢复方式、错误可归位到能力与地址、调用路径与配置路径对参数给出一致判定,以及一致性的**两个方向**各有一项违反用例 |
| `SampleVerificationTests` | **样例不随时间失效**:以静默方式运行 `testbed/` 下的可运行样例,并断言其退出码为零 |
`CapabilityModelIntentTests` 之所以以正向断言记录设计决策,是因为一旦实现被改回旧形态即会失败,从而把"为何这样设计"变为可执行记录;其此前的"未落地"项曾以 `Skip` 挂起以防迁移半途而废,而迁移既已完成,故现无挂起项。
### 6.1. 测试写法约定
本仓两个测试项目各承一侧——契约侧的 `tests/Cangming.Core.Driver.Tests` 与驱动侧的 `drivers/Cangming.Driver.IDS830ABS.Tests`——但共用同一套写法口径;由于口径若不落纸,下一个新建的测试文件就会按技能默认长出自己的风格而在同目录里并存两套,故新增测试一律以本节五条为先。
1. **框架与命令**:两个项目均为 xUnit v3(核心包 `xunit.v3`,且项目含 `Exe`,因为 v3 的测试项目本身即独立可执行程序),并保留 `Microsoft.NET.Test.Sdk` 与 `xunit.runner.visualstudio` 以 VSTest 模式运行,验证命令一律是:
```bash
dotnet test Cangming.Core.Driver.slnx
```
一次调用同时覆盖契约测试与驱动测试,故不按单个测试项目分开跑,以免改动一侧时漏掉另一侧的回归。
2. **命名口径**:两个项目的测试方法名一律按 `Test###` 编号(同一测试类内按主题分段:首段自 `Test001` 起、次段自 `Test101` 起、第三段自 `Test201` 起,依此类推),用例用途一律写进 `DisplayName`,函数上不再写 `/// `;非测试的私有助手方法不在此列。
3. **断言库**:一律用原生 `Assert.*`——两个项目现零处 `FluentAssertions`、零处 `Moq`——因此既不引入这两个库,也不为新增用例另立第二种断言写法,因为技能的口径是"仓内已有测试用什么就跟什么,两套断言并存才是麻烦"。
4. **结构与强度**:每条用例按 `// Arrange` / `// Act` / `// Assert` 分段标注且段间留空行,段可合并时写作 `// Arrange & Act` 或 `// Act & Assert`,无前置构造的纯调用型用例则只标 `// Act` 与 `// Assert`。`Act` 段只做一次动作,因为出现两次独立触发被测行为的调用,说明这其实是两条用例,须收拢为 `[Theory]` 或拆成两条。断言强度的唯一判据是**改坏验证**,即把实现里与该用例相关的那处判断改坏(条件取反、去掉边界检查、改默认值)之后该用例必须变红,改坏后仍绿者说明它没有守住任何东西。
5. **环境依赖用例**:本机不具备外部条件(如无 `vcan0` CAN 接口)时以 `Assert.Skip` / `Assert.SkipUnless` / `Assert.SkipWhen` 显式跳过以留痕(现例见 `SocketCanLiveTests` 的 `Assert.SkipUnless(InterfacePresent, "本机无 vcan0,真链路交接未执行")`),**不得静默 `return`**,因为跳过会出现在测试报告里而"通过"里不该混入未执行的用例;同理不得用 `#if` 包住、也不得删掉当下不适用的用例。
---
## 7. 样例
`testbed/Cangming.Core.Driver.Sample` 是可运行的样例与端到端验证,它以真实代码走一遍契约的各条路径:温度传感器示范"单设备、可连接、可配参、产出单一数据通道",蠕动泵示范"多通道、消耗资源、非原子部分生效、且不具备电源管理权"。
由于样例以引导式演示逐项断言预期并据退出码报出结论,因此它既是驱动作者的起点,也是契约的可执行说明。
```bash
dotnet run --project testbed/Cangming.Core.Driver.Sample # 引导式演示
dotnet run --project testbed/Cangming.Core.Driver.Sample -- --quiet # 仅输出汇总
```
样例所覆盖的路径如下:
| 段 | 验证内容 |
|---|---|
| 声明与实现的一致性 | 两个驱动皆通过双向一致性检查;并对照展示"泵未实现电源控制"这一不对称配置 |
| 作用域与能力视图 | 设备级地址只看到设备级能力、通道级地址只看到通道级能力;离线时无能力可调用 |
| 执行前校验 | 缺必填、类型不符、取值越界、参数间依赖不满足、作用域不符,各自产出对应的范畴与理由 |
| 默认值与参数来源 | 省略参数时由调用入口补齐默认值,且执行体可区分调用方给的值与声明回落的默认值 |
| 故障与部分生效 | 资源不足归位到具体通道且取须人工处理;非原子失败如实上报部分生效;越界在拒绝时即被拦下 |
| 步骤与能力的引用关系 | 步骤所声明的能力确已在清单中声明、产出通道与能力结果同源、步骤执行体复用能力执行体 |
该样例的通过与否由契约测试中的 `SampleVerificationTests` 以静默方式运行一次并断言退出码为零,因此样例不会随时间失效。
---
## 8. 扩展原则
1. **契约不可变**:已发布的接口一经消费方使用即进入兼容承诺范围,因此不得删除方法、不得修改签名、不得改变语义;确需演进时以新增接口承接,而非就地改造。
2. **契约准入判据**:任何新类型先过 §1.3 的契约条目测试,即"换一台物理设备、没有执行器,它还能被实现吗";答案为"不能"者属实现者的设计模式,不进契约。
3. **模块自治**:本库不引用本模块之外的任何类型,因此其公开 API 是自洽闭包;若其他模块需要本库的类型,方向只能是"它引用本库"。
4. **可加新接口**:能力扩展通过新增接口或新增能力描述符完成,既有实现因无需感知新接口而不受影响。
5. **不可加实现**:本库只含接口、枚举、记录、纯虚基类与值对象;业务逻辑一旦进入契约层,契约的稳定性就会被实现细节的迭代频率拖垮。
6. **向后兼容**:枚举新增值一律放在末尾,以保持既有数值在序列化与分支判断中的稳定;接口新增属性采用默认实现或以新接口承载,而不强制既有实现跟随修改。
7. **不背历史包袱**:确认为死代码的旧契约直接物理删除,因为零消费方的 `[Obsolete]` 只会延长混乱期。
8. **UI 装饰不入契约**:图标、显示名与按钮外观等纯 UI 装饰的迭代节奏与技术契约不同步,故一律由消费侧组合扩展,而契约只承载运行时必需信息。
---
## 9. 协作资产
**注释与文档风格**:本模块的 C# 注释与技术文档遵循 `csharp-doc-style` 技能,该技能的真源在共享技能库**游刃**(`gitee.com/fangyu_fy/youren`,路径 `skills/csharp-doc-style/SKILL.md`),本仓 `.agents/skills/csharp-doc-style/SKILL.md` 是随项目分发的下游拷贝、两者不一致时以游刃为准,而 `.agents/skills/` 是 DeepSeek Harness 的项目级技能发现根,故由工具自动加载。该规范的核心是**以复合长句承载含条件、因果、时序与权衡的技术事实**,禁止把完整逻辑链拆成孤立短句,同时要求 `` 只放一句话总述而把契约论述移入 ``;`#region` 则按需使用,仅当文件较长且确含多个可命名逻辑块时用于辅助导航,因为对短文件与单一职责类强行分区只会以形式噪声掩盖本就清晰的代码。
**Mermaid 优先**:文档需要表达流程、时序、状态或依赖关系时使用 Mermaid,而表格仅用于真正适合行列对照的数据;目录树因属路径约定而保留文本形式。
---
## 10. 目录结构
按功能族分目录:根目录为驱动核心抽象与身份类型,`Capabilities/` 承载能力模型,`Error/` 承载错误分类与异常,`Steps/` 承载步骤契约,`Supports/` 承载能力接口;命名空间与目录一致,即根目录类型在 `Cangming.Core.Driver`,而子目录类型在对应子命名空间。
```text
Cangming.Core.Driver/
├── Cangming.Core.Driver.csproj # net8.0;零 ProjectReference、零 PackageReference
├── README.md
├── IDriver.cs # IDriver
├── BaseDriver.cs # BaseDriver 抽象基类
├── DriverStatus.cs # DriverStatus struct
├── InvokeOutcome.cs # 调用终态(唯一的状态枚举,DriverTaskResult 亦取用)
├── DriverTaskResult.cs # 任务结果最小形态
├── InvokeResult.cs # 完整调用结果(含落地程度、故障与校验结果)
├── InvokeRequest.cs # 调用请求 + ParameterValue
├── DriverOperationMode.cs # DriverOperationMode enum
├── DeviceCode.cs # DeviceCode record struct
├── DeviceModel.cs # DeviceModel(含级联子型号)
├── DriverInfo.cs # DriverInfo record
├── DeviceInfo.cs # DeviceInfo record
├── IDriverData.cs # IDriverData + IScalarDriverData + DataQuality + IDeviceModelChannelInfo
├── IDriverSetting.cs # IDriverSetting
├── IDriverManifest.cs # IDriverManifest + IDriverBuilder
├── IDriverSettingManifest.cs # IDriverSettingManifest + IDriverSettingBuilder
├── IDeviceRegistry.cs # IDeviceRegistry + IDriverInfoService + IDriverBuildService
├── IDriverService.cs # IDriverService + IDriverSettingService
├── Capabilities/ # 能力模型(namespace .Capabilities)
│ ├── CapabilityConsistency.cs # 声明与实现的双向一致性校验
│ ├── CapabilityDescriptor.cs # 能力描述符(五段骨架)
│ ├── CapabilityManifest.cs # 能力清单(结构版本 + 拓扑 + 能力集 + 通道 + 设备级保证)
│ ├── CapabilitySet.cs # 型号级能力集
│ ├── CapabilityView.cs # 地址级能力视图
│ ├── DriverCapabilityFamily.cs # 能力族
│ ├── DriverCapabilityProfile.cs# 能力概况(推导所得,无可写状态)
│ ├── Condition/ # 静态条件与运行时谓词
│ ├── Effect/ # 副作用种类、原子性、影响范围
│ ├── Identity/ # CapabilityId + CapabilityOwner
│ ├── Invocation/ # CapabilityInvoker + ValidationResult + AppliedEffect
│ ├── Parameters/ # 八元参数类型集 + 量纲 + 单位 + 参数依赖
│ ├── Promise/ # 能力级与设备级保证
│ ├── Scope/ # 作用域种类、模式与地址
│ └── Topology/ # 拓扑声明 + 数据通道声明
├── Error/ # 错误(namespace .Error)
│ ├── DriverErrorCategory.cs # 错误分类
│ ├── DriverException.cs # 驱动异常
│ └── Recoverability.cs # 可恢复方式
├── Steps/ # 步骤契约(namespace .Steps)
│ ├── StepId.cs
│ ├── StepOwner.cs
│ ├── StepOrdering.cs
│ ├── StepFailurePolicy.cs
│ ├── StepKind.cs
│ ├── StepResultChannel.cs
│ ├── StepParameterBinding.cs
│ ├── StepRequest.cs
│ ├── StepContext.cs
│ ├── StepBody.cs
│ ├── StepResult.cs
│ ├── StepOutcome.cs
│ ├── StepExecutionState.cs
│ ├── StepDefinition.cs
│ ├── BaseDriverStep.cs
│ ├── IDriverStep.cs
│ ├── IDriverStepManifest.cs
│ ├── StepVerificationPhase.cs
│ ├── StepVerification.cs
│ ├── StepVerificationContext.cs
│ └── IDriverStepVerifier.cs
└── Supports/ # 能力接口(namespace .Supports)
├── DataChangedEventArgs.cs
├── IDriverSupportConnectivity.cs
├── IDriverSupportLifecycleControl.cs
├── IDriverSupportPowerControl.cs
├── IDriverSupportSelfTest.cs
├── IDriverSupportGeneralControls.cs
├── IDriverSupportDeviceSettings.cs
├── IDriverSupportFaultTolerance.cs
├── IDriverSupportRealTimeStatusQuery.cs
└── IDriverSupportRealTimeDataCollection.cs
```
契约测试统一放在**仓库根的 `tests/`**(与业务模块的 `src/` 平级),因为测试依赖测试框架而契约模块必须保持零引用,故两者不混入同一棵树。样例与端到端验证则放在**仓库根的 `testbed/`**,其性质是契约的消费方而非契约的一部分。
```text
tests/
└── Cangming.Core.Driver.Tests/ # 契约测试(xunit)
├── Cangming.Core.Driver.Tests.csproj
├── ContractSurface.cs # 公开面反射基础
├── ModuleBoundaryTests.cs # 模块自治
├── StepContractTests.cs # 步骤契约
├── DriverIdentityTests.cs # 身份三分离
├── ErrorAndResultTests.cs # 错误与结果
├── DeviceModelTests.cs # 型号值语义
├── CapabilityDescriptorShapeTests.cs # 能力描述符五段形状
├── CapabilityEffectPromiseConditionTests.cs # 效果、保证与条件
├── CapabilityManifestAndInvocationTests.cs # 拓扑、清单、视图与调用结果
├── CapabilityModelIntentTests.cs # 落地台账与执行入口校验
├── DiagnosticsAndConsistencyTests.cs # 诊断、参数校验与一致性
├── SampleVerificationTests.cs # 以静默方式运行 testbed 样例并断言其通过
└── TestDoubles.cs
```
样例与端到端验证放在**仓库根的 `testbed/`**,其性质是契约的消费方而非契约的一部分。
```text
testbed/
└── Cangming.Core.Driver.Sample/ # 可运行样例
├── Cangming.Core.Driver.Sample.csproj
├── Program.cs # 引导式演示与自验证
├── Pump/ # 多通道泵:通道作用域、消耗资源、非原子部分生效
├── Steps/ # 步骤引用能力的示例
└── TemperatureSensor/ # 单设备传感器:可连接、可配参、单一数据通道
```
---
## 11. 后续演进
能力模型五段骨架、执行入口与三层校验、步骤与能力的引用对接、通用控制的四向拆分、标量数据由通道声明解释,以及弱类型逃生口与空接口的清除均已落地。以下诸项尚属空白,但**均不影响契约的形状完备性**,因为它们各自属于消费侧或运行期的关注点。
| 缺口 | 说明 |
|------|------|
| 在途操作 | 会话句柄、进度与取消属独立一段;当前仅有 `SessionId` 关联标识与终态结果,而由于终态形状已定,后续补入不需改动既有类型 |
| 观察模型 | 状态与测量的推送、拉取对偶及其背压策略尚未建模,而当前 `IDriverData` 只承载单次采集结果的通用要素 |
| 门禁的执行点 | `CapabilityConsistency` 已可复用,但目前仅由测试调用;将其接入注册流程以在登记不合规驱动时即行拒绝,属消费侧尚待落实的一步 |
| 设置载体 | `IDriverSupportDeviceSettings` 已暴露 `ParameterSchemas` 并复用 `ParameterValidator`,但设置值本身仍以 `IDriverSetting` 为自由载体,尚未改由参数模式承载其读写 |