LE Host
本文综述 Bluetooth LE 协议栈接口,其中包括基于 GAP 的接口和基于 GATT 的接口。
SDK 中 Bluetooth LE协议栈和Profile的架构如下:
术语和概念
在蓝牙核心规范中, Profile 的定义不同于 Protocol 的定义。Protocol被定义为各层协议,例如Link Layer、L2CAP 、 SMP 和 ATT。不同于Protocol,Profile从使用蓝牙核心规范中各层协议的角度,定义蓝牙应用互操作性的实现。Profile定义Protocol中的可用特性和功能,以及蓝牙设备互操作性的实现,使蓝牙协议栈适用于各种场景的应用开发。
在蓝牙核心规范中,Profile和Protocol的关联如下图所示。
Profile由红色矩形框表示,包括 GAP
、 Profile #1
、 Profile #2
、 Profile #3
。蓝牙核心规范中的Profile分为两种类型:基于GAP的Profile( GAP
)和基于GATT的Profile( Profile #1
、 Profile #2
、 Profile #3
)。
GAP
GAP是所有的蓝牙设备均需实现的Profile,用于描述device discovery、connection、security requirement和authentication的行为和方法。GAP中的LE部分定义四种角色(Broadcaster、Observer、Peripheral和Central)。
Broadcaster 角色用于只通过广播发送数据的应用。
Observer 角色用于接收广播数据的应用。
Peripheral 角色用于通过广播发送数据并且可以建立链路的应用。
Central 角色用于接收广播数据并且建立一条或多条链路的应用。
GAP的位置
GAP层作为蓝牙协议层的组成模块,如下图所示,中间浅蓝框框内的部分( GAP
、 Bluetooth Host
、 Bluetooth Controller
)为蓝牙协议层。 Application
在蓝牙协议层之上, baseband/RF
位于蓝牙协议层之下。
GAP层给application提供访问Bluetooth Host的接口。application可以使用GAP来控制蓝牙协议栈启动某些操作,例如认证、创建连接等。
蓝牙协议栈可以使用GAP来通知application蓝牙状态的变化,例如监督超时、远程断开连接等。
基于GATT的Profile
在蓝牙核心规范中,另一种常用的Profile是基于GATT的Profile。GATT分为server和client两种角色。 server用于提供service数据。client可以访问service数据。基于GATT的Profile是基于server-client交互结构,适用于不同应用场景,用于蓝牙设备之间的特定数据交互。如下图所示,Profile是以Service和Characteristic的形式组成的。
功能设置
支持的蓝牙技术功能
GAP的功能
GAP层提供的接口的功能如下:
Advertising: 设置advertising参数,启动/停止advertising。
Scan: 设置scan参数,启动/停止scan。
Connection: 设置connection参数,创建connection,终止已建立的connection,更新connection参数。
配对: 设置配对参数,启动配对,使用passkey entry方式时输入/显示passkey。
密钥管理: 根据设备地址和地址类型查找key entry,保存/加载绑定信息的密钥,解析random address,删除绑定设备密钥。
其它:
设置GAP公共参数。
修改本地Filter Accept List。
生成/设置本地设备random address。
配置本地设备identity address。
…
提供的API
蓝牙协议栈 API 可以分为GAP API和GATT API两类。下图展示了 APP,GAP/GATT以及蓝牙协议栈三者间的关系。横线上方的部分由APP开发,横线下方的区域由Realtek开发。
本章向开发者简单介绍GAP/GATT APIs。对于RTL87x2G,GAP/GATT相关API的头文件位于: subsys\bluetooth\
,对于其他IC(RTL87x2C/RTL87x2D/RTL87x2E/RTL87x2H),头文件目录为 inc\bluetooth
。
下表简单介绍了GAP APIs。对于RTL87x2G,GAP相关API的头文件位于 subsys\bluetooth\bt_host\inc
,对于其他IC(RTL87x2C/RTL87x2D/RTL87x2E/RTL87x2H),头文件目录为 inc\bluetooth\gap
。
Header file |
Description |
API Reference |
---|---|---|
gap_adv.h |
Start/Stop legacy advertisement etc. |
|
gap_ext_adv.h |
Start/Stop extended advertisement etc. |
|
gap_pa_adv.h |
Start/Stop periodic advertisement etc. |
|
gap_scan.h |
Start/Stop legacy scan etc. |
|
gap_ext_scan.h |
Start/Stop extended scan etc. |
|
gap_conn_le.h |
Set/Get connection parameters, disconnect LE link etc. |
|
gap_storage_le.h |
Save/Load LE key etc. |
|
gap_bond_le.h |
Set/Get bond parameters, display/input bond keys etc. |
|
gap_bond_manager.h |
Definition GAP bond manager module APIs. |
|
gap_dtm.h |
Start/Stop DTM receiver/transmitter test. |
|
gap_aox.h |
Register callback, read antenna information etc. |
|
gap_aox_conn.h |
Start/Stop constant tone extension request procedure etc. |
|
gap_aox_connless_receiver.h |
Enable/Disable capturing IQ samples etc. |
|
gap_aox_connless_transmitter.h |
Enable/Disable constant tone extensions in periodic advertising. |
|
gap_callback_le.h |
Definition GAP callback messages. |
|
gap_msg.h |
Definition GAP messages. |
|
gap_privacy.h |
Set/Get privacy parameters, enable/disable privacy etc. |
|
gap_credit_based_conn.h |
GAP LE credit based connection exported functions. |
|
gap_ecfc.h |
GAP enhanced credit based flow control mode exported functions. |
|
gap.h |
Set/Get GAP general parameters. |
|
gap_le.h |
Initialize/set/get LE parameters of GAP etc. |
|
gap_le_types.h |
Definition types of device appearance, advertising data type, PHY etc. |
下表简单介绍了GATT APIs。对于RTL87x2G,GATT相关API的头文件位于 subsys\bluetooth\gatt_profile
,对于其他IC(RTL87x2C/RTL87x2D/RTL87x2E/RTL87x2H),头文件目录为 inc\bluetooth\profile
。
Header file |
Description |
API Reference |
---|---|---|
profile_client.h |
Register client callback with conn ID, send read/write request etc. |
|
profile_client_def.h |
Initialize client parameters, definition discovery procedure etc. |
|
profile_client_ext.h |
Register client callback with conn handle, send read/write request etc. |
|
profile_server.h |
Register server callback with conn ID, send indication/notification etc. |
|
profile_server_def.h |
Initialize server parameters, register builtin service etc. |
|
profile_server_ext.h |
Register server callback with conn handle, send indication/notification etc. |
GAP的初始化和启动流程
本节介绍如何在 app_le_gap_init()
中配置LE GAP参数以及GAP内部启动流程。
GAP启动流程
在main()中初始化GAP
int main(void) { ...... le_gap_init(APP_MAX_LINKS); gap_lib_init(); app_le_gap_init(); app_le_profile_init(); ...... }
参数说明:
- le_gap_init():
始化GAP并设置link数目。
- gap_lib_init():
初始化
gap_utils.lib
。- app_le_gap_init():
GAP参数的初始化。
- app_le_profile_init():
初始化基于GATT的Profiles。
在app task中启动蓝牙协议层
void app_main_task(void *p_param) { uint8_t event; os_msg_queue_create(&io_queue_handle, "ioQ", MAX_NUMBER_OF_IO_MESSAGE, sizeof(T_IO_MSG)); os_msg_queue_create(&evt_queue_handle, "evtQ", MAX_NUMBER_OF_EVENT_MESSAGE, sizeof(uint8_t)); gap_start_bt_stack(evt_queue_handle, io_queue_handle, MAX_NUMBER_OF_GAP_MESSAGE); ...... }
APP需要调用
gap_start_bt_stack()
来启动蓝牙协议层和GAP初始化流程。GAP内部初始化流程
流程
说明
1
gap_start_bt_stack
通过发送注册请求启动Bluetooth Host初始化流程。
2
Receive act info
stack已准备就绪。
3
Set pair mode
(必要步骤)
设置参数
GAP_PARAM_BOND_PAIRING_MODE
,GAP_PARAM_BOND_AUTHEN_REQUIREMENTS_FLAGS
,GAP_PARAM_BOND_IO_CAPABILITIES
和GAP_PARAM_BOND_OOB_ENABLED
。4
Set local IRK
(必要步骤)
设置本地设备的Identity Resolving Key(IRK),若
GAP_PARAM_BOND_GEN_LOCAL_IRK_AUTO
为true,则GAP层将使用自动生成的IRK,并将其存入flash,否则,GAP层将使用通过GAP_PARAM_BOND_SET_LOCAL_IRK
设置的值,默认值为全零。5
Set privacy timeout
(可选步骤)
若APP调用
le_privacy_set_param()
设置GAP_PARAM_PRIVACY_TIMEOUT
,则在初始化流程中GAP层将设置 Resolvable Private Address Timeout。6
Set extended adv mode
(可选步骤)
若APP调用
le_set_gap_param()
设置GAP_PARAM_USE_EXTENDED_ADV
,则在初始化流程中GAP层将设置LE Advertising Extensions
模式。7
Set random address
(可选步骤)
若APP调用
le_set_gap_param()
设置GAP_PARAM_RANDOM_ADDR
,则在初始化流程中GAP层将设置random address。8
Set fixed passkey
(可选步骤)
设置
GAP_PARAM_BOND_FIXED_PASSKEY
和GAP_PARAM_BOND_FIXED_PASSKEY_ENABLE
的值。9
Set default physical
(可选步骤)
若APP调用
le_set_gap_param()
设置GAP_PARAM_DEFAULT_PHYS_PREFER
,GAP_PARAM_DEFAULT_TX_PHYS_PREFER
或GAP_PARAM_DEFAULT_RX_PHYS_PREFER
, 则在初始化流程中GAP将设置默认physical。10
Register GATT services
(必要步骤)
注册基于GATT的services。
11
Send GAP_INIT_STATE_STACK_READY to APP
GAP初始化流程已完成。
GAP参数的初始化
以Bluetooth sample工程为例,可以通过修改 app_le_gap_init()
函数的代码,在 main.c
中实现GAP参数的初始化。
Device Name和Device Appearance的配置
在 gap_le.h
的 T_GAP_LE_PARAM_TYPE
中定义参数类型。
Device Name的配置
Device Name的配置用于设置该设备GAP Service中Device Name Characteristic的值。若在Advertising数据中设置Device Name,那么Advertising数据中的Device Name需要与GAP Service的Device Name Characteristic的值相同,否则会出现互操作性问题。
/** @brief GAP - Advertisement data (max size = 31 bytes, best kept short to conserve power) */
static const uint8_t adv_data[] =
{
/* Flags */
0x02, /* length */
GAP_ADTYPE_FLAGS, /* type="Flags" */
GAP_ADTYPE_FLAGS_LIMITED | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
/* Service */
0x03, /* length */
GAP_ADTYPE_16BIT_COMPLETE,
LO_WORD(GATT_UUID_SIMPLE_PROFILE),
HI_WORD(GATT_UUID_SIMPLE_PROFILE),
/* Local name */
0x0F, /* length */
GAP_ADTYPE_LOCAL_NAME_COMPLETE,
'B', 'L', 'E', '_', 'P', 'E', 'R', 'I', 'P', 'H', 'E', 'R', 'A', 'L',
};
void app_le_gap_init(void)
{
/* Device name and device appearance */
uint8_t device_name[GAP_DEVICE_NAME_LEN] = "BLE_PERIPHERAL";
......
/* Set device name and device appearance */
le_set_gap_param(GAP_PARAM_DEVICE_NAME, GAP_DEVICE_NAME_LEN, device_name);
......
}
Bluetooth Stack目前支持的Device Name字符串的最大长度是40字节(包括结束符)。如果device name字符串超过40字节,则会出现字符串被截断的情况。
#define GAP_DEVICE_NAME_LEN (39+1) //!< Max length of device name, if device name length exceeds it, it will be truncated.
Device Appearance的配置
Device Appearance的配置用于设置该设备GAP Service中Device Appearance Characteristic的值。若在Advertising数据中设置Device Appearance,那么Advertising数据中的Device Appearance需要与GAP Service的Device Appearance Characteristic的值相同,否则会出现互操作性问题。
Device Appearance用于描述设备的类型,例如键盘、鼠标、温度计、血压计等。在 gap_le_types.h
中定义可以使用的数值。
/** @defgroup GAP_LE_APPEARANCE_VALUES GAP Appearance Values
* @{
*/
#define GAP_GATT_APPEARANCE_UNKNOWN 0
#define GAP_GATT_APPEARANCE_GENERIC_PHONE 64
#define GAP_GATT_APPEARANCE_GENERIC_COMPUTER 128
#define GAP_GATT_APPEARANCE_GENERIC_WATCH 192
#define GAP_GATT_APPEARANCE_WATCH_SPORTS_WATCH 193
示例代码如下:
/** @brief GAP - scan response data (max size = 31 bytes) */
static const uint8_t scan_rsp_data[] =
{
0x03, /* length */
GAP_ADTYPE_APPEARANCE, /* type="Appearance" */
LO_WORD(GAP_GATT_APPEARANCE_UNKNOWN),
HI_WORD(GAP_GATT_APPEARANCE_UNKNOWN),
};
void app_le_gap_init(void)
{
/* Device name and device appearance */
uint16_t appearance = GAP_GATT_APPEARANCE_UNKNOWN;
......
/* Set device name and device appearance */
le_set_gap_param(GAP_PARAM_APPEARANCE, sizeof(appearance), &appearance);
......
}
Advertising参数的配置
在 gap_adv.h
的 T_LE_ADV_PARAM_TYPE
中定义Advertising参数类型,用户可以配置的Advertising参数如下:
void app_le_gap_init(void)
{
/* Advertising parameters */
uint8_t adv_evt_type = GAP_ADTYPE_ADV_IND;
uint8_t adv_direct_type = GAP_REMOTE_ADDR_LE_PUBLIC;
uint8_t adv_direct_addr[GAP_BD_ADDR_LEN] = {0};
uint8_t adv_chann_map = GAP_ADVCHAN_ALL;
uint8_t adv_filter_policy = GAP_ADV_FILTER_ANY;
uint16_t adv_int_min = DEFAULT_ADVERTISING_INTERVAL_MIN;
uint16_t adv_int_max = DEFAULT_ADVERTISING_INTERVAL_MAX;
......
/* Set advertising parameters */
le_adv_set_param(GAP_PARAM_ADV_EVENT_TYPE, sizeof(adv_evt_type), &adv_evt_type);
le_adv_set_param(GAP_PARAM_ADV_DIRECT_ADDR_TYPE, sizeof(adv_direct_type), &adv_direct_type);
le_adv_set_param(GAP_PARAM_ADV_DIRECT_ADDR, sizeof(adv_direct_addr), adv_direct_addr);
le_adv_set_param(GAP_PARAM_ADV_CHANNEL_MAP, sizeof(adv_chann_map), &adv_chann_map);
le_adv_set_param(GAP_PARAM_ADV_FILTER_POLICY, sizeof(adv_filter_policy), &adv_filter_policy);
le_adv_set_param(GAP_PARAM_ADV_INTERVAL_MIN, sizeof(adv_int_min), &adv_int_min);
le_adv_set_param(GAP_PARAM_ADV_INTERVAL_MAX, sizeof(adv_int_max), &adv_int_max);
le_adv_set_param(GAP_PARAM_ADV_DATA, sizeof(adv_data), (void *)adv_data);
le_adv_set_param(GAP_PARAM_SCAN_RSP_DATA, sizeof(scan_rsp_data), (void *)scan_rsp_data);
}
adv_evt_type表示Advertising类型,不同类型的advertising需要不同的参数,具体如下表所示。
adv_evt_type |
GAP_ADTYPE_ADV_IND |
GAP_ADTYPE_ADV_HDC_DIRECT_IND |
GAP_ADTYPE_ADV_SCAN_IND |
GAP_ADTYPE_ADV_NONCONN_IND |
GAP_ADTYPE_ADV_LDC_DIRECT_IND |
---|---|---|---|---|---|
adv_int_min |
Y |
Ignore |
Y |
Y |
Y |
adv_int_max |
Y |
Ignore |
Y |
Y |
Y |
adv_direct_type |
Ignore |
Y |
Ignore |
Ignore |
Y |
adv_direct_addr |
Ignore |
Y |
Ignore |
Ignore |
Y |
adv_chann_map |
Y |
Y |
Y |
Y |
Y |
adv_filter_policy |
Y |
Ignore |
Y |
Y |
Ignore |
Allow establish link |
Y |
Y |
N |
N |
Y |
Scan参数的配置
在 gap_scan.h
的 T_LE_SCAN_PARAM_TYPE
中定义Scan参数类型。
用户可以配置的Scan参数如下:
void app_le_gap_init(void)
{
/* Scan parameters */
uint8_t scan_mode = GAP_SCAN_MODE_ACTIVE;
uint16_t scan_interval = DEFAULT_SCAN_INTERVAL;
uint16_t scan_window = DEFAULT_SCAN_WINDOW;
uint8_t scan_filter_policy = GAP_SCAN_FILTER_ANY;
uint8_t scan_filter_duplicate = GAP_SCAN_FILTER_DUPLICATE_ENABLE;
/* Set scan parameters */
le_scan_set_param(GAP_PARAM_SCAN_MODE, sizeof(scan_mode), &scan_mode);
le_scan_set_param(GAP_PARAM_SCAN_INTERVAL, sizeof(scan_interval), &scan_interval);
le_scan_set_param(GAP_PARAM_SCAN_WINDOW, sizeof(scan_window), &scan_window);
le_scan_set_param(GAP_PARAM_SCAN_FILTER_POLICY, sizeof(scan_filter_policy),
&scan_filter_policy);
le_scan_set_param(GAP_PARAM_SCAN_FILTER_DUPLICATES, sizeof(scan_filter_duplicate),
&scan_filter_duplicate);
}
参数说明:
scan_mode: T_GAP_SCAN_MODE。
scan_interval: scan interval的取值范围为0x0004~0x4000 (单位为625us)。
scan_window: scan window的取值范围为0x0004~0x4000 (单位为625us)。
scan_filter_policy: T_GAP_SCAN_FILTER_POLICY。
scan_filter_duplicate: T_GAP_SCAN_FILTER_DUPLICATE,该参数用于决定是否过滤重复的Advertising数据,当scan_filter_policy参数为
GAP_SCAN_FILTER_DUPLICATE_ENABLE
时,将在协议栈中过滤重复的Advertising数据,且不会通知APP。
Bond Manager参数的配置
在 gap.h
的 T_GAP_PARAM_TYPE
中和 gap_bond_le.h
的 T_LE_BOND_PARAM_TYPE
中定义参数类型。用户可以配置的Bond Manager参数如下:
void app_le_gap_init(void)
{
/* GAP Bond Manager parameters */
uint8_t auth_pair_mode = GAP_PAIRING_MODE_PAIRABLE;
uint16_t auth_flags = GAP_AUTHEN_BIT_BONDING_FLAG;
uint8_t auth_io_cap = GAP_IO_CAP_NO_INPUT_NO_OUTPUT;
uint8_t auth_oob = false;
uint8_t auth_use_fix_passkey = false;
uint32_t auth_fix_passkey = 0;
uint16_t auth_sec_req_flags = GAP_AUTHEN_BIT_BONDING_FLAG;
......
/* Setup the GAP Bond Manager */
gap_set_param(GAP_PARAM_BOND_PAIRING_MODE, sizeof(auth_pair_mode), &auth_pair_mode);
gap_set_param(GAP_PARAM_BOND_AUTHEN_REQUIREMENTS_FLAGS, sizeof(auth_flags), &auth_flags);
gap_set_param(GAP_PARAM_BOND_IO_CAPABILITIES, sizeof(auth_io_cap), &auth_io_cap);
gap_set_param(GAP_PARAM_BOND_OOB_ENABLED, sizeof(auth_oob), &auth_oob);
le_bond_set_param(GAP_PARAM_BOND_FIXED_PASSKEY, sizeof(auth_fix_passkey), &auth_fix_passkey);
le_bond_set_param(GAP_PARAM_BOND_FIXED_PASSKEY_ENABLE, sizeof(auth_use_fix_passkey),
&auth_use_fix_passkey);
le_bond_set_param(GAP_PARAM_BOND_SEC_REQ_REQUIREMENT, sizeof(auth_sec_req_flags),
&auth_sec_req_flags);
}
参数说明:
auth_pair_mode: 决定设备是否处于可配对模式:
GAP_PAIRING_MODE_PAIRABLE
: 设备处于可配对模式。GAP_PAIRING_MODE_NO_PAIRING
: 设备处于不可配对模式。
auth_flags: 表示要求的security属性的位域:
auth_io_cap: T_GAP_IO_CAP,表示设备的输入输出能力。
auth_oob: 表示是否使能Out of Band (OOB):
true
: 设置OOB标志位。false
: 未设置OOB标志位。
auth_use_fix_passkey: 表示当配对方法为passkey entry且本地设备需要生成passkey时,是使用随机生成的passkey还是固定的passkey:
true
: 使用固定的passkey。false
: 使用随机生成的passkey。
auth_fix_passkey: 配对时使用的固定passkey的值,当auth_use_fix_passkey 参数为true时auth_fix_passkey参数是有效的。
auth_sec_req_enable: 决定在建立connection之后,是否发起配对流程。
auth_sec_req_flags: 表示要求的security属性的位域。
LE Advertising Extensions参数的配置
本节内容仅适用于使用LE Advertising Extensions的设备。
首先,设备需要配置 GAP_PARAM_USE_EXTENDED_ADV
以使用LE Advertising Extensions。
然后,根据设备使用的GAP角色配置extended advertising相关参数或extended scan相关参数。具体信息参见 LE Peripheral Extended ADV 和 LE Central Extended Scan。
配置GAP_PARAM_USE_EXTENDED_ADV
为使用LE Advertising Extensions,APP必须配置 GAP_PARAM_USE_EXTENDED_ADV
为true。
void app_le_gap_init(void)
{
/* LE Advertising Extensions parameters */
bool use_extended = true;
......
/* Use LE Advertising Extensions */
le_set_gap_param(GAP_PARAM_USE_EXTENDED_ADV, sizeof(use_extended), &use_extended);
......
}
Extended Advertising相关参数的配置
适用于Peripheral角色或Broadcaster角色。
开发者可调用 le_init_ext_adv_params_ext_conn()
设置GAP参数,以使用extended advertising PDUs发送Connectable Undirected Advertising数据包。
首先,调用 le_ext_adv_create_adv_handle()
创建advertising handle以识别advertising set。然后针对该advertising set,设置GAP extended advertising参数和advertising数据,示例代码如下:
void app_le_gap_init(void)
{
......
/* Initialize extended advertising related parameters */
le_init_ext_adv_params_ext_conn();
......
}
在 gap_ext_adv.h
中提供关于 extended advertising 的APIs,更多信息参见 gap_ext_adv.h
。 adv_event_prop
定义advertising的properties,不同adv_event_prop的advertising需要不同的参数。
根据使用的advertising PDUs,advertising event properties可分为两类:使用 legacy advertising PDUs 的advertising event properties和 extended advertising PDUs 的advertising event properties。
使用 legacy advertising PDUs 的advertising event properties如下表所示。若使用 legacy advertising PDUs ,advertising数据或scan response数据不能超过 31 字节。
adv_event_prop |
LE_EXT_ADV_LEGACY_ADV_CONN_SCAN_UNDIRECTED |
LE_EXT_ADV_LEGACY_ADV_CONN_HIGH_DUTY_DIRECTED |
LE_EXT_ADV_LEGACY_ADV_SCAN_UNDIRECTED |
LE_EXT_ADV_LEGACY_ADV_NON_SCAN_NON_CONN_UNDIRECTED |
LE_EXT_ADV_LEGACY_ADV_CONN_LOW_DUTY_DIRECTED |
---|---|---|---|---|---|
adv_handle |
Y |
Y |
Y |
Y |
Y |
primary_adv_interval_min |
Y |
Ignore |
Y |
Y |
Y |
primary_adv_interval_max |
Y |
Ignore |
Y |
Y |
Y |
primary_adv_channel_map |
Y |
Y |
Y |
Y |
Y |
peer_address_type |
Ignore |
Y |
Ignore |
Ignore |
Y |
p_peer_address |
Ignore |
Y |
Ignore |
Ignore |
Y |
filter_policy |
Y |
Ignore |
Y |
Y |
Ignore |
primary_adv_phy |
LE 1M PHY |
LE 1M PHY |
LE 1M PHY |
LE 1M PHY |
LE 1M PHY |
secondary_adv_phy |
Ignore |
Ignore |
Ignore |
Ignore |
Ignore |
Allow establish link |
Y |
Y |
N |
N |
Y |
Advertising data |
Y |
N |
Y |
Y |
N |
Scan response data |
Y |
N |
Y |
N |
N |
使用extended advertising PDUs的advertising event properties如下表所示。
在 bt5_peripheral_stack_api.h
和 bt5_peripheral_stack_api.c
中提供关于使用extended advertising PDUs的extended advertising参数设置的示例代码。
adv_event_prop |
LE_EXT_ADV_EXTENDED_ADV_NON_SCAN_NON_CONN_UNDIRECTED |
LE_EXT_ADV_EXTENDED_ADV_NON_SCAN_NON_CONN_DIRECTED |
LE_EXT_ADV_EXTENDED_ADV_CONN_UNDIRECTED |
LE_EXT_ADV_EXTENDED_ADV_CONN_DIRECTED |
LE_EXT_ADV_EXTENDED_ADV_SCAN_UNDIRECTED |
LE_EXT_ADV_EXTENDED_ADV_SCAN_DIRECTED |
---|---|---|---|---|---|---|
adv_handle |
Y |
Y |
Y |
Y |
Y |
Y |
primary_adv_interval_min |
Y |
Y |
Y |
Y |
Y |
Y |
primary_adv_interval_max |
Y |
Y |
Y |
Y |
Y |
Y |
primary_adv_channel_map |
Y |
Y |
Y |
Y |
Y |
Y |
peer_address_type |
Ignore |
Y |
Ignore |
Y |
Ignore |
Y |
p_peer_address |
Ignore |
Y |
Ignore |
Y |
Ignore |
Y |
filter_policy |
Y |
Ignore |
Y |
Ignore |
Y |
Ignore |
primary_adv_phy |
LE 1M PHY LE Coded PHY |
LE 1M PHY LE Coded PHY |
LE 1M PHY LE Coded PHY |
LE 1M PHY LE Coded PHY |
LE 1M PHY LE Coded PHY |
LE 1M PHY LE Coded PHY |
secondary_adv_phy |
LE 1M PHY LE 2M PHY LE Coded PHY |
LE 1M PHY LE 2M PHY LE Coded PHY |
LE 1M PHY LE 2M PHY LE Coded PHY |
LE 1M PHY LE 2M PHY LE Coded PHY |
LE 1M PHY LE 2M PHY LE Coded PHY |
LE 1M PHY LE 2M PHY LE Coded PHY |
Allow establish link |
N |
N |
Y |
Y |
N |
N |
Advertising data |
Y |
Y |
Y |
Y |
N |
N |
Scan response data |
N |
N |
N |
N |
Y |
Y |
Extended Scan相关参数的配置
适用于Central角色或Observer角色。
备注
要使用extended scan,必须将 GAP_PARAM_USE_EXTENDED_ADV
配置成true。
开发者可以调用 le_ext_scan_set_param()
来初始化extended scan参数,调用 le_ext_scan_set_phy_param()
来初始化extended scan PHY参数。之后,可以调用 le_ext_scan_start()
开启extended scanning。
示例代码如下:
static T_USER_CMD_PARSE_RESULT cmd_escan(T_USER_CMD_PARSED_VALUE *p_parse_value)
{
......
/* Initialize extended scan parameters */
le_ext_scan_set_param(GAP_PARAM_EXT_SCAN_LOCAL_ADDR_TYPE, sizeof(own_address_type),
&own_address_type);
le_ext_scan_set_param(GAP_PARAM_EXT_SCAN_PHYS, sizeof(scan_phys),
&scan_phys);
le_ext_scan_set_param(GAP_PARAM_EXT_SCAN_DURATION, sizeof(ext_scan_duration),
&ext_scan_duration);
le_ext_scan_set_param(GAP_PARAM_EXT_SCAN_PERIOD, sizeof(ext_scan_period),
&ext_scan_period);
le_ext_scan_set_param(GAP_PARAM_EXT_SCAN_FILTER_POLICY, sizeof(ext_scan_filter_policy),
&ext_scan_filter_policy);
le_ext_scan_set_param(GAP_PARAM_EXT_SCAN_FILTER_DUPLICATES, sizeof(ext_scan_filter_duplicate),
&ext_scan_filter_duplicate);
/* Initialize extended scan PHY parameters */
le_ext_scan_set_phy_param(LE_SCAN_PHY_LE_1M, &extended_scan_param[0]);
le_ext_scan_set_phy_param(LE_SCAN_PHY_LE_CODED, &extended_scan_param[1]);
/* Enable extended scan */
cause = le_ext_scan_start();
return (T_USER_CMD_PARSE_RESULT)cause;
}
Extended scan相关APIs在 gap_ext_scan.h
中提供,开发者可以参考注释使用接口。
其它参数的配置
GAP_PARAM_SLAVE_INIT_GATT_MTU_REQ的配置
void app_le_gap_init(void)
{
uint8_t slave_init_mtu_req = false;
......
le_set_gap_param(GAP_PARAM_SLAVE_INIT_GATT_MTU_REQ, sizeof(slave_init_mtu_req),
&slave_init_mtu_req);
......
}
该参数仅适用于Peripheral角色,决定在建立connection后是否主动发送exchange MTU request。
一些数目的配置
LE链路数量配置
由于支持多链路和APP的需求,开发者可以使用MP Tool工具配置LE链路数量。
本节以 LE Scatternet 示例工程为例介绍LE链路数量的配置。 假设LE Scatternet示例工程支持一个作为Peripheral角色的链路和一个作为Central角色的链路,因此:
LE Link number不得少于2。
LE master Link number不得少于1。
LE slave Link number不得少于1。
备注
LE master Link number 配置LE Central链路数量, LE slave Link number 配置LE Peripheral链路数量。
配置LE最大绑定设备数量
由于支持多链路,设备需要保存多个远程设备的绑定信息。 LE Scatternet示例工程可以使用MP Tool工具配置LE最大绑定设备数量。
在应用程序中初始化链路数量
如GAP启动流程中所述,应用程序调用 le_gap_init()
来初始化GAP并设置连接数。由 le_gap_init()
配置的连接数应小于或等于通过MP Tool工具配置的LE连接数。
void app_ble_gap_param_init(void)
{
......
/*Initialize ble link*/
uint8_t supported_max_le_link_num = le_get_max_link_num();
uint8_t link_num = ((MAX_BLE_LINK_NUM <= supported_max_le_link_num) ? MAX_BLE_LINK_NUM :
supported_max_le_link_num);
le_gap_init(link_num);
}
功能
GAP层设备状态
GAP层设备状态由advertising状态、scan状态和connection状态组成。若使能LE Advertising Extensions,将使用extended advertising状态来替代advertising状态。每一个状态都有相应的子状态,本节内容将介绍各子状态。
Advertising
Advertising 状态
Advertising状态有四个子状态,idle状态、start状态、advertising状态和stop状态,在 gap_msg.h
中定义Advertising状态的子状态。
/* GAP Advertising State */
#define GAP_ADV_STATE_IDLE 0 // Idle, no advertising.
#define GAP_ADV_STATE_START 1 // Start Advertising. A temporary state, haven't received the result.
#define GAP_ADV_STATE_ADVERTISING 2 // Advertising.
#define GAP_ADV_STATE_STOP 3 //Stop Advertising. A temporary state, haven't received the result.
idle状态
默认状态,不发送advertisement。
start状态
在idle状态启动advertising之后,启动advertising的流程尚未完成。start状态为临时状态,若成功启动advertising,则Advertising状态进入advertising状态;若启动advertising失败,则Advertising状态回到idle状态。
advertising状态
成功启动advertising。在此状态下,设备发送advertisement。若advertising类型是high duty cycle directed advertising,一旦超时,Advertising状态进入idle状态。或者当advertising类型为可连接广播时,一旦连上线,Advertising状态进入idle状态。
stop状态
在advertising状态停止advertising之后,停止advertising的流程尚未完成。stop状态为临时状态,若成功停止advertising,则Advertising状态进入idle状态;若停止advertising失败,则Advertising状态回到advertising状态。
Scan
Scan状态
Scan状态有四个子状态,idle状态、start状态、scanning状态和stop状态,在 gap_msg.h
中定义Scan状态的子状态。
/* GAP Scan State \*/
#define GAP_SCAN_STATE_IDLE 0 //Idle, no scanning.
#define GAP_SCAN_STATE_START 1 //Start scanning. A temporary state, haven't received the result.
#define GAP_SCAN_STATE_SCANNING 2 //Scanning.
#define GAP_SCAN_STATE_STOP 3 //Stop scanning, A temporary state, haven't received the result.
idle状态
默认状态,不进行scan。
start状态
在idle状态启动scan之后,启动scan的流程尚未完成。start状态为临时状态,若成功启动scan,则Scan状态进入scanning状态;若启动scan失败,则Scan状态回到idle状态。
scanning状态
成功启动scan。在此状态下,设备进行scan,接收advertisement。使用Extended Scan时,若 Duration 参数不为零且 Period 参数为零,一旦scan时间达到Duration,Scan状态将进入idle状态。
stop状态
在scanning状态停止scan之后,停止scan的流程尚未完成。stop状态为临时状态,若成功停止scan,则Scan状态进入idle状态;若停止scan失败,则Scan状态回到scanning状态。
Connection
Connection状态
由于支持多链路,当GAP Connection状态为idle状态时,Link状态可能是connected或disconnected。因此,Connection状态的变化需要结合GAP Connection状态和Link状态。
GAP Connection状态的子状态包括idle状态和connecting状态,GAP Connection状态只适用于Central角色,在 gap_msg.h
中定义GAP Connection状态的子状态。
#define GAP_CONN_DEV_STATE_IDLE 0 //!< Idle.
#define GAP_CONN_DEV_STATE_INITIATING 1 //!< Initiating Connection.
备注
对于Central角色, GAP一次只能创建一个连接,这意味着当GAP Connection状态处于connecting状态时,APP无法创建另一个链路。
Link状态有四个子状态,disconnected状态、connecting状态、connected状态和disconnecting状态,在 gap_msg.h
中定义Link状态的子状态。
/* Link Connection State \*/
typedef enum{
GAP_CONN_STATE_DISCONNECTED, // Disconnected.
GAP_CONN_STATE_CONNECTING, // Connecting.
GAP_CONN_STATE_CONNECTED, // Connected.
GAP_CONN_STATE_DISCONNECTING // Disconnecting.
}T_GAP_CONN_STATE;
作为central角色主动创建connection时的Connection状态变化不同于作为Peripheral角色被动接收connection indication时的Connection状态变化。以下章节分别介绍这两种场景。
Central角色主动的Connection状态转换
idle状态 | disconnected状态
GAP Connection状态为idle状态,Link状态为disconnected状态,未建立connection。
connecting状态 | connecting状态
Central创建connection,创建流程尚未完成。这是临时状态,GAP Connection状态为connecting状态,Link状态为connecting状态。若成功创建connection,GAP Connection状态转换为idle状态,Link状态转换为connected状态。若创建connection失败,GAP Connection状态回到idle状态,Link状态回到disconnected状态。在此状态下,Central可以断开链路,此时,GAP Connection状态回到idle状态,Link状态转换为disconnecting状态。
idle状态 | connected状态
成功创建connection,GAP Connection状态为idle状态,Link状态为connected状态。
idle状态 | disconnecting状态
Central终止connection,终止流程尚未完成。这是临时状态,GAP Connection状态为idle状态,Link状态为disconnecting状态。若成功终止connection,Link状态转换为disconnected状态。
Peripheral角色被动的Connection状态转换
peripheral接受connection
当Peripheral收到connect indication后,GAP Advertising状态将从advertising状态转换为idle状态,Link状态将从disconnected状态转换为connecting状态。当创建connection流程完成之后,Link状态将进入connected状态。
对端设备断开connection
当对端设备断开connection且本地设备收到disconnect indication后,本地设备的Link状态将从connected状态转换为disconnected状态。
Extended Advertising
Extended Advertising状态
不使用LE Advertising Extensions时,设备使用Advertising状态,可以视为存在一个简化的advertising set。本节内容仅适用于使用LE Advertising Extensions的设备。
由于支持LE Advertising Extensions,设备可以同时启动或停止多个advertising set,因此Extended Advertising状态的变化需要结合advertising set。Extended
Advertising状态有四个子状态,idle状态、start状态、advertising状态和stop状态,在 gap_ext_adv.h
中定义Extended Advertising状态的子状态。
/** @brief GAP extended advertising state. */
typedef enum
{
EXT_ADV_STATE_IDLE, /**< Idle, no advertising. */
EXT_ADV_STATE_START, /**< Start Advertising. A temporary state, haven't received the result. */
EXT_ADV_STATE_ADVERTISING, /**< Advertising. */
EXT_ADV_STATE_STOP /**< Stop Advertising. A temporary state, haven't received the result. */
}T_GAP_EXT_ADV_STATE;
idle状态
默认状态,不发送advertisement。
start状态
在idle状态启动extended advertising之后,启动extended advertising的流程尚未完成。start状态为临时状态,若成功启动extended advertising,则Extended Advertising状态进入advertising状态;若启动extended advertising失败,则Extended Advertising状态回到idle状态。
advertising状态
成功启动extended advertising。在此状态下,设备发送advertisement。若Duration参数不为零,一旦advertising时间达到Duration,Extended Advertising状态将进入idle状态。若Extended Advertising Events的最大值参数不为零,一旦达到Extended Advertising Events的最大值,Extended Advertising状态将进入idle状态。若广播可连接,一旦连上线,Extended Advertising状态将进入idle状态。
stop状态
在advertising状态停止extended advertising之后,停止extended advertising的流程尚未完成。stop状态为临时状态,若成功停止extended advertising,则Extended Advertising状态进入idle状态;若停止extended advertising失败,则Extended Advertising状态回到advertising状态。
GAP消息
GAP消息包括蓝牙状态消息和GAP API消息。蓝牙状态消息用于向APP通知蓝牙状态信息,包括device状态转换、connection状态转换以及bond状态转换等。GAP API消息用于向APP通知调用API后函数的执行状态。每个API都有相应的消息,更多关于GAP消息的信息参见 Bluetooth LE GAP消息 和 Bluetooth LE GAP回调函数 。
蓝牙状态消息
概述
本节介绍Bluetooth LE GAP消息模块,在 gap_msg.h
中定义GAP消息类型和消息数据结构。Bluetooth LE GAP消息可以分为以下四种类型:
/** @defgroup GAP_MSG_TYPE GAP Bluetooth Message Type Definitions
* @brief Define the subtype of Message IO_MSG_TYPE_BT_STATUS.
* @{
*/
#define GAP_MSG_LE_DEV_STATE_CHANGE 0x01 //!< Device state change msg type.
#define GAP_MSG_LE_CONN_STATE_CHANGE 0x02 //!< Connection state change msg type.
#define GAP_MSG_LE_CONN_PARAM_UPDATE 0x03 //!< Connection parameter update changed msg type.
#define GAP_MSG_LE_CONN_MTU_INFO 0x04 //!< Connection MTU size info msg type.
#define GAP_MSG_LE_AUTHEN_STATE_CHANGE 0x05 //!< Authentication state change msg type.
#define GAP_MSG_LE_BOND_PASSKEY_DISPLAY 0x06 //!< Bond passkey display msg type.
#define GAP_MSG_LE_BOND_PASSKEY_INPUT 0x07 //!< Bond passkey input msg type.
#define GAP_MSG_LE_BOND_OOB_INPUT 0x08 //!< Bond passkey oob input msg type.
#define GAP_MSG_LE_BOND_USER_CONFIRMATION 0x09 //!< Bond user confirmation msg type.
#define GAP_MSG_LE_BOND_JUST_WORK 0x0A //!< Bond user confirmation msg type.
#define GAP_MSG_LE_EXT_ADV_STATE_CHANGE 0x0B //!< Extended advertising state change msg type.
Bluetooth LE GAP消息处理流程如下:
APP调用
gap_start_bt_stack()
初始化Bluetooth LE GAP消息模块,初始化代码如下:void app_main_task(void *p_param) { uint8_t event; os_msg_queue_create(&io_queue_handle, "ioQ", MAX_NUMBER_OF_IO_MESSAGE, sizeof(T_IO_MSG)); os_msg_queue_create(&evt_queue_handle, "evtQ", MAX_NUMBER_OF_EVENT_MESSAGE, sizeof(uint8_t)); gap_start_bt_stack(evt_queue_handle, io_queue_handle, MAX_NUMBER_OF_GAP_MESSAGE); ...... }
GAP向io_queue_handle发送GAP消息,APP task接收到GAP消息,并调用
app_handle_io_msg()
来处理该消息。(事件:EVENT_IO_TO_APP,类型: IO_MSG_TYPE_BT_STATUS )void app_main_task(void *p_param) { ...... while (true) { if (os_msg_recv(evt_queue_handle, &event, 0xFFFFFFFF) == true) { if (event == EVENT_IO_TO_APP) { T_IO_MSG io_msg; if (os_msg_recv(io_queue_handle, &io_msg, 0) == true) { app_handle_io_msg(io_msg); } } ...... } ......
GAP消息处理函数如下:
void app_handle_io_msg(T_IO_MSG io_msg) { uint16_t msg_type = io_msg.type; switch (msg_type) { case IO_MSG_TYPE_BT_STATUS: { app_handle_gap_msg(&io_msg); } break; default: break; } } void app_handle_gap_msg(T_IO_MSG *p_gap_msg) { T_LE_GAP_MSG gap_msg; uint8_t conn_id; memcpy(&gap_msg, &p_gap_msg->u.param, sizeof(p_gap_msg->u.param)); APP_PRINT_TRACE1("app_handle_gap_msg: subtype %d", p_gap_msg->subtype); switch (p_gap_msg->subtype) { case GAP_MSG_LE_DEV_STATE_CHANGE: { app_handle_dev_state_evt(gap_msg.msg_data.gap_dev_state_change.new_state, gap_msg.msg_data.gap_dev_state_change.cause); } break; ...... }
Device状态消息
GAP_MSG_LE_DEV_STATE_CHANGE
该消息用于通知GAP Device状态( T_GAP_DEV_STATE
),GAP Device状态包括以下五种子状态:
gap_init_state: GAP初始化状态。
gap_adv_state: GAP Advertising状态。
gap_adv_sub_state: GAP Advertising子状态(该状态仅适用于gap_adv_state为
GAP_ADV_STATE_IDLE
的情况)。gap_scan_state: GAP Scan状态。
gap_conn_state: GAP Connection状态。
消息数据结构为 T_GAP_DEV_STATE_CHANGE
。
/** @brief Device State.*/
typedef struct
{
uint8_t gap_init_state: 1; //!< @ref GAP_INIT_STATE
uint8_t gap_adv_sub_state: 1; //!< @ref GAP_ADV_SUB_STATE
uint8_t gap_adv_state: 2; //!< @ref GAP_ADV_STATE
uint8_t gap_scan_state: 2; //!< @ref GAP_SCAN_STATE
uint8_t gap_conn_state: 2; //!< @ref GAP_CONN_STATE
} T_GAP_DEV_STATE;
/** @brief The msg_data of GAP_MSG_LE_DEV_STATE_CHANGE.*/
typedef struct
{
T_GAP_DEV_STATE new_state;
uint16_t cause;
} T_GAP_DEV_STATE_CHANGE;
示例代码如下:
void app_handle_dev_state_evt(T_GAP_DEV_STATE new_state, uint16_t cause)
{
APP_PRINT_INFO4("app_handle_dev_state_evt: init state %d, adv state %d, scan state %d, cause 0x%x",
new_state.gap_init_state, new_state.gap_adv_state, new_state.gap_scan_state, cause);
if (gap_dev_state.gap_init_state != new_state.gap_init_state)
{
if (new_state.gap_init_state == GAP_INIT_STATE_STACK_READY)
{
APP_PRINT_INFO0("GAP stack ready");
}
}
if (gap_dev_state.gap_scan_state != new_state.gap_scan_state)
{
if (new_state.gap_scan_state == GAP_SCAN_STATE_IDLE)
{
APP_PRINT_INFO0("GAP scan stop");
}
else if(new_state.gap_scan_state == GAP_SCAN_STATE_SCANNING)
{
APP_PRINT_INFO0("GAP scan start");
}
}
if (gap_dev_state.gap_adv_state != new_state.gap_adv_state)
{
if (new_state.gap_adv_state == GAP_ADV_STATE_IDLE)
{
if (new_state.gap_adv_sub_state == GAP_ADV_TO_IDLE_CAUSE_CONN)
{
APP_PRINT_INFO0("GAP adv stoped: because connection created");
}
else
{
APP_PRINT_INFO0("GAP adv stoped");
}
}
else if (new_state.gap_adv_state == GAP_ADV_STATE_ADVERTISING)
{
APP_PRINT_INFO0("GAP adv start");
}
}
gap_dev_state = new_state;
}
Connection相关消息
GAP_MSG_LE_CONN_STATE_CHANGE
该消息用于通知Link状态( T_GAP_CONN_STATE
)。
消息数据结构为 T_GAP_CONN_STATE_CHANGE
,示例代码如下:
void app_handle_conn_state_evt(uint8_t conn_id, T_GAP_CONN_STATE new_state, uint16_t disc_cause)
{
APP_PRINT_INFO4("app_handle_conn_state_evt: conn_id %d old_state %d new_state %d, disc_cause 0x%x",
conn_id, gap_conn_state, new_state, disc_cause);
switch (new_state)
{
case GAP_CONN_STATE_DISCONNECTED:
{
if ((disc_cause != (HCI_ERR | HCI_ERR_REMOTE_USER_TERMINATE))
&& (disc_cause != (HCI_ERR | HCI_ERR_LOCAL_HOST_TERMINATE)))
{
APP_PRINT_ERROR1("app_handle_conn_state_evt: connection lost cause 0x%x", disc_cause);
}
}
break;
case GAP_CONN_STATE_CONNECTED:
{
......
}
break;
default:
break;
}
gap_conn_state = new_state;
}
GAP_MSG_LE_CONN_PARAM_UPDATE
该消息用于通知connection参数更新状态,更新状态包括以下三个子状态:
- 若本地设备调用
le_update_conn_param()
更新Connection参数,当Connection参数更新请求成功但未收到connection update complete event时,GAP层将发送该状态消息。 - 更新失败,参数cause表示失败原因。
消息数据结构为 T_GAP_CONN_PARAM_UPDATE
,示例代码如下:
void app_handle_conn_param_update_evt(uint8_t conn_id, uint8_t status, uint16_t cause)
{
switch(status)
{
case GAP_CONN_PARAM_UPDATE_STATUS_SUCCESS:
......
break;
case GAP_CONN_PARAM_UPDATE_STATUS_FAIL:
......
break;
case GAP_CONN_PARAM_UPDATE_STATUS_PENDING:
......
break;
}
}
GAP_MSG_LE_CONN_MTU_INFO
该消息用于通知exchange MTU(Maximum Transmission Unit) procedure已完成。Exchange MTU
procedure的目的是更新client和server之间交互数据包的最大长度,即更新ATT_MTU。消息数据结构为 T_GAP_CONN_MTU_INFO
,示例代码如下:
void app_handle_conn_mtu_info_evt(uint8_t conn_id, uint16_t mtu_size)
{
APP_PRINT_INFO2("app_handle_conn_mtu_info_evt: conn_id %d, mtu_size %d", conn_id, mtu_size);
}
Authentication相关消息
配对方法与Authentication消息的对应关系如表 Authentication相关消息 所示。
Pairing Method |
Message |
---|---|
Just Works |
GAP_MSG_LE_BOND_JUST_WORK |
NumericComparison |
GAP_MSG_LE_BOND_USER_CONFIRMATION |
Passkey Entry |
GAP_MSG_LE_BOND_PASSKEY_INPUT, GAP_MSG_LE_BOND_PASSKEY_DISPLAY |
Out Of Band (OOB) |
GAP_MSG_LE_BOND_OOB_INPUT |
GAP_MSG_LE_AUTHEN_STATE_CHANGE
该消息表示新的Authentication状态。
- Authentication流程已开始。
- Authentication流程已完成,参数cause表示Authentication的结果。
消息数据结构为 T_GAP_AUTHEN_STATE
,示例代码如下:
void app_handle_authen_state_evt(uint8_t conn_id, uint8_t new_state, uint16_t cause)
{
APP_PRINT_INFO2("app_handle_authen_state_evt:conn_id %d, cause 0x%x", conn_id, cause);
switch (new_state)
{
case GAP_AUTHEN_STATE_STARTED:
{
APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_STARTED");
}
break;
case GAP_AUTHEN_STATE_COMPLETE:
{
if (cause == GAP_SUCCESS)
{
APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_COMPLETE pair succsee");
}
else
{
APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_COMPLETE pair failed");
}
}
break;
default:
break;
}
}
GAP_MSG_LE_BOND_PASSKEY_DISPLAY
该消息用于表示配对方法为Passkey Entry,且本地设备需要显示Passkey。
在本地设备显示Passkey,且对端设备需要输入相同的Passkey。一旦收到该消息,APP可以在用户终端界面显示Passkey(处理Passkey的方法取决于APP),此外APP需要调用 le_bond_passkey_display_confirm()
确认是否与对端设备配对。
消息数据结构为 T_GAP_BOND_PASSKEY_DISPLAY
,示例代码如下:
void app_handle_gap_msg(T_IO_MSG *p_gap_msg)
{
......
case GAP_MSG_LE_BOND_PASSKEY_DISPLAY:
{
uint32_t display_value = 0;
conn_id = gap_msg.msg_data.gap_bond_passkey_display.conn_id;
le_bond_get_display_key(conn_id, &display_value);
APP_PRINT_INFO1("GAP_MSG_LE_BOND_PASSKEY_DISPLAY:passkey %d", display_value);
le_bond_passkey_display_confirm(conn_id, GAP_CFM_CAUSE_ACCEPT);
}
break;
}
GAP_MSG_LE_BOND_PASSKEY_INPUT
该消息用于表示配对方法为Passkey Entry,且本地设备需要输入Passkey。
对端设备会显示Passkey,且本地设备需要输入相同的Passkey。一旦收到该消息,APP需要调用 le_bond_passkey_input_confirm()
确认是否与对端设备配对。
消息数据结构为 T_GAP_BOND_PASSKEY_INPUT
,示例代码如下:
void app_handle_gap_msg(T_IO_MSG *p_gap_msg)
{
......
case GAP_MSG_LE_BOND_PASSKEY_INPUT:
{
......
le_bond_passkey_input_confirm(conn_id, passkey, GAP_CFM_CAUSE_ACCEPT);
}
break;
}
GAP_MSG_LE_BOND_OOB_INPUT
该消息用于表示配对方法为OOB。
本地设备需要提供与对端设备交换获得的OOB数据。APP需要调用 le_bond_oob_input_confirm()
确认是否与对端设备配对。
消息数据结构为 T_GAP_BOND_OOB_INPUT
,示例代码如下:
void app_handle_gap_msg(T_IO_MSG *p_gap_msg)
{
......
case LE_GAP_MSG_TYPE_BOND_OOB_INPUT:
{
uint8_t oob_data[GAP_OOB_LEN] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
conn_id = gap_msg.msg_data.gap_bond_oob_input.conn_id;
APP_PRINT_INFO0("GAP_MSG_LE_BOND_OOB_INPUT");
le_bond_set_param(GAP_PARAM_BOND_OOB_DATA, GAP_OOB_LEN, oob_data);
le_bond_oob_input_confirm(conn_id, GAP_CFM_CAUSE_ACCEPT);
}
break;
}
GAP_MSG_LE_BOND_USER_CONFIRMATION
该消息用于表示配对方法为Numeric Comparison。
在本地设备和对端设备均显示需要校验的数值,用户需要确认该数值是否相同。APP需要调用 le_bond_user_confirm()
确认是否与对端设备配对。
消息数据结构为 T_GAP_BOND_USER_CONF
,示例代码如下:
void app_handle_gap_msg(T_IO_MSG *p_gap_msg)
{
......
case GAP_MSG_LE_BOND_USER_CONFIRMATION:
{
uint32_t display_value = 0;
conn_id = gap_msg.msg_data.gap_bond_user_conf.conn_id;
le_bond_get_display_key(conn_id, &display_value);
APP_PRINT_INFO1("GAP_MSG_LE_BOND_USER_CONFIRMATION: passkey %d", display_value);
le_bond_user_confirm(conn_id, GAP_CFM_CAUSE_ACCEPT);
}
break;
}
GAP_MSG_LE_BOND_JUST_WORK
该消息用于表示配对方法为Just Works。APP需要调用 le_bond_just_work_confirm()
确认是否与对端设备配对。
消息数据结构为 T_GAP_BOND_JUST_WORK_CONF
,示例代码如下:
void app_handle_gap_msg(T_IO_MSG *p_gap_msg)
{
......
case GAP_MSG_LE_BOND_JUST_WORK:
{
conn_id = gap_msg.msg_data.gap_bond_just_work_conf.conn_id;
le_bond_just_work_confirm(conn_id, GAP_CFM_CAUSE_ACCEPT);
APP_PRINT_INFO0("GAP_MSG_LE_BOND_JUST_WORK");
}
break;
}
Extended Advertising状态消息
GAP_MSG_LE_EXT_ADV_STATE_CHANGE
该消息用于通知Extended Advertising状态( T_GAP_EXT_ADV_STATE
)。
消息数据结构为 T_GAP_EXT_ADV_STATE_CHANGE
,示例代码如下:
void app_handle_ext_adv_state_evt(uint8_t adv_handle, T_GAP_EXT_ADV_STATE new_state, uint16_t cause)
{
for (int i = 0; i < APP_MAX_ADV_SET; i++)
{
if (ext_adv_state[i].adv_handle == adv_handle)
{
APP_PRINT_INFO2("app_handle_ext_adv_state_evt: adv_handle = %d oldState = %d",
ext_adv_state[i].adv_handle, ext_adv_state[i].ext_adv_state);
ext_adv_state[i].ext_adv_state = new_state;
break;
}
}
APP_PRINT_INFO2("app_handle_ext_adv_state_evt: adv_handle = %d newState = %d",
adv_handle, new_state);
switch (new_state)
{
/* device is idle */
case EXT_ADV_STATE_IDLE:
{
APP_PRINT_INFO2("EXT_ADV_STATE_IDLE: adv_handle %d, cause 0x%x", adv_handle, cause);
}
break;
/* device is advertising */
case EXT_ADV_STATE_ADVERTISING:
{
APP_PRINT_INFO2("EXT_ADV_STATE_ADVERTISING: adv_handle %d, cause 0x%x", adv_handle, cause);
}
break;
default:
break;
}
}
Bluetooth LE GAP回调函数
本节介绍Bluetooth LE GAP回调函数,GAP层使用注册的回调函数发送消息给APP。
不同于Bluetooth LE GAP消息,回调函数是直接被GAP层调用的。因此,不建议在回调函数内执行耗时操作,耗时操作会使底层处理流程延缓和暂停,在某些情况下会引发异常。若APP在收到GAP层发送的消息后确实需要执行耗时操作,在APP处理该消息时,可以通过APP回调函数将该消息发送到APP的队列 ,APP回调函数将在把消息发送到队列之后结束,因此该操作不会延缓底层处理流程。
部分常用的消息定义如下:
/* GAP API message */
//gap_le.h
#define GAP_MSG_LE_MODIFY_WHITE_LIST 0x01 //!<Response msg type for le_modify_white_list
#define GAP_MSG_LE_SET_RAND_ADDR 0x02 //!<Response msg type for le_set_rand_addr
#define GAP_MSG_LE_SET_HOST_CHANN_CLASSIF 0x03 //!<Response msg type for le_set_host_chann_classif
#define GAP_MSG_LE_WRITE_DEFAULT_DATA_LEN 0x04 //!<Response msg type for le_write_default_data_len
//gap_conn_le.h
#define GAP_MSG_LE_READ_RSSI 0x10 //!<Response msg type for le_read_rssi
#define GAP_MSG_LE_READ_CHANN_MAP 0x11 //!<Response msg type for le_read_chann_map
#define GAP_MSG_LE_DISABLE_SLAVE_LATENCY 0x12 //!<Response msg type for le_disable_slave_latency
#define GAP_MSG_LE_SET_DATA_LEN 0x13 //!<Response msg type for le_set_data_len
#define GAP_MSG_LE_DATA_LEN_CHANGE_INFO 0x14 //!<Notification msg type for data length changed
#define GAP_MSG_LE_CONN_UPDATE_IND 0x15 //!<Indication for le connection parameter update
#define GAP_MSG_LE_CREATE_CONN_IND 0x16 //!<Indication for create le connection
#define GAP_MSG_LE_PHY_UPDATE_INFO 0x17 //!<Information for LE PHY update
#define GAP_MSG_LE_UPDATE_PASSED_CHANN_MAP 0x18 //!<Response msg type for le_update_passed_chann_map
#define GAP_MSG_LE_REMOTE_FEATS_INFO 0x19 //!<Information for remote device supported features
#define GAP_MSG_LE_SET_CONN_TX_PWR 0x1A //!<Response msg type for le_set_conn_tx_power
#define GAP_MSG_LE_READ_REMOTE_VERSION 0x1B //!<Response msg type for le_read_remote_version
#define GAP_MSG_LE_ADV_SET_CONN_OWN_ADDR_TYPE_INFO 0x1C //!<Information of own address type for advertiser using adv set
//gap_bond_le.h
#define GAP_MSG_LE_BOND_MODIFY_INFO 0x20 //!<Notification msg type for bond modify
#define GAP_MSG_LE_KEYPRESS_NOTIFY 0x21 //!<Response msg type for le_bond_keypress_notify
#define GAP_MSG_LE_KEYPRESS_NOTIFY_INFO 0x22 //!<Notification msg type for le_bond_keypress_notify
#define GAP_MSG_LE_GATT_SIGNED_STATUS_INFO 0x23 //!<Notification msg type for le signed status information
/* The type of callback will only be used when gap_param_key_manager equals 2 and no matching key entry is found*/
#define GAP_MSG_LE_BOND_KEY_REQ 0x24 //!<Notification msg type for le bond key request information
//gap_scan.h
#define GAP_MSG_LE_SCAN_INFO 0x30 //!<Notification msg type for le scan
#define GAP_MSG_LE_DIRECT_ADV_INFO 0x31 //!<Notification msg type for le direct adv info
//gap_adv.h
#define GAP_MSG_LE_ADV_UPDATE_PARAM 0x40 //!<Response msg type for le_adv_update_param
#define GAP_MSG_LE_ADV_READ_TX_POWER 0x41 //!<Response msg type for le_adv_read_tx_power
#define GAP_MSG_LE_ADV_SET_TX_POWER 0x42 //!<Response msg type for le_adv_set_tx_power
//gap_ext_scan.h
#define GAP_MSG_LE_EXT_ADV_REPORT_INFO 0x50 //!<Notification msg type for le extended adv report
/* The type of callback will only be used after APP calls @ref le_ext_scan_gap_msg_info_way(false). */
#define GAP_MSG_LE_EXT_SCAN_STATE_CHANGE_INFO 0x51 //!<Notification msg type for extended scanning state
//gap_ext_adv.h
#define GAP_MSG_LE_EXT_ADV_START_SETTING 0x60 //!<Response msg type for le_ext_adv_start_setting
#define GAP_MSG_LE_EXT_ADV_REMOVE_SET 0x61 //!<Response msg type for le_ext_adv_remove_set
#define GAP_MSG_LE_EXT_ADV_CLEAR_SET 0x62 //!<Response msg type for le_ext_adv_clear_set
#define GAP_MSG_LE_EXT_ADV_ENABLE 0x63 //!<Response msg type for le_ext_adv_enable
#define GAP_MSG_LE_EXT_ADV_DISABLE 0x64 //!<Response msg type for le_ext_adv_disable
#define GAP_MSG_LE_SCAN_REQ_RECEIVED_INFO 0x65 //!<Notification msg type for le scan received info
/* The type of callback will only be used after APP calls @ref le_ext_adv_gap_msg_info_way(false). */
#define GAP_MSG_LE_EXT_ADV_STATE_CHANGE_INFO 0x66 //!<Notification msg type for extended advertising state
#define GAP_MSG_LE_GAP_STATE_MSG 0xB0
#define GAP_MSG_LE_CONN_INFO 0xD2 //!<Msg type for LE connection
Bluetooth LE GAP回调函数的使用方法如下:
注册回调函数
void app_le_gap_init(void) { ...... le_register_app_cb(app_gap_callback); }
处理GAP回调函数消息
T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data) { T_APP_RESULT result = APP_RESULT_SUCCESS; T_LE_CB_DATA *p_data = (T_LE_CB_DATA *)p_cb_data; switch (cb_type) { case GAP_MSG_LE_DATA_LEN_CHANGE_INFO: APP_PRINT_INFO3("GAP_MSG_LE_DATA_LEN_CHANGE_INFO: conn_id %d, tx octets 0x%x, max_tx_time 0x%x", p_data->p_le_data_len_change_info->conn_id, p_data->p_le_data_len_change_info->max_tx_octets, p_data->p_le_data_len_change_info->max_tx_time); break; ...... } }
Bluetooth LE GAP回调函数消息概述
本节介绍GAP回调函数消息,在 gap_callback_le.h
中定义GAP回调函数消息类型和消息数据。由于GAP层提供的大部分接口都是异步的,因此GAP层使用回调函数发送响应信息给APP。例如,APP调用 le_read_rssi()
读取Received Signal Strength
Indication (RSSI),若返回值为 GAP_CAUSE_SUCCESS
,则表示成功发送请求。此时,APP需要等待 GAP_MSG_LE_READ_RSSI
消息以获取结果。
Bluetooth LE GAP回调函数消息的详细信息如下:
gap_le.h相关消息
Callback type(cb_type)
Callback data(p_cb_data)
Reference API
GAP_MSG_LE_MODIFY_WHITE_LIST
T_LE_MODIFY_WHITE_LIST_RSP *p_le_modify_white_list_rsp
le_modify_white_list
GAP_MSG_LE_SET_RAND_ADDR
T_LE_SET_RAND_ADDR_RSP *p_le_set_rand_addr_rsp
le_set_rand_addr
GAP_MSG_LE_SET_HOST_CHANN_CLASSIF
T_LE_SET_HOST_CHANN_CLASSIF_RSP *p_le_set_host_chann_classif_rsp
le_set_host_chann_classif
GAP_MSG_LE_VENDOR_SET_MIN_REM_SCA
T_LE_CAUSE le_cause
le_vendor_set_rem_min_sca
gap_conn_le.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_READ_RSSI
T_LE_READ_RSSI_RSP *p_le_read_rssi_rsp
le_read_rssi
GAP_MSG_LE_READ_CHANN_MAP
T_LE_READ_CHANN_MAP_RSP *p_le_read_chann_map_rsp
le_read_chann_map
GAP_MSG_LE_DISABLE_SLAVE_LATENCY
T_LE_DISABLE_SLAVE_LATENCY_RSP *p_le_disable_slave_latency_rsp
le_disable_slave_latency
GAP_MSG_LE_SET_DATA_LEN
T_LE_SET_DATA_LEN_RSP *p_le_set_data_len_rsp
le_set_data_len
GAP_MSG_LE_DATA_LEN_CHANGE_INFO
T_LE_DATA_LEN_CHANGE_INFO *p_le_data_len_change_info
GAP_MSG_LE_CONN_UPDATE_IND
T_LE_CONN_UPDATE_IND *p_le_conn_update_ind
GAP_MSG_LE_CREATE_CONN_IND
T_LE_CREATE_CONN_IND *p_le_create_conn_ind
GAP_MSG_LE_PHY_UPDATE_INFO
T_LE_PHY_UPDATE_INFO *p_le_phy_update_info
GAP_MSG_LE_UPDATE_PASSED_CHANN_MAP
T_LE_UPDATE_PASSED_CHANN_MAP_RSP *p_le_update_passed_chann_map_rsp
le_update_passed_chann_map
GAP_MSG_LE_REMOTE_FEATS_INFO
T_LE_REMOTE_FEATS_INFO *p_le_remote_feats_info
GAP_MSG_LE_SET_CONN_TX_PWR
T_LE_CAUSE le_cause
le_set_conn_tx_power
- 该消息用于向APP通知在Link Layer的发送或接收方向其最大Payload长度或数据包的最大传输时间的变化。
- 该消息仅适用于Central角色。当对端设备请求更新Connection参数时,GAP层将通过回调函数发送该消息并检查回调函数的返回值。因此,APP可以返回
APP_RESULT_ACCEPT
以接受参数更新,或者返回APP_RESULT_REJECT
以拒绝参数更新。T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data) { ...... case GAP_MSG_LE_CONN_UPDATE_IND: APP_PRINT_INFO5("GAP_MSG_LE_CONN_UPDATE_IND: conn_id %d, conn_interval_max 0x%x, conn_interval_min 0x%x, conn_latency 0x%x,supervision_timeout 0x%x", p_data->p_le_conn_update_ind->conn_id, p_data->p_le_conn_update_ind->conn_interval_max, p_data->p_le_conn_update_ind->conn_interval_min, p_data->p_le_conn_update_ind->conn_latency, p_data->p_le_conn_update_ind->supervision_timeout); /* if reject the proposed connection parameter from peer device, use APP_RESULT_REJECT. */ result = APP_RESULT_ACCEPT; break; }
- 该消息仅适用于Peripheral角色。由APP决定是否建立connection。当central设备发起connection时,默认情况下,GAP层不会发送该消息并直接接受connection。若APP希望使用该功能,需要将
GAP_PARAM_HANDLE_CREATE_CONN_IND
设为true
,示例代码如下:T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data) { ...... uint8_t handle_conn_ind = true; le_set_gap_param(GAP_PARAM_HANDLE_CREATE_CONN_IND, sizeof(handle_conn_ind), &handle_conn_ind); } T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data) { ...... case GAP_MSG_LE_CREATE_CONN_IND: /* if reject the connection from peer device, use APP_RESULT_REJECT. */ result = APP_RESULT_ACCEPT; break; }
- 该消息表示Controller已经切换正在使用的发射机PHY或接收机PHY。
- 在connection建立成功后,controller会主动读取对端设备的feature。读取结果会通过该消息通知给application。
gap_bond_le.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_BOND_MODIFY_INFO
T_LE_BOND_MODIFY_INFO *p_le_bond_modify_info
GAP_MSG_LE_KEYPRESS_NOTIFY
T_LE_KEYPRESS_NOTIFY_RSP *p_le_keypress_notify_rsp
le_bond_keypress_notify
GAP_MSG_LE_KEYPRESS_NOTIFY_INFO
T_LE_KEYPRESS_NOTIFY_INFO *p_le_keypress_notify_info
GAP_MSG_LE_GATT_SIGNED_STATUS_INFO
T_LE_GATT_SIGNED_STATUS_INFO *p_le_gatt_signed_status_info
- 该消息表示SMP已收到keypress notification。
- 该消息表示GATT signed状态信息。
- 该消息用于向APP通知绑定信息已变更,更多信息参见 LE密钥管理。
gap_scan.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_SCAN_INFO
T_LE_SCAN_INFO *p_le_scan_info
le_scan_start
GAP_MSG_LE_DIRECT_ADV_INFO
T_LE_DIRECT_ADV_INFO *p_le_direct_adv_info
- Scan状态为
GAP_SCAN_STATE_SCANNING
,当蓝牙协议层收到advertising数据或scan response数据时,GAP将发送该消息以通知APP。 - Scan状态为
GAP_SCAN_STATE_SCANNING
且Scan Filter Policy为GAP_SCAN_FILTER_ANY_RPA
或GAP_SCAN_FILTER_WHITE_LIST_RPA
,当蓝牙协议层收到directed advertising数据包且其initiator地址为Resolvable Private Address (RPA),无法解析该RPA时,GAP层将发送该消息以通知APP。
gap_adv.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_ADV_UPDATE_PARAM
T_LE_ADV_UPDATE_PARAM_RSP *p_le_adv_update_param_rsp
le_adv_update_param
GAP_MSG_LE_ADV_READ_TX_POWER
T_LE_ADV_READ_TX_POWER_RSP *p_le_adv_read_tx_power_rsp
le_adv_read_tx_power
GAP_MSG_LE_ADV_SET_TX_POWER
T_LE_CAUSE le_cause
le_adv_set_tx_power
GAP_MSG_LE_VENDOR_ONE_SHOT_ADV
T_LE_CAUSE le_cause
le_vendor_one_shot_adv
gap_dtm.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_DTM_RECEIVER_TEST
T_LE_CAUSE le_cause
le_dtm_receiver_test
GAP_MSG_LE_DTM_TRANSMITTER_TEST
T_LE_CAUSE le_cause
le_dtm_transmitter_test
GAP_MSG_LE_DTM_TEST_END
T_LE_DTM_TEST_END_RSP *p_le_dtm_test_end_rsp
le_dtm_test_end
GAP_MSG_LE_DTM_ENHANCED_RECEIVER_TEST
T_LE_CAUSE le_cause
le_dtm_enhanced_receiver_test
GAP_MSG_LE_DTM_ENHANCED_TRANSMITTER_TEST
T_LE_CAUSE le_cause
le_dtm_enhanced_transmitter_test
GAP_MSG_LE_DTM_RECEIVER_TEST_V3
T_LE_CAUSE le_cause
le_dtm_receiver_test_v3
GAP_MSG_LE_DTM_TRANSMITTER_TEST_V3
T_LE_CAUSE le_cause
le_dtm_transmitter_test_v3
GAP_MSG_LE_DTM_TRANSMITTER_TEST_V4
T_LE_CAUSE le_cause
le_dtm_transmitter_test_v4
gap_ext_scan.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_EXT_ADV_REPORT_INFO
T_LE_EXT_ADV_REPORT_INFO *p_le_ext_adv_report_info
le_ext_scan_start
- GAP_MSG_LE_EXT_ADV_REPORT_INFO使用LE Advertising Extensions且Scan状态为
GAP_SCAN_STATE_SCANNING
,当蓝牙协议层收到advertising数据或scan response数据时,GAP层将发送该消息以通知APP。
gap_ext_adv.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_EXT_ADV_START_SETTING
T_LE_EXT_ADV_START_SETTING_RSP *p_le_ext_adv_start_setting_rsp
le_ext_adv_start_setting
GAP_MSG_LE_EXT_ADV_REMOVE_SET
T_LE_EXT_ADV_REMOVE_SET_RSP *p_le_ext_adv_remove_set_rsp
le_ext_adv_remove_set
GAP_MSG_LE_EXT_ADV_CLEAR_SET
T_LE_EXT_ADV_CLEAR_SET_RSP *p_le_ext_adv_clear_set_rsp
le_ext_adv_clear_set
GAP_MSG_LE_EXT_ADV_ENABLE
T_LE_CAUSE le_cause
le_ext_adv_enable
GAP_MSG_LE_EXT_ADV_DISABLE
T_LE_CAUSE le_cause
le_ext_adv_disable
- GAP_MSG_LE_EXT_ADV_START_SETTING该消息表示本地设备为一个advertising set设置extended advertising相关参数的操作已完成。
- GAP_MSG_LE_EXT_ADV_REMOVE_SET该消息表示本地设备移除一个advetising set的操作已完成。
- GAP_MSG_LE_EXT_ADV_CLEAR_SET该消息表示本地设备清除所有advertising sets的操作已完成。
- GAP_MSG_LE_EXT_ADV_ENABLE该消息表示本地设备启动一个或多个advertising sets的extended advertising的操作已完成。
- GAP_MSG_LE_EXT_ADV_DISABLE该消息表示本地设备停止一个或多个advertising sets的extended advertising的操作已完成。
- GAP_MSG_LE_SCAN_REQ_RECEIVED_INFO调用
le_ext_adv_set_adv_param()
使能scan request notifications且Extended Advertising状态为EXT_ADV_STATE_ADVERTISING
,当蓝牙协议层收到scan request,GAP层将使用该消息通知APP。
gap_vendor.h相关消息
Callback Type(cb_type)
Callback Data(p_cb_data)
Reference API
GAP_MSG_LE_VENDOR_ADV_3_DATA_ENABLE
T_LE_CAUSE le_cause
le_vendor_adv_3_data_enable
GAP_MSG_LE_VENDOR_ADV_3_DATA_SET
T_LE_VENDOR_ADV_3_DATA_SET_RSP *p_le_vendor_adv_3_data_set_rsp
le_vendor_adv_3_data_set
GAP_MSG_LE_VENDOR_DROP_ACL_DATA
T_LE_CAUSE le_cause
le_vendor_drop_acl_data
GAP_MSG_GAP_SW_RESET
T_LE_CAUSE le_cause
gap_sw_reset_req
APP消息流
APP消息流如图所示:
发送消息给APP的两种方法
回调函数
首先,APP需要注册回调函数。当上行消息送到GAP层之后,GAP层将调用注册的回调函数以通知APP处理该消息。
消息队列
首先,APP需要创建消息队列。当上行消息送到GAP层之后,GAP层将消息发送到消息队列,APP将循环接收消息队列中的消息。
初始化
注册回调函数
为接收GAP API消息,APP需要通过
le_register_app_cb()
注册APP回调函数。为接收Airplane Mode消息,APP需要通过
gap_register_app_cb()
注册APP回调函数。为接收写GAPS characteristic的消息,APP需要通过
gatt_register_callback()
注册APP回调函数。若APP需要使用services,为接收server消息,APP需要通过xxx_add_service()和
server_register_app_cb()
注册service回调函数。若APP需要使用clients,为接收client消息,APP需要通过xxx_add_client()注册client回调函数。
创建消息队列
为接收蓝牙状态消息,APP需要通过
gap_start_bt_stack()
创建IO消息队列。循环接收消息
APP main task循环接收消息。若收到的事件是发送给APP的,APP收到IO消息,那么APP将调用
app_handle_io_msg()
由APP处理该消息。否则,APP将调用gap_handle_msg()
由GAP层处理该消息。消息处理
若消息是由回调函数发送的,则由初始化过程中注册的函数处理该消息。若消息是由消息队列发送的,则由另一个函数处理该消息。
GAP信息存储
在 gap_storage_le.h
中定义常量和函数原型。本地协议栈信息和绑定信息保存在FTL中,更多关于FTL的信息参见 FTL简介。
FTL简介
Bluetooth stack和user application使用FTL作为抽象层保存或载入flash中的数据。
FTL布局
FTL可以分为以下四个区域:
Local stack information storage space
地址范围:0x0000 - 0x004F
该区域用于存储本地协议栈信息,包括device name、device appearance和本地设备IRK,更多信息参见 本地协议栈信息存储。
LE key storage space
地址范围:0x0050~(Offset_reserved-1)
该区域用于存储LE密钥信息,更多信息参见 绑定信息存储。
Offset_reserved是可变值:
Offset_reserved = 0x0064 + max_le_paired_device*device_block_size
。max_le_paired_device:该参数表示存储绑定信息设备的最大数目,可以通过
gap_config_max_le_paired_device()
配置,默认值为1,更多信息参见 API的参数配置。
Reserved space
地址范围:Offset_reserved~(APP_start_offset-1)
该区域为保留空间,因为
max_le_paired_device
参数是可配置的。LE key storage space可以使用该区域存储密钥信息。
APP storage space
地址范围:APP_start_offset~storage_address,更多信息参见文档 Memory。
APP可以使用该区域存储信息。
本地协议栈信息存储
Device Name存储
GAP层目前支持的device name字符串的最大长度是40字节(包括结束符)。
flash_save_local_name()
:保存device name到FTL。flash_load_local_name()
:从FTL载入device name。
若GAP service的Device Name characteristic是可写的,APP可以调用该函数保存device name。示例代码参见 GAP Service Characteristic的可写属性。
Device Appearance存储
Device Appearance用于描述设备的类型,例如键盘、鼠标、温度计、血压计等。
flash_save_local_appearance()
:保存device appearance到FTL。flash_load_local_appearance()
:从FTL载入device appearance。
若GAP service的Device Appearance characteristic是可写的,APP可以调用该函数保存device appearance。示例代码参见 GAP Service Characteristic的可写属性。
本地设备IRK存储
IRK是用于生成和解析RPA的128 bit密钥。示例代码参考 本地设备IRK的设置。
flash_save_local_irk()
:保存本地设备IRK到FTL。flash_load_local_irk()
:从FTL载入本地设备IRK。
绑定信息存储
绑定设备优先级管理
GAP层实现绑定设备优先级管理机制,其中优先级控制模块被保存在FTL中。LE设备有存储空间和优先级控制模块。
优先级控制模块包括以下两部分
- bond_num已保存绑定设备的数目。
- bond_idx数组已保存绑定设备的索引数组。GAP层可以根据绑定设备索引查找到其在FTL中的起始偏移。
优先级管理包括如下操作
添加一个绑定设备
移除一个绑定设备
清除所有绑定设备
GAP LE API:
le_bond_clear_all_keys()
将一个绑定设备设为最高优先级
GAP LE API:
le_set_high_priority_bond()
获取最高优先级设备
最高优先级设备为
bond_idx[bond_num - 1]
。GAP LE API:
le_get_high_priority_bond()
获取最低优先级设备
最低优先级设备为
bond_idx[0]
。GAP LE API:
le_get_low_priority_bond()
优先级管理示例如图所示:
Bluetooth LE密钥存储
Bluetooth LE密钥信息存储在LE key storage space。
对于RTL87x2C/RTL87x2D/RTL87x2E/RTL87x2H,LE FTL布局如图 LE FTL布局1 所示。
对于RTL87x2G,LE FTL布局如图 LE FTL布局2 所示。
LE key storage space可以分为以下两部分:
LE bond priority:LE优先级控制模块,更多信息参见 绑定设备优先级管理。
Bonded device keys storage block:设备索引0、索引1等等。
LE REMOTE BD:保存对端设备地址。
LE LOCAL LTK:保存本地设备 LTK。
LE REMOTE LTK:保存对端设备LTK。
LE REMOTE IRK:保存对端设备 IRK。
LE LOCAL CSRK:保存本地设备 CSRK。
LE REMOTE CSRK:保存对端设备CSRK。
LE CCCD DATA:保存 CCCD 数据。
LE LOCAL BD:保存本地设备地址,RTL87x2E/RTL87x2H/RTL87x2G设备支持存储,且需要将
le_local_addr_storage_flag
配置为1,打开后可以支持存储本地设备使用不同的local address与同一remote device配对生成的不同绑定信息。
配置
LE key storage space的大小与以下两个参数有关:
- LE绑定设备数目的最大值默认值为1,可以使用
gap_config_max_le_paired_device()
配置,更多信息参见 API的参数配置。 - CCCD数目的最大值默认值为16,可以使用
gap_config_ccc_bits_count()
配置,更多信息参见 API的参数配置。
LE Key Entry结构体
GAP层使用结构体 T_LE_KEY_ENTRY
管理绑定设备。
#define LE_KEY_STORE_REMOTE_BD_BIT 0x01
#define LE_KEY_STORE_LOCAL_LTK_BIT 0x02
#define LE_KEY_STORE_REMOTE_LTK_BIT 0x04
#define LE_KEY_STORE_REMOTE_IRK_BIT 0x08
#define LE_KEY_STORE_LOCAL_CSRK_BIT 0x10
#define LE_KEY_STORE_REMOTE_CSRK_BIT 0x20
#define LE_KEY_STORE_CCCD_DATA_BIT 0x40
#define LE_KEY_STORE_LOCAL_IRK_BIT 0x80
#define LE_KEY_STORE_REMOTE_CLIENT_SUPPORTED_FEATURES_BIT 0x0100
#define LE_KEY_STORE_LOCAL_BD_BIT 0x8000
/** @brief LE key entry */
typedef struct
{
bool is_used;
uint8_t idx;
uint16_t flags;
uint8_t local_bd_type;
uint8_t app_data;
uint8_t reserved[2];
T_LE_REMOTE_BD remote_bd;
T_LE_REMOTE_BD resolved_remote_bd;
uint8_t local_bd_addr[6];
} T_LE_KEY_ENTRY;
参数说明:
is_used: 是否使用。
idx: 设备索引,GAP使用idx查找绑定设备信息在FTL的存储位置。
flags: LE Key Storage Bits,表示密钥是否存在的位域。
local_bd_type: 配对过程中使用的local address type,T_GAP_LOCAL_ADDR_TYPE。
remote_bd: 对端设备地址。
resolved_remote_bd: 对端设备的identity address。
local_bd_addr: 本地设备地址。
LE密钥管理
当本地设备与对端设备配对或本地设备与绑定设备加密时,GAP层将发送 GAP_MSG_LE_AUTHEN_STATE_CHANGE
消息向APP通知authentication状态的变化。
void app_handle_authen_state_evt(uint8_t conn_id, uint8_t new_state, uint16_t cause)
{
APP_PRINT_INFO2("app_handle_authen_state_evt:conn_id %d, cause 0x%x", conn_id, cause);
switch (new_state)
{
case GAP_AUTHEN_STATE_STARTED:
{
APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_STARTED");
}
break;
case GAP_AUTHEN_STATE_COMPLETE:
{
if (cause == GAP_SUCCESS)
{
APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_COMPLETE pair success");
}
else
{
APP_PRINT_INFO0("app_handle_authen_state_evt: GAP_AUTHEN_STATE_COMPLETE pair failed");
}
}
break;
......
}
GAP_MSG_LE_BOND_MODIFY_INFO
用于向APP通知绑定信息已变更。
typedef struct
{
T_LE_BOND_MODIFY_TYPE type;
T_LE_KEY_ENTRY *p_entry;
} T_LE_BOND_MODIFY_INFO;
T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data)
{
T_APP_RESULT result = APP_RESULT_SUCCESS;
T_LE_CB_DATA *p_data = (T_LE_CB_DATA *)p_cb_data;
switch (cb_type)
{
case GAP_MSG_LE_BOND_MODIFY_INFO:
APP_PRINT_INFO1("GAP_MSG_LE_BOND_MODIFY_INFO: type 0x%x",
p_data->p_le_bond_modify_info->type);
break;
......
}
}
绑定信息变更类型的定义如下:
typedef enum
{
LE_BOND_DELETE,
LE_BOND_ADD,
LE_BOND_CLEAR,
LE_BOND_FULL,
LE_BOND_KEY_MISSING,
} T_LE_BOND_MODIFY_TYPE;
LE_BOND_DELETE:绑定信息已被删除,当满足以下条件时会发送该消息:
链路加密失败。
密钥存储空间已满,因此最低优先级的绑定信息会被删除。
LE_BOND_ADD:新增绑定设备,仅在第一次与对端设备配对时发送该消息。
LE_BOND_CLEAR:所有绑定消息已被删除,在调用
le_bond_clear_all_keys()
后会发送该消息。LE_BOND_FULL:密钥存储空间已满:
当参数
GAP_PARAM_BOND_KEY_MANAGER
设为true
时发送该消息。此时,GAP层不会自动删除绑定信息。设为
false
时不会发送该消息,GAP层将删除最低优先级的绑定信息,存储当前的绑定信息,然后发送LE_BOND_DELETE
消息。
LE_BOND_KEY_MISSING:链路加密失败且密钥失效,当参数
GAP_PARAM_BOND_KEY_MANAGER
设为true
时发送该消息。此时,GAP层不会自动删除绑定信息。设为false
时不会发送该消息,GAP层将删除绑定信息,发送LE_BOND_DELETE
消息。
GAP层内部LE设备优先级管理
与新设备配对
密钥存储空间未满:GAP层将在优先级控制模块添加绑定设备,发送
LE_BOND_ADD
消息给APP。该新增设备具有最高优先级。密钥存储空间已满:当
GAP_PARAM_BOND_KEY_MANAGER
为true
时,GAP层发送消息LE_BOND_FULL
给APP;当GAP_PARAM_BOND_KEY_MANAGER
为false
时,GAP层将从优先级控制模块移除最低优先级的绑定设备,发送LE_BOND_DELETE
消息。GAP层将在优先级控制模块添加绑定设备,发送LE_BOND_ADD
消息给APP。该新增设备具有最高优先级。
与绑定设备加密成功
GAP层将该绑定设备设为最高优先级。
与绑定设备加密失败
当
GAP_PARAM_BOND_KEY_MANAGER
为true
时,GAP层将发送LE_BOND_KEY_MISSING
给APP。当
GAP_PARAM_BOND_KEY_MANAGER
为false
时,GAP层将从优先级控制模块移除该绑定设备,发送LE_BOND_DELETE
给APP。
APIs
完整的API请参考 gap_storage_le.h
及 gap_bond_le.h
, 以下仅展示部分常用API:
/* gap_storage_le.h */
T_LE_KEY_ENTRY *le_find_key_entry(uint8_t *bd_addr, T_GAP_REMOTE_ADDR_TYPE bd_type);
T_LE_KEY_ENTRY *le_find_key_entry_v2(uint8_t *remote_bd_addr, T_GAP_REMOTE_ADDR_TYPE remote_bd_type,
uint8_t *local_bd_addr, uint8_t local_bd_type);
T_LE_KEY_ENTRY *le_find_key_entry_by_idx(uint8_t idx);
uint8_t le_get_bond_dev_num(void);
T_LE_KEY_ENTRY *le_get_low_priority_bond(void);
T_LE_KEY_ENTRY *le_get_high_priority_bond(void);
bool le_set_high_priority_bond(uint8_t *bd_addr, T_GAP_REMOTE_ADDR_TYPE bd_type);
bool le_set_high_priority_bond_v2(T_LE_KEY_ENTRY *p_entry);
bool le_resolve_random_address(uint8_t *unresolved_addr, uint8_t *resolved_addr,
T_GAP_IDENT_ADDR_TYPE *resolved_addr_type);
bool le_gen_bond_dev(uint8_t *bd_addr, T_GAP_REMOTE_ADDR_TYPE bd_type,
T_GAP_LOCAL_ADDR_TYPE local_bd_type,
/* gap_bond_le.h */
void le_bond_clear_all_keys(void);
T_GAP_CAUSE le_bond_delete_by_idx(uint8_t idx);
T_GAP_CAUSE le_bond_delete_by_bd(uint8_t *bd_addr, T_GAP_REMOTE_ADDR_TYPE bd_type);
LE 地址
本章节的目的是概述LE地址。 设备通过设备地址进行识别。所有LE设备都应具有LE地址,以将设备唯一标识给另一LE设备。
地址类型
设备是使用device address来识别的。device address可能是public device address或random device address。
random device address 有以下两种子类型:
Static address
Private address
private address 有以下两种子类型:
Non-resolvable private address
Resolvable private address
Static Address
static address的格式如下所示。
Non-resolvable Private Address
non-resolvable private address的格式如下所示。
Resolvable Private Address
resolvable private address的格式如下所示。
设备的Identity Address是设备在发送的数据包中使用的Public Device Address或Random Static Device Address。若设备使用Resolvable Private Address,该设备必须有一个Identity Address。 为了生成一个resolvable private address,设备一定要有Local Identity Resolving Key (IRK)或Peer Identity Resolving Key (IRK)。resolvable private address必须由IRK和随机生成的24-bit数生成。 resolvable private address可以被相应设备的IRK解析。若resolvable private address已被解析,设备可以关联该地址与对端设备。
备注
如果设备的静态地址发生更改,那么存储在对端设备中的地址将变得无效,并且无法使用旧地址重新连接。
IRK和Identity Address
SM 使用密钥分发方法,以在无线通信中实现身份认证和加密功能。IRK是用于生成和解析resolvable private address的128位密钥。若central已收到peripheral的IRK,那么central可以解析peripheral的resolvable private
address。若peripheral已收到central的IRK,那么peripheral可以解析central的resolvable private address。privacy概念仅防范未被分发IRK的设备。
Identity Information用于分发IRK。全为零的Identity Resolving Key数据字段表示设备没有有效的resolvable private address。
Identity Address Information用于分发Public Device Address或Random Static Device Address。
下图展示Central和Peripheral分发所有密钥和值的示例。
Resolving List和Resolution
Resolving List和Filter Accept List
通过增加和删除device identity,Host维护一个resolving list。一个device identity由对端设备的identity address以及本地设备和对端设备的IRK对组成。
当Controller执行address resolution,若Host的操作涉及resolving list中的一个对端设备,那么Host使用对端设备的identity address。同样地,倘若对端的设备地址已被解析,所有从Controller送往Host的event将使用对端的device identity。
在Controller中执行address resolution时,可以实现设备过滤。因为在检查设备是否存在于Filter Accept List之前,对端的device identity address可以被解析出来。
下图展示Controller resolving list和Controller Filter Accept List之间关系的逻辑表示。
一旦resolvable private address被解析出来,Host设置的Filter Accept List和filter policies应用于相应的identity address。
Address Resolution
若Controller中的address resolution已启用,当Controller收到本地或对端的resolvable private address, Controller将使用resolving list。
除了以下场景,其它时间均可启用address resolution。
已启用advertising(不包括periodic advertising)
已启用scanning
正在创建连线
当Controller中的address resolution已启用,从Host至Controller,涉及resolving list中对端设备的操作,Host必须使用对端设备的identity address。同样地,倘若对端的设备地址已被解析,所有从Controller送往Host的event将使用对端的device identity。
Bluetooth LE Privacy的示例代码位于 LE Peripheral Privacy 示例工程中。
GATT Profile Server
Profile- Server: 基于GATT的Profile在server端的实现的公共接口。
概述
Server是可以接收来自client的command和request且发送response、indication和notification给client的设备。GATT Profile定义作为GATT server和GATT client的LE设备的交互方式。Profile可能包含一个或多个GATT services,service是一组characteristics的集合,因而GATT server展示的是其characteristics。
用户可以使用Profile Server导出的APIs实现specific service。Profile server层级如图 Profile Server层级。Profile包括profile server layer和specific service。位于protocol stack之上的profile server layer封装供specific service访问protocol stack的接口,因此针对specific service的开发不涉及protocol stack的细节,使开发变得更简单和清晰。基于profile server layer的specific service是由application layer实现的,specific service由attribute value组成并提供接口供APP发送数据。
Profile Server交互
Profile server layer处理与protocol stack layer的交互,并提供接口用于设计specific service。Profile Server交互包括向server添加service、读取characteristic value、写入characteristic value、characteristic value notification和characteristic value indication。
添加Service
Protocol stack维护通过profile server layer添加的所有services的信息。首先,需要通过 server_init()
接口初始化service attribute table的总数目。
void app_le_profile_init(void)
{
server_init(2);
simp_srv_id = simp_ble_service_add_service(app_profile_callback);
bas_srv_id = bas_add_service(app_profile_callback);
server_register_app_cb(app_profile_callback);
......
}
Profile server layer提供 server_add_service()
接口用于向profile server layer添加service。
T_SERVER_ID simp_ble_service_add_service(void *p_func)
{
if (false == server_add_service(&simp_service_id,
(uint8_t *)simple_ble_service_tbl,
sizeof(simple_ble_service_tbl),
simp_ble_service_cbs))
{
APP_PRINT_ERROR0("simp_ble_service_add_service: fail");
simp_service_id = 0xff;
return simp_service_id;
}
pfn_simp_ble_service_cb = (P_FUN_SERVER_GENERAL_CB)p_func;
return simp_service_id;
}
下图表示一个server包含多个service tables,向该server添加service的情况。
当service添加到profile server layer后,GAP初始化流程会注册所有services,一旦完成注册流程,GAP层会发送 PROFILE_EVT_SRV_REG_COMPLETE
消息。
注册service的流程如下图所示。
在GAP初始化过程中,通过向protocol stack发送service注册请求以启动service注册流程,然后注册所有已添加services。若server通用回调函数不为NULL,一旦最后一个服务被成功注册,profile server
layer将通过注册的回调函数 app_profile_callback()
向APP发送 PROFILE_EVT_SRV_REG_COMPLETE
消息。
T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
{
T_APP_RESULT app_result = APP_RESULT_SUCCESS;
if (service_id == SERVICE_PROFILE_GENERAL_ID)
{
T_SERVER_APP_CB_DATA *p_param = (T_SERVER_APP_CB_DATA *)p_data;
switch (p_param->eventId)
{
case PROFILE_EVT_SRV_REG_COMPLETE:// srv register result event.
APP_PRINT_INFO1("PROFILE_EVT_SRV_REG_COMPLETE: result %d",
p_param->event_data.service_reg_result);
break;
......
}
Service的回调函数
Server通用回调函数
Server通用回调函数用于向APP发送事件,其中包含service注册完成事件和通过characteristic value
notification或indication发送数据完成事件。通过 server_register_app_cb()
初始化该回调函数。在 profile_server.h
中定义server通用回调函数。
Specific Service回调函数
为访问由specific service提供的attribute value,需要在specific service中实现回调函数,该回调函数用于处理来自client的read/write attribute vlaue和更新CCCD数值的流程。通过 server_add_service()
初始化该回调函数。在 profile_server.h
中定义回调函数的结构体。
/** @brief GATT service callbacks */
typedef struct
{
P_FUN_GATT_READ_ATTR_CB read_attr_cb; /**< Read callback function pointer.
Return value: @ref T_APP_RESULT. */
P_FUN_GATT_WRITE_ATTR_CB write_attr_cb; /**< Write callback function pointer.
Return value: @ref T_APP_RESULT. */
P_FUN_GATT_CCCD_UPDATE_CB cccd_update_cb; /**< Update cccd callback function pointer. */
} T_FUN_GATT_SERVICE_CBS;
- read_attr_cb读attribute回调函数,当client发送attribute read request时,该回调函数用于获取specific service提供的attribute value。
- write_attr_cb写attribute回调函数,当client发送attribute write request时,该回调函数用于写入specific service提供的attribute value。
- cccd_update_cb更新CCCD数值回调函数,用于通知specific service,service中相应CCCD数值已被client写入。
const T_FUN_GATT_SERVICE_CBS simp_ble_service_cbs =
{
simp_ble_service_attr_read_cb, // Read callback function pointer
simp_ble_service_attr_write_cb, // Write callback function pointer
simp_ble_service_cccd_update_cb // CCCD update callback function pointer
};
Write Indication Post Procedure回调函数
Write indication post procedure回调函数用于在处理client的write request之后执行一些后续流程。该回调函数是在write attribute回调函数中被初始化的。若无后续流程需要执行,write
attribute回调函数中的 p_write_post_proc
指针必须为NULL。在 profile_server.h
中定义Write indication post procedure回调函数。
Characteristic Value Read
该流程用于从server读取characteristic value。有四个子流程可以用于读取characteristic value,包括read characteristic value、read using characteristic UUID、read long characteristic values和read multiple characteristic values。一个可读的attribute必须配置可读permission。根据不同的attribute flag,可以从service或APP读取attribute value。
由Attribute Element提供Attribute Value
flag为 ATTRIB_FLAG_VALUE_INCL
的attribute会涉及该流程。
{
ATTRIB_FLAG_VALUE_INCL, /* flags */
{ /* type_value */
LO_WORD(0x2A04),
HI_WORD(0x2A04),
100,
200,
0,
LO_WORD(2000),
HI_WORD(2000)
}
5,/* bValueLen */
NULL,
GATT_PERM_READ /* permissions */
}
该流程中各层之间的交互如下图所示,protocol stack layer将从attribute element中读取attribute value,并直接在read response中响应该attribute value。
由APP提供Attribute Value且结果未挂起
flag为 ATTRIB_FLAG_VALUE_APPL
的attribute会涉及该流程。
{
ATTRIB_FLAG_VALUE_APPL, /* wFlags */
{ /* bTypeValue */
LO_WORD(GATT_UUID_CHAR_BLP_MEASUREMENT),
HI_WORD(GATT_UUID_CHAR_BLP_MEASUREMENT)
},
0, /* bValueLen */
NULL,
GATT_PERM_NONE /* wPermissions */
}
该流程中各层之间的交互如图所示。当本地设备收到read request时,protocol stack将发送read indication给profile server layer,profile server layer将调用read attribute回调函数获取specific service中的attribute value。然后,profile server layer通过read confirmation将数据传递给protocol stack。
示例代码如下, app_profile_callback()
的返回结果必须为 APP_RESULT_SUCCESS
。
T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
{
T_APP_RESULT app_result = APP_RESULT_SUCCESS;
......
else if (service_id == simp_srv_id)
{
TSIMP_CALLBACK_DATA *p_simp_cb_data = (TSIMP_CALLBACK_DATA *)p_data;
switch (p_simp_cb_data->msg_type)
{
case SERVICE_CALLBACK_TYPE_READ_CHAR_VALUE:
{
if (p_simp_cb_data->msg_data.read_value_index == SIMP_READ_V1)
{
uint8_t value[2] = {0x01, 0x02};
APP_PRINT_INFO0("SIMP_READ_V1");
simp_ble_service_set_parameter(SIMPLE_BLE_SERVICE_PARAM_V1_READ_CHAR_VAL, 2, &value);
}
}
break;
}
......
return app_result;
}
由APP提供Attribute Value且结果挂起
flag为 ATTRIB_FLAG_VALUE_APPL
的attribute会涉及该流程。
由于APP提供的attribute value不能立即被读取,specific service需要调用 server_attr_read_confirm()
传递attribute value。该流程中各层之间的交互如图所示。
示例代码如下, app_profile_callback()
的返回结果必须为 APP_RESULT_PENDING
。
T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
{
T_APP_RESULT app_result = APP_RESULT_PENDING;
......
else if (service_id == simp_srv_id)
{
TSIMP_CALLBACK_DATA *p_simp_cb_data = (TSIMP_CALLBACK_DATA *)p_data;
switch (p_simp_cb_data->msg_type)
{
case SERVICE_CALLBACK_TYPE_READ_CHAR_VALUE:
{
if (p_simp_cb_data->msg_data.read_value_index == SIMP_READ_V1)
{
uint8_t value[2] = {0x01, 0x02};
APP_PRINT_INFO0("SIMP_READ_V1");
simp_ble_service_set_parameter(SIMPLE_BLE_SERVICE_PARAM_V1_READ_CHAR_VAL, 2, &value);
}
}
break;
}
......
return app_result;
}
Characteristic Value Write
该流程用于向server写入characteristic value。有四个子流程可以用于写入characteristic value,包括write without response、signed write without response、write characteristic value和write long characteristic values。
Write Characteristic Value
- 由Attribute Element提供Attribute Valueflag为
ATTRIB_FLAG_VOID
的attribute会涉及该流程。uint8_t cha_val_v8_011[1] = {0x08}; const T_ATTRIB_APPL gatt_dfindme_profile[] = { ...... /* handle = 0x000e Characteristic value -- Value V8 */ { ATTRIB_FLAG_VOID, /* flags */ { /* type_value */ LO_WORD(0xB008), HI_WORD(0xB008), }, 1, /* bValueLen */ (void *)cha_val_v8_011, GATT_PERM_READ | GATT_PERM_WRITE /* permissions */ } ...... }
该流程中各层之间的交互如图所示,write request用于请求server写attribute value,且在写操作结束之后直接发送write response。
- 由APP提供Attribute Value且结果未挂起flag为
ATTRIB_FLAG_VALUE_APPL
的attribute会涉及该流程。该流程中各层之间的交互如图所示,当本地设备收到write request,protocol stack将发送write request indication给profile server layer,profile server layer将调用write attribute callback写入specific service中的attribute value。Profile server layer将通过write request confirmation返回写入结果。
若在profile server layer返回write confirmation之后server需要执行后续流程,回调函数指针
write_ind_post_proc()
不为NULL时,将调用该回调函数。由
server_add_service()
注册的srv_cbs回调函数通知APP,write_type为WRITE_REQUEST
。示例代码如下,app_profile_callback()
的返回结果必须为APP_RESULT_SUCCESS
。T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data) { T_APP_RESULT app_result = APP_RESULT_SUCCESS; ...... else if (service_id == simp_srv_id) { TSIMP_CALLBACK_DATA *p_simp_cb_data = (TSIMP_CALLBACK_DATA *)p_data; switch (p_simp_cb_data->msg_type) { case SERVICE_CALLBACK_TYPE_WRITE_CHAR_VALUE: { switch (p_simp_cb_data->msg_data.write.opcode) { case SIMP_WRITE_V2: { APP_PRINT_INFO2("SIMP_WRITE_V2: write type %d, len %d", p_simp_cb_data->msg_data.write.write_type, p_simp_cb_data->msg_data.write.len); } ...... return app_result; }
- 由APP提供Attribute Value且结果挂起flag为
ATTRIB_FLAG_VALUE_APPL
的attribute会涉及该流程。若写attribute value流程不能立即结束,specific service将会调用
server_attr_write_confirm()
。该流程中各层之间的交互如图所示。Write indication post procedure为可选流程。由
server_add_service()
注册的srv_cbs回调函数通知APP,write_type为WRITE_REQUEST
。示例代码如下,app_profile_callback()
的返回结果必须为APP_RESULT_PENDING
。T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data) { T_APP_RESULT app_result = APP_RESULT_PENDING; ...... else if (service_id == simp_srv_id) { TSIMP_CALLBACK_DATA *p_simp_cb_data = (TSIMP_CALLBACK_DATA *)p_data; switch (p_simp_cb_data->msg_type) { case SERVICE_CALLBACK_TYPE_WRITE_CHAR_VALUE: { switch (p_simp_cb_data->msg_data.write.opcode) { case SIMP_WRITE_V2: { APP_PRINT_INFO2("SIMP_WRITE_V2: write type %d, len %d", p_simp_cb_data->msg_data.write.write_type, p_simp_cb_data->msg_data.write.len); } ...... return app_result; }
写CCCD值
若本地设备收到client的write request以写CCCD,protocol stack将更新CCCD信息,profile server layer通过更新CCCD回调函数向APP通知CCCD信息已更新。该流程中各层之间的交互如图所示。
void simp_ble_service_cccd_update_cb(uint8_t conn_id, T_SERVER_ID service_id, uint16_t index, uint16_t cccbits) { TSIMP_CALLBACK_DATA callback_data; bool is_handled = false; callback_data.conn_id = conn_id; callback_data.msg_type = SERVICE_CALLBACK_TYPE_INDIFICATION_NOTIFICATION; APP_PRINT_INFO2("simp_ble_service_cccd_update_cb: index = %d, cccbits 0x%x", index, cccbits); switch (index) { case SIMPLE_BLE_SERVICE_CHAR_NOTIFY_CCCD_INDEX: { if (cccbits & GATT_CLIENT_CHAR_CONFIG_NOTIFY) { // Enable Notification callback_data.msg_data.notification_indification_index = SIMP_NOTIFY_INDICATE_V3_ENABLE; } else { // Disable Notification callback_data.msg_data.notification_indification_index = SIMP_NOTIFY_INDICATE_V3_DISABLE; } is_handled = true; } break; case SIMPLE_BLE_SERVICE_CHAR_INDICATE_CCCD_INDEX: { if (cccbits & GATT_CLIENT_CHAR_CONFIG_INDICATE) { // Enable Indication callback_data.msg_data.notification_indification_index = SIMP_NOTIFY_INDICATE_V4_ENABLE; } else { // Disable Indication callback_data.msg_data.notification_indification_index = SIMP_NOTIFY_INDICATE_V4_DISABLE; } is_handled = true; } break; default: break; } /* Notify APP. */ if (pfn_simp_ble_service_cb && (is_handled == true)) { pfn_simp_ble_service_cb(service_id, (void *)&callback_data); } }
由
server_add_service()
注册的srv_cbs回调函数通知APP,msg_type为SERVICE_CALLBACK_TYPE_INDIFICATION_NOTIFICATION
。T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data) { T_APP_RESULT app_result = APP_RESULT_SUCCESS; ...... else if (service_id == simp_srv_id) { TSIMP_CALLBACK_DATA *p_simp_cb_data = (TSIMP_CALLBACK_DATA *)p_data; switch (p_simp_cb_data->msg_type) { case SERVICE_CALLBACK_TYPE_INDIFICATION_NOTIFICATION: { switch (p_simp_cb_data->msg_data.notification_indification_index) { case SIMP_NOTIFY_INDICATE_V3_ENABLE: { APP_PRINT_INFO0("SIMP_NOTIFY_INDICATE_V3_ENABLE"); } break; case SIMP_NOTIFY_INDICATE_V3_DISABLE: { APP_PRINT_INFO0("SIMP_NOTIFY_INDICATE_V3_DISABLE"); } break; case SIMP_NOTIFY_INDICATE_V4_ENABLE: { APP_PRINT_INFO0("SIMP_NOTIFY_INDICATE_V4_ENABLE"); } break; case SIMP_NOTIFY_INDICATE_V4_DISABLE: { APP_PRINT_INFO0("SIMP_NOTIFY_INDICATE_V4_DISABLE"); } break; default: break; } } break; ...... return app_result; }
Write without Response/Signed Write without Response
Write without Response/Signed Write without Response和write characteristic value流程的区别在于server不会发送写入结果给client。
由APP提供Attribute Value
flag为
ATTRIB_FLAG_VALUE_APPL
的attribute会涉及该流程。该流程中各层之间的交互如图所示。当本地设备收到write command或signed write command,由
server_add_service()
注册的回调函数write_attr_cb()
将会被调用。由
server_add_service()
注册的srv_cbs回调函数将会通知APP,write_type为WRITE_WITHOUT_RESPONSE
或WRITE_SIGNED_WITHOUT_RESPONSE
。
Write Long Characteristic Values
Prepare Write
若characteristic value的长度大于write request支持的characteristic value的最大长度(ATT_MTU-3),client将使用prepare write request。需要被写入的值将先存储在profile server layer,然后profile server layer将处理prepare write requeset indication,并返回prepare write confirmation。该流程中各层之间的交互如图所示。
结果未挂起的Execute Write
在发送prepare write request之后,execute write quest用于完成写入attribute value的流程。由
server_add_service()
注册的srv_cbs回调函数通知APP,write_type为WRITE_LONG
。Write indication post procedure为可选流程。该流程中各层之间的交互如图所示。结果挂起的Execute Write
若写入操作不能立即完成,specific service将调用
server_exec_write_confirm()
。Write indication post procedure为可选流程。该流程中各层之间的交互如图所示。
Characteristic Value Notification
Server用该流程通知client一个characteristic value。Server主动调用 server_send_data()
发送数据,在发送流程完成之后,会通过server通用回调函数通知APP。该流程中各层之间的交互如图所示。
bool simp_ble_service_send_v3_notify(uint8_t conn_id, T_SERVER_ID service_id, void *p_value,
uint16_t length)
{
APP_PRINT_INFO0("simp_ble_service_send_v3_notify");
// send notification to client
return server_send_data(conn_id, service_id, SIMPLE_BLE_SERVICE_CHAR_V3_NOTIFY_INDEX, p_value,
length,
GATT_PDU_TYPE_ANY);
}
Characteristic Value Indication
Server用该流程给client指示一个characteristic value。一旦收到indication,client必须用confirmation响应。在server收到handle value confirmation之后,会通过server通用回调函数通知APP。该流程中各层之间的交互如图所示。
bool simp_ble_service_send_v4_indicate(uint8_t conn_id, T_SERVER_ID service_id, void *p_value,
uint16_t length)
{
APP_PRINT_INFO0("simp_ble_service_send_v4_indicate");
// send indication to client
return server_send_data(conn_id, service_id, SIMPLE_BLE_SERVICE_CHAR_V4_INDICATE_INDEX, p_value,
length, GATT_PDU_TYPE_ANY);
}
在收到handle value confirmation后,将调用 app_profile_callback()
。
T_APP_RESULT app_profile_callback(T_SERVER_ID service_id, void *p_data)
{
T_APP_RESULT app_result = APP_RESULT_SUCCESS;
if (service_id == SERVICE_PROFILE_GENERAL_ID)
{
T_SERVER_APP_CB_DATA *p_param = (T_SERVER_APP_CB_DATA *)p_data;
switch (p_param->eventId)
{
......
case PROFILE_EVT_SEND_DATA_COMPLETE:
APP_PRINT_INFO5("PROFILE_EVT_SEND_DATA_COMPLETE: conn_id %d, cause 0x%x, service_id %d, attrib_idx 0x%x, credits %d",
p_param->event_data.send_data_result.conn_id,
p_param->event_data.send_data_result.cause,
p_param->event_data.send_data_result.service_id,
p_param->event_data.send_data_result.attrib_idx,
p_param->event_data.send_data_result.credits);
if (p_param->event_data.send_data_result.cause == GAP_SUCCESS)
{
APP_PRINT_INFO0("PROFILE_EVT_SEND_DATA_COMPLETE success");
}
else
{
APP_PRINT_ERROR0("PROFILE_EVT_SEND_DATA_COMPLETE failed");
}
break;
default:
break;
}
}
}
Specific Service的实现
为实现一个用例,一个Profile需要包含一个或多个services,而service由characteristics组成,每个characteristic包括必选的characteristic value和可选的characteristic descriptor。因而,service、characteristic以及characteristic的组成成分(例如,值和descriptors)包含Profile数据,并存储于server的attributes中。
以下为开发specific service的步骤:
定义Service和Profile Spec。
定义Service Attribute Table。
定义Service与APP之间的接口。
定义
xxx_add_service()
,xxx_set_parameter()
,xxx_notify()
,xxx_indicate()
等API。用
T_FUN_GATT_SERVICE_CBS
实现回调函数xxx_ble_service_cbs
。
本节内容以simple LE service为例,简单介绍如何实现specific service。具体细节参见 simple_ble_service.c
和 simple_ble_service.h
中的源代码。
定义Service和Profile Spec
为实现specific service,定义Service和Profile Spec。
定义Service Attribute Table
由attribute elements组成的service通过service table定义,一个service table可以由多个services组成。
Attribute Element
Attribute Element是service的基本单元,其结构体定义在 gatt.h
中。
typedef struct
{
uint16_t flags; /**< Attribute flags @ref GATT_ATTRIBUTE_FLAG */
uint8_t type_value[2 + 14]; /**< 16 bit UUID + included value or 128 bit UUID */
uint16_t value_len; /**< Length of value */
void *p_value_context; /**< Pointer to value if @ref ATTRIB_FLAG_VALUE_INCL
and @ref ATTRIB_FLAG_VALUE_APPL not set */
uint32_t permissions; /**< Attribute permission @ref GATT_ATTRIBUTE_PERMISSIONS */
} T_ATTRIB_APPL;
Flags
Flags的可选值和描述如下。
Option Values
Description
ATTRIB_FLAG_LE
Used only for primary service declaration attributes if GATT over LE is supported.
ATTRIB_FLAG_VOID
Attribute value is neither supplied by APP nor included following 16bit UUID.
Attribute value is pointed by p_value_context and value_len shall be set to the length of attribute value.
ATTRIB_FLAG_VALUE_INCL
Attribute value is included following 16 bit UUID.
ATTRIB_FLAG_VALUE_APPL
APP has to supply attribute value.
ATTRIB_FLAG_UUID_128BIT
Attribute uses 128 bit UUID.
ATTRIB_FLAG_ASCII_Z
Attribute value is ASCII_Z string.
ATTRIB_FLAG_CCCD_APPL
APP will be informed if CCCD value is changed.
ATTRIB_FLAG_CCCD_NO_FILTER
APP will be informed about CCCD value when CCCD is written by client, no matter it is changed or not.
- -ATTRIB_FLAG_LE
仅适用于type为primary service declaration的attribute,表示primary service允许通过LE链路访问。
- -ATTRIB_FLAG_VOID, --ATTRIB_FLAG_VALUE_INCL, --ATTRIB_FLAG_VALUE_APPL
三者之一必须在attribute element中使用。
- -ATTRIB_FLAG_VALUE_INCL
表示attribute value被置于type_value的最后14字节(type_value的前2个字节用于保存UUID),且value_len是放入最后14字节区域的字节数目。由于type_value提供attribute value,p_value_context指针为NULL。
- -ATTRIB_FLAG_VALUE_APPL
表示由APP提供attribute value。只要协议栈涉及对该attribute value的操作,协议栈将与APP进行交互以完成相应处理流程。由于attribute value是由APP提供的,type_value仅保存UUID,value_len为0,且p_value_context指针为NULL。
- -ATTRIB_FLAG_VOID
表示attribute value既不放置于type_value的最后14个字节,也不由APP提供。此时,type_value仅保存UUID,p_value_context 指针指向attribute value,value_len表示attribute value的长度。
下表展示flags value与read attribute流程使用的actual value之间的关联。
APPL
APPL|SCII_Z
INCL
INCL|ASCII_Z
VOID
VOID|ASCII_Z
If Set
Value_len
Any(NULL)
Any(NULL)
Strlen(value)
Strlen(value)
Strlen(value)
Strlen(value)
type_value+2
Any(NULL)
Any(NULL)
Value
Value
Any(NULL)
Any(NULL)
p_value_context
Any(NULL)
Any(NULL)
Any(NULL)
Any(NULL)
Value
Value
Actual get by read attribute process
Actual length
Reply by APP
Reply by APP
Strlen(value)
Strlen(value)+1
Strlen(value)
Strlen(value)
Actual value
Reply by APP
Reply by APP
Value
Value+
\0
Value
Value+
\0
备注
APPL: ATTRIB_FLAG_VALUE_APPL
VOID: ATTRIB_FLAG_VOID
INCL: ATTRIB_FLAG_VALUE_INCL
ASCII_Z: ATTRIB_FLAG_ASCII_Z
Permissions
Attribute的Permissions指定read或write访问需要的安全级别,同样包括notification或indication。Permissions的值表示该attribute的permission。Attribute permissions是access permissions、encryption permissions、authentication permissions和authorization permissions的组合,其可用值如下。
Types |
Permissions |
---|---|
Read Permissions |
GATT_PERM_READ |
GATT_PERM_READ_AUTHEN_REQ |
|
GATT_PERM_READ_AUTHEN_MITM_REQ |
|
GATT_PERM_READ_AUTHOR_REQ |
|
GATT_PERM_READ_ENCRYPTED_REQ |
|
GATT_PERM_READ_AUTHEN_SC_REQ |
|
Write Permissions |
GATT_PERM_WRITE |
GATT_PERM_WRITE_AUTHEN_REQ |
|
GATT_PERM_WRITE_AUTHEN_MITM_REQ |
|
GATT_PERM_WRITE_AUTHOR_REQ |
|
GATT_PERM_WRITE_ENCRYPTED_REQ |
|
GATT_PERM_WRITE_AUTHEN_SC_REQ |
|
Notify/Indicate Permissions |
GATT_PERM_NOTIF_IND |
GATT_PERM_NOTIF_IND_AUTHEN_REQ |
|
GATT_PERM_NOTIF_IND_AUTHEN_MITM_REQ |
|
GATT_PERM_NOTIF_IND_AUTHOR_REQ |
|
GATT_PERM_NOTIF_IND_ENCRYPTED_REQ |
|
GATT_PERM_NOTIF_IND_AUTHEN_SC_REQ |
Service Table
Service包含一组attributes,其被称之为service table。一个service table包含各种类型的attributes,例如service declaration、characteristic declaration、characteristic value和characteristic descriptor declaration。
Service table的示例如下表所示,在 ble_peripheral示例工程 的 simple_ble_service.c
中实现。
Flags |
Attribute Type |
Attribute Value |
Permission |
---|---|---|---|
INCL | LE |
<<primary service declaration>> |
<<simple profile UUID-0xA00A>> |
read |
INCL |
<<characteristic declaration>> |
Property(read) |
read |
APPL |
<<characteristic value>> |
UUID(0xB001),Value not defined here. |
read |
VOID | ASCII_Z |
<<Characteristic User Description>> |
UUID(0x2901), Value defined in p_value_context. |
read |
INCL |
<<characteristic declaration>> |
Property(write | write without response) |
read |
APPL |
<<characteristic value>> |
UUID(0xB002),Value not defined here. |
write |
INCL |
<<characteristic declaration>> |
Property(notify) |
read |
APPL |
<<characteristic value>> |
UUID(0xB003), Value not defined here. |
none |
CCCD_APPL |
<<client characteristic configuration descriptor>> |
Default CCCD value |
read | write |
INCL |
<<characteristic declaration>> |
Property(indicate) |
read |
APPL |
<<characteristic value>> |
UUID(0xB004), Value not defined here. |
none |
CCCD_APPL |
<<client characteristic configuration descriptor>> |
Default CCCD value |
read | write |
备注
LE为ATTRIB_FLAG_LE的缩写
INCL为ATTRIB_FLAG_VALUE_INCL的缩写
APPL为ATTRIB_FLAG_VALUE_APPL的缩写
Service table的示例代码如下:
/**< @brief profile/service definition. */
const T_ATTRIB_APPL simple_ble_service_tbl[] =
{
/* <<Primary Service>>, .. */
{
(ATTRIB_FLAG_VALUE_INCL | ATTRIB_FLAG_LE), /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_PRIMARY_SERVICE),
HI_WORD(GATT_UUID_PRIMARY_SERVICE),
LO_WORD(GATT_UUID_SIMPLE_PROFILE), /* service UUID */
HI_WORD(GATT_UUID_SIMPLE_PROFILE)
},
UUID_16BIT_SIZE, /* bValueLen */
NULL, /* p_value_context */
GATT_PERM_READ /* permissions */
},
/* <<Characteristic>> demo for read */
{
ATTRIB_FLAG_VALUE_INCL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHARACTERISTIC),
HI_WORD(GATT_UUID_CHARACTERISTIC),
GATT_CHAR_PROP_READ /* characteristic properties */
/* characteristic UUID not needed here, is UUID of next attrib. */
},
1, /* bValueLen */
NULL,
GATT_PERM_READ /* permissions */
},
{
ATTRIB_FLAG_VALUE_APPL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_SIMPLE_V1_READ),
HI_WORD(GATT_UUID_CHAR_SIMPLE_V1_READ)
},
0, /* bValueLen */
NULL,
#if SIMP_SRV_AUTHEN_EN
GATT_PERM_READ_AUTHEN_REQ /* permissions */
#else
GATT_PERM_READ /* permissions */
#endif
},
{
ATTRIB_FLAG_VOID | ATTRIB_FLAG_ASCII_Z, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_USER_DESCR),
HI_WORD(GATT_UUID_CHAR_USER_DESCR),
},
(sizeof(v1_user_descr) - 1), /* bValueLen */
(void *)v1_user_descr,
GATT_PERM_READ /* permissions */
},
/* <<Characteristic>> demo for write */
{
ATTRIB_FLAG_VALUE_INCL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHARACTERISTIC),
HI_WORD(GATT_UUID_CHARACTERISTIC),
(GATT_CHAR_PROP_WRITE | GATT_CHAR_PROP_WRITE_NO_RSP) /* characteristic properties */
/* characteristic UUID not needed here, is UUID of next attrib. */
},
1, /* bValueLen */
NULL,
GATT_PERM_READ /* permissions */
},
{
ATTRIB_FLAG_VALUE_APPL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_SIMPLE_V2_WRITE),
HI_WORD(GATT_UUID_CHAR_SIMPLE_V2_WRITE)
},
0, /* bValueLen */
NULL,
#if SIMP_SRV_AUTHEN_EN
GATT_PERM_WRITE_AUTHEN_REQ /* permissions */
#else
GATT_PERM_WRITE /* permissions */
#endif
},
/* <<Characteristic>>, demo for notify */
{
ATTRIB_FLAG_VALUE_INCL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHARACTERISTIC),
HI_WORD(GATT_UUID_CHARACTERISTIC),
(GATT_CHAR_PROP_NOTIFY) /* characteristic properties */
/* characteristic UUID not needed here, is UUID of next attrib. */
},
1, /* bValueLen */
NULL,
GATT_PERM_READ /* permissions */
},
{
ATTRIB_FLAG_VALUE_APPL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_SIMPLE_V3_NOTIFY),
HI_WORD(GATT_UUID_CHAR_SIMPLE_V3_NOTIFY)
},
0, /* bValueLen */
NULL,
GATT_PERM_NONE /* permissions */
},
/* client characteristic configuration */
{
ATTRIB_FLAG_VALUE_INCL | ATTRIB_FLAG_CCCD_APPL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_CLIENT_CONFIG),
HI_WORD(GATT_UUID_CHAR_CLIENT_CONFIG),
/* NOTE: this value has an instantiation for each client, a write to */
/* this attribute does not modify this default value: */
LO_WORD(GATT_CLIENT_CHAR_CONFIG_DEFAULT), /* client char. config. bit field */
HI_WORD(GATT_CLIENT_CHAR_CONFIG_DEFAULT)
},
2, /* bValueLen */
NULL,
#if SIMP_SRV_AUTHEN_EN
(GATT_PERM_READ_AUTHEN_REQ | GATT_PERM_WRITE_AUTHEN_REQ) /* permissions */
#else
(GATT_PERM_READ | GATT_PERM_WRITE) /* permissions */
#endif
},
/* <<Characteristic>> demo for indicate */
{
ATTRIB_FLAG_VALUE_INCL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHARACTERISTIC),
HI_WORD(GATT_UUID_CHARACTERISTIC),
(GATT_CHAR_PROP_INDICATE) /* characteristic properties */
/* characteristic UUID not needed here, is UUID of next attrib. */
},
1, /* bValueLen */
NULL,
GATT_PERM_READ /* permissions */
},
{
ATTRIB_FLAG_VALUE_APPL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_SIMPLE_V4_INDICATE),
HI_WORD(GATT_UUID_CHAR_SIMPLE_V4_INDICATE)
},
0, /* bValueLen */
NULL,
GATT_PERM_NONE /* permissions */
},
/* client characteristic configuration */
{
ATTRIB_FLAG_VALUE_INCL | ATTRIB_FLAG_CCCD_APPL, /* flags */
{ /* type_value */
LO_WORD(GATT_UUID_CHAR_CLIENT_CONFIG),
HI_WORD(GATT_UUID_CHAR_CLIENT_CONFIG),
/* NOTE: this value has an instantiation for each client, a write to */
/* this attribute does not modify this default value: */
LO_WORD(GATT_CLIENT_CHAR_CONFIG_DEFAULT), /* client char. config. bit field */
HI_WORD(GATT_CLIENT_CHAR_CONFIG_DEFAULT)
},
2, /* bValueLen */
NULL,
#if SIMP_SRV_AUTHEN_EN
(GATT_PERM_READ_AUTHEN_REQ | GATT_PERM_WRITE_AUTHEN_REQ) /* permissions */
#else
(GATT_PERM_READ | GATT_PERM_WRITE) /* permissions */
#endif
},
};
定义Service与APP之间的接口
当service的attribute value被读取或写入时,将通过APP注册的回调函数通知APP。以simple LE service为例,定义类型为 TSIMP_CALLBACK_DATA
的数据结构以保存需要通知的结果。
typedef struct
{
uint8_t conn_id;
T_SERVICE_CALLBACK_TYPE msg_type;
TSIMP_UPSTREAM_MSG_DATA msg_data;
} TSIMP_CALLBACK_DATA;
msg_type
表示操作类型是读操作、写操作或更新CCCD操作。typedef enum { SERVICE_CALLBACK_TYPE_INDIFICATION_NOTIFICATION = 1, /**< CCCD update event */ SERVICE_CALLBACK_TYPE_READ_CHAR_VALUE = 2, /**< client read event */ SERVICE_CALLBACK_TYPE_WRITE_CHAR_VALUE = 3, /**< client write event */ } T_SERVICE_CALLBACK_TYPE;
msg_data
保存读操作、写操作或更新CCCD操作的数据。
定义xxx_add_service() , xxx_set_parameter(), xxx_notify(), xxx_indicate()等API
- xxx_add_service()用于向profile server layer添加service table,注册针对attribute的读操作、写操作或更新CCCD操作的回调函数。
- xxx_set_parameter()用于供APP设置service相关数据。
- xxx_notify()用于发送notification数据。
- xxx_indicate()用于发送indication数据。
用 T_FUN_GATT_SERVICE_CBS
实现回调函数xxx_ble_service_cbs
xxx_ble_service_cbs用于处理client的读操作、写操作或更新CCCD操作。
const T_FUN_GATT_SERVICE_CBS simp_ble_service_cbs =
{
simp_ble_service_attr_read_cb, // Read callback function pointer
simp_ble_service_attr_write_cb, // Write callback function pointer
simp_ble_service_cccd_update_cb // CCCD update callback function pointer
};
在xxx_ble_service_add_service()中调用 server_add_service()
以注册该回调函数。
T_SERVER_ID simp_ble_service_add_service(void *p_func)
{
if (false == server_add_service(&simp_service_id,
(uint8_t *)simple_ble_service_tbl,
sizeof(simple_ble_service_tbl),
simp_ble_service_cbs))
{
APP_PRINT_ERROR0("simp_ble_service_add_service: fail");
simp_service_id = 0xff;
return simp_service_id;
}
pfn_simp_ble_service_cb = (P_FUN_SERVER_GENERAL_CB)p_func;
return simp_service_id;
}
GATT Profile Client
Profile- Client: 基于GATT的Profile在client端的实现的公共接口。
概述
Profile的client接口给开发者提供discover services、接收和处理indication和notification、给GATT Server发送read/write request的功能。
下图展示了Profile client层级。Profile包括profile client layer和specific profile client。位于protocol stack之上的profile client layer封装供specific client访问protocol stack的接口,因此针对specific client的开发不涉及protocol stack的细节,使开发变得更简单和清晰。基于profile client layer的specific client是由application layer实现的,其实现不同于specific server的实现。profile client不涉及attribute table,提供收集和获取信息的功能,而不是提供service和信息。
Profile Client Layer
Profile Client Layer处理与protocol stack layer的交互,提供接口用于设计specific client。Client将对server进行discover services和discover characteristics、读取和写入attribute、接收和处理notifications和indications相关操作。
Client通用回调函数
Client通用回调函数用于向APP发送 client_all_primary_srv_discovery()
的结果,client_id为 CLIENT_PROFILE_GENERAL_ID
。该回调函数由 client_register_general_client_cb()
初始化。
void app_le_profile_init(void)
{
client_init(3);
......
client_register_general_client_cb(app_client_callback);
}
static T_USER_CMD_PARSE_RESULT cmd_srvdis(T_USER_CMD_PARSED_VALUE *p_parse_value)
{
uint8_t conn_id = p_parse_value->dw_param[0];
T_GAP_CAUSE cause;
cause = client_all_primary_srv_discovery(conn_id, CLIENT_PROFILE_GENERAL_ID);
return (T_USER_CMD_PARSE_RESULT)cause;
}
T_APP_RESULT app_client_callback(T_CLIENT_ID client_id, uint8_t conn_id, void *p_data)
{
T_APP_RESULT result = APP_RESULT_SUCCESS;
APP_PRINT_INFO2("app_client_callback: client_id %d, conn_id %d",
client_id, conn_id);
if (client_id == CLIENT_PROFILE_GENERAL_ID)
{
T_CLIENT_APP_CB_DATA *p_client_app_cb_data = (T_CLIENT_APP_CB_DATA *)p_data;
switch (p_client_app_cb_data->cb_type)
{
case CLIENT_APP_CB_TYPE_DISC_STATE:
......
}
}
}
若APP不使用client_id为 CLIENT_PROFILE_GENERAL_ID
的 client_all_primary_srv_discovery()
,APP不需要注册该通用回调函数。
Specific Client回调函数
添加Client
Protocol client layer维护所有添加的specific clients的信息。首先,初始化需要添加的client table的总数目,profile client layer提供 client_init()
接口用于初始化client table数目。
Protocol client layer提供 client_register_spec_client_cb()
接口以注册specific client回调函数。图 向Profile Client Layer添加Specific Clients 展示client layer包含多个specific client tables,添加specific client的情况。
APP向profile client layer添加specific client之后,APP将记录每个已添加specific client对应返回的client id,以实现后续的数据交互流程。
回调函数
需要在specific client模块实现specific client回调函数,在 profile_client.h
中定义specific client回调函数数据结构。
typedef struct
{
P_FUN_DISCOVER_STATE_CB discover_state_cb; //!< Discovery state callback function pointer
P_FUN_DISCOVER_RESULT_CB discover_result_cb; //!< Discovery result callback function pointer
P_FUN_READ_RESULT_CB read_result_cb; //!< Read response callback function pointer
P_FUN_WRITE_RESULT_CB write_result_cb; //!< Write result callback function pointer
P_FUN_NOTIFY_IND_RESULT_CB notify_ind_result_cb;//!< Notify Indication callback function pointer
P_FUN_DISCONNECT_CB disconnect_cb; //!< Disconnection callback function pointer
} T_FUN_CLIENT_CBS;
参数说明:
discover_state_cb: discovery状态回调函数,用于向specific client模块通知client_xxx_discovery 的discover状态。
discover_result_cb: discovery结果回调函数,用于向specific client模块通知client_xxx_discovery的discover结果。
read_result_cb: read结果回调函数,用于向specific client模块通知
client_attr_read()
或client_attr_read_using_uuid()
的读取结果。write_result_cb: write结果回调函数,用于向specific client模块通知
client_attr_write()
的写入结果。notify_ind_result_cb: notification或indication回调函数,用于向specific client模块通知收到来自server的notification或indication数据。
disconnect_cb: disconnection回调函数,用于向specific client模块通知一条LE链路已断开。
Discovery流程
若本地设备不保存server的handle信息,那么在与server建立connection后, client通常会执行discovery流程。Specific client需要调用 client_xxx_discovery()
启动discovery流程,然后specific client需要处理回调函数 discover_state_cb()
中的discovery状态以及回调函数 discover_result_cb()
中的discovery结果。
该流程中各层中间的交互如图所示。
Discovery状态
Reference API |
T_DISCOVERY_STATE |
---|---|
client_all_primary_srv_discovery() |
DISC_STATE_SRV_DONE, DISC_STATE_FAILED |
client_by_uuid_srv_discovery() |
DISC_STATE_SRV_DONE, DISC_STATE_FAILED |
client_by_uuid128_srv_discovery() |
DISC_STATE_SRV_DONE, DISC_STATE_FAILED |
client_all_char_discovery() |
DISC_STATE_CHAR_DONE, DISC_STATE_FAILED |
client_all_char_descriptor_discovery() |
DISC_STATE_CHAR_DESCRIPTOR_DONE, DISC_STATE_FAILED |
client_relationship_discovery() |
DISC_STATE_RELATION_DONE, DISC_STATE_FAILED |
client_by_uuid_char_discovery() |
DISC_STATE_CHAR_UUID16_DONE, DISC_STATE_FAILED |
client_by_uuid128_char_discovery() |
DISC_STATE_CHAR_UUID128_DONE, DISC_STATE_FAILED |
Discovery结果
Reference API |
T_DISCOVERY_RESULT_TYPE |
T_DISCOVERY_RESULT_DATA |
---|---|---|
client_all_primary_srv_discovery() |
DISC_RESULT_ALL_SRV_UUID16 |
T_GATT_SERVICE_ELEM16 *p_srv_uuid16_disc_data |
client_all_primary_srv_discovery() |
DISC_RESULT_ALL_SRV_UUID128 |
T_GATT_SERVICE_ELEM128 *p_srv_uuid128_disc_data |
client_by_uuid_srv_discovery(), client_by_uuid128_srv_discovery() |
DISC_RESULT_SRV_DATA |
T_GATT_SERVICE_BY_UUID_ELEM *p_srv_disc_data |
client_all_char_discovery() |
DISC_RESULT_CHAR_UUID16 |
T_GATT_CHARACT_ELEM16 *p_char_uuid16_disc_data |
client_all_char_discovery() |
DISC_RESULT_CHAR_UUID128 |
T_GATT_CHARACT_ELEM128 *p_char_uuid128_disc_data |
client_all_char_descriptor_discovery() |
DISC_RESULT_CHAR_DESC_UUID16 |
T_GATT_CHARACT_DESC_ELEM16 *p_char_desc_uuid16_disc_data |
client_all_char_descriptor_discovery() |
DISC_RESULT_CHAR_DESC_UUID128 |
T_GATT_CHARACT_DESC_ELEM128 *p_char_desc_uuid128_disc_data |
client_relationship_discovery() |
DISC_RESULT_RELATION_UUID16 |
T_GATT_RELATION_ELEM16 *p_relation_uuid16_disc_data |
client_relationship_discovery() |
DISC_RESULT_RELATION_UUID128 |
T_GATT_RELATION_ELEM128 *p_relation_uuid128_disc_data |
client_by_uuid_char_discovery() |
DISC_RESULT_BY_UUID16_CHAR |
T_GATT_CHARACT_ELEM16 *p_char_uuid16_disc_data |
client_by_uuid_char_discovery() |
DISC_RESULT_BY_UUID128_CHAR |
T_GATT_CHARACT_ELEM128 *p_char_uuid128_disc_data |
Characteristic Value Read
该流程用于读取server的characteristic value。在profile client layer有两个子流程可用于读取characteristic value:Read Characteristic Value by Handle和Read Characteristic Value by UUID。
Read Characteristic Value by Handle
当client已知Characteristic Value Handle时,该流程可用于读取server的characteristic value。Read Characteristic Value by Handle流程包含三个阶段,Phase 2为可选阶段。
- Phase 1调用
client_attr_read()
以读取characteristic value。 - Phase 2可选阶段。若characteristic value的长度大于(ATT_MTU-1)字节,Read Response仅包含characteristic value的前(ATT_MTU-1)字节,之后则使用Read Long Characteristic Value流程读取characteristic value。
- Phase 3Profile client layer调用
read_result_cb()
以返回读取结果。
该流程中各层之间的交互如图所示。
Read Characteristic Value by UUID
当client仅已知UUID时,该流程可用于读取server的characteristic value。Read Characteristic Value by UUID流程包含三个阶段,Phase 2为可选阶段。
- Phase 1调用
client_attr_read_using_uuid()
以读取characteristic value。 - Phase 2可选阶段。若characteristic value的长度大于(ATT_MTU-4)字节,Read by Type Response仅包含characteristic value的前(ATT_MTU-4)字节,之后则使用Read Long Characteristic Value流程读取characteristic value。
- Phase 3Profile client layer调用
read_result_cb()
以返回读取结果。
该流程中各层之间的交互如图所示。
Characteristic Value Write
该流程用于写入server的characteristic value。在profile client layer有四个子流程可用于写入characteristic value:Write without Response、Signed Write without Response、Write Characteristic Value和Write Long Characteristic Values。
Write Characteristic Value
当client已知Characteristic Value Handle时,该流程可用于写入server的characteristic value。当characteristic value的长度小于或等于(ATT_MTU-3)字节,将使用该流程。否则,将使用Write Long Characteristic Values流程。
该流程中各层之间的交互如图所示。
Write Long Characteristic Values
当client已知Characteristic Value Handle,且characteristic value的长度大于(ATT_MTU-3)字节时,该流程可用于写入server的characteristic value。
该流程中各层之间的交互如图所示。
Write Without Response
当client已知Characteristic Value Handle,且client不需要写入操作成功执行的应答时,该流程可用于写入server的characteristic value。characteristic value的长度小于或等于(ATT_MTU-3)字节。
该流程中各层之间的交互如图所示。
Signed Write without Response
当client已知Characteristic Value Handle,且ATT Bearer未加密时,该流程可用于写入server的characteristic value。该流程仅适用于Characteristic Properties的authenticated位已使能,client与server设备已绑定的情况。characteristic value的长度小于或等于(ATT_MTU-15)字节。
该流程中各层之间的交互如图所示。
Characteristic Value Notification
该流程适用于server已被配置为向client通知characteristic value,且不需要成功接收notification的应答的情况。
该流程中各层之间的交互如图所示。
profile client layer未存储service handle信息,因此profile client layer无法确定发送该notification的specific client。profile client layer将调用所有注册的specific clients回调函数,因此specific client需要检查是否需要处理该notification。示例代码如下:
static T_APP_RESULT bas_client_notify_ind_cb(uint8_t conn_id, bool notify, uint16_t handle,
uint16_t value_size, uint8_t *p_value)
{
T_APP_RESULT app_result = APP_RESULT_SUCCESS;
T_BAS_CLIENT_CB_DATA cb_data;
uint16_t *hdl_cache;
hdl_cache = bas_table[conn_id].hdl_cache;
cb_data.cb_type = BAS_CLIENT_CB_TYPE_NOTIF_IND_RESULT;
if (handle == hdl_cache[HDL_BAS_BATTERY_LEVEL])
{
cb_data.cb_content.notify_data.battery_level = *p_value;
}
else
{
return APP_RESULT_SUCCESS;
}
if (bas_client_cb)
{
app_result = (*bas_client_cb)(bas_client, conn_id, &cb_data);
}
return app_result;
}
Characteristic Value Indication
该流程适用于server已被配置为向client通知characteristic value,且需要成功接收indication的应答的情况。
结果未挂起的Characteristic Value Indication
回调函数
notify_ind_result_cb()
的返回结果不为APP_RESULT_PENDING
。该流程中各层之间的交互如图所示。结果挂起的Characteristic Value Indication
回调函数
notify_ind_result_cb()
的返回结果为APP_RESULT_PENDING
。APP需要调用client_attr_ind_confirm()
以发送confirmation。该流程中各层之间的交互如图所示。profile client layer未存储service handle信息,因此profile client layer无法确定发送该indication的specific client。profile client layer将调用所有注册的specific clients回调函数,因此specific client需要检查是否需要处理该indication,示例代码如下:
static T_APP_RESULT simp_ble_client_notif_ind_result_cb(uint8_t conn_id, bool notify, uint16_t handle, uint16_t value_size, uint8_t *p_value) { T_APP_RESULT app_result = APP_RESULT_SUCCESS; T_SIMP_CLIENT_CB_DATA cb_data; uint16_t *hdl_cache; hdl_cache = simp_table[conn_id].hdl_cache; cb_data.cb_type = SIMP_CLIENT_CB_TYPE_NOTIF_IND_RESULT; if (handle == hdl_cache[HDL_SIMBLE_V3_NOTIFY]) { cb_data.cb_content.notif_ind_data.type = SIMP_V3_NOTIFY; cb_data.cb_content.notif_ind_data.data.value_size = value_size; cb_data.cb_content.notif_ind_data.data.p_value = p_value; } else if (handle == hdl_cache[HDL_SIMBLE_V4_INDICATE]) { cb_data.cb_content.notif_ind_data.type = SIMP_V4_INDICATE; cb_data.cb_content.notif_ind_data.data.value_size = value_size; cb_data.cb_content.notif_ind_data.data.p_value = p_value; } else { return app_result; } /* Inform APP the notif/ind result. */ if (simp_client_cb) { app_result = (*simp_client_cb)(simp_client, conn_id, &cb_data); } return app_result; }
Sequential Protocol
Request-response Protocol
许多ATT PDUs是sequential request-response protocol。一旦client向server发送request,在收到该server发送的response之前,client不能发送request。Server发送的indication同样是sequential request-response protocol。
以下流程均是sequential request-response protocol。
Discovery流程
Read Characteristic Value By Handle流程
Read Characteristic Value By UUID流程
Write Characteristic Value流程
Write Long Characteristic Values流程
在当前流程完成之前,APP不能启动其它流程。否则,其它流程会启动失败。
当建立connection成功后时,蓝牙协议层可能会发送exchange MTU request。GAP层将发送 GAP_MSG_LE_CONN_MTU_INFO
消息以通知APP,exchange MTU流程已完成。在收到 GAP_MSG_LE_CONN_MTU_INFO
消息后,APP可以启动以上流程。
void app_handle_conn_mtu_info_evt(uint8_t conn_id, uint16_t mtu_size)
{
APP_PRINT_INFO2("app_handle_conn_mtu_info_evt: conn_id %d, mtu_size %d", conn_id, mtu_size);
app_discov_services(conn_id, true);
}
Commands
在ATT中,不要求response的command没有流控机制。
Write Without Response
Signed Write Without Response
由于资源有限,蓝牙协议层对commands采用流控机制。
在GAP层中维护credits数目实现对Write Command和Signed Write Command的流控,在收到蓝牙协议层的响应之前,允许APP发送credits笔command。蓝牙协议层能够缓存credits笔command的数据。
当profile client layer向协议层发送command时,credits数目减1。
当command发送给server时,协议层会发送响应给profile client layer,credits数目加1。
credits数目大于0时,才可以发送command。
回调函数 write_result_cb()
可以通知当前的credits数目。APP也可以将参数类型设为 GAP_PARAM_LE_REMAIN_CREDITS
,调用 le_get_gap_param()
函数获取credits数目。
void test(void)
{
uint8_t wds_credits;
le_get_gap_param(GAP_PARAM_LE_REMAIN_CREDITS, &wds_credits);
}
GAP用例
Bluetooth LE GAP用例
本节介绍如何使用Bluetooth LE GAP接口,以下为一些典型用例。
本地设备IRK的设置
IRK是用于生成和解析RPA的128位密钥。本地设备IRK的默认值为全零。
若设备支持RPA的生成且生成RPA作为其本地地址,在分发IRK时该设备必须发送包含有效IRK的Identity Information。
若设备不支持生成RPA作为本地地址,在分发IRK时该设备发送包含IRK为全零的Identity Information。此时,本地设备不会使用RPA,APP不需要设置IRK。
GAP层提供两种设置本地设备IRK的方法。
自动生成本地设备的IRK
若APP希望使用该功能,那么APP需要将
GAP_PARAM_BOND_GEN_LOCAL_IRK_AUTO
设为true
。当GAP启动时,GAP层会调用flash_load_local_irk()
以载入本地设备IRK。若载入失败,则会自动生成IRK,然后调用flash_save_local_irk()
保存本地设备IRK。因此,使用自动生成本地设备IRK功能时,APP不能使用flash_load_local_irk()
和flash_save_local_irk()
。void app_le_gap_init(void) { ...... uint8_t irk_auto = true; le_bond_set_param(GAP_PARAM_BOND_GEN_LOCAL_IRK_AUTO, sizeof(uint8_t), &irk_auto); }
APP生成本地设备的IRK
默认情况下,GAP层会使用该方法,默认IRK为全零。APP可以调用
le_bond_set_param()
设置GAP_PARAM_BOND_SET_LOCAL_IRK
以更改本地设备IRK。void app_le_gap_init(void) { ...... T_LOCAL_IRK le_local_irk = {0, 1, 0, 5, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 9}; le_bond_set_param(GAP_PARAM_BOND_SET_LOCAL_IRK, GAP_KEY_LEN, le_local_irk.local_irk); }
该参数仅在GAP启动过程中是有效的,因此,APP需要在调用
gap_start_bt_stack()
之前设置GAP_PARAM_BOND_SET_LOCAL_IRK
。APP可以生成本地设备IRK,并调用
flash_save_local_irk()
保存IRK。之后APP可以调用flash_load_local_irk()
以载入本地设备IRK,然后设置GAP_PARAM_BOND_SET_LOCAL_IRK
以更改本地设备IRK。
GAP Service Characteristic的可写属性
示例代码位于 LE Scatternet 中。
GAP service的Device Name characteristic和Device Appearance characteristic具有可选的可写属性,该可写属性默认是关闭的。APP可以通过调用 gaps_set_parameter()
设置 GAPS_PARAM_APPEARANCE_PROPERTY
和 GAPS_PARAM_DEVICE_NAME_PROPERTY
来配置可写属性。
可写属性的配置
void app_le_gap_init(void) { uint8_t appearance_prop = GAPS_PROPERTY_WRITE_ENABLE; uint8_t device_name_prop = GAPS_PROPERTY_WRITE_ENABLE; T_LOCAL_APPEARANCE appearance_local; T_LOCAL_NAME local_device_name; if (flash_load_local_appearance(&appearance_local) == 0) { gaps_set_parameter(GAPS_PARAM_APPEARANCE, sizeof(uint16_t), &appearance_local.local_appearance); } if (flash_load_local_name(&local_device_name) == 0) { gaps_set_parameter(GAPS_PARAM_DEVICE_NAME, GAP_DEVICE_NAME_LEN, local_device_name.local_name); } gaps_set_parameter(GAPS_PARAM_APPEARANCE_PROPERTY, sizeof(appearance_prop), &appearance_prop); gaps_set_parameter(GAPS_PARAM_DEVICE_NAME_PROPERTY, sizeof(device_name_prop), &device_name_prop); gatt_register_callback(gap_service_callback); }
GAP Service回调函数消息处理
APP需要调用
gatt_register_callback()
以注册回调函数,该回调函数用于处理GAP service消息。T_APP_RESULT gap_service_callback(T_SERVER_ID service_id, void *p_para) { T_APP_RESULT result = APP_RESULT_SUCCESS; T_GAPS_CALLBACK_DATA *p_gap_data = (T_GAPS_CALLBACK_DATA *)p_para; APP_PRINT_INFO2("gap_service_callback conn_id = %d msg_type = %d\n", p_gap_data->conn_id, p_gap_data->msg_type); if (p_gap_data->msg_type == SERVICE_CALLBACK_TYPE_WRITE_CHAR_VALUE) { switch (p_gap_data->msg_data.opcode) { case GAPS_WRITE_DEVICE_NAME: { T_LOCAL_NAME device_name; memcpy(device_name.local_name, p_gap_data->msg_data.p_value, p_gap_data->msg_data.len); device_name.local_name[p_gap_data->msg_data.len] = 0; flash_save_local_name(&device_name); } break; case GAPS_WRITE_APPEARANCE: { uint16_t appearance_val; T_LOCAL_APPEARANCE appearance; LE_ARRAY_TO_UINT16(appearance_val, p_gap_data->msg_data.p_value); appearance.local_appearance = appearance_val; flash_save_local_appearance(&appearance); } break; default: break; } } return result; }
APP需要将device name和device appearance保存到Flash ,具体内容参见 本地协议栈信息存储。
本地设备使用Static Random Address
示例代码位于 LE Scatternet 中。
在advertising、scanning和connection时,默认使用的local address type是Public Address,可以配置为Static Random Address。
- Random address的生成和存储APP第一次可以调用
le_gen_rand_addr()
生成static random address。然后将生成的地址存储到Flash。如果Flash已经存储过random地址,则可以直接使用。然后调用le_set_gap_param()
的GAP_PARAM_RANDOM_ADDR
来设置random address。注意le_set_gap_param()
的GAP_PARAM_RANDOM_ADDR
只有在初始化之前调用才有效,Stack ready之后应该调用le_set_rand_addr()
。 - 设置Identity AddressStack默认使用public address作为Identity Address。APP需要调用
le_cfg_local_identity_address()
将Identity Address修改为static random address。不正确的Identity Address设置会导致配对后无法重连。 - 设置local address typePeripheral角色或Broadcaster角色调用
le_adv_set_param()
设置local address type以使用本地设备的Static Random Address。Central角色或Observer角色调用le_scan_set_param()
设置local address type以使用本地设备的Static Random Address,示例代码如下:void app_le_gap_init(void) { ...... T_APP_STATIC_RANDOM_ADDR random_addr; bool gen_addr = true; uint8_t local_bd_type = GAP_LOCAL_ADDR_LE_RANDOM; if (app_load_static_random_address(&random_addr) == 0) { if (random_addr.is_exist == true) { gen_addr = false; } } if (gen_addr) { if (le_gen_rand_addr(GAP_RAND_ADDR_STATIC, random_addr.bd_addr) == GAP_CAUSE_SUCCESS) { random_addr.is_exist = true; app_save_static_random_address(&random_addr); } } le_cfg_local_identity_address(random_addr.bd_addr, GAP_IDENT_ADDR_RAND); le_set_gap_param(GAP_PARAM_RANDOM_ADDR, 6, random_addr.bd_addr); //only for peripheral,broadcaster le_adv_set_param(GAP_PARAM_ADV_LOCAL_ADDR_TYPE, sizeof(local_bd_type), &local_bd_type); //only for central,observer le_scan_set_param(GAP_PARAM_SCAN_LOCAL_ADDR_TYPE, sizeof(local_bd_type), &local_bd_type); ...... }
Central角色调用
le_connect()
设置local address type以使用本地设备的Static Random Address。示例代码如下:
static T_USER_CMD_PARSE_RESULT cmd_con(T_USER_CMD_PARSED_VALUE *p_parse_value) { ...... #if F_BT_LE_USE_STATIC_RANDOM_ADDR T_GAP_LOCAL_ADDR_TYPE local_addr_type = GAP_LOCAL_ADDR_LE_RANDOM; #else T_GAP_LOCAL_ADDR_TYPE local_addr_type = GAP_LOCAL_ADDR_LE_PUBLIC; #endif ...... cause = le_connect(GAP_PHYS_CONN_INIT_1M_BIT, addr, (T_GAP_REMOTE_ADDR_TYPE)addr_type, local_addr_type, 1000); ...... }
Physical (PHY)设置
示例代码位于 LE Scatternet 中。
LE中必须实现符号速率为1 mega symbol per second (Msym/s),一个symbol表示一个bit,支持的比特率为1 megabit per second (Mb/s),即 LE 1M PHY
。1 Msym/s符号速率可以选择支持纠错编码,即 LE Coded PHY
,共有两种编码方案:S = 2,即两个symbol表示一个bit,支持的比特率为500 Kb/s;S = 8,即八个symbol表示一个bit,支持的比特率为125 Kb/s。若支持可选符号速率2 Msym/s,比特率为2 Mb/s,即 LE 2M PHY
。2
Msym/s符号速率只支持未编码的数据, LE 1M PHY
和 LE 2M PHY
统称为 LE Uncoded PHYs
[1]。
- 设置Default PHYAPP可以指定其发射机PHY和接收机PHY的优先值,用于随后所有建立在LE transport上的connection。
void app_le_gap_init(void) { uint8_t phys_prefer = GAP_PHYS_PREFER_ALL; uint8_t tx_phys_prefer = GAP_PHYS_PREFER_1M_BIT | GAP_PHYS_PREFER_2M_BIT | GAP_PHYS_PREFER_CODED_BIT; uint8_t rx_phys_prefer = GAP_PHYS_PREFER_1M_BIT | GAP_PHYS_PREFER_2M_BIT | GAP_PHYS_PREFER_CODED_BIT; le_set_gap_param(GAP_PARAM_DEFAULT_PHYS_PREFER, sizeof(phys_prefer), &phys_prefer); le_set_gap_param(GAP_PARAM_DEFAULT_TX_PHYS_PREFER, sizeof(tx_phys_prefer), &tx_phys_prefer); le_set_gap_param(GAP_PARAM_DEFAULT_RX_PHYS_PREFER, sizeof(rx_phys_prefer), &rx_phys_prefer); }
- 读取connection的PHY类型成功建立connection后,APP可以调用
le_get_conn_param()
以读取TX PHY和RX PHY类型。 - 检查对端设备的Features成功建立connection后,蓝牙协议层会读取对端设备的Features。GAP层将通过
GAP_MSG_LE_REMOTE_FEATS_INFO
向APP通知对端设备的Features,APP可以检查对端设备是否支持LE 2M PHY
或LE Coded PHY
。T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data) { T_APP_RESULT result = APP_RESULT_SUCCESS; T_LE_CB_DATA *p_data = (T_LE_CB_DATA *)p_cb_data; switch (cb_type) { #if F_BT_LE_5_0_SET_PHY_SUPPORT case GAP_MSG_LE_REMOTE_FEATS_INFO: { uint8_t remote_feats[8]; APP_PRINT_INFO3("GAP_MSG_LE_REMOTE_FEATS_INFO: conn id %d, cause 0x%x, remote_feats %b", p_data->p_le_remote_feats_info->conn_id, p_data->p_le_remote_feats_info->cause, TRACE_BINARY(8, p_data->p_le_remote_feats_info->remote_feats)); if (p_data->p_le_remote_feats_info->cause == GAP_SUCCESS) { memcpy(remote_feats, p_data->p_le_remote_feats_info->remote_feats, 8); if (remote_feats[LE_SUPPORT_FEATURES_MASK_ARRAY_INDEX1] & LE_SUPPORT_FEATURES_LE_2M_MASK_BIT) { APP_PRINT_INFO0("GAP_MSG_LE_REMOTE_FEATS_INFO: support 2M"); } if (remote_feats[LE_SUPPORT_FEATURES_MASK_ARRAY_INDEX1] & LE_SUPPORT_FEATURES_LE_CODED_PHY_MASK_BIT) { APP_PRINT_INFO0("GAP_MSG_LE_REMOTE_FEATS_INFO: support CODED"); } } } break; #endif } }
- 切换PHY
le_set_phy()
用于设置connection (由conn_id确定)的PHY preferences,而Controller有可能不能成功切换PHY (例如对端设备不支持请求的PHY)或者认为当前PHY更好。static T_USER_CMD_PARSE_RESULT cmd_setphy(T_USER_CMD_PARSED_VALUE *p_parse_value) { uint8_t conn_id = p_parse_value->dw_param[0]; uint8_t all_phys; uint8_t tx_phys; uint8_t rx_phys; T_GAP_PHYS_OPTIONS phy_options = GAP_PHYS_OPTIONS_CODED_PREFER_S8; T_GAP_CAUSE cause; if (p_parse_value->dw_param[1] == 0) { all_phys = GAP_PHYS_PREFER_ALL; tx_phys = GAP_PHYS_PREFER_1M_BIT; rx_phys = GAP_PHYS_PREFER_1M_BIT; } else if (p_parse_value->dw_param[1] == 1) { all_phys = GAP_PHYS_PREFER_ALL; tx_phys = GAP_PHYS_PREFER_2M_BIT; rx_phys = GAP_PHYS_PREFER_2M_BIT; } ...... cause = le_set_phy(conn_id, all_phys, tx_phys, rx_phys, phy_options); return (T_USER_CMD_PARSE_RESULT)cause; }
- PHY的更新
GAP_MSG_LE_PHY_UPDATE_INFO
用于向APP通知Controller使用的发射机PHY或接收机PHY的更新结果。T_APP_RESULT app_gap_callback(uint8_t cb_type, void *p_cb_data) { T_APP_RESULT result = APP_RESULT_SUCCESS; T_LE_CB_DATA *p_data = (T_LE_CB_DATA *)p_cb_data; switch (cb_type) { #if F_BT_LE_5_0_SET_PHY_SUPPORT case GAP_MSG_LE_PHY_UPDATE_INFO: APP_PRINT_INFO4("GAP_MSG_LE_PHY_UPDATE_INFO:conn_id %d, cause 0x%x, rx_phy %d, tx_phy %d", p_data->p_le_phy_update_info->conn_id, p_data->p_le_phy_update_info->cause, p_data->p_le_phy_update_info->rx_phy, p_data->p_le_phy_update_info->tx_phy); break; #endif } }
蓝牙协议栈相关特性设置
蓝牙协议栈相关特性的配置分为LE Link数目的配置和API的参数配置。
示例代码位于 LE Scatternet 工程中。
API的参数配置
APP可以使用 gap_config.h
中的API,以配置蓝牙协议栈相关特性,例如LE绑定设备数目的最大值和CCCD数目的最大值。APP需要按照以下配置方法使用:
在otp_config.h中增加宏定义
#define BT_STACK_CONFIG_ENABLE #ifdef BT_STACK_CONFIG_ENABLE void bt_stack_config_init(void); #endif
- 在main.c中配置蓝牙协议栈相关特性LE绑定设备数目最大值的默认值为1。由于支持多链路,APP使用
gap_config_max_le_paired_device()
配置LE绑定设备数目的最大值。...... #include <gap_config.h> #include <otp_config.h> ...... #ifdef BT_STACK_CONFIG_ENABLE #include "app_section.h" APP_FLASH_TEXT_SECTION void bt_stack_config_init(void) { gap_config_max_le_paired_device(APP_MAX_LINKS); } #endif
请求配对
iOS系统未提供启动security流程的接口。若peripheral设备希望与iOS设备配对,那么peripheral设备需要请求配对。
下面介绍一种本地设备启动security流程的方法,以及一种本地设备作为GATT Server请求iOS启动security流程的方法。
调用le_bond_pair函数
APP可以调用 le_bond_pair()
以启动security流程。当LE链路状态为 GAP_CONN_STATE_CONNECTED
时,APP才能调用 le_bond_pair()
。
void app_handle_conn_state_evt(uint8_t conn_id, T_GAP_CONN_STATE new_state, uint16_t disc_cause)
{
......
switch (new_state)
{
......
case GAP_CONN_STATE_DISCONNECTED:
{
le_bond_pair(conn_id);
}
break;
default:
break;
}
}
Service的security要求
GATT Profile流程用于访问信息,该信息可能要求client是认证的或有加密的连线,才能对characteristic进行读或写操作。
若service有security要求,client发送请求进行读或写操作时,物理链路是未认证或未加密的,那么server必须发送Error Response。希望进行读或写操作的client使用GAP authentication流程,使物理链路经过认证,然后client可以发送请求进行读或写操作。
如下图所示,当iOS设备收到未认证或未加密的Error Response,iOS设备将启动security流程。
Attribute Element是service的基本单元,其结构体定义在 gatt.h
中。
typedef struct
{
uint16_t flags; /**< Attribute flags @ref GATT_ATTRIBUTE_FLAG */
uint8_t type_value[2 + 14]; /**< 16 bit UUID + included value or 128 bit UUID */
uint16_t value_len; /**< Length of value */
void *p_value_context; /**< Pointer to value if @ref ATTRIB_FLAG_VALUE_INCL
and @ref ATTRIB_FLAG_VALUE_APPL not set */
uint32_t permissions; /**< Attribute permission @ref GATT_ATTRIBUTE_PERMISSIONS */
} T_ATTRIB_APPL;
Permissions参数用于定义attribute的权限,更多信息参见 Attribute Element。
Characteristic的认证要求示例代码如下,对其进行读或写操作时要求认证的链路。
/* client characteristic configuration 27*/
{
(ATTRIB_FLAG_VALUE_INCL | /* wFlags */
ATTRIB_FLAG_CCCD_APPL),
{ /* bTypeValue */
LO_WORD(GATT_UUID_CHAR_CLIENT_CONFIG),
HI_WORD(GATT_UUID_CHAR_CLIENT_CONFIG),
/* NOTE: this value has an instantiation for each client, a write to */
/* this attribute does not modify this default value: */
LO_WORD(GATT_CLIENT_CHAR_CONFIG_DEFAULT), /* client char. config. bit field */
HI_WORD(GATT_CLIENT_CHAR_CONFIG_DEFAULT)
},
2, /* bValueLen */
NULL,
(GATT_PERM_READ_AUTHEN_REQ | GATT_PERM_WRITE_AUTHEN_REQ) /* wPermissions */
}
GATT Profile使用案例
本章用于展示如何使用 GATT profile接口。以下是一些典型的使用案例。
ANCS Client
示例代码位于 LE Peripheral。 请参考 LE Peripheral 中的 ANCS Client 章节。
常见问题
备注
不支持多线程。因此,涉及调用API和处理消息的操作必须在同一任务中执行。
SDK提供的API可以分为同步API和异步API。同步API的结果通过返回值指示,例如:le_adv_set_param()
。如果 le_adv_set_param()
的返回值为 GAP_CAUSE_SUCCESS
,则表示应用程序已成功设置 GAP广播参数。异步API的结果通过GAP消息通知,例如:le_adv_start()
。如果 le_adv_start()
的返回值为 GAP_CAUSE_SUCCESS
,则表示开始广播的请求已成功发送。开始广播的结果通过GAP消息通知,即 GAP_MSG_LE_DEV_STATE_CHANGE
消息。
LE示例工程
使用LE physical transport的设备可以定义为四种GAP角色,SDK提供相应的示例APP供用户参考。
Broadcaster
发送advertisement。
不能建立connection。
示例APP: LE Broadcaster。
Observer
对advertisement进行scan。
不能发起connection。
示例APP: LE Observer。
Peripheral
发送advertisement。
作为peripheral角色建立一条LE链路。
示例APP: LE Peripheral。
Central
对advertisement进行scan。
作为central角色发起connection。
示例APP: LE Central。
多重角色
Peripheral和Central。
示例APP: LE Scatternet。
使用LE Advertising Extensions的Peripheral
使用LE Advertising Extensions发送advertisement。
同时使能一个或多个advertising set。
设定advertising set使能的duration或最大的extended advertising events数目。
使用extended advertising PDUs传输更多数据(使用LE Advertising Extensions的observer或central作为对端设备)。
可以作为peripheral角色建立一条LE链路,PHY为
LE 1M PHY
;PHY为LE 2M PHY
或LE Coded PHY
(使用LE Advertising Extensions的central作为对端设备)。示例APP: LE Peripheral Extended ADV。
使用LE Advertising Extensions的Central
在primary advertising channel (
LE 1M PHY
or/andLE Coded PHY
)对advertising数据包进行extended scan。设定scan的duration或period scan。
可以作为central角色发起一条LE链路,PHY为
LE 1M PHY
;PHY为LE 2M PHY
或LE Coded PHY
(使用LE Advertising Extensions的peripheral作为对端设备)。示例APP: LE Central Extended Scan。
Peripheral + Privacy
发送advertisement。
作为peripheral角色建立一条LE链路。
当对端设备使用resolvable private address时,可以使用Filter Accept List。
示例APP: LE Peripheral Privacy。
参考资料
Bluetooth SIG. Core_v5.4 [M]. 2023, 190.