Zephyr BLE 协议栈自动发现 CCC
自动发现 CCC 是指在订阅某一个特征值的通知或指示时,可以只用特征值的 handle,不用指定 CCC 的 handle,协议栈会自动执行一个发现过程,帮我们查找的 CCC 的 handle。
那么如何使用呢?
我们先来看看正常情况下如何订阅一个特征值的通知:
static struct bt_gatt_subscribe_params subscribe_params; // 静态变量 or 全局变量
subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.notify = notify_function_cb; // 通知回调函数
subscribe_params.value_handle = value_handle; // 特征值 handle
subscribe_params.ccc_handle = ccc_handle; // 特征值 CCC 的 handle,在之前的发现过程中需要保存
bt_gatt_subscribe(conn, &subscribe_params);
这样成功订阅之后,每次 GATT Server 发生来一个通知,协议栈都会回调 nofity_function_cb()
回调函数。 但这样我们需要在 GATT 发现过程中手动查找 CCC 的 handle 并保存,稍微麻烦一点。
现在我们来看看如何用协议栈的自动发现 CCC 。
先来看看订阅参数结构体:
struct bt_gatt_subscribe_params {
/** Notification value callback */
bt_gatt_notify_func_t notify;
/** Subscribe CCC write request response callback */
bt_gatt_write_func_t write;
/** Subscribe value handle */
uint16_t value_handle;
/** Subscribe CCC handle */
uint16_t ccc_handle;
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
/** Subscribe End handle (for automatic discovery) */
uint16_t end_handle;
/** Discover parameters used when ccc_handle = 0 */
struct bt_gatt_discover_params *disc_params;
#endif /* CONFIG_BT_GATT_AUTO_DISCOVER_CCC */
/** Subscribe value */
uint16_t value;
/** Subscription flags */
ATOMIC_DEFINE(flags, BT_GATT_SUBSCRIBE_NUM_FLAGS);
sys_snode_t node;
};
使能自动发现 CCC CONFIG_BT_GATT_AUTO_DISCOVER_CCC
后,该结构体多了两个参数 end_handle
和 disc_params
。
-
end_handle
自动发现 CCC 过程中的最后一个 handle 值。 -
disc_params
GATT 发现参数指针,需要我们手动将其指向一个正确结构体(静态结构体)。
于是我们现在订阅一个特征值的通知过程如下:
static struct bt_gatt_subscribe_params subscribe_params; // 静态变量 or 全局变量
static struct bt_gatt_discover_params discovery_params; // 静态变量 or 全局变量
subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.notify = notify_function_cb; // 通知回调函数
subscribe_params.value_handle = value_handle; // 特征值 handle
subscribe_params.ccc_handle = 0; // ccc_handle 为 0 代表执行自动发现CCC过程
subscribe_params.end_handle = 0xFFFF;
subscribe_params.disc_params = &discovery_params;
bt_gatt_subscribe(conn, &subscribe_params);
这样就能够成功订阅特征值的通知了。
关键点如下:
配置内核 CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y
,然后在bt_gatt_subscribe_params
结构体里,
-
ccc_handle
值要为 0 。 -
disc_params
需要指向一个已有的bt_gatt_discover_params
结构体。 -
end_handle
可以为 0xFFFF,用于自动发现 CCC 过程中的最后一个 handle 。
注意:使用自动发现 CCC 对于 end_handle
的选择需要十分注意,否则就会包含隐藏的 BUG 。自动发现 CCC 的过程找到的是 之间的第一个 CCC 的 handle 。这会导致这样的问题,若该范围内有一个 CCC,但是该特征值本身是没有 CCC 的,但是自动发现 CCC 的过程也会找到它,这就导致了错误。
因此在使用自动发现 CCC 订阅通知时,需要提前知道该特征值是否具有通知属性。如果具有通知属性,则可以直接将 end_handle
设置为 0xFFFF,不会产生任何隐藏 BUG 。
原理浅析
在整个工程里全局搜索 CONFIG_BT_GATT_AUTO_DISCOVER_CCC
就能够看到整个实现了。
核心部分为:
int bt_gatt_subscribe(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params)
{
......
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
if (!params->ccc_handle) {
return gatt_ccc_discover(conn, params);
}
#endif
err = gatt_write_ccc(conn, params);
if (err) {
gatt_sub_remove(conn, sub, NULL, NULL);
return err;
}
}
......
return 0;
}
可以看到,在 bt_gatt_subscribe()
函数里,若开启了 CONFIG_BT_GATT_AUTO_DISCOVER_CCC
宏,并且传入参数的 ccc_handle
值为 0,则会先执行一个 GATT 发现过程,将发现得到的 CCC handle 存入参数 params
里,最后调用 gatt_write_ccc()
写服务端的 CCC 使能通知。