Matter Actions 集群(0x0025)服务端实现指南:ActionsCluster 架构、Delegate 接口与实战接入
2026/9/19 22:44:27 网站建设 项目流程

Matter Actions 集群(0x0025)服务端实现指南:ActionsCluster 架构、Delegate 接口与实战接入

【免费下载链接】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

本篇技术指南以 connectedhomeip 仓库中 src/app/clusters/actions-server/README.md 为骨架,深入讲解 Matter **Actions 集群(Cluster ID0x0025)**服务端的完整实现。你将掌握:为何该集群必须遵循 "Scope: Node" 约束、ActionsCluster与旧版ActionsServer两套接入方式的区别、Actions::Delegate全部接口的语义,以及如何通过GenerateEvent异步上报StateChanged/ActionFailed事件,最终能在桥接设备(如 bridge-app)或任意聚合端点(aggregator endpoint)上正确落地一个可用的 Actions 集群。

集群概述与"Node 单例"作用域约束

Actions 集群用于在 Matter 设备上描述并控制"动作"(Action)——例如场景(Scene)、序列(Sequence)、自动化(Automation)、通知(Notification)等。它的数据中心模型定义可参考仓库中的规范快照 data_model/1.7/clusters/ActionsCluster.xml,其中明确写有:

<classification hierarchy="base" role="application" picsCode="ACT" scope="Node"/>

scope="Node"是理解本集群实现的关键前提,它带来三条硬性约束:

  1. 只能出现在一个端点上:集群在整个节点上必须且只能存在于一个端点,通常是被桥接设备的聚合端点(aggregator endpoint),例如 bridge 类应用中的 endpoint 1;
  2. 同一时刻仅允许一个ActionsServer实例:构造函数会对实例计数并记录诊断日志(详见下文源码分析);
  3. Delegate 接口不带EndpointId参数:因为设计上就只面向单一集群实例,无需在回调里区分端点。

正是基于这一约束,服务端实现才形成了"应用提供数据、集群层负责协议"的分层结构。

两层架构:新实现与向后兼容包装

实现分为两层,源码目录 src/app/clusters/actions-server/ 下同时包含新老两套代码:

ActionsCluster (一个实例,位于聚合端点) └── Actions::Delegate (由应用提供)

ActionsCluster:新代码的规范实现(推荐)

ActionsCluster继承自DefaultServerCluster,是规范实现,代码见 ActionsCluster.h 与 ActionsCluster.cpp。它在聚合端点上实例化一次,并通过RegisteredServerCluster<ActionsCluster>注册到数据模型。其核心职责包括:

  • 属性读取ReadAttribute):支持ActionList(ID0x0000)、EndpointLists(ID0x0001)、SetupURL(ID0x0002,可选)以及ClusterRevisionFeatureMap(当前编码为 0,即不使用 feature map),见 ActionsCluster.cpp;
  • 命令分发InvokeCommand):对 12 条命令统一执行"解码 → 校验 action 存在 → 校验命令位掩码支持"三步检查后,再转发给 Delegate 的Handle*回调(见下文命令表格);
  • 属性变更通知ActionListModified()EndpointListsModified()分别对ActionListEndpointLists触发NotifyAttributeChanged,见 ActionsCluster.cpp;
  • 事件生成GenerateEventStateChanged/ActionFailed事件投递到 fabric,见 ActionsCluster.cpp。

ActionsServer/CodegenIntegration:旧应用兼容包装

ActionsServer是包裹在ActionsCluster外层的一层薄壳,面向基于旧版 Ember/ZAP 生成 API 编写的应用,实现见 CodegenIntegration.h 与 CodegenIntegration.cpp。它:

  • 为聚合端点持有一个RegisteredServerCluster<ActionsCluster>
  • 构造时从 Ember RAM 读取初始属性状态(如SetupURL,通过Attributes::SetupURL::GetDefault读取并转成CharSpan,见 CodegenIntegration.cpp);
  • 暴露旧版ActionListModified(EndpointId)/EndpointListModified(EndpointId)回调,对错误端点的调用会被静默忽略VerifyOrReturn(aEndpoint == ...),见 CodegenIntegration.cpp);
  • 使用静态计数sInstanceCount追踪活跃实例:超过 1 个实例时打印诊断日志"ActionsServer: %u instances active (multiple aggregator endpoints in use)."(CodegenIntegration.cpp)——注意:多聚合端点设备(例如 EP1 上是 Zigbee 桥、EP2 上是 Z-Wave 桥)各自托管一个 Actions 集群是合法的,因此计数仅用于诊断而非报错;
  • 析构函数会在未调用Shutdown()的情况下自动补调用,避免数据模型注册表中残留悬垂指针(CodegenIntegration.cpp)。

