Matter SDK 设备信息提供者(DeviceInfoProvider)示例实现解析:FixedLabel、UserLabel、区域设置与日历类型
2026/9/19 23:26:46 网站建设 项目流程

Matter SDK 设备信息提供者(DeviceInfoProvider)示例实现解析:FixedLabel、UserLabel、区域设置与日历类型

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

导读

本篇文章围绕 examples/providers/README.md 展开,深入解析 Matter(Connected Home over IP,CHIP)SDK 中"应用专用 Provider(provider)"的示例实现机制。文章以 Device Info Provider 为主线,完整介绍其职责边界、两个示例实现(空实现版与 All-Clusters 常量版)的代码结构与设计意图、四个迭代器接口(FixedLabel / UserLabel / SupportedLocales / SupportedCalendarTypes)的底层语义、持久化存储实现,以及如何在真实应用(如 all-clusters-app)中注册使用。读完本文,你将掌握如何为自己的 Matter 产品自定义设备信息提供者,并理解 Matter 1.5 之后对示例默认值采取的"安全拒绝"策略。


1. Provider 目录的定位:应用专用的可替换实现

examples/providers/README.md 开篇就明确了该目录的核心定位:

This folder contains example implementations of 'providers' that are generally application-specific. Final applications should generally have their own implementations of these.

即:examples/providers/存放的是**应用专用(application-specific)**的 Provider 示例实现。所谓 Provider,在 Matter SDK 中指的是向协议栈上层(尤其是各类 Cluster)提供业务数据的一组可插拔接口。最终产品不应直接复用这些示例代码,而应基于它们派生并定制自己的实现。

从目录结构看,该目录共包含 6 个文件:

文件作用
DeviceInfoProviderImpl.h / .cpp空实现版 Device Info Provider(安全默认)
AllClustersExampleDeviceInfoProviderImpl.h / .cpp带常量数据的 All-Clusters 示例版
BUILD.gn两个静态库目标的 GN 构建配置
README.md目录说明(本文的关联文档)

其中 README 明确点名的Device Info Provider是目录中唯一被详细介绍的主题,其职责是:

Supports device-specific information like labels, locales and calendar types.

即向设备端 Cluster 提供**标签(labels)、区域设置(locales)和日历类型(calendar types)**等设备特有信息。这三类数据分别对应 Matter 规范中的 Fixed Label、User Label、Localization Configuration 和 Time Format Localization 等 Cluster。


2. 核心接口:DeviceInfoProvider 抽象基类

在深入示例实现之前,需要先理解 SDK 定义的抽象基类 src/include/platform/DeviceInfoProvider.h。它是所有设备信息 Provider 的公共契约,位于chip::DeviceLayer命名空间下。

2.1 四个纯虚迭代器接口

基类定义了四个必须实现的纯虚迭代器(= 0),分别覆盖 README 中提到的 labels、locales、calendar types:

纯虚方法返回迭代器类型数据来源 Cluster元素类型
IterateFixedLabel(EndpointId)FixedLabelIterator *Fixed Label(固定标签)FixedLabelType
IterateUserLabel(EndpointId)UserLabelIterator *User Label(用户标签)UserLabelType
IterateSupportedLocales()SupportedLocalesIterator *Localization Configuration(区域配置)CharSpan
IterateSupportedCalendarTypes()SupportedCalendarTypesIterator *Time Format Localization(时间格式本地化)CalendarType

所有迭代器都基于同一个模板Iterator<T>定义,其协议非常简单:

