connectedhomeip YAML 测试的 Pseudo-cluster(伪集群)命令参考:Matter 测试驱动基础设施解析
【免费下载链接】connectedhomeipMatter (formerly Project CHIP) creates more connections between more objects, simplifying development for manufacturers and increasing compatibility for consumers, guided by the Connectivity Standards Alliance.项目地址: https://gitcode.com/GitHub_Trending/co/connectedhomeip
Pseudo-cluster(伪集群)是 connectedhomeip(Matter SDK)YAML 集成测试框架中的一类特殊"集群":它们不通过 Matter 协议在线路上传输,而是在测试运行器进程内直接执行,用于完成配网、等待、发现、日志、文件系统操作等测试步骤,是编写可运行 YAML 测试套件的事实标准设施。本文以官方生成的命令参考文档 yaml_pseudocluster.md 为主体骨架,结合 pseudo_clusters 目录下的完整实现源码与真实测试套件用法,系统讲解六大伪集群的全部命令、参数类型、可选标记,以及伪集群从定义、文档生成到运行时调度分发的完整机制,让读者既能照表编写 YAML 测试,也能深入理解其底层实现原理。
什么是 Pseudo-cluster,为什么 YAML 测试需要它
在 Matter 的 YAML 测试模型中,每个测试步骤通常是一个"命令":cluster指定集群名,command指定命令名,运行器会将其编码为 Matter 交互协议(IM)请求发送给被测设备(DUT)并校验响应。然而,真实的测试流程中有大量步骤并不属于任何标准 Matter 集群,例如:
- 用配对码(QR code payload)执行配网流程;
- 等待固定毫秒数、等待设备重新上线;
- 在本地网络中搜索可配网的设备;
- 在测试机本地创建、比较、删除文件;
- 向测试日志输出信息或向测试人员发起交互式提问。
这些步骤无法被编码为设备侧命令,因此测试框架引入了Pseudo-cluster这一抽象层:它在语义上仍然以"集群 + 命令"的形式出现在 YAML 文件中,但实际执行时由测试运行器在进程内直接处理,不会发给任何设备。从源码结构看,这一层对应 pseudo_clusters 目录:每个伪集群是一个继承 PseudoCluster 的类,类名即集群名,类方法即命令名,通过 XML 定义声明命令签名。
当前仓库默认注册的伪集群共 7 个(见 pseudo_clusters.py 中的get_default_pseudo_clusters()),其中 6 个进入了官方命令参考文档:
| 伪集群名称 | 集群 Code | 职责 |
|---|---|---|
| CommissionerCommands | 0xFFF1FD04 | 配网、取消配对、签发 NOC 链 |
| DelayCommands | 0xFFF1FD02 | 等待(设备上线、毫秒、消息、属性值) |
| DiscoveryCommands | 0xFFF1FD05 | mDNS 发现可配网设备/专员 |
| EqualityCommands | 0xFFF1FD08 | 布尔/有符号/无符号数值相等性断言 |
| LogCommands | 0xFFF1FD01 | 日志输出与人机交互提示 |
| SystemCommands | 0xFFF1FD03 | 启动/重启/复位测试装置、文件与 OTA 镜像操作 |
其余命令(如 WebRTC 相关的VerifyVideoStream等,见 webrtc.py)未进入文档表格。值得注意:伪集群的集群 Code 均落在0xFFF1FDxx厂商/测试保留区间内,与真实 Matter 集群 ID 隔离,避免命名与 ID 冲突。
命令参考文档是怎么生成的:定义即文档
文档 yaml_pseudocluster.md 的头部注释明确指出:该文件由脚本自动生成,禁止手工编辑。生成脚本为 generate_pseudo_cluster_doc_tables.py,其工作方式如下:
- 调用
get_default_pseudo_clusters()拿到默认伪集群实例列表; - 读取每个实例的
definition属性——这是一段内嵌的 XML(格式与 ZAP 集群定义一致),用xml.etree.ElementTree解析; - 遍历每个
<command>及其<arg>子元素,提取name、type、optional属性; - 以 Markdown 表格形式写入
docs/testing/yaml_pseudocluster.md。
也就是说,伪集群的命令清单完全由源码中的 XML 定义驱动,文档只是这份定义的可读快照。因此,当你在仓库中看到文档与源码不一致时(例如文档中FindResponse只有supportsTcp一个布尔字段,而 discovery_commands.py 中实际已拆分为supportsTcpClient与supportsTcpServer),应以 XML 定义为事实依据——这正是本文件被设计为"自动生成、勿手工编辑"的原因。
下表汇总了各集群在官方文档中的命令、参数、参数类型与可选性标记("arg optional" 列中false表示必填,true表示可选)。
CommissionerCommands
| command | args | arg type | arg optional |
|---|---|---|---|
| PairWithCode | nodeId payload discoverOnce | node_id char_string boolean | false false true |
| Unpair | nodeId | node_id | false |
| GetCommissionerNodeId | |||
| GetCommissionerNodeIdResponse | nodeId | node_id | false |
| GetCommissionerRootCertificate | |||
| GetCommissionerRootCertificateResponse | RCAC | OCTET_STRING | false |
| IssueNocChain | Elements nodeId | octet_string node_id | false false |
| IssueNocChainResponse | NOC ICAC RCAC IPK | octet_string octet_string octet_string octet_string | false false false false |
DelayCommands
| command | args | arg type | arg optional |
|---|---|---|---|
| WaitForCommissioning | |||
| WaitForCommissionee | nodeId expireExistingSession | node_id bool | false true |
| WaitForMs | ms | int16u | false |
| WaitForMessage | registerKey message | char_string char_string | false false |
DiscoveryCommands
| command | args | arg type | arg optional |
|---|---|---|---|
| FindCommissionable | |||
| FindCommissionableByShortDiscriminator | value | int16u | false |
| FindCommissionableByLongDiscriminator | value | int16u | false |
| FindCommissionableByCommissioningMode | |||
| FindCommissionableByVendorId | value | vendor_id | false |
| FindCommissionableByDeviceType | value | devtype_id | false |
| FindCommissioner | |||
| FindCommissionerByVendorId | value | vendor_id | false |
| FindCommissionerByDeviceType | value | devtype_id | false |
| FindResponse | hostName instanceName longDiscriminator shortDiscriminator vendorId productId commissioningMode deviceType deviceName rotatingId rotatingIdLen pairingHint pairingInstruction supportsTcp numIPs port mrpRetryIntervalIdle mrpRetryIntervalActive mrpRetryActiveThreshold isICDOperatingAsLIT | char_string char_string int16u int16u vendor_id int16u int8u devtype_id char_string octet_string int64u int16u char_string boolean int8u int16u int32u int32u int16u boolean | false false false false false false false false false false false false false false false false true true true true |
EqualityCommands
| command | args | arg type | arg optional |
|---|---|---|---|
| BooleanEquals | Value1 Value2 | boolean boolean | false false |
| SignedNumberEquals | Value1 Value2 | int64s int64s | false false |
| UnsignedNumberEquals | Value1 Value2 | int64u int64u | false false |
| EqualityResponse | Equals | bool | false |
LogCommands
| command | args | arg type | arg optional |
|---|---|---|---|
| Log | message | char_string | false |
| UserPrompt | message expectedValue | char_string char_string | false true |
SystemCommands
| command | args | arg type | arg optional |
|---|---|---|---|
| Start | registerKey discriminator port minCommissioningTimeout kvs filepath otaDownloadPath endUserSupportLogPath networkDiagnosticsLogPath crashLogPath | char_string int16u int16u int16u char_string char_string char_string char_string char_string char_string | true true true true true true true true true true |
| Stop | registerKey | char_string | true |
| Reboot | registerKey | char_string | true |
| FactoryReset | registerKey | char_string | true |
| CreateOtaImage | otaImageFilePath rawImageFilePath rawImageContent | char_string char_string char_string | false false false |
| CompareFiles | file1 file2 | char_string char_string | false false |
| CreateFile | filePath fileContent | char_string char_string | false false |
| DeleteFile | filePath | char_string | false |
CommissionerCommands:在 YAML 里完成配网与证书签发
配网是绝大多数集成测试的前置步骤,CommissionerCommands 把"用配对码配网"这一操作抽象成了 YAML 命令。其核心命令语义如下:
- PairWithCode:携带目标节点
nodeId、配对码payload(Setup QR/Manual Pairing Code 字符串),可选的discoverOnce控制是否只发现一次后即开始配网。真实用法可参考 TestCommissionerNodeId.yaml,其中先用Administrator Commissioning集群的OpenBasicCommissioningWindow打开配网窗口,再由指定身份(identity: "beta")执行PairWithCode。 - Unpair:按
nodeId取消配对、移除该节点所在的 fabric。 - GetCommissionerNodeId / GetCommissionerNodeIdResponse:读取当前专员自身的节点 ID,响应以
nodeId返回。典型用法见 TestCommissionerNodeId.yaml:GetCommissionerNodeId的响应值被saveAs: commissionerNodeIdAlpha保存,供后续步骤引用。 - GetCommissionerRootCertificate / GetCommissionerRootCertificateResponse:获取专员根证书(RCAC,OCTET_STRING)。
- IssueNocChain / IssueNocChainResponse:按给定的
Elements(NOC 链元素,octet_string)与nodeId签发新的 NOC 链,响应返回NOC、ICAC、RCAC与IPK四个 OCTET_STRING。
需要注意,源码定义 commissioner_commands.py 中还包含文档未收录的EstablishPASESession(参数nodeId+payload),用于显式建立 PASE 会话,说明 XML 定义是此类命令最权威的清单来源。
DelayCommands:测试流程中的"时间控制"
集成测试常常需要等待异步事件(如设备重启后重新上线),DelayCommands 提供四个等待原语:
- WaitForCommissioning:无参数,等待配网流程完成。
- WaitForCommissionee:等待指定
nodeId的节点作为已配网设备出现;可选的expireExistingSession为 true 时使既有会话过期。真实用法见 TestArmFailSafe.yaml:设备执行SystemCommands.Reboot之后,紧接着用WaitForCommissionee等待其重新被检索到。 - WaitForMs:休眠
ms(int16u)毫秒。其实现(delay_commands.py 中的WaitForMs)先刷新 stdout,再执行await asyncio.sleep(int(duration_in_ms) / 1000)。 - WaitForMessage:通过
registerKey与message两个 char_string 参数等待一条注册消息(由 AccessoryServerBridge 机制实现跨进程消息同步)。
源码定义中还包含文档未收录的WaitForAttributeValue(参数attribute、expectedValue、expectedDurationMs、cluster、endpoint)。该命令的实现在 delay_commands.py 中值得单独一提:它以 100ms 为间隔轮询读取属性,直到值等于期望值或超过expectedDurationMs + valueWaitExtraDurationMs(默认 250ms)的超时窗口;轮询过程中会忽略中间态网络/设备错误(设备可能临时不可达后自愈),并将最近 10 次尝试的历史记录进超时异常信息,便于排障。
DiscoveryCommands:在测试内驱动 mDNS 发现
发现类命令用于在测试中触发 mDNS 发现并校验发现响应,其命令族可划分为两组:
搜索可配网设备(Commissionable):FindCommissionable(无过滤)、按短/长 discriminator(int16u)、按配网模式(FindCommissionableByCommissioningMode)、按 Vendor ID(vendor_id)、按设备类型(devtype_id)过滤的五个变体。
搜索专员(Commissioner):FindCommissioner、FindCommissionerByVendorId、FindCommissionerByDeviceType。
统一响应FindResponse携带极其丰富的字段,覆盖 Matter 配网发现的全部 TXT 记录:主机名、实例名、长短 discriminator、Vendor/Product ID、配网模式(int8u)、设备类型、设备名、rotatingId 及其长度、pairingHint/pairingInstruction、TCP 支持标志、IP 数量、端口,以及仅 ICD/LIT 场景需要的mrpRetryIntervalIdle、mrpRetryIntervalActive、mrpRetryActiveThreshold、isICDOperatingAsLIT(后四个可选)。
该伪集群被发现测试套件大量使用:TestDiscovery.yaml 从第 94 行起连续二十余个步骤调用DiscoveryCommands,例如:
- label: "Check Instance Name" cluster: "DiscoveryCommands" command: "FindCommissionable" response: values: - name: "instanceName" saveAs: deviceInstanceNameBeforeReboot constraints: minLength: 16 maxLength: 16 isUpperCase: true isHexString: true该示例展示了伪集群命令与 YAML 测试框架的完整交互:调用FindCommissionable后,响应值可被saveAs保存为运行时变量,并可用constraints(如长度、大小写、十六进制校验)对发现结果做强校验;后续步骤还可以用FindCommissionableByLongDiscriminator(value 引用变量discriminator)、FindCommissionableByShortDiscriminator、FindCommissionableByCommissioningMode等分别校验_L、_S、_CM等 mDNS 服务子类型,实现对 TXT 记录逐字段的断言(见 TestDiscovery.yaml)。
EqualityCommands:纯本地数值比较断言
当测试需要"比较两个值是否相等"且结果要参与后续断言时,可以使用 EqualityCommands——它是一个完全在本地执行的比较器,不涉及任何网络交互:
- BooleanEquals(Value1/Value2,boolean)→ 返回
EqualityResponse.Equals(bool); - SignedNumberEquals(Value1/Value2,int64s)→ 有符号 64 位整数比较;
- UnsignedNumberEquals(Value1/Value2,int64u)→ 无符号 64 位整数比较。
其实现(equality_commands.py)非常直接:Compare(request)从参数列表中提取Value1/Value2并返回value1 == value2,三个命令都把它包装进{'value': {'Equals': ...}}响应中。命令名称的选择(区分布尔、有符号、无符号)是为了让解析器能按正确的类型语义解释传入的字面量。
LogCommands:日志输出与人工交互
- Log:向测试日志输出
message(char_string),实现为空操作(日志由运行器统一记录)。 - UserPrompt:向测试人员展示
message并等待输入;可选的expectedValue提供预期答案。其实现(log_commands.py)在提供了expectedValue时会调用input()阻塞读取用户输入,并把输入值作为expectedValue返回,供 YAML 步骤继续断言。
源码中还有文档未收录的PromptWithResponse(参数message、可选placeHolder与parseStr)及其响应PromptResponse。PromptWithResponse在运行器中走专门分支处理(见下节),并支持parseStr=true时用ast.literal_eval将用户输入解析为字面量(数字、布尔等),实现"输入即数据"。另外,运行器通过PseudoClusters.is_manual_step()(pseudo_clusters.py)将LogCommands.UserPrompt识别为"人工步骤",触发step_manual钩子,确保人工交互步骤在自动化与人工两种运行模式下都有明确的处理路径。
SystemCommands:测试装置的生命周期与文件操作
SystemCommands 是支撑"多设备/重启/OTA/文件对比"类测试的关键伪集群,几乎所有复杂测试套件都会用到它。其命令分四类:
装置生命周期(配合 AccessoryServerBridge,见 accessory_server_bridge.py):
- Start:启动一个测试装置实例。全部 10 个参数均可选:
registerKey(实例注册名)、discriminator(int16u)、port(int16u)、minCommissioningTimeout(int16u)、kvs(KVS 存储路径)、filepath、otaDownloadPath(OTA 下载目录)、endUserSupportLogPath、networkDiagnosticsLogPath、crashLogPath(各类日志落盘路径)。源码定义中还有文档未收录的traceDecode(int8u)。 - Stop / Reboot / FactoryReset:均以可选的
registerKey指定目标实例,分别执行停止、重启与恢复出厂设置。
文件与镜像操作:
- CreateOtaImage:由
rawImageFilePath+rawImageContent生成 OTA 镜像并写入otaImageFilePath(三者均为必填 char_string),用于构造测试用 OTA 镜像。 - CompareFiles:对比
file1与file2两个文件内容。 - CreateFile / DeleteFile:在测试机本地创建(
filePath+fileContent)或删除文件,常用于准备/清理诊断日志等测试产物。
真实用例遍布多个套件:OTA 传输测试 OTA_SuccessfulTransfer.yaml 中用SystemCommands控制装置并准备镜像;TestDiagnosticLogs.yaml 中用Start配置networkDiagnosticsLogPath/crashLogPath并用CompareFiles/CreateFile校验日志输出;TestArmFailSafe.yaml 中一个典型的重启流程步骤是:
- label: "Reboot target device" cluster: "SystemCommands" command: "Reboot"紧随其后的DelayCommands.WaitForCommissionee则确保设备重启后重新上线,两个伪集群协同完成了"重启并等待恢复"这一常见测试模式。
参数类型约定速查
伪集群命令参数使用的类型与 Matter IDL/ZAP 类型体系一致,整理如下(可在各集群 XML 定义中逐一核对):
| 类型 | 含义 |
|---|---|
| node_id | 64 位节点 ID |
| char_string | 字符串 |
| boolean / bool | 布尔值 |
| octet_string / OCTET_STRING | 字节串(十六进制表示) |
| int8u / int16u / int32u / int64u | 无符号 8/16/32/64 位整数 |
| int64s | 有符号 64 位整数 |
| vendor_id | 厂商 ID(16 位) |
| devtype_id | 设备类型 ID |
| any | 任意类型(如WaitForAttributeValue.expectedValue) |
arg optional列标记参数是否为可选:false为必填,true为可选。若命令有响应(response),响应命令与参数会以独立条目(如FindResponse、EqualityResponse)出现在同一表格中。
运行时调度:Pseudo-cluster 如何被分派执行
理解伪集群的关键在于运行时的分派逻辑,它位于 runner.py 的run_step主循环中,对每个测试步骤依次判断:
- 若步骤被
PseudoClusters.is_manual_step()判定为人工步骤(UserPrompt或VerifyVideoStream),走hooks.step_manual()分支; - 否则若
config.pseudo_clusters.supports(request)返回 true(即按request.cluster+request.command能在已注册伪集群中找到对应方法),则直接调用config.pseudo_clusters.execute(request, ...)在进程内执行;其中PromptWithResponse还有专门分支:先通过hooks.show_prompt()弹出交互界面,再按parseStr决定是否用ast.literal_eval解析输入; - 只有上述都不命中时,步骤才会交给
config.adapter.encode()编码为真实的 Matter 请求发送给设备。
PseudoClusters.execute()(pseudo_clusters.py)用inspect.signature检查目标命令方法签名,只注入方法声明接受的 kwargs(如WaitForAttributeValue需要的runner与config),并保证definitions这类正式参数被单独注入;命令返回的None会被规范化为空成功状态,返回 dict 则作为响应值进入后续的post_process_response断言流程。因此,伪集群命令的响应与真实设备响应在 YAML 断言层面完全等价,saveAs、constraints、value校验等能力对两者一视同仁。
如何扩展:自定义 Pseudo-cluster
伪集群机制是开放可扩展的。PseudoCluster 是一个抽象基类,其文档字符串给出了完整的自定义示例:继承PseudoCluster,声明name类属性(即 YAML 中的 cluster 名),把命令实现为async def方法(方法名即 command 名),可选地提供definitionXML 以获得自动的命令名/参数名校验:
class CustomCommand(PseudoCluster): name = 'CustomCommands' async def MyCustomMethod(self, request): pass对应的 YAML 步骤为:
- label: "Call a custom method" cluster: "CustomCommands" command: "MyCustomMethod" arguments: values: - name: "MyCustomParameter" value: "this_is_a_custom_value"若提供definitionXML(<cluster><code>0xFFF1FD00</code>...格式),运行器还能自动校验命令名与参数名是否与定义一致。自定义伪集群通过PseudoClusters.add()注册进集合,即可与内置伪集群一起参与supports()/execute()分派。
总结与实战建议
Pseudo-cluster 是 connectedhomeip YAML 测试框架"测试步骤即命令"抽象的关键支撑:它以标准集群的语法形态,在进程内完成配网、等待、发现、断言、日志、文件系统与装置生命周期等测试基础设施操作,使 YAML 测试套件能够表达完整的多设备、多身份、OTA、诊断日志等复杂场景。编写测试时:
- 命令清单以官方参考文档 yaml_pseudocluster.md 为速查入口,以 pseudo_clusters/clusters 下的 XML 定义为最终依据(文档为脚本快照,可能与最新源码存在细微差异);
- 直接参考 TestDiscovery.yaml、TestCommissionerNodeId.yaml、TestArmFailSafe.yaml、TestDiagnosticLogs.yaml、OTA_SuccessfulTransfer.yaml 等套件,可以看到伪集群命令与真实集群命令在同一 YAML 文件中无缝混排的实际范式;
- 需要扩展测试能力时,遵循
PseudoCluster基类契约实现自定义伪集群并注册即可,响应处理与断言机制无需任何改动。
【免费下载链接】connectedhomeipMatter (formerly Project CHIP) creates more connections between more objects, simplifying development for manufacturers and increasing compatibility for consumers, guided by the Connectivity Standards Alliance.项目地址: https://gitcode.com/GitHub_Trending/co/connectedhomeip
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考