值得留意的是 actions-server.h 头文件本身只做了一件事——#include <app/clusters/actions-server/CodegenIntegration.h>并在注释中声明"仅用于向后兼容,新代码应直接使用 ActionsCluster.h"。

ZAP 生成回调:刻意留空的桩

ZAP 生成的插件回调(MatterActionsClusterInitCallback等)在 CodegenIntegration.cpp 中被实现为空桩。应用直接实例化ActionsServer并通过Init()注册到 codegen 数据模型提供者——这正是标准的 code-driven 集群模式:集群生命周期由应用拥有,而非 ZAP 生成的脚手架

用法:新老两种接入方式

新代码(code-driven 数据模型)

auto cluster = std::make_unique<ActionsCluster>(aggregatorEndpointId, myDelegate); // 通过 RegisteredServerCluster<ActionsCluster> 注册,并调用 Init()。

ActionsCluster的构造签名(见 ActionsCluster.h)还支持两个可选参数:

ActionsCluster(EndpointId endpointId, Actions::Delegate & delegate, OptionalAttributesSet optionalAttributes = {}, std::optional<CharSpan> setupURL = std::nullopt);

其中OptionalAttributesSetOptionalAttributeSet<Actions::Attributes::SetupURL::Id>——即只有SetupURL是可声明为可选的属性;setupURL直接给出该属性的初始值。

旧代码(向后兼容)

// 通常在 emberAfActionsClusterInitCallback 中调用,并加保护确保只执行一次: sActionsDelegateImpl = std::make_unique<MyDelegate>(); sActionsServer = std::make_unique<ActionsServer>(aggregatorEndpointId, *sActionsDelegateImpl); sActionsServer->Init(); // 当动作列表发生变化时: sActionsServer->ActionListModified(aggregatorEndpointId); // 析构函数会自动完成 Shutdown。

Init()Shutdown()都是幂等的:Init()内部用VerifyOrReturnError(!mRegistered, ...)防止重复注册,Shutdown()VerifyOrReturn(mRegistered)防止重复注销(见 CodegenIntegration.cpp)。

这一模式在仓库中有真实落地案例:

  • examples/bridge-app/linux/bridged-actions-stub.cpp:在emberAfActionsClusterInitCallback中创建LinuxActionsDelegateImplActionsServer并调用Init()
  • examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp:同样在回调中创建,并额外校验endpoint == 1emberAfContainsServer(endpoint, Actions::Id)以及"仅初始化一次"(VerifyOrReturn(!sActionsDelegateImpl && !sActionsServer)),关闭时在emberAfActionsClusterShutdownCallback中调用Shutdown()

Delegate 接口:应用侧数据源与命令处理

应用必须提供一个具体的Actions::Delegate实现,完整接口定义见 ActionsDelegate.h。按职责可分为三类:

1. 集合遍历(属性数据源)

接口语义终止条件
ReadActionAtIndex(uint16_t index, ActionStructStorage & action)按索引返回第 N 个动作;索引假定从 0 开始且无空洞返回CHIP_ERROR_PROVIDER_LIST_EXHAUSTED表示已到列表末尾
ReadEndpointListAtIndex(uint16_t index, EndpointListStorage & epList)按索引返回第 N 个端点列表,同样假定连续索引同上
HaveActionWithId(uint16_t aActionId, uint16_t & aActionIndex)按 action ID 快速查找(O(n)),并通过出参返回命中索引返回true/false