template <typename T> class Iterator { public: virtual size_t Count() = 0; // 返回总共将迭代的条目数 virtual bool Next(T & item) = 0; // 成功取回下一个元素返回 true,无更多条目返回 false virtual void Release() = 0; // 释放迭代器分配的内存,指针离开作用域前必须调用 };

其中元素类型在基类中做了别名绑定:

using FixedLabelType = app::Clusters::FixedLabel::Structs::LabelStruct::Type; using UserLabelType = app::Clusters::UserLabel::Structs::LabelStruct::Type; using CalendarType = app::Clusters::TimeFormatLocalization::CalendarTypeEnum;

2.2 平台侧定义的常量约束

基类头部同时定义了 Provider 实现需要遵守的容量上限(src/include/platform/DeviceInfoProvider.h):

static constexpr size_t kMaxUserLabelListLength = 4; // 每个 Endpoint 的用户标签列表最大 4 条 static constexpr size_t kMaxLabelNameLength = 16; // 标签名最大 16 字节 static constexpr size_t kMaxLabelValueLength = 16; // 标签值最大 16 字节 static constexpr size_t kMaxActiveLocaleLength = 35; // 活动区域设置串最大 35 字节

这些常量直接决定了示例实现中内部缓冲区的尺寸(详见第 4 节)。

2.3 User Label 的写操作与全局 Provider 注册

除了读侧迭代器,基类还向派生类暴露了一组protected 虚函数,用于对 User Label 列表做持久化读写:

  • SetUserLabelAt(endpoint, index, userLabel):设置指定索引处的标签;
  • DeleteUserLabelAt(endpoint, index):删除指定索引处的标签;
  • SetUserLabelLength(endpoint, val)/GetUserLabelLength(endpoint, val):读写标签列表长度。

公共的列表级操作(SetUserLabelListClearUserLabelListAppendUserLabel)则由基类在 src/platform/DeviceInfoProvider.cpp 中基于上述原语组合实现,例如AppendUserLabel会先读取当前长度、校验不超过kMaxUserLabelListLength(否则返回CHIP_ERROR_NO_MEMORY),再依次写入长度与新标签。

全局访问通过GetDeviceInfoProvider()/SetDeviceInfoProvider(DeviceInfoProvider *)完成,两者维护一个进程级单例指针(src/platform/DeviceInfoProvider.cpp)。


3. 两个示例实现的设计意图:空实现 vs 常量数据

目录中提供了两套风格截然不同的实现,体现了不同的使用场景:

  • DeviceInfoProviderImpl(空实现):所有迭代器返回空数据,是Matter 1.5 之后引入的"安全默认"
  • AllClustersExampleDeviceInfoProviderImpl(常量实现):填充硬编码的示例值,专供 all-clusters 类测试应用使用。

3.1 空实现版:防止坏数据泄漏进产品

在 DeviceInfoProviderImpl.h 的头部,源码用四个感叹号标注了非常醒目的 WARNING:

DO NOT USE THESE DEFAULT IMPLEMENTATIONS WITH DEFAULT VALUES IN PRODUCTION PRODUCTS WITHOUT AUDITING THEM! ... Here, all providers have empty implementations to force empty lists which prevent bad values from leaking into products like happened before Matter 1.5.

这段话传递了两个关键信息:

  1. 历史教训:Matter 1.5 之前,示例默认值曾直接泄漏到产品中;
  2. 设计对策:此后默认示例强制返回空列表,如果产品确实要用 FixedLabel、LocalizationConfiguration、Time Format Localization 等 Cluster,必须重新实现 Provider,且填入的值必须经过正确性审查。

具体到代码(DeviceInfoProviderImpl.cpp):

DeviceInfoProvider::FixedLabelIterator * DeviceInfoProviderImpl::IterateFixedLabel(EndpointId endpoint) { // We don't include fixed label data in this sample one. Returning nullptr returns empty list. (void) endpoint; return nullptr; }

IterateFixedLabel直接返回nullptr,按照基类文档约定("returns nullptr if no iterator instances are available"),调用方会将其解释为空列表。

3.2 常量实现版:面向 all-clusters 测试

AllClustersExampleDeviceInfoProviderImpl则为测试目的填充了经过审查的常量值,其头部警告措辞相对缓和,但也明确指出"FixedLabel cluster, if used, should have values that have been vetted for correctness in the product"(AllClustersExampleDeviceInfoProviderImpl.cpp)。

两套实现的差异汇总:

维度DeviceInfoProviderImplAllClustersExampleDeviceInfoProviderImpl
FixedLabel返回nullptr(空列表)返回 1 条硬编码标签direction=up/down
UserLabel持久化存储读写持久化存储读写
SupportedLocales2 个:en-USen-GB8 个:en-USde-DEfr-FRen-GBes-ESzh-CNit-ITja-JP
SupportedCalendarTypes1 个:kGregorian1 个:kGregorian

4. 源码级剖析:四个迭代器的完整实现

下面以AllClustersExampleDeviceInfoProviderImpl为主要样本,逐个剖析四个迭代器的实现(空实现版逻辑相同,只是数据量为 0 或nullptr)。

4.1 FixedLabelIteratorImpl:按 Endpoint 区分的固定标签

DeviceInfoProvider::FixedLabelIterator * AllClustersExampleDeviceInfoProviderImpl::IterateFixedLabel(EndpointId endpoint) { return chip::Platform::New<FixedLabelIteratorImpl>(endpoint); }

迭代器在堆上通过chip::Platform::New分配,Release()时通过chip::Platform::Delete(this)释放(这与基类"必须调用 Release() 释放内存"的契约一致)。

FixedLabelIteratorImpl::Next()的实现展示了按 Endpoint 返回不同值的手法(AllClustersExampleDeviceInfoProviderImpl.cpp):

case 0: output.label = "direction"_span; if (mEndpoint == 1) { output.value = "up"_span; } else { output.value = "down"_span; } break;

即固定标签名direction,对 Endpoint 1 返回up,其余 Endpoint 返回down。源码注释还提醒:真实产品中这些值很可能需要按 Endpoint 动态生成,此时应把 span 指向正确的 buffer+size,而非直接使用const char*支撑的CharSpan

4.2 UserLabelIteratorImpl:基于持久化存储的读写

User Label 与前两者最大的不同是可写——用户(或控制器)可以通过 Cluster 命令增删标签。因此示例实现将其落到PersistentStorageDelegate上。

写入侧SetUserLabelAt把标签序列化为 TLV 结构后按 key 存储(AllClustersExampleDeviceInfoProviderImpl.cpp):

uint8_t buf[UserLabelTLVMaxSize()]; TLV::TLVWriter writer; writer.Init(buf); TLV::TLVType outerType; ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); ReturnErrorOnFailure(writer.PutString(kLabelNameTag, userLabel.label)); // ContextTag(0) ReturnErrorOnFailure(writer.PutString(kLabelValueTag, userLabel.value)); // ContextTag(1) ReturnErrorOnFailure(writer.EndContainer(outerType)); return mStorage->SyncSetKeyValue( DefaultStorageKeyAllocator::UserLabelIndexKey(endpoint, static_cast<uint32_t>(index)).KeyName(), buf, static_cast<uint16_t>(writer.GetLengthWritten()));

其中:

  • kLabelNameTag/kLabelValueTag分别是TLV::ContextTag(0)/ContextTag(1)
  • 缓冲区大小由UserLabelTLVMaxSize()决定,即TLV::EstimateStructOverhead(kMaxLabelNameLength, kMaxLabelValueLength)(16 字节 name + 16 字节 value + TLV 结构开销);
  • 存储 key 由DefaultStorageKeyAllocator::UserLabelLengthKey(endpoint)(长度)与UserLabelIndexKey(endpoint, index)(条目)统一生成。

读取侧UserLabelIteratorImpl::Next()先从存储读出 TLV 字节,再用TLV::ContiguousBufferTLVReader逐字段解析,最终把 label/value 拷贝进内部缓冲后以CharSpan返回:

Platform::CopyString(mUserLabelNameBuf, label); Platform::CopyString(mUserLabelValueBuf, value); output.label = CharSpan::fromCharString(mUserLabelNameBuf); output.value = CharSpan::fromCharString(mUserLabelValueBuf);

迭代器的Count()Next()均基于构造时读到的列表长度mTotal工作,超出范围即返回false结束迭代。两个示例实现(空实现版与常量版)的 User Label 读写逻辑完全一致,因为这一部分与"示例数据"无关,属于通用存储逻辑。

4.3 SupportedLocalesIteratorImpl:区域设置清单

空实现版硬编码 2 个区域(DeviceInfoProviderImpl.cpp):

static const char * kAllSupportedLocales[kNumSupportedLocales] = { "en-US", "en-GB" };

常量实现版扩展为 8 个常用区域(AllClustersExampleDeviceInfoProviderImpl.cpp):

static const char * kAllSupportedLocales[kNumSupportedLocales] = { "en-US", "de-DE", "fr-FR", "en-GB", "es-ES", "zh-CN", "it-IT", "ja-JP" };

区域字符串是 BCP-47 风格的语言-国家/地区格式,直接对应 Localization Configuration Cluster 中SupportedLocales属性的枚举值。

4.4 SupportedCalendarTypesIteratorImpl:日历类型清单

两套实现都只支持一种日历——公历:

static const CalendarType kAllSupportedCalendarTypes[kNumSupportedCalendarTypes] = { app::Clusters::TimeFormatLocalization::CalendarTypeEnum::kGregorian };

该枚举值来自TimeFormatLocalizationCluster,其迭代器元素类型是CalendarType(枚举),与区域设置迭代器返回的CharSpan不同。


5. 构建配置:两个静态库目标

examples/providers/BUILD.gn 将两套实现分别封装为静态库:

static_library("device_info_provider_please_do_not_reuse_as_is") { output_name = "libMatterDeviceInfoProviderExample" sources = [ "DeviceInfoProviderImpl.cpp", "DeviceInfoProviderImpl.h", ] public_deps = [ "${chip_root}/src/lib/support", "${chip_root}/src/platform", ] public_configs = [ ":include_providers_dir" ] } static_library("all_clusters_device_info_provider") { output_name = "libMatterAllClustersDeviceInfoProviderExample" sources = [ "AllClustersExampleDeviceInfoProviderImpl.cpp", "AllClustersExampleDeviceInfoProviderImpl.h", ] public_deps = [ "${chip_root}/src/lib/support", "${chip_root}/src/platform", ] public_configs = [ ":include_providers_dir" ] }

值得注意的命名细节:

  • 第一个目标名device_info_provider_please_do_not_reuse_as_is本身就携带了"请勿照搬复用"的语义,与源码头部的 WARNING 相呼应;
  • include_providers_dirconfig 同时把examples根目录加入 include 路径,因此下游可以通过#include <providers/AllClustersExampleDeviceInfoProviderImpl.h>这种形如providers/...的头文件引用方式引入实现。

两个库都依赖${chip_root}/src/lib/support${chip_root}/src/platform,因为它们使用了DefaultStorageKeyAllocatorTLVPlatform内存管理等 SDK 基础设施。


6. 在真实应用中注册使用:all-clusters-app 实例

示例 Provider 在 all-clusters-app 的 fuzzing 入口中有完整使用示范(examples/all-clusters-app/linux/fuzzing-main.cpp):

#include <providers/AllClustersExampleDeviceInfoProviderImpl.h> // ... chip::DeviceLayer::AllClustersExampleDeviceInfoProviderImpl gAllClustersExampleDeviceInfoProvider; // ... DeviceLayer::SetDeviceInfoProvider(&gAllClustersExampleDeviceInfoProvider);

完整的接入流程是:

  1. 实例化:在全局或初始化阶段构造 Provider 子类对象;
  2. 设置存储委托:调用SetStorageDelegate(PersistentStorageDelegate *)注入非易失存储(基类SetStorageDelegate对空指针会VerifyOrDie,src/platform/DeviceInfoProvider.cpp),User Label 的持久化读写依赖该存储;
  3. 注册全局实例:调用SetDeviceInfoProvider(&provider)挂入DeviceLayer的全局指针;
  4. 协议栈使用:此后 Fixed Label、User Label、Localization Configuration、Time Format Localization 等 Cluster 的数据读写都会路由到该 Provider。

这四条步骤同样适用于任何自定义 Provider:继承DeviceInfoProvider→ 实现四个迭代器与四个 User Label 原语 → 注入存储 → 全局注册


7. 面向产品开发者的最佳实践

综合 README 的定位说明与源码中的 WARNING,可以总结出以下实践要点:

  1. 示例仅供学习与测试examples/providers/中的实现是"应用专用"的示例,最终产品应拥有自己的实现;
  2. 空实现是安全基线:Matter 1.5 之后,默认空实现刻意返回空列表,避免未审查的示例值进入产品;如果你确实启用 FixedLabel、LocalizationConfiguration、TimeFormatLocalization 等 Cluster,务必重写 Provider 并提供经过验证的值;
  3. User Label 需要持久化:该能力依赖PersistentStorageDelegate,务必在注册 Provider 前通过SetStorageDelegate注入有效存储,否则断言失败;
  4. 遵守容量约束:标签名与标签值均不超过 16 字节、每 Endpoint 用户标签最多 4 条(kMaxUserLabelListLength),列表操作在超限时返回CHIP_ERROR_NO_MEMORY
  5. 迭代器生命周期管理:所有迭代器通过chip::Platform::New分配,调用方必须在迭代结束后调用Release(),且迭代期间不允许修改标签数据。

参考文件索引

  • 关联文档:examples/providers/README.md
  • 空实现版:DeviceInfoProviderImpl.h / DeviceInfoProviderImpl.cpp
  • 常量实现版:AllClustersExampleDeviceInfoProviderImpl.h / AllClustersExampleDeviceInfoProviderImpl.cpp
  • 构建配置:examples/providers/BUILD.gn
  • 抽象基类:src/include/platform/DeviceInfoProvider.h
  • 基类通用实现:src/platform/DeviceInfoProvider.cpp
  • 应用接入示例:examples/all-clusters-app/linux/fuzzing-main.cpp

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

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

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

立即咨询