NCS Matter例程详解
本文将会简单介绍Nordic Matter开发流程,然后详细分析一个Matter over Thread窗帘例程代码
1. Matter简介
什么是Matter?
从产品角度:
- Matter是一个跨生态的智能家居标准,有众多大厂支持
- 消费者购买Matter产品无需考虑品牌、部署。只要支持Matter,都是开箱即用的
从技术角度:
- Matter是基于IPv6的应用层协议(CHIP, Connect Home over IP)
- 建立在成熟的网络协议之上(Wi-Fi/Thread/Ethernet )
- Matter有一套成熟的设备发现和入网机制(UDP-SD或Bluetooth LE)
- Matter协议是安全的。设备必须经过认证才能加入Matter网络;入网后,设备会获得一个证书用于加密通讯。
开发Matter其实是在开发数据模型
Matter规定了许多设备类型,厂商只能开发Matter已经规定的设备类型。如下图为Matter1.0发布时规定的部分设备类型:
这些不同的设备类型,其实就是不同的数据模型。在Matter协议栈中,应用层之下就是Data Model层:
- Node:一个具有完整Matter stack的实体,具有唯一的网络地址。大多数情况下,一个设备就是一个Node。
- Endpoint:功能交互端点。例如一个Node可以有“锁”和“温度传感器”两个Endpoint。
- Cluster:每个Endpoint可能有多个具体的功能集合,就是cluster。例如锁的控制、电池电量的上报
- Attribute/Events/Commands:也就是所谓的属性/事件/服务,是数据实体。
Endpoint0比较特殊,是必须的,它负责Matter基本功能的Cluster。
一个具体的示例:
一个门锁设备,Endpoint0 提供基本信息、访问控制、配网等Matter基本功能的Cluster。
Endpoint1提供Identify和门锁基本功能的Cluster。
每个Cluster由多个Attributes组成,Attributes就是实际存储器中的变量或常量
Matter的网络拓扑
由于Matter只是应用层协议,所以Matter的网络拓扑就是它采用的底层通讯方式的网络拓扑:
Matter网络建立后,互相之间通过前述数据模型进行交互。手机、智能音箱等可以操控所有设备。
- Wi-Fi:只要AP覆盖到的地方,设备都可以入网。AP之间通过以太网互联,在家庭中很常见。
- Thread:功耗更低,且Thread设备之间可以构成Mesh,只要多级跳转最终能连接到Border Router即可。一些位于中转位置的节点最好是常供电的(如Light Switch)。
- 其他本地网络:如Zigbee,BLE Mesh等未在Matter标准中使用的协议,需要一个Matter Bridge来做中转。Matter Bridge负责向Matter网络提供数据模型,处理Matter交互,然后将其转换为其他协议。Matter并不规定Matter之外的协议如何处理,因此开发者可以让Bridge自由适配任何其他协议。
- 互联网:Matter协议是局域网的。Matter生态商(如苹果、谷歌、亚马逊、三星)负责让你的手机可以通过外网发送到家中的控制中枢,从而控制家中的Matter设备。
Thread网络是支持IPv6 UDP的。但Thread网络要和其他网络连接,需要边界路由器(OTBR, OpenThread Border Router)。
Apple Home Pod已经内置Border Router。此外iPhone 15 Pro和Pro Max已经内置Thread网卡,可以直接控制Thread设备。
Matter设备发现与入网
BLE是目前最常见的入网方式,流程为:
- 设备发出BLE广播
- 【设备发现】手机扫描二维码后,或者输入Manual Pairing Code后,根据Paring Code信息自动连接对应的BLE广播
- 【PASE】二者通过Out-of-band信息(二维码中的passcode)建立加密通道。确认设备经过认证,安装后续加密通讯需要的证书(NOC, Node Operational Certificate)。然后设备入网(传输Wi-Fi密钥,Thread网络Key等等),这个过程无需人工输入密码。
- 【CASE】设备入网后,每次通讯都要建立一个新的AES加密连接。
以上过程在SDK中都已经提供,无需再开发。
其他的设备发现方式还有DNS-SD或Wi-Fi Soft-AP。
Matter Controller
Matter Controller是网络中非常重要的节点,在消费者家中负责设备的远程控制和入网。如Apple HomePod.
在Matter开发环境中,Matter Controller也非常重要。可以使用CHIP Tool作为Matter Controller。可以直接用命令行的方式直观地进行配网、数据模型交互。
CHIP Tool可以运行在Linux和mac OS环境中(Windows中需要Linux虚拟机):
CHIP Tool是一个软件命令行工具,为了控制Matter设备,它所在的机器需要有BLE和IP网络。其中IP网络分为Thread, Wi-Fi和以太网。如果你是开发Matter Over Wi-Fi,则需要BLE和Wi-Fi/以太网;如果你是开发Matter Over Thread,则需要BLE和Thread。
- Wi-Fi/以太网:PC和树莓派自带Wi-Fi或以太网,无需额外准备。如果是Windows内的Linux虚拟机,则需要确保Linux虚拟机和宿主机在同一局域网下(Bridge模式)。
- Thread:PC和树莓派通常没有Thread网卡,因此需要一个Nordic开发板来充当网卡。比较推荐的是上图的nRF52840 Dongle,然后烧录Thread RCP例程(
nrf/samples/openthread/coprocessor
) - BLE:PC和树莓派自带蓝牙网卡,无需额外准备。如果是Windows内的Linux虚拟机,则需要一个USB蓝牙网卡,通过USB透传进虚拟机。购买一个USB网卡也可以;使用Nordic nRF52840 Dongle也可以,烧录HCI_USB例程(
zephyr/samples/bluetooth/hci_usb
);如果只有nRF52832(无USB),也可以烧录HCI_UART例程(zephyr/samples/bluetooth/hci_uart
),按照例程文档说明通过命令行挂载一下蓝牙网卡即可。
2. Matter开发流程
Matter SDK是开源的,用Nordic SDK和硬件开发和评估Matter也不需要申请资质,软件全部是公开可下载的。开发前,先下载好技术文档。
下载对应Matter版本的3个文档:
- Matter Core Specification
- Matter Device Library Specification
- Matter Application Cluster Specification
但最终要开发Matter产品并上市,还是要成为CSA会员并对设备进行认证。参考认证流程。
芯片选型
主要是根据Flash RAM占用情况选用合适的芯片。Nordic有多个Matter例程,并且提供了它们在不同开发板上的资源占用情况。最好按照Nordic提供的芯片组合来开发自己的Matter产品,这样开发量最小:
注意:NCS中,通过无线进行OTA一定需要双分区。由于Matter应用比较大,因此需要一个外部flash作为,其大小不低于内部Flash(52840为1M Bytes, 5340为1.5M Bytes)。且最好使用QSPI,并且注意使用QSPI专用的高速引脚(参考Nordic官方开发板)。
申请或购买Nordic开发板,直接在开发板运行Matter例程
按照NCS Matter例程文档在开发板上编译和烧录,运行例程。
进行项目开发
搭建Matter开发环境,安装CHIP Tool;进行软件与硬件开发;添加自己的endpoint, cluster。
如有必要,还可以添加Matter之外的蓝牙广播和蓝牙服务。
产品优化
优化功耗,主要是网络的功耗和一些外设的功耗。
生成Factory Data
认证通过后,进行产测工具的开发,通过证书为每台设备生成Factory Data
Matter文档与SDK
以上只是一个粗略的流程总结,具体开发步骤还是要参考Matter文档。
Nordic官方文档:
-
Matter官方文档可能出现一些NCS已经做好了,因此不必要再重复的内容(如编译Matter SDK)。为了避免混淆,最好主要参考前两个NCS中的文档。必要时,前两个文档会跳转到第三个文档中的内容。
NCS中已经包含了 Matter SDK (https://github.com/project-chip/connectedhomeip)作为子仓库,无需再单独下载Matter SDK。(modules/lib/matter)
NCS中Matter例程的路径为:nrf/samples/matter/
此外还有两个成熟的商业级例程:
nrf/applications/matter_weather_station
nrf/applications/matter_bridge
3. 运行Matter窗帘例程
本次演示首先通过nRF Connect for VS Code中的“Copy a sample”功能,拷贝了nrf/samples/matter/window_covering
工程。工程放在SDK外部。
并且把SDK和这个工程放在同一个VS Code workspace中,这样做是为了方便后续代码跳转阅读。
注意最新的nRF Connect for VS Code插件进行了更新:要在build界面选择SDK和toolchain的版本。
编译烧录流程参考例程文档。烧录之后就可以用CHIP Tool进行配网和控制。
如果有iPhone手机和HomePod,则可以直接配置到Apple Home中。
通过串口LOG的网址打开二维码,Home APP中扫描即可。由于例程用的证书都是一样的,用例程文档里的二维码也可:
窗帘开关反映为LED的亮度。可以用Button控制窗帘,也可以在手机APP中控制。
更多按钮和LED的功能,参考例程文档。
4. 窗帘例程代码解析
工程文件分析
以下文件和NCS 2.6.x之前版本的作用是一样的:
- 工程通用的配置文件prj.conf/ prj_release.conf
- boards/下的配置文件与设备树overlay
- 用于Flash分区的Partition Manager文件(yml)
- 工程配置菜单文件Kconfig
- 工程源码管理文件CMakeLists.txt
然后是新增的内容:
sysbuild
Sysbuild代替了原来的parent-child image配置. 是为了多镜像工程服务的。
每个子镜像也可以单独添加配置,和原来的child_image/文件夹差不多。
但是Sysbuild也可以添加一个High-level的配置,这些High-level的配置将应用到所有子镜像(App, Bootloader, 以及可能存在的网络核固在的网络核固件)中:
- Kconfig.sysbuild 与 sysbuild.conf
- sysbuild.cmake (本例程中未使用)
此外,有一部分应用层代码并不位于本工程中,而是位于nrf/samples/matter/common
这是所有Matter例程共用的一部分代码。这部分代码是CMakeLists.txt中下面这一行引入的:
# Include all source files that are located in the Matter common directory. |
代码分析
main.cpp
首先看main.cpp
int main() |
例程代码是C++写的,但是开发者后续也可添加C代码,和其他的NCS工程开发起来没有什么区别。C代码添加自己的线程即可,和C++互相不干扰。
main函数中,只是调用了AppTask这个类的启动函数,便启动了Matter协议栈。
并且,正常情况下这个函数应该永远不退出。
AppTask.h
class AppTask { |
AppTask这个类,涵盖了应用层的所有任务。
注意到,这个类采用了单例模式,其默认构造函数没有定义(为了安全,应该改为private,空的构造函数)。
意思是,这个类只能有1个对象实例,代码的其他地方不能通过静态定义或者动态allocate这个对象。
要调用这个类的函数时,必须使用AppTask::Instance()
这个唯一的实例对象,例如:
CHIP_ERROR err = AppTask::Instance().StartApp(); |
这也是非常合理的,因为这个类管理的是当前设备的硬件行为。一块板子上的硬件外设本来就有唯一性,定义多个对象实例没有意义。
AppTask.cpp
前面看到,在main.cpp中执行了AppTask::Instance().StartApp()这个函数。
CHIP_ERROR AppTask::StartApp() |
它首先初始化了Matter。然后处理事件循环。
Matter初始化
AppTask::Init()
中进行了Matter协议栈的初始化。
所有Nrf::
类中的函数,都是nrf/samples/matter/common
提供的,不是Matter SDK原始的API。其目的是封装和简化Matter例程代码。
第一部分是初始化Matter Stack
第二部分是注册硬件按钮回调函数
第三部分是注册Matter事件回调函数,这部分最重要。设备何时开启蓝牙广播、何时入网、断开连接,都在里面有回调.现在注册的是common里面提供的默认回调函数。你可以把这个函数拷贝出来到AppTask.cpp,并重写自己的功能,再注册回去。
第四部分是开启Matter协议栈。
AppTask事件循环
这个事件循环,其实和Zephyr Work Queue (k_work)的功能差不多。在各种Matter或者硬件中断的回调函数中,我们希望能够快速退出回调,防止卡中断或者协议栈,因此可以把一些耗时的任务提交到Workqueue中去运行(Workqueue有单独的线程)。
使用Zephyr Work Queue 当然是可以的,但是比较麻烦的是每次都要自己定义一个k_work结构体,还要初始化这个work,注册k_work的回调,重复性的代码比较多。
Matter例程是C++编写的,因此利用了C++的Lambda表达式可以作为匿名函数的功能:
简单理解,Lambda表达式就是:
[args...] { |
这是个匿名函数。直接把它整体丢进队列,事件循环中就可以从队列中取出这个函数,然后执行了。省去了定义函数名和函数指针的麻烦,例如:
// Matter Identify事件回调,要求设备LED闪烁,以辨识设备 |
此外,[]中的内容叫作捕获组,可以把当前函数的局部变量捕获到lambada表达式中,当作参数传递使用,非常方便。例如:
// 硬件按钮事件回调,需要调用比较耗时的开启或关闭窗帘函数 |
zcl_callbacks.cpp
前面Matter协议栈初始化后,BLE和Thread就全部被Matter接管并初始化好了,无需开发者再关注。
包括配网的功能已经全部被SDK实现。
Matter要开发的,其实就是如何处理数据交互(Endpoint, Cluster, Attributes)。
Matter的数据模型都是通过ZAP Tool进行图形化编辑的,编辑后自动生成代码。
但是自动生成的代码是空的,应用层需要实现这个代码。
例如,zcl_callbacks.cpp中,就实现了当自己Attributes被网络中的其他设备修改时,要执行的回调函数。
每个Attribute都有自己的ID,通过ID来判断是哪个Attribute被修改了。
WindowCovering.cpp
WindowCovering类,提供的就是“真正”的窗帘控制代码了。前面ZCL的回调之中调用的就是WindowCovering类的代码。
但是实际上这里是用LED的亮度变化来模拟窗帘的打开程度。这个class的代码不重要,开发者完全可以把它删了改成自己的。只需要能够完美处理zcl_callback.cpp中的回调事件即可。
而且由于同时支持竖向窗帘和横向窗帘,这个类的代码写的比较复杂。
这里简单分为两类:
- 窗帘目标位置发生改变:执行StartMove函数。注意这里并没有传递参数,告知具体的目标位置是什么。因为Attribute是随时可读的,可以在后续具体执行运动时,再去Get这个Attribute值。
- 窗帘实际位置发生改变:更新LED状态。同理,也不需要传参。
这里直接跳到以下函数:
从前面的StartMove(….LIFT)
,调用到上面展示的DriveCurrentLiftPosition()
函数,中间跳转了很多层。本质上就是为了模拟窗帘的运动。
DriveCurrentLiftPosition()
是一个软定时器的回调函数。每200ms把当前位置向目标位置挪动一步,一步是初始差值的5%,用来模拟窗帘慢慢移动到窗帘的实际位置。这里细节我们不必过于深究,只需要知道Matter API如何调用:
- 要Get Attribute值,需要调用
Attributes::属性名::Get(Endpoint(), xxx)
- 要Set Attribute值,需要调用
Attributes::属性名::Set(Endpoint(), xxx)
这里的参数Endpoint()其实就是个整数。因为WindowCovering这个类,也是一个单例模式的类。它对应的就是Endpoint1:
// WindowCovering.h中class的定义 |
此外,注意上述Attribute操作的API是在窗帘Cluster的name space调用的,如果开发其他产品,要换成其他的namespace:
using namespace chip::app::Clusters::WindowCovering; |
总结
AppTask这个类负责应用层的杂项业务代码,有自己的事件循环(占用main线程)。也负责Matter协议栈的初始化。
default_zap/zap_generated下的代码是zap tool根据Matter数据模型自动生成的。用户可以用zap tool来修改当前工程的Matter数据模型。自动生成的代码只有定义,没有实现。开发者需要实现这些应用层代码。
WindowCovering这个类,是Nordic提供的应用层示例代码,用LED的灯光亮度来模拟窗帘的运动。开发者完全可以实现自己的类,而不必继续使用这个类。只需学习它的Attribute的操作方法,以及各种Matter API的使用即可。
5. 实战-增加电量显示
参考Matter标准文档
在添加新的Cluster前,首先要参考Matter的标准文档。
电池电量不属于application cluster,而是在核心规范中定义。我们直接参考matter-1-3-core-specification.pdf
电源相关的cluster定义在11.7 Power Source Cluster。
使用ZAP Tool修改数据模型
为自己的当前build打开一个nRF Connect命令行:
现在也可以从这里打开:
输入以下命令:
west zap-gui |
现在直接执行上述命令,会自动连网安装zap tool,不需要自己手动下载。也不需要添加参数设置位置。会按照SDK的zap tool的相对路径来查找。
在Endpoint 1中,因为PowerSource不属于application cluster,而是Matter的Cluster,所以在CHIP(Connect Home over IP)分类下寻找。
选中Power Source Cluster,并开启Server。Server的含义是存储Attribute的地方,可以被Client读取或修改。
再点击右侧齿轮进行进一步修改。
可以先什么都不改,直接用
west zap-generate
生成代码,相当于进行了一次代码格式化。代码格式化后commit一次。后续改了zap,再生成代码,就方便通过git看自动生成了哪些callback。
每个Cluster会有很多Feature。例如核心规范中规定,有4个Feature。电源线供电,电池供电,可充电电池,可更换电池。
其中,Conformance字段表示某种依赖性。方括号[]的意思是依赖,如果想要使能RECHG或者REPLC,就必须要使能BAT。
由于我们要显示电池电量,电池电量这个Attribute肯定是属于BAT这个Feature的,因此Feature Map的Bit 1就要置1。也就是把FeatureMap置为0x02:
(这里显示out of range是ZAP Tool的显示bug,实际是正确的)
继续阅读文档,发现BatPercentRemaining这个Attribute就是我们需要的电池电量。取值范围是0-200。
并且其依赖[BAT]这个Feature,这个我们前面已经设置好了。
但是,后面还有三个Attribute,他们的Conformance是不带方括号的BAT。
不带方括号的意思是,当使能了BAT,就必须要开启这三项attribute:
- BatChargeLevel:枚举(正常、电量低、状态危险)
- BatReplacementNeeded:bool,电池是否需要更换
- BatReplaceability:枚举(未定义、不可更换、用户可更换、返厂可更换)
总共要开启4项:
点击File->Save保存。保存后,通过git会发现window-app.zap数据模型文件已经新增了Power Source cluster。
自动生成数据模型对应的代码
使用west zap-generate
自动按照默认路径生成代码.
west zap-generate |
现在不需要添加参数设置位置。会按照SDK的zap tool的相对路径来查找。
通过git查看新增的代码:
自动生成代码后,会发现只增加了一个InitCallback.
因为电池电量不可能被其他设备写,只能被读,所以不会新增一个像窗帘那样的AttributeChangedCallback.
添加回调
在zcl_callbacks.cpp中实现上述新增的callback
void emberAfPowerSourceClusterInitCallback(EndpointId endpoint) { |
这个多出来的回调并没有那么严格,上面是一个示例,只是打印一些log而已。
只需要学会电池电量的attribute如何设置即可。后续在业务代码中直接调用相关函数,进行设置。注意把代码放到事件循环中执行。
验证效果
在设备详情页面多了电量显示。但是一直是0,因为只在InitCallback中设置电量是不够的,要在程序运行起来后去设置。
注意电量范围是0-200,因此要设置200才是100%电量