ActionStructStorageEndpointListStorage定义在 ActionsStructs.h:前者内部缓冲区kActionNameMaxSize = 128,后者名称缓冲区同为 128、端点数组上限kEndpointListMaxSize = 256,均通过Set()完成带截断的拷贝,保证在受限内存(嵌入式)环境下安全。这些容量常量与数据模型 XML 中的约束一致(ActionStruct.Name最大 128 字节、EndpointListStruct.Endpoints最大 256 项)。

2. 命令处理(每个命令一个回调)

Handle*系列方法每个对应一条 Actions 命令,返回Status::Success表示接受,返回其他状态码表示拒绝(如NotFoundInvalidCommand)。以下是规范快照 ActionsCluster.xml 与 ActionsDelegate.h 对应的完整 12 条命令及签名:

命令(ID)Delegate 方法额外参数含义
InstantAction(0x00)HandleInstantAction(actionId, invokeId)
InstantActionWithTransition(0x01)HandleInstantActionWithTransition(actionId, transitionTime, invokeId)transitionTime:从当前状态过渡到新状态的时间
StartAction(0x02)HandleStartAction(actionId, invokeId)
StartActionWithDuration(0x03)HandleStartActionWithDuration(actionId, duration, invokeId)duration:保持 start 状态的时长
StopAction(0x04)HandleStopAction(actionId, invokeId)
PauseAction(0x05)HandlePauseAction(actionId, invokeId)
PauseActionWithDuration(0x06)HandlePauseActionWithDuration(actionId, duration, invokeId)duration:保持 pause 状态的时长
ResumeAction(0x07)HandleResumeAction(actionId, invokeId)
EnableAction(0x08)HandleEnableAction(actionId, invokeId)
EnableActionWithDuration(0x09)HandleEnableActionWithDuration(actionId, duration, invokeId)duration:保持 active 状态的时长
DisableAction(0x0A)HandleDisableAction(actionId, invokeId)
DisableActionWithDuration(0x0B)HandleDisableActionWithDuration(actionId, duration, invokeId)duration:保持 disable 状态的时长

所有命令的invokeId均为可选参数(Optional<uint32_t>),对应数据模型 XML 中InvokeID字段的optionalConform声明,用于把异步事件与特定命令调用关联起来。

3. 命令分发前的统一校验

ActionsCluster::InvokeCommand对每条命令先执行统一的DECODE_AND_VALIDATE流程(见 ActionsCluster.cpp):

  1. DataModel::Decode解码 TLV 入参,失败返回Status::InvalidCommand
  2. 调用ValidateActionExists,通过mDelegate.HaveActionWithId查找 action,找不到返回Status::NotFound
  3. 调用ValidateCommandSupported,校验该 action 的supportedCommands位掩码是否包含当前命令(action.supportedCommands.Raw() & (1 << commandId)),不支持返回Status::InvalidCommand,见 ActionsCluster.cpp。

另外,ActionsCluster::AcceptedCommands在 ActionsCluster.cpp 中一次性声明全部 12 条命令为 accepted;Attributes通过AttributeListBuilder合并强制属性(kMandatoryMetadata)与可选的SetupURL。这意味着应用只需在 Delegate 中通过supportedCommands位掩码声明每条 action 支持哪些命令,服务端会自动拒绝未声明的调用——这一机制与数据模型 XML 中CommandBits位图(bit 0 到 bit 11)一一对应。

异步事件生成:状态变更上报

当命令被接受后,action 的实际状态发生变化时,应用需要异步调用ActionsCluster::GenerateEvent向 fabric 发送对应 Matter 事件(示例见 ActionsCluster.h):

cluster.GenerateEvent(Events::StateChanged::Type{ actionId, invokeId, newState }); cluster.GenerateEvent(Events::ActionFailed::Type{ actionId, invokeId, state, error });

底层实现(ActionsCluster.cpp)会先检查集群上下文mContext是否为空(未注册时生成事件会打印错误日志),随后通过mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId)以集群所在端点发出事件。

两个事件的数据结构与优先级来自数据模型规范:

  • StateChanged(事件 ID0x00,优先级info):字段ActionID(uint16)、InvokeID(uint32)、NewState(ActionStateEnum);
  • ActionFailed(事件 ID0x01,优先级info):字段ActionIDInvokeIDNewState以及Error(ActionErrorEnum)。

