目录
nimble 蓝牙开发API
设置蓝牙设备地址 BD_ADDR
蓝牙设备地址存在两种类型:公共地址和随机地址。蓝牙地址都是 48 位,6字节长。
随机地址由细分为3种类型:不可解析随机地址,可解析随机地址,静态随机地址。
在开始任意数据传输之前,都必须设置蓝牙设备地址,或公共地址,或随机地址,或者公共地址和随机地址都设置。
蓝牙公共设备地址通过系统配置宏 MYNEWT_VAL_BLE_PUBLIC_DEV_ADDR 设置,在 host 和 controller 同步后会用宏定义中设置的公共地址配置 host 的公共标识地址。
蓝牙随机设备地址的设置可以使用如下接口:
int ble_hs_id_set_rnd(const uint8_t *rnd_addr);
//rnd_addr : 6 字节随机设备地址,注意设备地址的字节序。
//返回值:随机地址设置成功返回0,失败返回错误码。
//可以先获取随机地址
int ble_hs_id_gen_rnd(int nrpa, ble_addr_t *out_addr);
//nrpa 随机地址类型 0静态 1私人
//out_addr 生成的地址将被写入
//例如
ble_addr_t addr;
ble_hs_id_gen_rnd(1, &addr);
ble_hs_id_set_rnd(addr.val);
在启动任何蓝牙流程之前,可以使用下面的函数来确定当前最合适使用的蓝牙地址,优先检索随机地址(如果随机设备地址已经设置的话)。
//确定用于自动地址类型解析的最佳地址类型。
int ble_hs_id_infer_auto(int privacy, uint8_t *out_addr_type);
//privacy : 0/1,表示是否使用私有地址。
//out_addr_type : 输出合适的地址类型,供蓝牙 GAP 流程使用。
//返回值:成功返回0,BLE_HS_ENOADDR 设备没有合适的地址,BLE主机核心代码出错返回其他。
//如果函数执行成功,最佳地址类型的计算如下,即 out_addr_type 为:
//privacy=1 如果有随机静态地址则0x03否则0x02
//privacy=0 如果有随机静态地址则0x00否则0x01
#define BLE_OWN_ADDR_PUBLIC (0x00)
#define BLE_OWN_ADDR_RANDOM (0x01)
#define BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT (0x02)
#define BLE_OWN_ADDR_RPA_RANDOM_DEFAULT (0x03)
//可以使用以下函数检索设备的一个身份地址。设备可以有两个身份地址:一个是公开的,一个是随机的。id_addr_type参数指定要检索哪个。
int ble_hs_id_copy_addr(uint8_t id_addr_type, uint8_t *out_id_addr,
int *out_is_nrpa);
//例如:
static uint8_t ble_addr_type;
ble_hs_id_infer_auto(0, &ble_addr_type);
uint8_t addr_val[6] = {0};
ble_hs_id_copy_addr(ble_addr_type, addr_val, NULL);
GAP 应用
GAP 应用用于 BLE 设备发送广播、扫描广播与建立连接。
注意,GAP 应用不包含连接建立之后的数据交换。
其中,BLE 在 GAP 应用中可以扮演 4 种角色:
角色 | 应用特性 |
---|---|
Broadcaster | 用于发送不可链接广播,并对 Observer 发送的扫描请求做出响应,不能与 Observer 建立连接 |
Observer | 接收 Broadcaster 发送的广播,可以选择向 Broadcaster 发送扫描请求,并接收对应的扫描响应 |
Peripheral | 用于发送可连接广播,并根据接收到连接请求与 Central 建立连接 |
Central | 接收可连接广播,并向 Peripheral 发送连接请求,并建立连接 |
广播类型
对于常用的传统广播,广播类型共有 4 种:
广播类型 | 描述 |
---|---|
ADV_IND | 可连接、可扫描、不定向广播 |
ADV_DIRECT_IND | 可连接、不可扫描、定向广播 |
ADV_SCAN_IND | 不可连接、可扫描、不定向广播 |
ADV_NONCONN_IND | 不可连接、不可扫描、不定向广播 |
在下面的《广播类型的确定》一节会讲解,广播类型的确定方法,广播类型由 struct ble_gap_adv_params 结构体的 conn_mode 参数和 disc_mode 参数共同确定。
GAP 事件
GAP 事件包含了 BLE 设备在数据交换过程中所有可能产生的异步事件,换句话说,即使是 GATT 应用也会产生 GAP 事件。
nimble Host 中所有可能的 GAP 事件如下:
GAP 事件枚举值 | GAP 事件宏 | GAP 事件描述 |
---|---|---|
0 | BLE_GAP_EVENT_CONNECT | 连接尝试事件,可能连接成功,也可能连接失败 |
1 | BLE_GAP_EVENT_DISCONNECT | 连接终止事件 |
2 | RFU | Reserved |
3 | BLE_GAP_EVENT_CONN_UPDATE | 更新连接参数完成事件 |
4 | BLE_GAP_EVENT_CONN_UPDATE_REQ | 表示连接的对端设备尝试更新连接参数事件 |
5 | BLE_GAP_EVENT_L2CAP_UPDATE_REQ | 表示连接的对端设备尝试更新连接参数事件 |
6 | BLE_GAP_EVENT_TERM_FAILURE | 连接终止失败事件 |
7 | BLE_GAP_EVENT_DISC | 广播报告事件,表示在发现流程中扫描到广播报文 |
8 | BLE_GAP_EVENT_DISC_COMPLETE | 发现流程结束事件 |
9 | BLE_GAP_EVENT_ADV_COMPLETE | 广播流程结束事件 |
10 | BLE_GAP_EVENT_ENC_CHANGE | 连接加密状态更改事件 |
11 | BLE_GAP_EVENT_PASSKEY_ACTION | 配对过程中的密钥查询完成事件 |
12 | BLE_GAP_EVENT_NOTIFY_RX | 接收一个属性的 notification / indication 事件 |
13 | BLE_GAP_EVENT_NOTIFY_TX | 发送一个属性的 notifycation / indication 完成事件 |
14 | BLE_GAP_EVENT_SUBSCRIBE | 订阅事件,表示对端设备(GATT 客户端)修改了 CCCD (客户端特征配置描述符)的值,使 CCCD 中的 notify / indicate 的标志位发生改变 |
15 | BLE_GAP_EVENT_MTU | 表示 MTU 交换流程完成事件 |
16 | BLE_GAP_EVENT_IDENTITY_RESOLVED | 配对成功后产生的事件,标识地址成功解析事件? |
17 | BLE_GAP_EVENT_REPEAT_PAIRING | 重复配对事件 |
18 | BLE_GAP_EVENT_PHY_UPDATE_COMPLETE | PHY 层更新事件 |
19 | BLE_GAP_EVENT_EXT_DISC | 在发现流程中收到一个扩展广播报告事件 |
20 | BLE_GAP_EVENT_PERIODIC_SYNC | 发现流程中建立周期同步广播链路事件 |
21 | BLE_GAP_EVENT_PERIODIC_REPORT | 周期广播记录事件 |
22 | BLE_GAP_EVENT_PERIODIC_SYNC_LOST | 周期同步广播链路丢失事件 |
23 | BLE_GAP_EVENT_SCAN_REQ_RCVD | 扩展广播下的扫描请求事件 |
24 | BLE_GAP_EVENT_PERIODIC_TRANSFER | 收到周期同步广播传输事件 |
GAP 事件回调函数
GAP 事件回调函数类型如下:
typedef int ble_gap_event_fn(struct ble_gap_event *event, void *arg);
//struct ble_gap_event 类型参数,唯一的代表了当前产生的 GAP 事件,包含事件类型以及事件相关信息。
//arg, 这是自定义的 GAP 回调函数形参,启动 GAP 流程时传入。
//返回值:处理成功返回 0,处理失败返回非 0 错误码。
GAP 事件结构体:struct ble_gap_event
struct ble_gap_event
结构体为 GAP 事件结构体,可以唯一的代表一个已发生的 GAP 事件,其数据结构如下所示:
struct ble_gap_event {
/* BLE_GAP_EVENT codes. 事件类型 */
uint8_t type;
/**
* A discriminated union containing additional details concerning the GAP
* event. The 'type' field indicates which member of the union is valid.
type 字段 指向有效的成员
*/
union {
/* 存储各类事件的事件信息结构,太长了,省略不写*/
};
};
struct ble_gap_event 事件结构体主要由两个参数组合而成:
- type, 表示 GAP 事件类型。
- union 联合体,存储各类 GAP 事件的事件相关信息数据结构参数,具体参数由 type 决定。
GAP 事件类型 type 和 GAP 事件数据结构对象的映射关系如下:
GAP 事件类型 type | type 对应的 GAP 事件数据结构参数 |
---|---|
BLE_GAP_EVENT_CONNECT | connect |
BLE_GAP_EVENT_DISCONNECT | disconnect |
BLE_GAP_EVENT_DISC | disc |
BLE_GAP_EVENT_EXT_DISC | ext_disc |
BLE_GAP_EVENT_DISC_COMPLETE | disc_complete |
BLE_GAP_EVENT_ADV_COMPLETE | adv_complete |
BLE_GAP_EVENT_CONN_UPDATE | conn_update |
BLE_GAP_EVENT_L2CAP_UPDATE_REQ / BLE_GAP_EVENT_CONN_UPDATE_REQ | conn_update_req |
BLE_GAP_EVENT_TERM_FAILURE | term_failure |
BLE_GAP_EVENT_ENC_CHANGE | enc_change |
BLE_GAP_EVENT_PASSKEY_ACTION | passkey |
BLE_GAP_EVENT_NOTIFY_RX | notify_rx |
BLE_GAP_EVENT_NOTIFY_TX | notify_tx |
BLE_GAP_EVENT_SUBSCRIBE | subscribe |
BLE_GAP_EVENT_MTU | mtu |
BLE_GAP_EVENT_IDENTITY_RESOLVED | identity_resolved |
BLE_GAP_EVENT_REPEAT_PAIRING | repeat_pairing |
BLE_GAP_EVENT_PHY_UPDATE_COMPLETE | phy_updated |
BLE_GAP_EVENT_PERIODIC_SYNC | periodic_sync |
BLE_GAP_EVENT_PERIODIC_REPORT | periodic_report |
BLE_GAP_EVENT_PERIODIC_SYNC_LOST | periodic_sync_lost |
BLE_GAP_EVENT_SCAN_REQ_RCVD | scan_req_rcvd |
BLE_GAP_EVENT_PERIODIC_TRANSFER | periodic_transfer |
发送蓝牙广播 - boardcaster
boardcaster 流程:
广告流程抽象逻辑图如下:
API 执行流程图如下:
发送蓝牙广播相关的 API
这些 API 原型在 nimble 协议栈目录下的 nimble/host/include/host/ble_gap.h 文件中。
设置广播数据
下面的函数用于设置标准的、符合规范的广播数据 AD structure:
int ble_gap_adv_set_fields(const struct ble_hs_adv_fields *adv_fields);
//adv_fields :这个结构体包含所有 AD structure 数据类型抽象,因为蓝牙5.2规范 对广播数据提出了格式和类型要求,所以我们只能在广播数据中设置指定的参数类型,传输有限的数据。注意:蓝牙 5.2 规范要求广播数据不能超过 31 个字节。不使用的参数请设置为 NULL。
//返回值 : 成功返回 0, 失败返回非 0 错误码。
struct ble_hs_adv_fields
结构体对标准蓝牙广播数据的抽象如下:
struct ble_hs_adv_fields {
/*** 0x01 - Flags.
#define BLE_HS_ADV_F_DISC_LTD 0x01
#define BLE_HS_ADV_F_DISC_GEN 0x02
#define BLE_HS_ADV_F_BREDR_UNSUP 0x04 BLE-only (BR/EDR unsupported) */
uint8_t flags;
/*** 0x02,0x03 - 16-bit service class UUIDs. */
ble_uuid16_t *uuids16;
uint8_t num_uuids16;
unsigned uuids16_is_complete:1;
/*** 0x04,0x05 - 32-bit service class UUIDs. */
ble_uuid32_t *uuids32;
uint8_t num_uuids32;
unsigned uuids32_is_complete:1;
/*** 0x06,0x07 - 128-bit service class UUIDs. */
ble_uuid128_t *uuids128;
uint8_t num_uuids128;
unsigned uuids128_is_complete:1;
/*** 0x08,0x09 - Local name. */
uint8_t *name;
uint8_t name_len;
unsigned name_is_complete:1;
/*** 0x0a - Tx power level. */
int8_t tx_pwr_lvl;
unsigned tx_pwr_lvl_is_present:1;
/*** 0x0d - Slave connection interval range. */
uint8_t *slave_itvl_range;
/*** 0x16 - Service data - 16-bit UUID. */
uint8_t *svc_data_uuid16;
uint8_t svc_data_uuid16_len;
/*** 0x17 - Public target address. */
uint8_t *public_tgt_addr;
uint8_t num_public_tgt_addrs;
/*** 0x19 - Appearance. */
uint16_t appearance;
unsigned appearance_is_present:1;
/*** 0x1a - Advertising interval. */
uint16_t adv_itvl;
unsigned adv_itvl_is_present:1;
/*** 0x20 - Service data - 32-bit UUID. */
uint8_t *svc_data_uuid32;
uint8_t svc_data_uuid32_len;
/*** 0x21 - Service data - 128-bit UUID. */
uint8_t *svc_data_uuid128;
uint8_t svc_data_uuid128_len;
/*** 0x24 - URI. */
uint8_t *uri;
uint8_t uri_len;
/*** 0xff - Manufacturer specific data. */
uint8_t *mfg_data;
uint8_t mfg_data_len;
};
该结构体参数说明如下:
参数 | 描述 |
---|---|
flag | Flag类型AD Type,1字节,包含8个布尔位 |
uuids16,num_uuids16,uuids16_is_complete:1 | 完整的/不完整的16位服务UUID列表 |
uuids32,num_uuids32,uuids32_is_complete:1 | 完整的/不完整的32位服务UUID列表 |
uuids128,num_uuids128,uuids128_is_complete:1 | 完整的/不完整的128位服务UUID列表 |
name,name_len,name_is_complete:1 | 完整的/不完整的设备名 |
tx_pwr_lvl,tx_pwr_lvl_is_present:1 | 指示发送数据包的功率等级 |
slave_itvl_range | 指示当前设备的可接收连接事件间隔范围值 |
svc_data_uuid16,svc_data_uuid16_len | 16位服务UUID以及相关的服务数据 |
public_tgt_addr,num_public_tgt_addrs | 用于指示该广播一个或多个的目的地址 |
appearance,appearance_is_present:1 | 指示当前蓝牙设备的外观,一般跟一个图标相关联,比如手机、电脑、手表之类的。 |
adv_itvl,adv_itvl_is_present:1 | 指示当前设备的广播时间间隔,单位:0.625ms |
svc_data_uuid32,svc_data_uuid32_len | 32位服务UUID以及相关的服务数据 |
svc_data_uuid128,svc_data_uuid128_len | 128位服务UUID以及相关的服务数据 |
uri,uri_len | URI |
mfg_data,mfg_data_len | 制造商自定义数据 |
标准蓝牙广播数据格式定义在蓝牙 5.2 规范的 卷3,Part C,第 11 节;标准广播数据类型格式定义在 CSS 的 Part A,第 1 节。
如果按照标准格式,那么我们只能设置有限类型的数据。
同样的,nimble 也提供了一个接口用于设置自由格式的广播数据:
int ble_gap_adv_set_data(const uint8_t *data, int data_len);
//data : 这是自定义数据的地址。
//data_len : 自定义数据的长度,同样需要注意的是,广播数据长度不能超过 31 字节。
//返回值 :成功返回 0,失败返回非 0 错误码。
这个接口允许我们自由设置广播负载数据,也就是说可以不符合规范。
广播 PDU (Protocol Data Unit , 可以理解为蓝牙协议帧) 负载数据应在广播启动之前设置。
设置扫描响应数据的
扫描响应负载数据的格式规范跟上面的广播负载数据的格式规范是一样的。而且 扫描响应也是广播的一种 。所以扫描响应数据和广播数据的类型都是 AD Structure。
下面的函数用于设置标准的规范的扫描响应数据:
int ble_gap_adv_rsp_set_fields(const struct ble_hs_adv_fields *rsp_fields);
//rsp_fields : 扫描响应数据对象,长度不超过 31 字节,数据类型跟广播数据一样。
//返回值:成功返回 0,失败返回非 0 错误码。
同样的,nimble 也提供了一个可以自由设置响应扫描负载的 API:
int ble_gap_adv_rsp_set_data(const uint8_t *data, int data_len);
//data:扫描响应负载存储地址;
//data_len:扫描响应负载长度,注意,长度不能超过 31 个字节。
扫描响应 PDU 负载数据应该广播启动之前设置。
启动广播发送
下面的函数用于启动广播发送,并注册 GAP 事件回调函数:
int ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr,
int32_t duration_ms,
const struct ble_gap_adv_params *adv_params,
ble_gap_event_fn *cb, void *cb_arg);
//own_addr_type : 本机蓝牙设备地址类型,可以由ble_hs_id_infer_auto() 函数确认。设置广播数据包中使用的蓝牙设备地址类型,如果使用了公共地址,则设置为公共地址类型,否则请设置为随机地址类型
//direct_addr : 如果使用定向广播,则设置为目的设备地址,否则为 NULL。
//duration_ms : 广播流程时间, 如果设置为宏 BLE_HS_FOREVER 则永远不会超时,否则会产生一个 BLE_GAP_EVENT_ADV_COMPLETE 广播完成事件。
//adv_params : struct ble_gap_adv_params 对象,广播参数,设置广播的类型,广播间隔等参数。
//cb :GAP 事件回调函数,由应用层注册的回调函数,用于处理广播事件的事件,比如 BLE_GAP_EVENT_ADV_COMPLETE 事件。
//cb_arg : 事件回调函数参数。
//返回值 :成功返回 0,失败返回非 0 错误码
struct ble_gap_adv_params
结构体为广播流程抽象,用于设置广播流程参数:
struct ble_gap_adv_params{
uint8_t conn_mode
/*
连接模式。可以是以下常量之一:
BLE_GAP_CONN_MODE_NON(不可连接; 3.C.9.3.2)。
BLE_GAP_CONN_MODE_DIR(可直接连接; 3.C.9.3.3)。
BLE_GAP_CONN_MODE_UND(无定向连接; 3.C.9.3.4)。
*/
uint8_t disc_mode
/*
发现模式。可以是以下常量之一:
BLE_GAP_DISC_MODE_NON(不可发现; 3.C.9.2.2)。
BLE_GAP_DISC_MODE_LTD(有限可发现; 3.C.9.2.3)。
BLE_GAP_DISC_MODE_GEN(一般可发现; 3.C.9.2.4)。
*/
uint16_t itvl_min
//最小广播间隔(如果0表示使用默认值,单位:0.625ms)。
uint16_t itvl_max
//最大广播间隔(如果0表示使用默认值,单位:0.625ms)。
uint8_t channel_map
//广播信道掩码位,如果为0,则使用默认默认值。
uint8_t filter_policy
//广播过滤器策略。如何除开扫描请求和连接请求。
uint8_t high_duty_cycle
//是否使用高占空比模式,即快速广播模式。
}
广播类型的确定
广播类型的确定方法如下:
广播类型 | conn_mode 参数 | disc_mode 参数 | |
---|---|---|---|
ADV_IND | 可连接、可扫描、不定向广播 | BLE_GAP_CONN_MODE_UND | BLE_GAP_DISC_MODE_GEN |
ADV_DIRECT_IND | 可连接、不可扫描、定向广播 | BLE_GAP_CONN_MODE_DIR | BLE_GAP_DISC_MODE_NON |
ADV_NONCONN_IND | 不可连接、可扫描、不定向广播 | BLE_GAP_CONN_MODE_NON | BLE_GAP_DISC_MODE_NON |
ADV_SCAN_IND | 不可连接、不可扫描、不定向广播 | BLE_GAP_CONN_MODE_NON | BLE_GAP_DISC_MODE_GEN |
发送广播产生的事件及 GAP 事件回调函数
因为 boardcaster 发送的是不可连接广播, 在设置了广播周期之后,则会触发广播完成事件。
注意,设备发送扫描响应 PDU 不会触发事件,直接发送广播启动之前设置的扫描响应数据,如果应用层没有设置扫描响应数据,那么扫描响应 PDU 的负载位域长度为0。
所以,boardcaster 广播启动时注册的回调函数只需要处理广播周期结束之后的广播完成事件即可。
boardcaster 流程分析
此部分需要结合 nimble 应用代码。
所有的 nimble 蓝牙操作之只能在 host 与 controller 同步之后调用,即在 ble_hs_cfg.sync_cb
回调中操作。
在 ble_gap_adv_set_fields()
中,host 通过 HCI 发送设置广告 PDU 负载数据的命令(OpCode = 0x2008), 来设置广告 PDU 负载数据。
在 ble_gap_adv_start()
中,host 协议栈会根据自定义的参数来发送相对应的广告流程参数设置 HCI 命令,并通过广告流程启动 HCI 命令来启动广告流程。
最后,host 协议栈会启动 host 的软件定时器来对广告周期进行定时。如果超时则会在 host 软件定时器的回调函数中发送停止广告流程 HCI 命令,并调用应用层定义的回调函数来向应用层报告 因广告周期超时而产生的广告完成事件 。
扫描蓝牙广播 - observer
主动扫描和被动扫描
observer存在两个模式, 主动扫描和被动扫描 ,这两种模式的区别在于:主动扫描模式下,observer的 controller 在收到一个可扫描的广播帧之后会发送扫描请求帧 SCAN_REQ,然后尝试接收 advertiser 发送的扫描响应 SCAN_RSP 。
扫描流程
扫描流程抽象逻辑图:
API 执行流程图如下:
扫描蓝牙广播相关的 API
这些 API 原型在 nimble 协议栈目录下的 /nimble/host/include/host/ble_gap.h 文件中。
启动广播接收
下面的 API 用于启动蓝牙的发现流程(即扫描):
int ble_gap_disc(uint8_t own_addr_type, int32_t duration_ms,
const struct ble_gap_disc_params *disc_params,
ble_gap_event_fn *cb, void *cb_arg);
//own_addr_typr : 发送 SCAN_REQ 使用的地址类型,如果是被动扫描,则不使用,因为被动扫描不会发送 SCAN_REQ。
//duration_ms : host 侧设置的发现周期(扫描周期),单位 ms。
//disc_params : 发现流程参数,即扫描参数,比如扫描窗口、扫描间隔与扫描屏蔽策略等。
//cb : GAP 事件回调函数。发现流程中会产生两种事件:接收到广播后产生广告报告事件;发现周期结束后的发现完成事件。
//cd_arg : GAP 事件回调函数参数。
//返回值:0或其他非 0 错误码。
struct ble_gap_disc_params
为发现流程参数结构体,其数据结构如下:
struct ble_gap_disc_params {
//扫描间隔,单位 0.625ms
uint16_t itvl;
//扫描窗口宽度, 要小于扫描间隔 单位 0.625ms
uint16_t window;
//扫描屏蔽过滤策略
uint8_t filter_policy;
//是否使用限制扫描模式
uint8_t limited:1;
//是否使用被动扫描,默认主动
uint8_t passive:1;
//是否过滤重复广播
uint8_t filter_duplicates:1;
};
解析广播负载
如下函数用于解析广播 PDU 负载:
int ble_hs_adv_parse_fields(struct ble_hs_adv_fields *adv_fields,
const uint8_t *src, uint8_t src_len);
//adv_fields : 广播负载解析后填充的标准广播数据对象。
//src:广播 PDU 负载源数据。
//src_len : 广播 PDU 负载长度。
//返回值: 0 或者其他非 0 错误码。
我们可以根据解析出来的 adv_fields ,访问我们感兴趣的内容。
例如:
//当event type 为 BLE_GAP_EVENT_DISC 时,type 对应的 GAP 事件数据结构参数为 disc
struct ble_hs_adv_fields fields;
ble_hs_adv_parse_fields(&fields, event->disc.data,event->disc.length_data);
//fields 为我们解析出来的内容。具体上面发送蓝牙广播中有
//disc结构体
/** @brief Advertising report */
struct ble_gap_disc_desc {
/** Advertising PDU type. Can be one of following constants:
* - BLE_HCI_ADV_RPT_EVTYPE_ADV_IND
* - BLE_HCI_ADV_RPT_EVTYPE_DIR_IND
* - BLE_HCI_ADV_RPT_EVTYPE_SCAN_IND
* - BLE_HCI_ADV_RPT_EVTYPE_NONCONN_IND
* - BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP
*/
uint8_t event_type;
/** Advertising Data length */
uint8_t length_data;
/** Advertiser address */
ble_addr_t addr;
/** Received signal strength indication in dBm (127 if unavailable) */
int8_t rssi;
/** Advertising data */
uint8_t *data;
/** Directed advertising address. Valid for BLE_HCI_ADV_RPT_EVTYPE_DIR_IND
* event type (BLE_ADDR_ANY otherwise).
*/
ble_addr_t direct_addr;
};
扫描蓝牙广播产生的事件及 GAP 回调函数
扫描流程中会产生两种事件:
- 扫描到广播报文之后会产生 广播报告事件 ,事件码为宏
BLE_GAP_EVENT_DISC = 0x07
,此时我们可以在 GAP 回调函数中解析扫描到的广播负载数据。 - 自定义的发现周期到期之后会产生 发现流程完成事件 ,事件码为宏
BLE_GAP_EVENT_DISC_COMPLETE = 0x08
,我们可以在 GAP 回调函数中重新开始扫描或其他操作。
observer流程分析
此部分需要结合 nimble 代码。
所有的 nimble 蓝牙操作之只能在 host 与 controller 同步之后调用,即在 ble_hs_cfg.sync_cb
回调中操作。
通过 ble_gap_disc()
开启扫描流程,host 通过 HCI 命令设置 controller 的扫描参数,然后再通过 HCI 命令使能 controller 的扫描。
在自定义的 GAP 回调函数中处理扫描产生的事件。产生的事件有两类:
-
controller 发送广告报告 HCI 事件数据包给 host,产生了广告报告事件。
-
设置的发现周期超时后,由 host 软件定时器引起的发现完成事件(扫描完成事件)。
连接状态从机 - peripheral
如果要进入连接状态下的从机,那么必须发送可连接的广播。
连接状态从机流程
从机连接流程抽象逻辑图:
广播状态:
连接状态:
在连接建立之后,BLE 设备只允许在连接事件中进行数据交换,Peripheral 会做为连接状态下的从机,与主机在连接事件中进行点对点通信。
连接状态从机 API
我们使用 发送广播的 API 来发送可连接广播,即:
int ble_gap_adv_start(uint8_t own_addr_type, const ble_addr_t *direct_addr,
int32_t duration_ms,
const struct ble_gap_adv_params *adv_params,
ble_gap_event_fn *cb, void *cb_arg);
只在 API 的使用上存在一定的区别,我们要在 adv_params 参数中设置广播 PDU 类型为可连接广播,如下:
/*
* 选择广告 PDU 类型,ADV_IND
*/
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; /* 不定向可连接模式 */
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; /* 通用发现模式 */
其他操作同发送广播一致。
peripheral 产生的事件及 GAP 回调函数
由于发送的是可连接的广播,所以我们可以与其他 BLE 设备建立连接。
所以,产生的事件类型较多:
-
由于广播周期结束产生的广播完成事件;
-
由于连接建立而导致的连接事件,此时 controller 会分配连接句柄,host 会分配连接对象;
-
由于连接状态主机发送连接更新 PDU 而导致的连接更新事件;
-
由于断开连接流程而导致的断开连接事件;
-
其他未处理事件等。
peripheral 流程分析
我们只需启动可连接广播 PDU 发送流程即可实现 peripheral。
此部分分析需要查看 nimble 协议栈的代码。
连接状态主机 - Central
连接状态主机首先扫描广播,并与可连接广播 建立连接。
通常使用两个步骤来建立连接:
- 扫描流程,发现想要连接的可连接设备;
- 建立连接流程,与指定的可连接设备建立连接(指定对端地址)。
连接状态主机流程
主机连接抽象逻辑图
广播状态:
连接状态:
在启动连接流程时,我们需要指定连接的从机地址,连接建立后的连接事件参数等。
API 运行逻辑图如下
连接状态主机API
首先执行扫描蓝牙广播流程
该部分同 observer 一致,使用下面的 API 来启动扫描:
int ble_gap_disc(uint8_t own_addr_type, int32_t duration_ms,
const struct ble_gap_disc_params *disc_params,
ble_gap_event_fn *cb, void *cb_arg);
需要注意的是,此时的 GAP 事件回调函数可以只处理扫描流程产生的事件,即:
- 广告报告事件;
- 扫描完成事件。
我们需要在广播报告事件中,处理广播报文,查找想要连接的 BLE 设备。
再启动建立连接流程 API
在上面的广告报告事件中,我们可以根据解析出来的广告 PDU 负载来选择我们想要连接的设备(比如服务 UUID 参数,设备命参数等)。
我们可以使用如下的 API 来建立连接:
int ble_gap_connect(uint8_t own_addr_type, const ble_addr_t *peer_addr,
int32_t duration_ms,
const struct ble_gap_conn_params *params,
ble_gap_event_fn *cb, void *cb_arg);
//own_addr_type : 连接使用的本地设备地址类型;
//peer_addr : 想要连接的对端设备地址,如果为 NULL,则与白名单上的设备建立连接;
//duration_ms : 建立连接的周期时间,如果在这个时间内没有建立连接成功,则产生连接建立失败事件;
//param : 连接建立参数,如果为 NULL,则使用默认值;
//cb : 连接建立流程 GAP 回调函数;
//cb_arg : GAP 回调函数参数。
//返回值 : 成功返回 0,失败返回错误码。
我们通过 struct ble_gap_conn_params
结构体来设置连接流程参数,该结构体数据结构如下:
struct ble_gap_conn_params {
//扫描间隔, 单位 0.625ms
uint16_t scan_itvl;
//扫描窗口宽度, 要小于扫描间隔 单位 0.625ms
uint16_t scan_window;
//连接间隔时间最小值 单位 1.25ms
uint16_t itvl_min;
//连接间隔时间最大值 单位 1.25ms
uint16_t itvl_max;
//连接延迟
uint16_t latency;
//监控超时时间, 单位 10ms
uint16_t supervision_timeout;
//连接事件的最小长度, 单位 0.625ms
uint16_t min_ce_len;
//连接事件的最大长度, 单位 0.625ms
uint16_t max_ce_len;
};
连接建立流程产生的 GAP 事件
产生的事件如下:
- 连接事件,标识连接建立情况,可能成功,也可能失败;
连接建立后,我们可使用 ble_gap_conn_find
函数 通过句柄 来获取 链接的相关信息
int ble_gap_conn_find(uint16_t handle, struct ble_gap_conn_desc *out_desc)
struct ble_gap_conn_desc
结构体如下
/** @brief Connection descriptor */
struct ble_gap_conn_desc {
/** Connection security state */
struct ble_gap_sec_state sec_state;
/** Local identity address */
ble_addr_t our_id_addr;
/** Peer identity address */
ble_addr_t peer_id_addr;
/** Local over-the-air address */
ble_addr_t our_ota_addr;
/** Peer over-the-air address */
ble_addr_t peer_ota_addr;
/** Connection handle */
uint16_t conn_handle;
/** Connection interval */
uint16_t conn_itvl;
/** Connection latency */
uint16_t conn_latency;
/** Connection supervision timeout */
uint16_t supervision_timeout;
/** Connection Role
* Possible values BLE_GAP_ROLE_SLAVE or BLE_GAP_ROLE_MASTER
*/
uint8_t role;
/** Master clock accuracy */
uint8_t master_clock_accuracy;
}
- 广告报告事件,在广告报告事件中,如果发现了想要连接的设备,那么我们需要先失能 GAP 的扫描流程,才能重新启动 GAP 的建立连接流程。
/*
* 失能当前正在执行的发现流程
*/
ble_gap_disc_cancel();
/*
* 启动 GAP master 的连接流程,定向连接
*/
ble_gap_connect(g_own_addr_type, &(event->disc.addr), 10000,
NULL, conn_event, NULL);
-
断开连接事件;
-
连接更新事件;
-
其他事件。
GATT 应用
GATT 应用用于在已连接设备之间进行数据交换。
GATT 存在两种角色,分别对应已连接的两个设备:
GATT角色 | 角色描述 |
---|---|
GATT服务器 | 定义服务和特征的设备 |
GATT客户端 | 发送数据请求来访问服务与特征的BLE设备 |
GATT 服务器应用以及相关 API
GATT 服务器负责定义一些列的服务(service)供 GATT 客户端访问,服务代表了 GATT 服务器端实现的一种功能。
服务(service)抽象数据结构
struct ble_gatt_svc_def
结构体是对服务定义的抽象,可以唯一的用来表示一个 GATT 服务,其数据结构抽象如下:
struct ble_gatt_svc_def {
//这是服务声明的属性类型,主服务还是次级服务
uint8_t type;//BLE_GATT_SVC_TYPE_PRIMARY or BLE_GATT_SVC_TYPE_SECONDARY
//这是服务声明属性的属性值,即服务 UUID
//存储服务 UUID 的地址,这是每一个 UUID 结构对象的第一个参数
const ble_uuid_t *uuid;
//指向其他服务指针数组(表示引用服务)
const struct ble_gatt_svc_def **includes;
//服务包含的特征定义, characteristics 数组
const struct ble_gatt_chr_def *characteristics;
}
特征(Characteristic)数据结构
struct ble_gatt_chr_def
是对特征定义的抽象,可以唯一的用来表示一个特征定义,其数据结构如下:
struct ble_gatt_chr_def {
//即特征值的 UUID 指针
const ble_uuid_t *uuid;
//读取写入该特征时的回调, 特征定义时,这个回调函数必须设定
ble_gatt_access_fn *access_cb;
//回调使用的可选参数
void *arg;
//特征定义中的特征描述符定义数组。
//不需要定义 CCCD(客户端特征配置描述符),如果特征值属性的 notify 位和 indication 位置位,则 CCCD 自动包含
struct ble_gatt_dsc_def *descriptors;
//特征值属性的属性权限
ble_gatt_chr_flags flags;
//访问 该特征属性的最小秘钥大小
uint8_t min_key_size;
//特征值声明属性的属性句柄
uint16_t *val_handle;
};
其中 access_cb 的函数原型如下:
/*
* GATT 访问函数类,特征值或除 CCCD 外的其他特征描述符属性访问回调函数
*/
typedef int ble_gatt_access_fn(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
//conn_handle, 连接句柄,指示当前访问的属性属于哪一个 GATT 服务器对象;
//attr_handle, 属性句柄,表示当前访问的是哪一个属性;
//ctxt, struct ble_gatt_access_ctxt 类型对象,表示属性访问的上下文抽象数据结构;
//arg, 特征定义结构体中自定义的属性访问回调函数参数
//返回值: 成功返回 0,错误返回非 0 错误码
struct ble_gatt_access_ctxt
属性访问上下文抽象数据结构,该结构体如下:
struct ble_gatt_access_ctxt {
//属性访问的操作种类
uint8_t op;
/*BLE_GATT_ACCESS_OP_READ_CHR,BLE_GATT_ACCESS_OP_WRITE_CHR,
BLE_GATT_ACCESS_OP_READ_DSC,BLE_GATT_ACCESS_OP_WRITE_DSC*/
//如果是读操作,则必须使用本地数据填充这个结构体作为读响应;
//如果是写操作,则这个结构体存储的是待写的数据
struct os_mbuf *om;
union {
//如果op是读/写特征值,则chr有效,表示读/写的特征定义对象
const struct ble_gatt_chr_def *chr;
//如果op是读/写特征描述符,则dsc有效,表示读/写的特征描述符对象
const struct ble_gatt_dsc_def *dsc;
};
};
特征描述符数据结构
struct ble_gatt_dsc_def
是特征描述符的抽线结构,结构体构成如下:
struct ble_gatt_dsc_def {
/**
* Pointer to descriptor UUID; use BLE_UUIDxx_DECLARE macros to declare
* proper UUID; NULL if there are no more characteristics in the service.
* 指向特征描述符 UUID 存储地址
*/
const ble_uuid_t *uuid;
/** Specifies the set of permitted operations for this descriptor. 描述符属性权限*/
uint8_t att_flags;
/** Specifies minimum required key size to access this descriptor. */
uint8_t min_key_size;
/** Callback that gets executed when the descriptor is read or written.
* 特征描述符被访问时的回调函数,回调函数必须存在
*/
ble_gatt_access_fn *access_cb;
/** Optional argument for callback. */
void *arg;
};
由于 CCCD 特征描述符不需要应用开发人员指定,只需要在特征定义中的 Flag 标志位置位 notify 或 indicate 即可,而其他的特征描述符用得很少,所以我们在这里就不详细介绍特征描述符数据结构了,
GATT 服务注册相关 API
GATT 服务注册需要两步:
-
使用资源计数 API -
int ble_gatts_count_cfg(const struct ble_gatt_svc_def *defs)
,进行资源计数,更新全局计数器变量; -
使用注册 API -
int ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs)
,添加自定义的服务定义到全局服务定义指针数组中;
GATT 服务资源计数
资源计数 API 原型如下:
/**
* Adjusts a host configuration object's settings to accommodate the specified
* service definition array. This function adds the counts to the appropriate
* fields in the supplied configuration object without clearing them first, so
* it can be called repeatedly with different inputs to calculate totals. Be
* sure to zero the GATT server settings prior to the first call to this
* function.
* 调整主机协议栈的配置对象的设置以适应指定的服务定义数组,它会增加合合适的位域参数,这个函数可以重复调用
*
* @param defs The service array containing the resource
* definitions to be counted.
*
* @return 0 on success;
* BLE_HS_EINVAL if the svcs array contains an
* invalid resource definition.
*/
int ble_gatts_count_cfg(const struct ble_gatt_svc_def *defs);
//defs,struct ble_gatt_svc_def 类型的结构体指针,表示应用程序自定义的服务定义数组。
//返回值:成功返回 0,错误返回非 0 错误码。
GATT 服务注册
服务注册 API 原型如下:
/**
* Queues a set of service definitions for registration. All services queued
* in this manner get registered when ble_gatts_start() is called.
* 所有以这种方式入队的服务会在 ble_gatts_start() 调用时注册,
* 调用这个函数注册的服务必须在 host 和 controller 同步之前进行
*
* @param svcs An array of service definitions to queue for
* registration. This array must be
* terminated with an entry whose 'type'
* equals 0.
*
* @return 0 on success;
* BLE_HS_ENOMEM on heap exhaustion.
*/
int ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs);
//svcs,struct ble_gatt_svc_def 类型的结构体指针,表示应用程序自定义的服务定义数组。
//返回值:成功返回0,失败返回非 0 错误码。
使用上述两个 API 的注意
- 使用上述两个 API 注册 GATT 服务时必须保证协议栈还没有同步,即 Host 协议栈还没有启动;
- 上述两个 API 使用之后并没有将 GATT 服务真正的注册进 Host 中,只更新了一些全局变量用来记录自定义的 GATT 服务,真正的 GATT 服务注册会在 Host 协议栈真正启动时调用的 ble_gatts_start() 函数中注册。
GATT 服务器流程
GATT 服务器数据交换逻辑流程与 Peripheral 一致:
广播状态:
连接状态:
不同的是,在连接状态中, GATT 客户端会使用 GATT 功能来访问 GATT 服务器上定义的 GATT 服务,然后触发自定义的回调函数,或 GAP 事件,比如:
GATT客户端使用功能 | GATT服务器对应的处理器 |
---|---|
读/写特征值 | 会调用的特征定义中设置的自定义特征访问回调函数 |
写CCCD | 会触发订阅GAP事件 |
交换MTU | 出发MTTU交换事件 |
读/写特征描述符 | 调用特征描述符定义中设置的自定义属性访问回调函数 |
API 执行流程如下:
GATT 服务器产生的事件
- 可能产生的广播流程完成事件;
- 连接产生的连接完成事件;
- 各种原因导致的连接断开事件;
- 由自己启动的连接参数更新事件;
- 由 GATT 客户端访问 CCCD 产生的订阅事件;
- 由 GATT 的 MTU 交换功能产生的 MTU 事件;
- 其他的事件,不是很常用,就不例举了。
GATT 客户端应用及其相关 API
GATT 客户端用来访问 GATT 服务器定义的服务(service)与特征(Characteristic)。
由于 GATT 客户端只能属性类型、属性句柄、和属性值感兴趣,所以对于 GATT 客户端,重新抽象了服务(service)、特征(Characteristic)、特征描述符的数据结构。
GATT 客户端侧的服务(service)抽象数据结构
当 GATT 客户端查找 GATT 服务器上定义的服务时,GATT 客户端侧服务的抽象数据结构如下:
struct ble_gatt_svc {
//服务起始句柄,也是服务声明属性的句柄
uint16_t start_handle;
//服务结束句柄,由于服务是一个属性组,所以存在结束句柄
uint16_t end_handle;
//服务 UUID,这是服务声明属性的属性值
ble_uuid_any_t uuid;
};
GATT 客户端侧的属性的抽象结构
GATT 客户端由于需要访问属性值,所以存在对属性的抽象,其数据结构如下:
struct ble_gatt_attr {
//访问的属性句柄
uint16_t handle;
//偏移
uint16_t offset;
//保存属性值的缓存区
struct os_mbuf *om;
};
GATT 客户端对特征定义的抽象数据结构
当 GATT 客户端访问 GATT 服务器上的特征定义时,获得的 GATT 特征定义抽象数据结构如下:
struct ble_gatt_chr {
uint16_t def_handle; /* 特征值声明属性的属性句柄 */
uint16_t val_handle; /* 特征值属性句柄 */
uint8_t properties; /* 特征值属性访问权限 */
ble_uuid_any_t uuid; /* 特征值 UUID */
};
GATT 客户端对特征描述符的抽象数据结构
当 GATT 客户端查找 GATT 服务器定义的特征描述符时,获得的特征描述符抽象数据结构如下:
struct ble_gatt_dsc {
uint16_t handle; /* 特征描述符声明属性的属性句柄 */
ble_uuid_any_t uuid; /* 特征描述符的 UUID */
};
GATT 客户端的数据交换抽象逻辑图
GATT 客户端的数据交换流程与 Central 的数据交换流程基本上是一致的:
广播状态:
连接状态:
不同的是,在进入连接状态之后,GATT 客户端会使用服务发现 GATT 功能来查找 GATT 服务器定义的服务与特征,其抽象的查找流程如下:
GATT 客户端使用的相关 API
发现所有的主服务 API
/**
* Initiates GATT procedure: Discover All Primary Services.
* 该流程不会产生 GAP 事件,当收到 ATT_READ_BY_GROUP_TYPE_RSP 时,通过 cb 向应用层报告发现的服务定义
* 只有在解析出一个主服务定义后,和主服务发现流程执行结束后才会调用自定义的回调函数
*
* @param conn_handle The connection over which to execute the
* procedure.
* @param cb The function to call to report procedure status
* updates; null for no callback.
* @param cb_arg The optional argument to pass to the callback
* function.
*/
int ble_gattc_disc_all_svcs(uint16_t conn_handle,
ble_gatt_disc_svc_fn *cb, void *cb_arg);
//conn_handle, 连接句柄,用于确定访问哪一个 GATT 服务器;
//cb,自定义的收到响应时调用回调函数,主服务发现回调函数;
//cb_arg, 自定义的回调函数参数。
//返回值:函数执行成功返回0,失败返回错误码。
主服务发现回调函数类型如下:
typedef int ble_gatt_disc_svc_fn(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_svc *service,
void *arg);
//conn_handle, 连接句柄,执行该回调的连接对象;
//error, 错误状态指示结构体;
//service, 查找到的主服务抽象数据结构;
//arg, 自定义的回调函数参数。
//返回值: 0 或其他非0值;
当查找到一个主服务定义,或者主服务发现流程结束时,都会调用该回调函数。
在指定的主服务内查找所有的特征定义 API
/**
* Initiates GATT procedure: Discover All Characteristics of a Service.
* 对于指定的主服务,启动的特征声明发现流程
*
* @param conn_handle The connection over which to execute the
* procedure.
* @param start_handle The handle to begin the search at (generally
* the service definition handle).
* @param end_handle The handle to end the search at (generally the
* last handle in the service).
* @param cb The function to call to report procedure status
* updates; null for no callback.
* @param cb_arg The optional argument to pass to the callback
* function.
*
* @return 0 on success; nonzero on failure.
*/
int ble_gattc_disc_all_chrs(uint16_t conn_handle, uint16_t start_handle,
uint16_t end_handle, ble_gatt_chr_fn *cb,
void *cb_arg);
//conn_handle, 连接句柄,表示当前通信对象;
//start_handle, 服务定义起始句柄,也是服务声明的句柄;
//end-handle, 服务定义结束句柄;
//cb, 自定义的响应回调函数,查找到特征定义时调用;
//cd_arg, 自定义的回调函数参数。
//返回值: 0 或错误码。
查找特征定义的回调函数类型如下:
typedef int ble_gatt_chr_fn(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg);
//conn_handle, 连接句柄,指示连接对象;
//error, 状态指示结构体;
//chr, 查找到的特征定义;
//arg, 自定义的回调函数参数。
//返回值:0或错误码。
注意:在一个特征定义查找流程中,该函数可以被调用多次,因为一个服务中可能包含多个特征定义。
在指定的服务内查找所有的特征描述符 API
当服务范围内的特征定义查找完毕后,我们需要查找服务内的所有特征描述符。
/**
* Initiates GATT procedure: Discover All Characteristic Descriptors.
*
* @param conn_handle The connection over which to execute the
* procedure.
* @param chr_val_handle The handle of the characteristic value
* attribute.
* @param chr_end_handle The last handle in the characteristic
* definition.
* @param cb The function to call to report procedure status
* updates; null for no callback.
* @param cb_arg The optional argument to pass to the callback
* function.
*
* @return 0 on success; nonzero on failure.
*/
int ble_gattc_disc_all_dscs(uint16_t conn_handle, uint16_t start_handle,
uint16_t end_handle,
ble_gatt_dsc_fn *cb, void *cb_arg);
//conn_handle, 表示连接对象;
//start_handle, 服务的起始句柄,即服务声明属性句柄;
//end_handle, 服务结束句柄;
//cb, 自定义的响应回调函数;
//cb_arg, 自定义的回调函数参数。
//返回值:0或错误码。
响应回调函数如下:
typedef int ble_gatt_dsc_fn(uint16_t conn_handle,
const struct ble_gatt_error *error,
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc,
void *arg);
//conn_handle, 连接句柄,指示连接对象;
//error, 错误状态指示对象;
//char_val_handle, 特征值句柄,表示查找的特征描述符所属的特征值;
//dsc, 表示查找的特征描述符对象;
//arg, 自定义的回调函数参数。
//返回值:0或错误码。
读指定属性句柄的属性值 API
int ble_gattc_read(uint16_t conn_handle, uint16_t attr_handle,
ble_gatt_attr_fn *cb, void *cb_arg);
//conn_handle, 连接句柄,表示连接对象;
//attr_handle, 要读的属性句柄;
//cb, 收到读响应时的自定义回调函数;
//cb_arg, 回调函数参数。
//返回值:0 或错误码。
属性访问回调函数如下:
/**
* The host will free the attribute mbuf automatically after the callback is
* executed. The application can take ownership of the mbuf and prevent it
* from being freed by assigning NULL to attr->om.
*/
typedef int ble_gatt_attr_fn(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg);
//conn_handle, 连接句柄,指示连接对象;
//error, 错误状态指示对象;
//attr, 属性对象,存储访问的属性值;
//arg, 自定义的回调函数;
//返回值:0 或错误码。
如果没有出现错误的话,即 error 中的 status == 0,那么 attr 中存储访问的属性值。
写指定属性句柄的属性值 API
/**
* Initiates GATT procedure: Write Characteristic Value (flat buffer version).
*
* @param conn_handle The connection over which to execute the
* procedure.
* @param attr_handle The handle of the characteristic value to write
* to.
* @param value The value to write to the characteristic.
* @param value_len The number of bytes to write.
* @param cb The function to call to report procedure status
* updates; null for no callback.
* @param cb_arg The optional argument to pass to the callback
* function.
*
* @return 0 on success; nonzero on failure.
*/
int ble_gattc_write_flat(uint16_t conn_handle, uint16_t attr_handle,
const void *data, uint16_t data_len,
ble_gatt_attr_fn *cb, void *cb_arg);
//conn_handle, 连接句柄,指示连接对象;
//attr_handle, 准备写的属性句柄;
//data, 等待写的数据缓存区;
//data_len, 等待写的数据长度;
//cb,收到写响应时的回调函数;
//cb_arg, 回调函数参数。
//返回值: 0或错误码
写属性值的回调函数类型与读属性值的回调函数类型是一致的,在写响应回调函数中,我们可以通过 error 对象来判断是否写属性成功。
GATT 客户端需要处理的 GAP 事件
对比 Central, GAP 客户端需要处理更多的事件,如下:
- 扫描广告期间的广告报告事件,查找想要连接的 BLE 设备,并启动连接;
- 连接事件,连接成功后,启动 GATT 服务发现功能;
- 接收 GATT 服务器发送的 notification / indication 事件,如果 GATT 客户端写
- CCCD 属性的 notify / indicate 位为 1,那么可能会收到属性的 notification /
- indication,并触发该事件;
- MTU 交换事件,如果 GATT 启动了 MTU 交换流程,则会产生该事件;
其他事件。