1. 简介 在Matter设备中添加自己的蓝牙广播和服务是常见的需求。主要来自于两个方面:
一是功能的角度,因为Matter规定了每一种设备类型只能有它固定的操作,如果厂商有特殊的配置要传给设备,是没法通过Matter传输的。因为Apple, Google, Samsung等手机的家庭APP里不会有为厂商单独配置的界面。
实际上Matter也支持厂商自定义Cluster,只不过目前各大手机的家庭APP无法操作这些自定义Cluster。
二是生态的角度,智能家具厂商往往都有自己的手机APP。虽然Matter出现的目标是打破生态壁垒,但厂商肯定还是不愿意放弃自己的APP生态的,主打一个“我全都要”。
本文将会基于nRF Connect SDK v3.0.2说明如何在Nordic芯片平台上给Matter工程添加自己的蓝牙广播和服务。
2. 多蓝牙广播方案分析 对于Matter over Wi-Fi 设备,Wi-Fi和BLE不是同一个射频硬件,只要互相不干扰就行。
对于Matter over Thread 设备,Nordic只需要一颗MCU,就可以实现BLE和Thread共用射频硬件,它们实际上在物理层是分时共存的:
在Matter例程里,BLE广播只有2个应用场景:
Matter配网阶段的设备发现:配网阶段,手机扫码后根据二维码信息扫描附近的BLE广播,确认要连接的是哪个设备。BLE连接后传输网络密钥,设备通过网络密钥链接到对应的网络后(Thread或者Wi-Fi),BLE的使命就结束了。设备入网后,BLE就关闭,只保留Thread或者Wi-Fi。
设备OTA升级:通过Zephyr的MCUMgr SMP Server蓝牙服务升级,需要手机APP中集成了MCUMgr SMP Client来对设备进行升级。
我们会发现例程本身就有“Matter广播与私有广播共存”的用法了,因为SMP服务并不是Matter标准规定的,而是 Zephyr 通用的BLE OTA升级方案。
除此之外,nRF Connect SDK还支持多广播集(Multi ADV sets)共存,直接分时发出多个广播,可以各自具有不同的地址,从外部看起来就像是有多台BLE设备在广播一样。
因此,在Matter里面添加广播,有两个方案。
方案一:Matter 蓝牙广播仲裁器 Matter已经把SDK平台提供的蓝牙功能封装成了自己的API,其具体的实现位于:
v3.0.2\modules\lib\matter\src\platform\Zephyr\BLEManagerImpl.cpp
其中,有一个功能叫做广播仲裁器(Advertising Arbiter),是一个C++类。广播还是同一个广播,但是可以让你替换广播数据,从而实现替换广播名称、广播的UUID等等,在NCS中甚至还能改MAC地址(需要修改SDK源码)。
这个是Matter本身就提供的功能,也是推荐使用的方案。在Matter(CHIP)协议栈初始化之后,CHIP Server启动之前,可以通过仲裁器注册一组广播请求,包含广播数据、广播间隔、回调函数、优先级等等。
CHIP Server启动后,每个需要蓝牙广播的软件模块可以主动插入请求(Insert Request)或者取消请求(Cancel Request)。仲裁器会根据优先级排列当前所有的插入请求的广播,并执行优先级最高的(top-priority)广播。
比如你可以这样设置优先级:
Matter配网广播:0,最高
私有BLE广播:1
OTA广播:uint8_max
当一个广播成为优先级最高的广播时,会触发提前注册好的onStarted回调函数;或者不再成为优先级最高的广播时,会触发提前注册好的onStopped回调函数。
整个框架都是完善的,Matter SDK已经提供好了。全程只有一个蓝牙广播,只需切换它的内容。
方案二:Zephyr多广播集(Multi ADV sets) Matter蓝牙仲裁器的方案有一个小缺点,就是没办法让产品一开机就同时广播Matter配网和厂商的私有蓝牙,需要用户先进行某种操作。比如,有以下几种方案:
通过产品上的按钮来选择当前应该进行哪个广播
先发出Matter配网广播,等产品配网成功后,再发出厂商自己的广播供APP连接
先发出厂商自己广播,等连接完厂商的APP之后,再去APP里操作开启Matter配网广播
如果你不能接受这个小缺点,可以看Nordic的多广播共存方案,可以直接开启多个广播:
这时,使用的蓝牙广播API就不是普通的bt_le_adv_start(),而是bt_le_ext_adv_start()。用这个API可以创建多个广播集。
可能有人对这里的API名字中的”扩展广播“有误解,这里需要解释一下。
在Bluetooth Core Spec 5.0中,有一个新特性叫做扩展广播(Extended Advertising),它其实包含四个部分:
允许255字节的广播数据PDU和扫描响应数据PDU,但是必须位于0-36信道。原来的广播信道37, 38, 39仍然只有31字节的PDU。
允许通过多个扩展广播包形成链的形式,广播至多1650字节数据 。初始的广播包在37,38,39信道,然后跳到0-36信道。
允许广播有多个实例集合,这个广播实例可以是普通广播也可以是前面第1项描述的”变长“的广播
新特性:周期性广播。让广播间隔不再随机,这样observer端就可以不用持续开启扫描窗口,从而节省功耗
因此扩展广播(Extended Advertising) 指的是以上4个功能,而不是很多科普文章只提到的1号的功能。
参考连接:蓝牙™ Core 5.0功能 增强版 |蓝牙™ 技术网站
Zephyr 的bt_le_ext_adv API可以实现以上所有4个功能。我们这里需要用到的是功能3。具体的链路层广播分时共存是由Nordic SoftDevice Controller自动实现的。
如果要使用这个方案,就需要修改NCS中的Matter源代码,把蓝牙广播仲裁器里面使用的广播的API改成bt_le_ext_adv_start()。
此外,扩展广播的connected()回调函数定义是不一样的,除了要记录conn之外,还能知道是从哪个广播连进来的:
实际应用开发中,我们可能有多个蓝牙业务的软件模块,每个模块里面各自有自己的广播和服务。软件模块可以各自注册不同的bt_le_ext_adv_cb。
3. 多蓝牙连接与多蓝牙服务方案分析 Zephyr的蓝牙只有一个GATT Server。因此,无论你用前面的哪个蓝牙多广播方案,无论外部的BLE主机是通过哪个广播连接进来的,看到的蓝牙服务都是一样的。
普通广播连接时回调 在普通的蓝牙广播(bt_le_adv_start())场景下,蓝牙连接成功时,在connected()回调函数里,一定要把conn记录下来作为句柄:
如果从机被多个主机连接,开发者应该保存每个conn指针。后续进行GATT特征读写的时候,就靠conn来指定发送数据给哪个主机,同时也靠conn来判断数据是从哪台主机发来的。
注意,这种connected()回调函数可以注册很多个,形成链表。当连接成功时,所有connected()函数会依次执行。任何软件模块都可以注册自己的connected()来获取蓝牙连接、断开相关的事件。
代码里的bt_conn_ref()是给conn指针增加引用计数,防止其资源被操作系统自动释放。连接断开后再用bt_conn_unref()减少引用计数即可。
如果一个连接被多次引用,那么后续连接断开后,一定要释放相同的次数,否则将会出现内存泄漏 。
一般在connected()回调函数里面调用了一次bt_conn_ref(),那么就在disconnected()回调函数里面调用一次bt_conn_unref()即可。
即使有多个软件模块各自注册了自己的connected()和disconnected(),在里面各自执行bt_conn_ref()和bt_conn_unref()也没关系。只要保证它们的数量是成对的即可
扩展广播连接时回调 与普通广播的回调函数不同,扩展广播的connected()回调函数不是放在一个链表里,而是独立的。
这意味着,当你有多个扩展广播时,如果一个蓝牙主机连接其中一个扩展广播,那么只有那个扩展广播的connected()回调函数会执行,这就避免了混乱。
但是如果此时还有已注册的普通connected()回调,还是会把整个链表都调用一遍。
但是,所有的disconnect()回调函数仍然是处于同一个链表的。因此一定要在disconnected()回调函数里面判断一下当前的*conn句柄是不是建立连接的时候保存的那个,如果是,再进行相关释放动作。例如:
static struct bt_le_ext_adv *ext_adv1 = NULL ;static struct bt_conn *ext_conn1 = NULL ;static void connected1 (struct bt_le_ext_adv *extadv, struct bt_le_ext_adv_connected_info *info) { ext_conn1 = bt_conn_ref(info->conn); } struct bt_le_ext_adv_cb adv_ext_cb1 = { .connected = connected1 }; static void disconnected (struct bt_conn *conn, uint8_t reason) { if (conn != ext_conn1) { return ; } bt_conn_unref(ext_conn1); ext_conn1 = NULL ; } BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = NULL , .disconnected = disconnected, };
4. 多蓝牙地址分析 要让自己的蓝牙广播和Matter共存,并且两者的蓝牙设备地址不同。
Zephyr本身就支持多蓝牙地址,在Zephyr中它被称为蓝牙身份(Bluetooth Identity)。
蓝牙身份保存在一个数组中,数组长度是CONFIG_BT_ID_MAX,默认是1。
可以通过各种方式,在蓝牙协议栈初始化(bt_enable())之前或之后,创建各种蓝牙地址,并保存到蓝牙身份数组。
这一方面详细的介绍,请参考《NCS(Zephyr)中的蓝牙地址详解 》。
蓝牙广播地址切换 不论是普通广播还是扩展广播,在开启广播时都要使用bt_le_adv_param作为广播相关的配置参数。
struct bt_le_adv_param { uint8_t id; ... }
广播参数结构体的第一个成员就是id。大多数蓝牙例程的广播参数都使用0作为id,也就是使用蓝牙身份数组的第1个身份。
在开启广播的时候,我们可以修改设置广播参数中的id来选择自己用哪个蓝牙身份。
Matter使用的蓝牙身份 首先我们要明确几个要求:
Matter标准规定,Matter的配网广播必须使用Random Static Address ,且每次上电都要变化,从而确保隐私。
在v3.0.2/modules/lib/matter/src/platform/Zephyr/BLEAdvertisingArbiter.cpp中,可以看到每次广播开启时都设置了使用的id。
Matter创建的蓝牙身份 前面使用的sBtIid是在v3.0.2/modules/lib/matter/src/platform/Zephyr/BLEManagerImpl.cpp中赋值的:
在CHIP_ERROR BLEManagerImpl::_Init()函数中,有:
#ifdef CONFIG_BT_BONDABLE bt_addr_le_t idsAddr[CONFIG_BT_ID_MAX]; size_t idsCount = CONFIG_BT_ID_MAX; err = bt_enable (nullptr ); VerifyOrReturnError (err == 0 , MapErrorZephyr (err)); settings_load (); bt_id_get (idsAddr, &idsCount); err = InitRandomStaticAddress (idsCount > 1 , id); VerifyOrReturnError (err == 0 && id == kMatterBleIdentity, MapErrorZephyr (err)); #else err = InitRandomStaticAddress (false , id); VerifyOrReturnError (err == 0 && id == kMatterBleIdentity, MapErrorZephyr (err)); err = bt_enable (nullptr ); VerifyOrReturnError (err == 0 , MapErrorZephyr (err)); #endif
首先,Matter的蓝牙广播是不允许绑定的。Matter协议栈会根据系统的其他蓝牙广播是否支持绑定(CONFIG_BT_BONDABLE)来执行不同的初始化过程:
创建完毕后,在初始化Matter蓝牙广播仲裁器时传入参数,给sBtId赋值:
BLEAdvertisingArbiter::Init (static_cast <uint8_t >(id));
注意,不要直接打开或者关闭CONFIG_BT_BONDABLE。这只是一个中间的临时选项。
要开启蓝牙绑定功能:开启CONFIG_BT_SMP=y,它会自动select开启CONFIG_BT_BONDABLE。
5. 双蓝牙共存方案实例 基于前面对Matter源代码的分析,以及《NCS(Zephyr)中的蓝牙地址详解 》中对Zephyr初始化蓝牙地址的介绍。我们会发现双蓝牙地址共存的方案有很多影响因素:
使用前面介绍的“蓝牙仲裁器方案”或“多扩展广播方案”?
自己的蓝牙服务是否需要绑定?
自己的私有蓝牙广播是否需要使用公共地址(Public Address)?
自己的芯片是单核蓝牙SoC(如nRF54L15)还是双核蓝牙SoC(如nRF5340)?
这样一共是2×2×2×2,有16种可能性的组合,实在太多,本文无法一一分析介绍。
本文在这里列出2种比较常见的方案,读者可以举一反三,实现自己的多蓝牙地址方案。
案例一:“蓝牙仲裁器方案” + 需绑定 + 使用公共地址 + 单核SoC (1)设置两个地址 首先,设置:
CONFIG_BT_ID_MAX=2 CONFIG_BT_SMP=y CONFIG_BT_MAX_CONN=2
#include <zephyr/bluetooth/controller.h>
单核Soc,直接在Matter初始化之前设置公共地址到Controller层。
由于我们开启了蓝牙绑定CONFIG_BT_SMP=y功能,Matter协议栈里面会先执行bt_enable()和settings_load(),从而设置好id=0的蓝牙公共地址。
然后Matter会再创建自己用的id=1的随机静态地址。
(2)使用广播仲裁器编写自己的蓝牙广播c++代码 写了一个c++类AppBle,使用单例模式,部分代码:
BT_CONN_CB_DEFINE (conn_callbacks) = { .connected = AppBle::Connected, .disconnected = AppBle::Disconnected, .le_param_updated = AppBle::LeParamUpdated, .security_changed = AppBle::SecurityChanged, }; namespace {constexpr uint8_t kFlags = BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR;constexpr uint8_t k_ccs_uuid[] = {BT_COMMUNICATION_SERVICE};constexpr char k_adv_name[] = "My_ADV" ;} using namespace ::chip;using namespace ::chip::DeviceLayer;bool AppBle::Init (uint8_t priority) { mAdvData[0 ] = BT_DATA (BT_DATA_FLAGS, &kFlags, sizeof (kFlags)); mAdvData[1 ] = BT_DATA (BT_DATA_NAME_COMPLETE, k_adv_name, static_cast <uint8_t >(strlen (k_adv_name))); mScanRespData[0 ] = BT_DATA (BT_DATA_UUID128_ALL, k_ccs_uuid, sizeof (k_ccs_uuid)); mAdvertisingRequest.priority = priority; mAdvertisingRequest.options = (BT_LE_ADV_OPT_USE_IDENTITY | BT_LE_ADV_OPT_CONN); mAdvertisingRequest.minInterval = mAdvIntervalMin; mAdvertisingRequest.maxInterval = mAdvIntervalMax; mAdvertisingRequest.advertisingData = ::chip::Span <bt_data>(mAdvData); mAdvertisingRequest.scanResponseData = ::chip::Span <bt_data>(mScanRespData); mAdvertisingRequest.onStarted = [](int rc) { if (rc == 0 ) { Instance ().mIsStarted = true ; LOG_INF ("private advertising started." ); } else { LOG_ERR ("Failed to start Private BLE advertising: %d" , rc); } }; mAdvertisingRequest.onStopped = []() { Instance ().mIsStarted = false ; LOG_INF ("Private advertising stopped." ); }; } bool AppBle::StartServer () { Instance ().mConn = nullptr ; PlatformMgr ().LockChipStack (); CHIP_ERROR ret = BLEAdvertisingArbiter::InsertRequest (mAdvertisingRequest); PlatformMgr ().UnlockChipStack (); if (CHIP_NO_ERROR != ret) { LOG_ERR ("Could not start private advertising" ); return false ; } return true ; } void AppBle::StopServer () { if (!mIsStarted){ LOG_WRN ("private ADV was already stopped" ); return ; } PlatformMgr ().LockChipStack (); BLEAdvertisingArbiter::CancelRequest (mAdvertisingRequest); PlatformMgr ().UnlockChipStack (); } void AppBle::Connected (bt_conn *conn, uint8_t err) { if (Instance ().mIsStarted) { if (err || !conn) { LOG_ERR ("private ADV connection failed (err %u)" , err); return ; } LOG_INF ("## private ADV connected: %s ##" , LogAddress (conn)); LOG_INF ("Will disconnect if no data transfer after %ds" , AppBle::Instance ().mDisconnectTimeout); Instance ().mConn = bt_conn_ref (conn); bt_conn_set_security (conn, BT_SECURITY_L3); } } void AppBle::Disconnected (bt_conn *conn, uint8_t reason) { if (!Instance ().mIsStarted) { LOG_DBG ("private Adv not started" ); Instance ().mConn = nullptr ; return ; } if (conn != Instance ().mConn) { LOG_DBG ("Not private Connection" ); return ; } LOG_INF ("## private ADV disconnected: %s" , LogAddress (conn)); if (Instance ().mConn != nullptr ) { bt_conn_unref (Instance ().mConn); Instance ().mConn = nullptr ; } } void AppBle::SecurityChanged (bt_conn *conn, bt_security_t level, enum bt_security_err err) { if (!Instance ().mIsStarted){ LOG_DBG ("private Adv not started" ); return ; } if (conn != Instance ().mConn) { LOG_DBG ("Not private Connection" ); return ; } if (!err) { LOG_INF ("BT Security changed: %s level %u" , LogAddress (conn), level); } else { LOG_ERR ("BT Security failed: level %u err %d" , level, err); } } void AppBle::LeParamUpdated (struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout) { if (!Instance ().mIsStarted){ LOG_DBG ("private Adv not started" ); return ; } if (conn != Instance ().mConn) { LOG_DBG ("Not private Connection" ); return ; } LOG_INF ("private ADV conn interval changed with %s" , LogAddress (conn)); LOG_INF ("interval=%d, latency=%d, timeout=%d" , interval, latency, timeout); } char * AppBle::LogAddress (bt_conn *conn) {#if CONFIG_LOG static char addr[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str (bt_conn_get_dst (conn), addr, sizeof (addr)); return addr; #endif return NULL ; }
然后在AppTask.cpp中,注册自己的私有广播:
CHIP_ERROR AppTask::Init () { ReturnErrorOnFailure (Nrf::Matter::PrepareServer ()); ReturnErrorOnFailure (Nrf::Matter::RegisterEventHandler (AppTask::DefaultMatterEventHandler, 0 )); AppBle::Instance ().Init (1 ); Nrf::PostTask ([] { AppTask::Instance ().StartBLEAdvertisement (); }); Nrf::PostTask ([] { AppBle::Instance ().StartServer (); }); return Nrf::Matter::StartServer (); }
(3)修改广播仲裁器源码 在v3.0.2/modules/lib/matter/src/platform/Zephyr/BLEAdvertisingArbiter.h
修改广播仲裁器源码,当检测到广播参数包含我们设置的BT_LE_ADV_OPT_USE_IDENTITY时,就临时切换到id=0的蓝牙身份(公共地址)。其他情况下,就正常使用Matter自己的id=1的蓝牙身份。
蓝牙广播仲裁器方案的优势是,对SDK的修改最少,只有上面展示的部分。并且这种修改完全不影响正常的Matter例程
案例二:“扩展广播方案” + 需绑定 + 使用公共地址 + 双核SoC (1)设置两个地址 首先,设置:
CONFIG_BT_ID_MAX=2 CONFIG_BT_SMP=y
由于是双核SoC,必须在bt_enable()和settings_load()之间用HCI命令设置公共地址。
在v3.0.2/modules/lib/matter/src/platform/Zephyr/BLEManagerImpl.cpp中:
由于我们开启了蓝牙绑定CONFIG_BT_SMP=y功能,把《NCS(Zephyr)中的蓝牙地址详解 》介绍的HCI设置公共地址的函数插入到上图的位置。那么在settings_load()时就会自动创建id=0的公共地址蓝牙身份。在后续的InitRandomStaticAddress()中,会创建id=1的静态随机地址蓝牙身份,供Matter使用。
(2)把Matter广播改成扩展广播 先设置
CONFIG_BT_EXT_ADV=y CONFIG_BT_EXT_ADV_MAX_ADV_SET=2 CONFIG_BT_MAX_CONN=2
v3.0.2\modules\lib\matter\src\platform\Zephyr\BLEManagerImpl.h:
把connected回调函数从C++私有成员(private)改成C++公共成员(public)。
v3.0.2\modules\lib\matter\src\platform\Zephyr\BLEManagerImpl.cpp:
把connected回调函数取消注册,因为后面要在扩展广播里面注册:
先extern声明一下:
extern bt_conn *matter_conn;
然后在disconnected回调函数中,添加判断:当前断开事件是不是属于Matter广播的断开事件,如果不是就不管。
最后是v3.0.2\modules\lib\matter\src\platform\Zephyr\BLEAdvertisingArbiter.cpp,这个改动就太大了,直接贴全部代码:
#include "BLEAdvertisingArbiter.h" #include <lib/support/CodeUtils.h> #include <lib/support/logging/CHIPLogging.h> #include <platform/CHIPDeviceLayer.h> #include <platform/Zephyr/BLEManagerImpl.h> #include <system/SystemError.h> #include <zephyr/bluetooth/conn.h> bt_le_ext_adv *matter_ext_adv = NULL ; struct bt_conn *matter_conn = NULL ;static void matter_ext_adv_connected_cb (struct bt_le_ext_adv *extadv, struct bt_le_ext_adv_connected_info *info) { __ASSERT(extadv == matter_ext_adv, "Matter adv wrong" ); printk ("## Matter ext adv connected %p ##\n" , info->conn); matter_conn = info->conn; chip::DeviceLayer::Internal::BLEManagerImpl::HandleConnect (matter_conn, 0 ); } struct bt_le_ext_adv_cb adv_matter_cb = { .connected = matter_ext_adv_connected_cb }; namespace chip {namespace DeviceLayer {namespace BLEAdvertisingArbiter {namespace {sys_slist_t sRequests;bool sIsInitialized = false ;bool sWasDisconnection = false ;uint8_t sBtId = 0 ;const BLEAdvertisingArbiter::Request & ToRequest (const sys_snode_t * node) { return *static_cast <const BLEAdvertisingArbiter::Request *>(node); } void NotifyAdvertisingStopped (const sys_snode_t * node) { VerifyOrReturn (node); const Request & request = ToRequest (node); if (request.onStopped != nullptr ) { request.onStopped (); } } CHIP_ERROR RestartAdvertising () { int err; static bool first_time = true ; if (first_time) { first_time = false ; bt_le_adv_param first_param = BT_LE_ADV_PARAM_INIT (BT_LE_ADV_OPT_CONN, BT_GAP_ADV_FAST_INT_MIN_1, BT_GAP_ADV_FAST_INT_MAX_1,NULL ); first_param.id = sBtId; err = bt_le_ext_adv_create (&first_param, &adv_matter_cb, &matter_ext_adv); if (err){ printk ("===Failed to create a connectable advertising set (err %d)\n" , err); } } ReturnErrorOnFailure (System::MapErrorZephyr (bt_le_ext_adv_stop (matter_ext_adv))); ReturnErrorCodeIf (sys_slist_is_empty (&sRequests), CHIP_NO_ERROR); const Request & top = ToRequest (sys_slist_peek_head (&sRequests)); bt_le_adv_param params = BT_LE_ADV_PARAM_INIT (top.options, top.minInterval, top.maxInterval, nullptr ); params.id = sBtId; err = bt_le_ext_adv_set_data (matter_ext_adv, top.advertisingData.data (), top.advertisingData.size (), top.scanResponseData.data (), top.scanResponseData.size ()); if (err) { printk ("===Failed to set advertising data (err %d)\n" , err); } err = bt_le_ext_adv_update_param (matter_ext_adv, ¶ms); if (err) { printk ("===Update Matter adv param (err %d)\n" , err); } const int result = bt_le_ext_adv_start (matter_ext_adv, NULL ); if (err) { printk ("===Failed to start advertising (err %d)\n" , err); } if (result < 0 ) { ChipLogProgress (DeviceLayer, "Advertising start failed, will retry once connection is released" ); } if (top.onStarted != nullptr ) { top.onStarted (result); } return System::MapErrorZephyr (result); } BT_CONN_CB_DEFINE (conn_callbacks) = { .disconnected = [](struct bt_conn * conn, uint8_t reason) { sWasDisconnection = true ; }, .recycled = []() { if (sWasDisconnection) { SystemLayer ().ScheduleLambda ([] { if (!sys_slist_is_empty (&sRequests)) { RestartAdvertising (); } }); sWasDisconnection = false ; } }, }; } CHIP_ERROR Init (uint8_t btId) { if (sIsInitialized) { return CHIP_ERROR_INCORRECT_STATE; } sBtId = btId; sIsInitialized = true ; return CHIP_NO_ERROR; } CHIP_ERROR InsertRequest (Request & request) { if (!sIsInitialized) { return CHIP_ERROR_INCORRECT_STATE; } CancelRequest (request); sys_snode_t * prev = nullptr ; sys_snode_t * node = nullptr ; SYS_SLIST_FOR_EACH_NODE (&sRequests, node) { if (request.priority < ToRequest (node).priority) { break ; } prev = node; } if (prev == nullptr ) { NotifyAdvertisingStopped (sys_slist_peek_head (&sRequests)); sys_slist_prepend (&sRequests, &request); } else { sys_slist_insert (&sRequests, prev, &request); } if (sys_slist_peek_head (&sRequests) == &request) { return RestartAdvertising (); } return CHIP_NO_ERROR; } void CancelRequest (Request & request) { if (!sIsInitialized) { return ; } const bool isTopPriority = (sys_slist_peek_head (&sRequests) == &request); VerifyOrReturn (sys_slist_find_and_remove (&sRequests, &request)); if (isTopPriority) { RestartAdvertising (); } } } } }
主要思路:
一开始要用bt_le_ext_adv_create创建扩展广播
广播停止的API要改成扩展广播停止API
广播启动API要改成一系列扩展广播的数据更新、参数更新、扩展广播启动API。注意到广播参数中的id用的是sBtId,也就是1.
要在扩展广播专属的connected回调函数中,调用前一步我们取消注册的Matter自己的HandleConnect函数。这也是为什么前面我们要把它改成C++公共成员。
(3)再添加自己的扩展广播 前一步修改已经让Matter可以运行了。再在自己的文件里等待蓝牙初始化完成,然后创建自己的广播即可:
#include <zephyr/bluetooth/bluetooth.h> #include <zephyr/bluetooth/conn.h> bt_le_ext_adv *my_ext_adv = NULL ; struct bt_conn *my_conn = NULL ;static uint8_t mfg_data[] = { 0xFF , 0xFF , 0x30 ,0x31 ,0x32 ,0x33 };static const struct bt_data ad[] = { BT_DATA_BYTES (BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), BT_DATA (BT_DATA_NAME_COMPLETE, "Common" , sizeof ("Common" ) - 1 ), BT_DATA (BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof (mfg_data)), }; void disconnected (struct bt_conn *conn, uint8_t reason) { if (conn != my_conn) { return ; } printk ("## My private disconnected (reason 0x%02x) ##\n" , reason); if (my_conn) { bt_conn_unref (my_conn); my_conn = NULL ; } } BT_CONN_CB_DEFINE (conn_callbacks) = { .connected = NULL , .disconnected = disconnected, .security_changed = NULL , }; static void my_ext_adv_connected_cb (struct bt_le_ext_adv *extadv, struct bt_le_ext_adv_connected_info *info) { __ASSERT(extadv == my_ext_adv, "My private adv wrong" ); printk ("## My private ext adv connected %p ##\n" , info->conn); my_conn = bt_conn_ref (info->conn); } struct bt_le_ext_adv_cb my_ext_adv_cb = { .connected = my_ext_adv_connected_cb }; int my_private_ble_thread_entry (void ) { while (!bt_is_ready ()) { k_sleep (K_MSEC (100 )); } struct bt_le_adv_param adv_param0 = { .id = 0 , .sid = 0U , .secondary_max_skip = 0U , .options = (BT_LE_ADV_OPT_USE_IDENTITY | BT_LE_ADV_OPT_CONN), .interval_min = 600 , .interval_max = 800 , .peer = NULL , }; int err; err = bt_le_ext_adv_create (&adv_param0, &my_ext_adv_cb, &my_ext_adv); if (err) { printk ("Create my ext adv (err %d)\n" , err); return err; } err = bt_le_ext_adv_set_data (my_ext_adv, ad , ARRAY_SIZE (ad), NULL , 0 ); if (err) { printk ("Set my ext adv data (err %d)\n" , err); return err; } err = bt_le_ext_adv_start (my_ext_adv, BT_LE_EXT_ADV_START_DEFAULT); if (err) { printk ("Start common adv (err %d)\n" , err); return err; } printk ("## My private ext adv started ##\n" ); return 0 ; } K_THREAD_DEFINE (my_private_ble_thread_id, 4096 , my_private_ble_thread_entry, NULL , NULL , NULL , 7 , 0 , 0 );
注意自己的蓝牙服务和connected回调函数里面,仅需处理conn == my_conn的情况即可。
可以发现这种方式对SDK改动较大,编译其他Matter工程时要注意