配套的枚举取值(定义于 ActionsCluster.xml):

  • ActionStateEnumInactive(0)、Active(1)、Paused(2)、Disabled(3);
  • ActionErrorEnumUnknown(0,其他未列出的原因)、Interrupted(1,被另一条命令或交互打断);
  • ActionTypeEnumOther(0)、Scene(1)、Sequence(2)、Automation(3)、Exception(4)、Notification(5)、Alarm(6);
  • EndpointListTypeEnumOther(0)、Room(1)、Zone(2)。

正如 ActionsDelegate.h 接口注释所强调的:"Handle*命令回调的实现需要按需调用OnStateChangedOnActionFailed来生成规范要求的事件"——即事件生成是 Delegate 侧在状态真正变化后主动触发的,服务端只在命令被接受时返回Status::Success,并不会替应用伪造状态变迁。

测试覆盖与验证入口

仓库为 actions-server 提供了两组单元测试,均基于pw_unit_test框架(ClusterTester辅助类):

  • tests/TestActionsCluster.cpp:验证ActionsCluster核心逻辑。测试中定义了MockActionsDelegate(内部持有ActionStructStorage mActions[kMaxActions]EndpointListStorage mEndpointLists[kMaxEndpointLists],并跟踪HandleInstantAction是否被调用、记录mLastActionId/mLastInvokeId/mReturnStatus),覆盖属性列表编码、命令分发的校验路径以及事件生成等场景;
  • tests/TestActionsClusterBackwardCompatability.cpp:验证ActionsServer兼容包装层的注册/注销(Init/Shutdown)与旧回调行为。

构建入口见 BUILD.gn(该目录同时提供 app_config_dependent_sources.gni 与 app_config_dependent_sources.cmake,供 GN 与 CMake 两套构建体系按配置裁剪源码)。

端到端示例:桥接应用中的 Actions 集群

以 examples/bridge-app/linux/bridged-actions-stub.cpp 为例,可看到完整的落地方案:

  • LinuxActionsDelegateImplReadActionAtIndex从内部std::vector<Action*>中取出可见getIsVisible())的 action 并填充ActionStructStorage,找不到目标索引时返回CHIP_ERROR_PROVIDER_LIST_EXHAUSTED
  • ReadEndpointListAtIndexGetEndpointListInfo返回的EndpointListInfo构造DataModel::List<const EndpointId>后调用epList.Set(...)
  • HaveActionWithId线性遍历查找 action ID 并回填索引;
  • Handle*命令在示例中暂返回Status::NotFound(未实现),而 all-clusters-app 的 bridged-actions-stub.cpp 中的emberAfActionsClusterInitCallback则展示了"仅 endpoint 1、仅一次、先校验再构造"的标准防御式初始化。

在 ZAP 配置侧,all-clusters-app.zap 中 Actions 集群以"code": 37(0x0025)、"side": "server""enabled": 1声明,并逐个启用了 12 条传入命令——这与ActionsCluster::AcceptedCommands的声明保持一致。

小结

本集群的落地要点可归纳为四条:

  1. 位置唯一:Actions 集群是 "Scope: Node" 集群,只放在聚合端点上,一个节点通常一个实例(多聚合端点时每端点一个);
  2. 新代码用ActionsCluster:code-driven 数据模型下直接构造并RegisteredServerCluster注册;旧 Ember/ZAP 应用继续用ActionsServer包装层,享受幂等的Init()/Shutdown()与自动清理;
  3. Delegate 是数据与行为的唯一来源:属性靠ReadActionAtIndex/ReadEndpointListAtIndex遍历,命令靠 12 个Handle*回调执行,位掩码supportedCommands控制命令可见性;
  4. 事件手动上报:状态真正变化后调用GenerateEvent发送StateChanged/ActionFailed,让 fabric 上的订阅者感知动作生命周期。

如需从零实践,建议从 tests/TestActionsCluster.cpp 中的MockActionsDelegate入手理解接口契约,再对照 bridge-app 的集成方式将集群挂到自己的聚合端点上。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询