蓝牙设备地址存在两种类型:公共地址和随机地址。蓝牙地址都是 48 位,6字节长。
其中,随机地址由细分为3种类型:不可解析随机地址,可解析随机地址,静态随机地址。
在开始任意数据传输之前,都必须设置蓝牙设备地址,或公共地址,或随机地址,或者公共地址和随机地址都设置。
蓝牙公共设备地址通过系统配置宏 MYNEWT_VAL_BLE_PUBLIC_DEV_ADDR 设置,在 host 和 controller 同步后会用宏定义中设置的公共地址配置 host 的公共标识地址。
蓝牙随机设备地址的设置可以使用如下接口:
int ble_hs_id_set_rnd(const uint8_t *rnd_addr);
参数说明:
返回值:随机地址设置成功返回0,失败返回错误码。
在启动任何蓝牙流程之前,可以使用下面的函数来检查当前最合适使用的蓝牙地址,优先检查随机地址(如果随机设备地址已经设置的话)。
int ble_hs_id_infer_auto(int privacy, uint8_t *out_addr_type);
参数说明:
privacy : 0/1,表示是否使用一个解析地址。
out_addr_type : 输出合适的地址类型,供蓝牙 GAP 流程使用。
返回值:成功返回0,失败返回错误码。
如果函数执行成功,那么 out_addr_type 可能的返回值如下:
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 事件包含了 BLE 设备在数据交换过程中所有可能产生的异步事件,换句话说,即使是 GATT 应用也会产生 GAP 事件。
nimble Host 中所有可能的 GAP 事件如下:
GAP 事件枚举值 | GAP 事件宏 | GAP 事件描述 |
---|---|---|
0 | BLE_GAP_EVENT_CONNECT | 连接尝试事件,可能连接成功,也可能连接失败 |
1 | BLE_GAP_EVENT_DISCONNECT | 连接终止事件 |
2 | RFU | RFU |
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 事件回调函数类型如下:
typedef int ble_gap_event_fn(struct ble_gap_event *event, void *arg);
参数说明:
返回值:处理成功返回 0,处理失败返回非 0 错误码。
struct ble_gap_event 结构体为 GAP 事件结构体,可以唯一的代表一个已发生的 GAP 事件,其数据结构如下所示:
struct ble_gap_event {
/**
* Indicates the type of GAP event that occurred. This is one of the
* 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 事件结构体主要由两个参数组合而成:
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 |
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);
参数说明:
返回值 : 成功返回 0, 失败返回非 0 错误码。
struct ble_hs_adv_fields 结构体对标准蓝牙广播数据的抽象如下:
struct ble_hs_adv_fields {
/*** 0x01 - Flags. */
uint8_t flags;
/*** 0x02,0x03 - 16-bit service class UUIDs. */
const ble_uuid16_t *uuids16;
uint8_t num_uuids16;
unsigned uuids16_is_complete:1;
/*** 0x04,0x05 - 32-bit service class UUIDs. */
const ble_uuid32_t *uuids32;
uint8_t num_uuids32;
unsigned uuids32_is_complete:1;
/*** 0x06,0x07 - 128-bit service class UUIDs. */
const ble_uuid128_t *uuids128;
uint8_t num_uuids128;
unsigned uuids128_is_complete:1;
/*** 0x08,0x09 - Local name. */
const 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. */
const uint8_t *slave_itvl_range;
/*** 0x16 - Service data - 16-bit UUID. 包含*/
const uint8_t *svc_data_uuid16;
uint8_t svc_data_uuid16_len;
/*** 0x17 - Public target address. */
const 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. */
const uint8_t *svc_data_uuid32;
uint8_t svc_data_uuid32_len;
/*** 0x21 - Service data - 128-bit UUID. */
const uint8_t *svc_data_uuid128;
uint8_t svc_data_uuid128_len;
/*** 0x24 - URI. */
const uint8_t *uri;
uint8_t uri_len;
/*** 0xff - Manufacturer specific data. 制造商特定数据,前两个字节是制造商 ID */
const uint8_t *mfg_data;
uint8_t 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);
参数说明:
返回值:成功返回 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 {
/** Advertising mode. Can be one of following constants:
* - BLE_GAP_CONN_MODE_NON (non-connectable; 3.C.9.3.2).
* - BLE_GAP_CONN_MODE_DIR (directed-connectable; 3.C.9.3.3).
* - BLE_GAP_CONN_MODE_UND (undirected-connectable; 3.C.9.3.4).
* 连接模式
*/
uint8_t conn_mode;
/** Discoverable mode. Can be one of following constants:
* - BLE_GAP_DISC_MODE_NON (non-discoverable; 3.C.9.2.2).
* - BLE_GAP_DISC_MODE_LTD (limited-discoverable; 3.C.9.2.3).
* - BLE_GAP_DISC_MODE_GEN (general-discoverable; 3.C.9.2.4).
* 发现模式
*/
uint8_t disc_mode;
/** Minimum advertising interval, if 0 stack use sane defaults,最小广告间隔,0 表示使用默认值,单位:0.625ms */
uint16_t itvl_min;
/** Maximum advertising interval, if 0 stack use sane defaults,最大广告间隔,0 表示使用默认值,单位:0.625ms */
uint16_t itvl_max;
/** Advertising channel map , if 0 stack use sane defaults,主广告信道掩码,0 表示使用默认值 */
uint8_t channel_map;
/** Advertising Filter policy,广告屏蔽策略 */
uint8_t filter_policy;
/** If do High Duty cycle for Directed Advertising,是否在定向广告时使用高占空比 */
uint8_t high_duty_cycle:1;
};
struct ble_gap_adv_params 结构体参数说明如下:
广播类型的确定方法如下:
广播类型 | 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_CONN_MODE_NON |
ADV_NONCONN_IND | BLE_GAP_CONN_MODE_NON | BLE_GAP_CONN_MODE_NON |
ADV_SCAN_IND | BLE_GAP_CONN_MODE_NON | BLE_GAP_DISC_MODE_GEN |
因为 boardcaster 发送的是不可连接广播,在设置了广播周期之后,则会触发广播完成事件。
注意,设备发送扫描响应 PDU 不会触发事件,直接发送广播启动之前设置的扫描响应数据,如果应用层没有设置扫描响应数据,那么扫描响应 PDU 的负载位域长度为0。
所以,boardcaster 广播启动时注册的回调函数只需要处理广播周期结束之后的广播完成事件即可。
此部分需要结合 nimble 应用代码。
所有的 nimble 蓝牙操作之只能在 host 与 controller 同步之后使用。
在 ble_gap_adv_set_fields()
中,host 通过 HCI 发送设置广告 PDU 负载数据的命令(OpCode = 0x2008), 来设置广告 PDU 负载数据。
在 ble_gap_adv_start()
中,host 协议栈会根据自定义的参数来发送相对应的广告流程参数设置 HCI 命令,并通过广告流程启动 HCI 命令来启动广告流程。
最后,host 协议栈会启动 host 的软件定时器来对广告周期进行定时。如果超时则会在 host 软件定时器的回调函数中发送停止广告流程 HCI 命令,并调用应用层定义的回调函数来向应用层报告因为广告周期超时而产生的广告完成事件
observer存在两个模式,主动扫描和被动扫描,这两种模式的区别在于:主动扫描模式下,observer的 controller 在收到一个可扫描的广播帧之后会发送扫描请求帧 SCAN_REQ,然后尝试接收 advertiser 发送的扫描响应 SCAN_RSP 。
这些 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 {
/** Scan interval in 0.625ms units */
uint16_t itvl;
/** Scan window in 0.625ms units */
uint16_t window;
/** Scan filter policy */
uint8_t filter_policy;
/** If limited discovery procedure should be used */
uint8_t limited:1;
/** If passive scan should be used */
uint8_t passive:1;
/** If enable duplicates filtering */
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 ,访问我们感兴趣的内容。
扫描流程中会产生两种事件:
1.扫描到广播报文之后会产生广告报告事件,事件码为宏 BLE_GAP_EVENT_DISC = 0x07,此时我们可以在 GAP 回调函数中解析扫描到的广播负载数据。
2.自定义的发现周期到期之后会产生发现流程完成事件,事件码为宏 BLE_GAP_EVENT_DISC_COMPLETE = 0x08,我们可以在 GAP 回调函数中重新开始扫描或其他操作。
此部分需要结合 nimble 代码。
所有的 nimble 蓝牙操作之只能在 host 与 controller 同步之后调用。
通过 ble_gap_disc()
开启扫描流程,host 通过 HCI 命令设置 controller 的扫描参数,然后再通过 HCI 命令使能 controller 的扫描。
在自定义的 GAP 回调函数中处理扫描产生的事件。产生的事件有两类:
controller 发送广告报告 HCI 事件数据包给 host,产生了广告报告事件。
设置的发现周期超时后,由 host 软件定时器引起的发现完成事件(扫描完成事件)。
如果要进入连接状态下的从机,那么必须发送可连接的广播。
在连接建立之后,BLE 设备只允许在连接事件中进行数据交换,Peripheral 会做为连接状态下的从机,与主机在连接事件中进行点对点通信。
我们使用发送广播的 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; /* 通用发现模式 */
其他操作同发送广播一致。
由于发送的是可连接的广播,所以我们可以与其他 BLE 设备建立连接。
所以,产生的事件类型较多:
我们只需启动可连接广播 PDU 发送流程即可实现 peripheral。
此部分分析需要查看 nimble 协议栈的代码。
连接状态主机首先扫描广播,并与可连接广播 建立连接。
通常使用两个步骤来建立连接:
在启动连接流程时,我们需要指定连接的从机地址,连接建立后的连接事件参数等。
该部分同 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 设备。
在上面的广告报告事件中,我们可以根据解析出来的广告 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 {
/** Scan interval in 0.625ms units */
uint16_t scan_itvl;
/** Scan window in 0.625ms units */
uint16_t scan_window;
/** Minimum value for connection interval in 1.25ms units */
uint16_t itvl_min;
/** Maximum value for connection interval in 1.25ms units */
uint16_t itvl_max;
/** Connection latency */
uint16_t latency;
/** Supervision timeout in 10ms units */
uint16_t supervision_timeout;
/** Minimum length of connection event in 0.625ms units */
uint16_t min_ce_len;
/** Maximum length of connection event in 0.625ms units */
uint16_t max_ce_len;
};
产生的事件如下:
/*
* 失能当前正在执行的发现流程
*/
ble_gap_disc_cancel();
/*
* 启动 GAP master 的连接流程,定向连接
*/
ble_gap_connect(g_own_addr_type, &(event->disc.addr), 10000,
NULL, conn_event, NULL);
GATT 应用用于在已连接设备之间进行数据交换。
GATT 服务器负责定义一些列的服务(service)供 GATT 客户端访问,服务代表了 GATT 服务器端实现的一种功能。
struct ble_gatt_svc_def 结构体是对服务定义的抽象,可以唯一的用来表示一个 GATT 服务,其数据结构抽象如下:
struct ble_gatt_svc_def {
/**
* One of the following:
* o BLE_GATT_SVC_TYPE_PRIMARY - primary service
* o BLE_GATT_SVC_TYPE_SECONDARY - secondary service
* o 0 - No more services in this array.
* 这是服务声明的属性类型,主服务还是次级服务
*/
uint8_t type;
/**
* Pointer to service UUID; use BLE_UUIDxx_DECLARE macros to declare
* proper UUID; NULL if there are no more characteristics in the service.
* 这是服务声明属性的属性值,即服务 UUID
* 存储服务 UUID 的地址,这是每一个 UUID 结构对象的第一个参数
*/
const ble_uuid_t *uuid;
/**
* Array of pointers to other service definitions. These services are
* reported as "included services" during service discovery. Terminate the
* array with NULL.
* 服务包含的引用服务对象数组
*/
const struct ble_gatt_svc_def **includes;
/**
* Array of characteristic definitions corresponding to characteristics
* belonging to this service.
* 这是服务包含的特征定义
*/
const struct ble_gatt_chr_def *characteristics;
};
struct ble_gatt_svc_def 的参数说明:
struct ble_gatt_chr_def 是对特征定义的抽象,可以唯一的用来表示一个特征定义,其数据结构如下:
struct ble_gatt_chr_def {
/**
* Pointer to characteristic UUID; use BLE_UUIDxx_DECLARE macros to declare
* proper UUID; NULL if there are no more characteristics in the service.
* 特征 UUID,即特征值的 UUID,这是特征值 UUID 的存储地址
*/
const ble_uuid_t *uuid;
/**
* Callback that gets executed when this characteristic is read or
* written.
* 当这个特征被读或写时调用的回调函数,特征定义时,这个回调函数必须存在
*/
ble_gatt_access_fn *access_cb;
/**
* Optional argument for callback.
* 回调函数参数
*/
void *arg;
/**
* Array of this characteristic's descriptors. NULL if no descriptors.
* Do not include CCCD; it gets added automatically if this
* characteristic's notify or indicate flag is set.
* 特征定义中的特征描述符定义数组
* 不要包含 CCCD(客户端特征配置描述符),如果特征值属性的 notify 位和 indication 位置位,则 CCCD 自动包含
*/
struct ble_gatt_dsc_def *descriptors;
/**
* Specifies the set of permitted operations for this characteristic.
* 特征值属性的属性权限
*/
ble_gatt_chr_flags flags;
/** Specifies minimum required key size to access this characteristic. */
uint8_t min_key_size;
/**
* At registration time, this is filled in with the characteristic's value
* attribute handle.
* 应用层提供的用于保存特征值声明属性的属性句柄的地址
*/
uint16_t *val_handle;
};
特征定义结构体 struct ble_gatt_chr_def 参数说明如下:
其中 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);
参数说明:
返回值: 成功返回 0,错误返回非 0 错误码
struct ble_gatt_access_ctxt 属性访问上下文抽象数据结构,该结构体如下:
struct ble_gatt_access_ctxt {
uint8_t op;
struct os_mbuf *om;
union {
const struct ble_gatt_chr_def *chr;
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 服务注册需要两步:
int ble_gatts_count_cfg(const struct ble_gatt_svc_def *defs)
,进行资源计数,更新全局计数器变量;int ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs)
,添加自定义的服务定义到全局服务定义指针数组中;资源计数 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);
参数说明:
返回值:成功返回 0,错误返回非 0 错误码。
服务注册 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);
参数说明:
返回值:成功返回0,失败返回非 0 错误码。
注意:
ble_gatts_start()
函数中注册。GATT 服务器数据交换逻辑流程与 Peripheral 一致:
不同的是,在连接状态中, GATT 客户端会使用 GATT 功能来访问 GATT 服务器上定义的 GATT 服务,然后触发自定义的回调函数,或 GAP 事件,比如:
API 执行流程如下:
·
·
·
GATT 客户端用来访问 GATT 服务器定义的服务(service)与特征(Characteristic)。
由于 GATT 客户端只能属性类型、属性句柄、和属性值感兴趣,所以对于 GATT 客户端,重新抽象了服务(service)、特征(Characteristic)、特征描述符的数据结构。
当 GATT 客户端查找 GATT 服务器上定义的服务时,GATT 客户端侧服务的抽象数据结构如下:
struct ble_gatt_svc {
uint16_t start_handle; /* 服务起始句柄,也是服务声明属性的句柄 */
uint16_t end_handle; /* 服务结束句柄 */
ble_uuid_any_t uuid; /* 服务 UUID */
};
参数说明:
GATT 客户端由于需要访问属性值,所以存在对属性的抽象,其数据结构如下:
struct ble_gatt_attr {
uint16_t handle; /* 属性句柄 */
uint16_t offset;
struct os_mbuf *om; /* 属性值 */
};
当 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 服务器定义的特征描述符时,获得的特征描述符抽象数据结构如下:
struct ble_gatt_dsc {
uint16_t handle; /* 特征描述符声明属性的属性句柄 */
ble_uuid_any_t uuid; /* 特征描述符的 UUID */
};
参数说明:
GATT 客户端的数据交换流程与 Central 的数据交换流程基本上是一致的:
不同的是,在进入连接状态之后,GATT 客户端会使用服务发现 GATT 功能来查找 GATT 服务器定义的服务与特征,其抽象的查找流程如下:
/**
* 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);
参数说明:
返回值:函数执行成功返回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);
参数说明:
返回值: 0 或其他非0值;
当查找到一个主服务定义,或者主服务发现流程结束时,都会调用该回调函数。
/**
* 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);
参数说明:
返回值: 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);
参数说明:
返回值:0或错误码。
注意:在一个特征定义查找流程中,该函数可以被调用多次,因为一个服务中可能包含多个特征定义。
当服务范围内的特征定义查找完毕后,我们需要查找服务内的所有特征描述符。
/**
* 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);
参数说明:
返回值: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);
参数说明:
返回值:0或错误码。
int ble_gattc_read(uint16_t conn_handle, uint16_t attr_handle,
ble_gatt_attr_fn *cb, void *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);
参数说明:
返回值:0 或错误码。
如果没有出现错误的话,即 error 中的 status == 0,那么 attr 中存储访问的属性值。
/**
* 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);
参数说明:
返回值: 0或错误码
写属性值的回调函数类型与读属性值的回调函数类型是一致的,在写响应回调函数中,我们可以通过 error 对象来判断是否写属性成功。
对比 Central, GAP 客户端需要处理更多的事件,如下: