LE Host

本文综述 Bluetooth LE 协议栈接口,其中包括基于 GAP 的接口和基于 GATT 的接口。

SDK 中 Bluetooth LE 协议栈和 Profile 的架构如下:

../../../../../_images/SW_Architecture.png

软件架构图

术语和概念

在蓝牙核心规范中, Profile 的定义不同于 Protocol 的定义。Protocol被定义为各层协议,例如Link Layer、L2CAPSMPATT 。不同于Protocol,Profile从使用蓝牙核心规范中各层协议的角度,定义蓝牙应用互操作性的实现。Profile定义Protocol中的可用特性和功能,以及蓝牙设备互操作性的实现,使蓝牙协议栈适用于各种场景的应用开发。

在蓝牙核心规范中,Profile和Protocol的关联如下图所示。

../../../../../_images/Bluetooth_Profile.png

蓝牙Profile

Profile由红色矩形框表示,包括 GAPProfile #1Profile #2Profile #3 。蓝牙核心规范中的Profile分为两种类型:基于GAP的Profile( GAP )和基于GATT的Profile( Profile #1Profile #2Profile #3 )。

GAP

GAP是所有的蓝牙设备均需实现的Profile,用于描述device discovery、connection、security requirement和authentication的行为和方法。GAP中的BLE部分定义四种角色(Broadcaster、Observer、Peripheral和Central),用于优化各种应用场景。

  • Broadcaster 用于只通过广播发送数据的应用。

  • Observer 用于接收广播数据的应用。

  • Peripheral 用于通过广播发送数据并且可以建立链路的应用。

  • Central 用于接收广播数据并且建立一条或多条链路的应用。

GAP的位置

GAP层作为蓝牙协议层的组成模块,如下图所示,中间浅蓝框框内的部分( GAPUpper StackLower Stack` )为蓝牙协议层。 Application 在蓝牙协议层之上, baseband/RF 位于蓝牙协议层之下。GAP层给application提供访问Upper Stack的接口。

../../../../../_images/GAP_Location_in_SDK.png

GAP在SDK中的位置

基于GATT的Profile

在蓝牙核心规范中,另一种常用的Profile是基于GATT的Profile。GATT分为server和client两种角色。 Server用于提供service数据。Client可以访问service数据。基于GATT的Profile是基于server-client交互结构,适用于不同应用场景,用于蓝牙设备之间的特定数据交互。如下图所示,Profile是以Service和Characteristic的形式组成的。

../../../../../_images/Gatt_based_profile_hierarchy_structure.png

基于GATT的Profile层级结构

功能设置

支持的蓝牙技术功能

支持的蓝牙技术功能在 功能支持 的表格 - 支持的蓝牙技术功能 中详细介绍。

GAP的功能

GAP层提供的接口的功能如下:

  • Advertising: 设置/获取advertising参数,启动/停止advertising。

  • Scan: 设置/获取scan参数,启动/停止scan。

  • Connection: 设置connection参数,创建connection,终止已建立的connection,更新connection参数。

  • 配对: 设置配对参数,启动配对,使用passkey entry方式时输入/显示passkey,删除绑定设备密钥。

  • 密钥管理: 根据设备地址和地址类型查找key entry,保存/加载绑定信息的密钥,解析random address。

  • 其它:

    • 设置GAP公共参数,例如device appearance和device name

    • 获取支持的最大BLE链路数目

    • 修改local whitelist

    • 生成/设置本地设备random address

    • 配置本地设备identity address

    • ...

提供的API

蓝牙协议栈API可以分为GAP API和GATT API两类。下图展示了APP,GAP/GATT以及蓝牙协议栈三者间的关系。横线上方的部分由APP开发,横线下方的区域由Realtek开发。

../../../../../_images/GATT_GAP_API_arch.png

GATT GAP APIs结构

本章向开发者简单介绍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

Definition of GAP APIs

Header file

Description

API Reference

gap_adv.h

Start/Stop legacy Advertisement etc.

GAP Broadcaster Role

gap_ext_adv.h

Start/Stop extended Advertisement etc.

GAP LE Extended Adv Module

gap_pa_adv.h

Start/Stop periodic Advertisement etc.

GAP LE PA Adv Module

gap_scan.h

Start/Stop legacy scan etc.

GAP Observer Role

gap_ext_scan.h

Start/Stop extended scan etc.

GAP LE Extended Scan Module

gap_conn_le.h

Set/Get connection parameters, disconnect LE link etc.

GAP LE Connection Module

gap_storage_le.h

Save/Load LE key etc.

GAP LE Storage

gap_bond_le.h

Set/Get bond parameters, display/input bond keys etc.

GAP LE Bond Manager

gap_bond_manager.h

Definition GAP bond manager module APIs.

GAP Bond Manager

gap_dtm.h

Start/Stop DTM receiver/transmitter test

GAP LE Direct Test Mode

gap_aox.h

Register callback, Read antenna information etc.

GAP LE AoA/AoD Module

gap_aox_conn.h

Start/Stop Constant Tone Extension Request procedure etc.

GAP LE AoA/AoD Connection Module

gap_aox_connless_receiver.h

Enable/Disable capturing IQ samples etc.

GAP LE AoA/AoD Connectionless Receiver Module

gap_aox_connless_transmitter.h

Enable/Disable Constant Tone Extensions in periodic advertising.

GAP LE AoA/AoD Connectionless Transmitter Module

gap_callback_le.h

Definition GAP callback messages.

GAP Callback Message

gap_msg.h

Definition GAP messages.

GAP Message

gap_privacy.h

Set/Get privacy parameters, enable/disable privacy etc.

GAP LE Privacy

gap_credit_based_conn.h

GAP LE Credit Based Connection Exported Functions.

GAP LE Credit Based Connection Module

gap_ecfc.h

GAP enhanced credit based flow control mode Exported Functions.

GAP ECFC Module

gap.h

Set/Get GAP general parameters such as bd address,airplane mode etc.

GAP Common

gap_le.h

Initialize/set/get LE parameters of GAP etc.

GAP LE Common Module

gap_le_types.h

Definition types of device Appearance,Advertising Data Type, PHY etc.

GAP LE Related Definitions

下表简单介绍了GATT APIs。对于RTL87x2G,GATT相关API的头文件位于 subsys\bluetooth\gatt_profile ,对于其他IC(RTL87x2C/RTL87x2D/RTL87x2E/RTL87x2H),头文件目录为 inc\bluetooth\profile

Definition of GATT APIs

Header file

Description

API Reference

profile_client.h

Register client callback with conn id, send read/write request etc.

GATT Client Legacy API

profile_client_def.h

Initialize client parameters, definition discovery procedure etc.

GATT Client Common API

profile_client_ext.h

Register client callback with conn handle, send read/write request etc.

GATT Client Extension API

profile_server.h

Register server callback with conn id, send indication/notification etc.

GATT Server Legacy API

profile_server_def.h

Initialize server parameters, register builtin service etc.

GATT Server Common API

profile_server_ext.h

Register server callback with conn handle, send indication/notification etc.

GATT Server Extension API

GAP的初始化和启动流程

本节介绍如何在 app_le_gap_init 中配置LE GAP参数以及GAP内部启动流程。

GAP启动流程

  1. 在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。

  2. 在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初始化流程。

  3. GAP内部初始化流程

    ../../../../../_images/GAP_Internal_Initialization_Flow.png

    GAP内部初始化流程

    流程图说明

    流程

    说明

    1

    gap_start_bt_stack

    通过发送注册请求启动upper stack初始化流程

    2

    Receive act info

    stack已准备就绪

    3

    Set pair mode

    (必要步骤)

    设置参数 GAP_PARAM_BOND_PAIRING_MODEGAP_PARAM_BOND_AUTHEN_REQUIREMENTS_FLAGSGAP_PARAM_BOND_IO_CAPABILITIESGAP_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_PASSKEYGAP_PARAM_BOND_FIXED_PASSKEY_ENABLE 的值

    9

    Set default physical

    (可选步骤)

    若APP调用 le_set_gap_param() 设置 GAP_PARAM_DEFAULT_PHYS_PREFER , GAP_PARAM_DEFAULT_TX_PHYS_PREFERGAP_PARAM_DEFAULT_RX_PHYS_PREFER , 则在初始化流程中GAP将设置默认physical

    10

    Set minimum remote SCA

    (可选步骤)

    若APP调用 le_set_gap_param() 设置 GAP_PARAM_SET_REM_MIN_SCA , 则在初始化流程中GAP层将设置默认remote Sleep Clock Accuracy(SCA)的最小值

    11

    Register GATT services

    (必要步骤)

    注册基于GATT的services

    12

    Send GAP_INIT_STATE_STACK_READY to APP

    GAP初始化流程已完成

GAP参数的初始化

通过修改 app_le_gap_init 函数的代码,在 main.c 中实现GAP参数的初始化。

Device Name和Device Appearance的配置

gap_le.hT_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);
   ......
}

BT 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.hT_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需要不同的参数,具体如下表所示。

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.hT_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.hT_GAP_PARAM_TYPE 中和 gap_bond_le.hT_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属性的位域:

    • GAP_AUTHEN_BIT_NONE

    • GAP_AUTHEN_BIT_BONDING_FLAG

    • GAP_AUTHEN_BIT_MITM_FLAG

    • GAP_AUTHEN_BIT_SC_FLAG

    • GAP_AUTHEN_BIT_KEYPRESS_FLAG

    • GAP_AUTHEN_BIT_FORCE_BONDING_FLAG

    • GAP_AUTHEN_BIT_SC_ONLY_FLAG

    • GAP_AUTHEN_BIT_FORCE_BONDING_FLAG

    • GAP_AUTHEN_BIT_SC_ONLY_FLAG

    • GAP_AUTHEN_BIT_SC_BR_FLAG

    • GAP_AUTHEN_BIT_FORCE_CENTRAL_ENCRYPT_FLAG

  • 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 ADVBT 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.hadv_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 字节。

使用legacy advertising PDUs的Extended Advertising参数设置

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.hbt5_peripheral_stack_api.c 中提供关于使用extended advertising PDUs的extended advertising参数设置的示例代码。 若只使用一个advertising set,advertising数据或scan response数据的最大长度可参考下表。

使用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_UND IRECTED

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

1024 bytes

Y

1024 bytes

Y

245 bytes

Y

239 bytes

N

N

Scan response data

N

N

N

N

Y

991 bytes

Y

991 bytes

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的需求,开发者可以使用MCUConfig工具配置LE链路数量。

本节以 LE Scatternet 示例工程为例介绍LE链路数量的配置。 假设LE Scatternet示例工程支持一个作为Peripheral角色的链路和一个作为Central角色的链路,因此:

  1. LE链路数量不得少于2。

  2. LE主链路数量不得少于1。

  3. LE从链路数量不得少于1。

备注

LE master Link number 配置LE Central链接个数, LE slave Link number 配置LE Peripheral链接个数。

配置LE最大绑定设备数量

由于支持多链路,设备需要保存多个远程设备的绑定信息。 LE散网项目可以使用MCUConfig工具配置LE最大绑定设备数量。

在应用程序中初始化链接个数

如GAP启动流程中所述,应用程序调用 le_gap_init() 来初始化GAP并设置连接数。由 le_gap_init() 配置的连接数应小于或等于通过MCUConfig工具配置的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.
../../../../../_images/Advertising_State_Transition_Machanism.png

Advertising状态的状态转换

  1. idle状态

默认状态,不发送advertisement。

  1. start状态

在idle状态启动advertising之后,启动advertising的流程尚未完成。start状态为临时状态,若成功启动advertising,则Advertising状态进入advertising状态;若启动advertising失败,则Advertising状态回到idle状态。

  1. advertising状态

成功启动advertising。在此状态下,设备发送advertisement。若advertising类型是high duty cycle directed advertising,一旦超时,Advertising状态进入idle状态。

  1. 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.
../../../../../_images/Scan_State_Transition_Machanism.png

Scan状态的状态转换

../../../../../_images/Scan_State_Transition_Machanism_with_Extended_Scan.png

使用Extended Scan时Scan状态的状态转换

  1. idle状态

默认状态,不进行scan。

  1. start状态

在idle状态启动scan之后,启动scan的流程尚未完成。start状态为临时状态,若成功启动scan,则Scan状态进入scanning状态;若启动scan失败,则Scan状态回到idle状态。

  1. scanning状态

成功启动scan。在此状态下,设备进行scan,接收advertisement。使用Extended Scan时,若 Duration 参数不为零且 Period 参数为零,一旦scan时间达到Duration,Scan状态将进入idle状态。

  1. 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_msg.h 中定义GAP Connection状态的子状态。

#define GAP_CONN_DEV_STATE_IDLE            0   //!< Idle.
#define GAP_CONN_DEV_STATE_INITIATING      1   //!< Initiating Connection.

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;

备注

  • 当GAP Connection状态为connecting状态时,application不能主动创建另一条链路。

作为central角色主动创建connection时的Connection状态变化不同于作为Peripheral角色被动接收connection indication时的Connection状态变化。以下章节分别介绍这两种场景。

Central角色主动的Connection状态转换
../../../../../_images/Active_State_Transition_Machanism.png

主动的Connection状态转换

  1. idle状态 | disconnected状态

GAP Connection状态为idle状态,Link状态为disconnected状态,未建立connection。

  1. 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状态。

  1. idle状态 | connected状态

成功创建connection,GAP Connection状态为idle状态,Link状态为connected状态。

  1. idle状态 | disconnecting状态

Central终止connection,终止流程尚未完成。这是临时状态,GAP Connection状态为idle状态,Link状态为disconnecting状态。若成功终止connection,Link状态转换为disconnected状态。

Peripheral角色被动的Connection状态转换
../../../../../_images/Passive_State_Transition_Machanism.png

被动的Connection状态转换

  1. peripheral接受connection

当Peripheral收到connect indication后,GAP Advertising状态将从advertising状态转换为idle状态,Link状态将从disconnected状态转换为connecting状态。当创建connection流程完成之后,Link状态将进入connected状态。

  1. 对端设备断开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;
../../../../../_images/Extended_Advertising_State_Transition_Machanism_for_one_Advertising_Set.png

针对一个advertising set的Extended Advertising状态的转换

  1. idle状态

默认状态,不发送advertisement。

  1. start状态

在idle状态启动extended advertising之后,启动extended advertising的流程尚未完成。start状态为临时状态,若成功启动extended advertising,则Extended Advertising状态进入advertising状态;若启动extended advertising失败,则Extended Advertising状态回到idle状态。

  1. advertising状态

成功启动extended advertising。在此状态下,设备发送advertisement。若Duration参数不为零,一旦advertising时间达到Duration,Extended Advertising状态将进入idle状态。若Extended Advertising Events的最大值参数不为零,一旦达到Extended Advertising Events的最大值,Extended Advertising状态将进入idle状态。

  1. 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消息处理流程如下:

  1. 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);
       ......
    }
    
  2. 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);
                }
             }
             ......
          }
       ......
    
  3. 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参数更新状态,更新状态包括以下三个子状态:

  • GAP_CONN_PARAM_UPDATE_STATUS_PENDING
    若本地设备调用 le_update_conn_param() 更新Connection参数,当Connection参数更新请求成功但未收到connection update complete event时,GAP层将发送该状态消息。
  • GAP_CONN_PARAM_UPDATE_STATUS_SUCCESS
    更新成功。
  • GAP_CONN_PARAM_UPDATE_STATUS_FAIL
    更新失败,参数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相关消息 所示。

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状态。

  • GAP_AUTHEN_STATE_STARTED
    Authentication流程已开始
  • GAP_AUTHEN_STATE_COMPLETE
    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:
      {
         uint32_t passkey = 888888;
         conn_id = gap_msg.msg_data.gap_bond_passkey_input.conn_id;
         APP_PRINT_INFO1("GAP_MSG_LE_BOND_PASSKEY_INPUT: conn_id %d", conn_id);
         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 Work。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回调函数的使用方法如下:

  1. 注册回调函数

    void app_le_gap_init(void)
    {
       ......
       le_register_app_cb(app_gap_callback);
    }
    
  2. 处理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回调函数消息的详细信息如下:

  1. gap_le.h相关消息

    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

  2. gap_conn_le.h相关消息

    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_r ead_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

    • GAP_MSG_LE_DATA_LEN_CHANGE_INFO
      该消息用于向APP通知在Link Layer的发送或接收方向其最大Payload长度或数据包的最大传输时间的变化。
    • GAP_MSG_LE_CONN_UPDATE_IND
      该消息仅适用于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;
      }
      
    • GAP_MSG_LE_CREATE_CONN_IND
      该消息仅适用于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;
      }
      
    • GAP_MSG_LE_PHY_UPDATE_INFO
      该消息表示Controller已经切换正在使用的发射机PHY或接收机PHY。
    • GAP_MSG_LE_REMOTE_FEATS_INFO
      在connection建立成功后,controller会主动读取对端设备的feature。读取结果会通过该消息通知给application。
  3. gap_bond_le.h相关消息

    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;

    • GAP_MSG_LE_KEYPRESS_NOTIFY_INFO
      该消息表示SMP已收到keypress notification。
    • GAP_MSG_LE_GATT_SIGNED_STATUS_INFO
      该消息表示GATT signed状态信息。
    • GAP_MSG_LE_BOND_MODIFY_INFO
      该消息用于向APP通知绑定信息已变更,更多信息参见 LE密钥管理
  4. gap_scan.h相关消息

    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;

    • GAP_MSG_LE_SCAN_INFO
      Scan状态为 GAP_SCAN_STATE_SCANNING ,当蓝牙协议层收到advertising数据或scan response数据时,GAP将发送该消息以通知APP。
    • GAP_MSG_LE_DIRECT_ADV_INFO
      Scan状态为 GAP_SCAN_STATE_SCANNING 且Scan Filter Policy为 GAP_SCAN_FILTER_ANY_RPAGAP_SCAN_FILTER_WHITE_LIST_RPA ,当蓝牙协议层收到directed advertising数据包且其initiator地址为Resolvable Private Address (RPA),无法解析该RPA时,GAP层将发送该消息以通知APP。
  5. gap_adv.h相关消息

    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

  6. gap_dtm.h相关消息

    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_dt m_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

  7. gap_ext_scan.h相关消息

    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。
  8. gap_ext_adv.h相关消息

    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。
  9. gap_vendor.h相关消息

    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消息流如图所示:

../../../../../_images/APP_Message_Flow.png

APP消息流

  1. 发送消息给APP的两种方法

    1. 回调函数
      首先,APP需要注册回调函数。当上行消息送到GAP层之后,GAP层将调用注册的回调函数以通知APP处理该消息。
    2. 消息队列
      首先,APP需要创建消息队列。当上行消息送到GAP层之后,GAP层将消息发送到消息队列,APP将循环接收消息队列中的消息。
  2. 初始化

    1. 注册回调函数
      * 为接收GAP API消息,APP需要通过 le_register_app_cb() 注册APP回调函数。
      * 为接收Airplane Mode消息,APP需要通过 gap_register_app_cb() 注册APP回调函数。
      * 为接收写GAPS characteristic的消息,APP需要通过 gatt_register_callback() 注册APP回调函数。
      * 若使用Peripheral角色的APP需要使用services,为接收server消息,APP需要通过xxx_add_service()和 server_register_app_cb() 注册service回调函数。
      * 若使用Central角色的APP需要使用clients,为接收client消息,APP需要通过xxx_add_client()注册client回调函数。
    2. 创建消息队列
      为接收蓝牙状态消息,APP需要通过 gap_start_bt_stack() 创建IO消息队列。
  3. 循环接收消息
    APP main task循环接收消息。若收到的事件是发送给APP的,APP收到IO消息,那么APP将调用 app_handle_io_msg() 由APP处理该消息。否则,APP将调用 gap_handle_msg() 由GAP层处理该消息。
  4. 消息处理
    若消息是由回调函数发送的,则由初始化过程中注册的函数处理该消息。若消息是由消息队列发送的,则由另一个函数处理该消息。

GAP信息存储

gap_storage_le.h 中定义常量和函数原型。本地协议栈信息和绑定信息保存在FTL中,更多关于FTL的信息参见 FTL简介

FTL简介

BT stack和user application使用FTL作为抽象层保存或载入flash中的数据。

FTL布局
../../../../../_images/FTL_Layout.png

FTL布局

FTL可以分为以下四个区域:

  1. Local stack information storage space

    • 地址范围:0x0000 - 0x004F

    • 该区域用于存储本地协议栈信息,包括device name、device appearance和本地设备IRK,更多信息参见 本地协议栈信息存储

  2. 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的参数配置

  3. Reserved space

    • 地址范围:Offset_reserved~(APP_start_offset-1)

    • 该区域为保留空间,因为 max_le_paired_device 参数是可配置的。LE key storage space可以使用该区域存储密钥信息。

  4. 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中的起始偏移。

优先级管理包括如下操作

  1. 添加一个绑定设备

../../../../../_images/Add_A_Bond_Device.png

增加一个绑定设备

  1. 移除一个绑定设备

../../../../../_images/Remove_A_Bond_Device.png

移除一个绑定设备

  1. 清除所有绑定设备

../../../../../_images/Clear_All_Bond_Devices.png

清除所有绑定设备

  1. 将一个绑定设备设为最高优先级

../../../../../_images/Set_A_Bond_Device_High_Priority.png

将一个绑定设备设为最高优先级

  1. 获取最高优先级设备

../../../../../_images/Get_High_Priority_Device.png

获取最高优先级设备

  1. 获取最低优先级设备

../../../../../_images/Get_Low_Priority_Device.png

获取最低优先级设备

优先级管理示例如图所示:

../../../../../_images/Priority_Manager_Example.png

优先级管理示例

Bluetooth LE密钥存储

Bluetooth LE密钥信息存储在LE key storage space。

对于RTL87x2C/RTL87x2D/RTL87x2E/RTL87x2H,LE FTL布局如图 LE FTL布局1 所示。

../../../../../_images/LE_FTL_Layout_1.png

LE FTL布局1

对于RTL87x2G,LE FTL布局如图 LE FTL布局2 所示。

../../../../../_images/LE_FTL_Layout_2.png

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的大小与以下两个参数有关:

  1. LE绑定设备数目的最大值
    默认值为1,可以使用 gap_config_max_le_paired_device() 配置,更多信息参见 API的参数配置
  2. 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_REMOTE_SRV_CHANGE_AWARE_STATE_BIT     0x0200
#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层内部BLE设备优先级管理
APIs

完整的API请参考 gap_storage_le.hgap_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的格式如下所示。

../../../../../_images/static_address_format.png

static address的格式

Non-resolvable private address

non-resolvable private address的格式如下所示。

../../../../../_images/non_resolvable_private_address_format.png

non-resolvable private address的格式

Resolvable private address

resolvable private address的格式如下所示。

../../../../../_images/resolvable_private_address_format.png

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已被解析,设备可以关联该地址与对端设备。

地址的分类如下表所示:

地址类型

type

sub-type

sub-type

Description

Public address

NA

NA

与legacy address相同

Random address

Static address

NA

每次上电会变

NA

Private address

Resolvable address

地址需要解析后才能进行识别,因此更加安全

NA

NA

Non-resolvable address

这种地址类型并不常见

每次设备开机时,静态设备地址都会改变,会遇到以下问题:

  1. 手机将会把耳机识别为不同的设备。

  2. 由于地址的改变,之前的配对信息无法使用,因此必须重新进行配对。

IRK和Identity Address

Security Manager(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分发所有密钥和值的示例。

../../../../../_images/Transport_Specific_Key_Distribution.png

Transport Specific Key Distribution

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之间关系的逻辑表示。

../../../../../_images/logical_presentation_of_resolvinglist_and_whitelist.png

Resolving List和Device 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

  • 已启用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的BLE设备的交互方式。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发送数据。

../../../../../_images/Profile_Server_Hierarchy.png

Profile Server层级

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的情况。

../../../../../_images/Add_Services_to_Server.png

向Server添加Services

当service添加到profile server layer后,GAP初始化流程会注册所有services,一旦完成注册流程,GAP层会发送 PROFILE_EVT_SRV_REG_COMPLETE 消息。

注册service的流程如下图所示。

../../../../../_images/Register_Service_Process.png

注册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。

../../../../../_images/Read_Characteristic_Value-Attribute_Value_Supplied_in_Attribute_Element.png

读Characteristic Value--由Attribute Element提供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。

../../../../../_images/Read_Characteristic_Value_Attribute_Value_Supplied_by_Application_without_Result_Pending.png

读Characteristic Value--由APP提供Attribute Value且结果未挂起

示例代码如下, 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。该流程中各层之间的交互如图所示。

../../../../../_images/Read_Characteristic_Value_Attribute_Value_Supplied_by_Application_with_Result_Pending.png

读Characteristic Value--由APP提供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 Value
    flag为 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。

    ../../../../../_images/Write_Characteristic_Value_Attribute_Value_Supplied_in_Attribute_Element.png

    Write Characteristic Value--由Attribute Element提供Attribute Value

  • 由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时,将调用该回调函数。

    ../../../../../_images/Write_Characteristic_Value_Attribute_Value_Supplied_by_Application_without_Result_Pending.png

    Write Characteristic Value--由APP提供Attribute Value且结果未挂起

    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为可选流程。

    ../../../../../_images/Write_Characteristic_Value_Attribute_Value_Supplied_by_Application_with_Result_Pending.png

    Write Characteristic Value--由APP提供Attribute Value且结果挂起

    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信息已更新。该流程中各层之间的交互如图所示。

    ../../../../../_images/Write_Characteristic_Value_Write_CCCD_Value.png

    Write Characteristic Value--写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_RESPONSEWRITE_SIGNED_WITHOUT_RESPONSE

    ../../../../../_images/WriteSigned_Write_without_Response_Attribute_Value_Supplied_by_Application.png

    Write without Response/Signed Write without Response--由APP提供Attribute Value

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。该流程中各层之间的交互如图所示。

    ../../../../../_images/Write_Long_Characteristic_Values_Prepare_Write_Procedure.png

    Write Long Characteristic Value--Prepare Write流程

  • 结果未挂起的Execute Write

    在发送prepare write request之后,execute write quest用于完成写入attribute value的流程。由 server_add_service() 注册的srv_cbs回调函数通知APP,write_type为 WRITE_LONG 。Write indication post procedure为可选流程。该流程中各层之间的交互如图所示。

    ../../../../../_images/Write_Long_Characteristic_Values_Execute_Write_without_Result_Pending.png

    Write Long Characteristic Values--结果未挂起的Execute Write

  • 结果挂起的Execute Write

    若写入操作不能立即完成,specific service将调用 server_exec_write_confirm() 。Write indication post procedure为可选流程。该流程中各层之间的交互如图所示。

    ../../../../../_images/Write_Long_Characteristic_Values_Execute_Write_with_Result_Pending.png

    Write Long Characteristic Values--结果挂起的Execute Write

Characteristic Value Notification

Server用该流程通知client一个characteristic value。Server主动调用 server_send_data() 发送数据,在发送流程完成之后,会通过server通用回调函数通知APP。该流程中各层之间的交互如图所示。

../../../../../_images/Characteristic_Value_Notification.png

Characteristic Value Notification

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。该流程中各层之间的交互如图所示。

../../../../../_images/Characteristic_Value_Indication.png

Characteristic Value Indication

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的步骤:

  1. 定义Service和Profile Spec。

  2. 定义Service Attribute Table。

  3. 定义Service与APP之间的接口。

  4. 定义 xxx_add_service()xxx_set_parameter()xxx_notify()xxx_indicate() 等API。

  5. T_FUN_GATT_SERVICE_CBS 实现回调函数 xxx_ble_service_cbs

本节内容以simple LE service为例,简单介绍如何实现specific service。具体细节参见 simple_ble_service.csimple_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;
  1. Flags

    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之间的关联。

    Flags 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

  2. Permissions

    Attribute的Permissions指定read或write访问需要的安全级别,同样包括notification或indication。Permissions的值表示该attribute的permission。Attribute permissions是access permissions、encryption permissions、authentication permissions和authorization permissions的组合,其可用值如下。

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 中实现。

Service Table示例

Flags

Attribute Type

Attribute Value

Per mission

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接口给开发者提供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和信息。

../../../../../_images/Profile_Client_Hierarchy.png

Profile Client层级

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_IDclient_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的情况。

../../../../../_images/Add_Specific_Clients_to_Profile_Client_Layer.png

向Profile Client Layer添加Specific Clients

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结果。

该流程中各层中间的交互如图所示。

../../../../../_images/GATT_Discovery_Procedure.png

GATT Discovery流程

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结果
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_RE SULT_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 3
    Profile client layer调用 read_result_cb 以返回读取结果。

该流程中各层之间的交互如图所示。

../../../../../_images/Read_Characteristic_Value_by_Handle.png

Read Characteristic Value by Handle流程

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 3
    Profile client layer调用 read_result_cb 以返回读取结果。

该流程中各层之间的交互如图所示。

../../../../../_images/Read_Characteristic_Value_by_UUID.png

Read Characteristic Value by UUID流程

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流程。

该流程中各层之间的交互如图所示。

../../../../../_images/Write_Characteristic_Value.png

Write Characteristic Value流程

Write Long Characteristic Values

当client已知Characteristic Value Handle,且characteristic value的长度大于(ATT_MTU-3)字节时,该流程可用于写入server的characteristic value。

该流程中各层之间的交互如图所示。

../../../../../_images/Write_Long_Characteristic_Value.png

Write Long Characteristic Value流程

Write Without Response

当client已知Characteristic Value Handle,且client不需要写入操作成功执行的应答时,该流程可用于写入server的characteristic value。characteristic value的长度小于或等于(ATT_MTU-3)字节。

该流程中各层之间的交互如图所示。

../../../../../_images/Write_without_Response.png

Write Without Response流程

Signed Write without Response

当client已知Characteristic Value Handle,且ATT Bearer未加密时,该流程可用于写入server的characteristic value。该流程仅适用于Characteristic Properties的authenticated位已使能,client与server设备已绑定的情况。characteristic value的长度小于或等于(ATT_MTU-15)字节。

该流程中各层之间的交互如图所示。

../../../../../_images/Signed_Write_without_Response.png

Signed Write without Response流程

Characteristic Value Notification

该流程适用于server已被配置为向client通知characteristic value,且不需要成功接收notification的应答的情况。

该流程中各层之间的交互如图所示。

../../../../../_images/Characteristic_Value_Notification_2.png

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的应答的情况。

  1. 结果未挂起的Characteristic Value Indication

    回调函数 notify_ind_result_cb 的返回结果不为 APP_RESULT_PENDING 。该流程中各层之间的交互如图所示。

    ../../../../../_images/Characteristic_Value_Indication_without_Result_Pending.png

    结果未挂起的Characteristic Value Indication流程

  2. 结果挂起的Characteristic Value Indication

    回调函数 notify_ind_result_cb 的返回结果为 APP_RESULT_PENDING 。APP需要调用 client_attr_ind_confirm() 以发送confirmation。该流程中各层之间的交互如图所示。

    ../../../../../_images/Characteristic_Value_Indication_with_Result_Pending.png

    结果挂起的Characteristic Value Indication流程

    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的方法。

  1. 自动生成本地设备的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);
    }
    
  2. 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_PROPERTYGAPS_PARAM_DEVICE_NAME_PROPERTY 来配置可写属性。

  1. 可写属性的配置

    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);
    }
    
  2. 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。

  1. 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()
  2. 设置Identity Address
    Stack默认使用public address作为Identity Address。APP需要调用 le_cfg_local_identity_address() 将Identity Address修改为static random address。不正确的Identity Address设置会导致配对后无法重连。
  3. 设置local address type
    Peripheral角色或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]

  1. 设置Default PHY
    APP可以指定其发射机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);
    }
    
  2. 读取connection的PHY类型
    成功建立connection后,APP可以调用 le_get_conn_param() 以读取TX PHY和RX PHY类型。
    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:
             {
                ......
                data_uart_print("Connected success conn_id %d\r\n", conn_id);
    #if F_BT_LE_5_0_SET_PHY_SUPPORT
                {
                   uint8_t tx_phy;
                   uint8_t rx_phy;
                   le_get_conn_param(GAP_PARAM_CONN_RX_PHY_TYPE, &rx_phy, conn_id);
                   le_get_conn_param(GAP_PARAM_CONN_TX_PHY_TYPE, &tx_phy, conn_id);
                   APP_PRINT_INFO2("GAP_CONN_STATE_CONNECTED: tx_phy %d, rx_phy %d", tx_phy, rx_phy);
                }
    #endif
             }
             break;
          default:
             break;
       }
    }
    
  3. 检查对端设备的Features
    成功建立connection后,蓝牙协议层会读取对端设备的Features。GAP层将通过 GAP_MSG_LE_REMOTE_FEATS_INFO 向APP通知对端设备的Features,APP可以检查对端设备是否支持 LE 2M PHYLE 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
       }
    }
    
  4. 切换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;
    }
    
  5. 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需要按照以下配置方法使用:

  1. 在otp_config.h中增加宏定义

    /*=====================================================*
    *                upperstack configuration
    *=====================================================*/
    #define BT_STACK_CONFIG_ENABLE
    
    #ifdef BT_STACK_CONFIG_ENABLE
    void bt_stack_config_init(void);
    #endif
    
  2. 在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设备需要请求配对。

在SDK中提供两种本地设备启动security流程的方法,以及一种本地设备作为GATT Server请求iOS启动security流程的方法。

配置GAP_PARAM_BOND_SEC_REQ_ENABLE

GAP_PARAM_BOND_SEC_REQ_ENABLE 参数决定在连线建立成功时是否启动security流程。若该参数设置为true,当连线建立成功时,GAP层将自动启动security流程。

void app_le_gap_init(void)
{
   ......
   uint8_t  auth_sec_req_enable = true;
   uint16_t auth_sec_req_flags = GAP_AUTHEN_BIT_BONDING_FLAG;
   le_bond_set_param(GAP_PARAM_BOND_SEC_REQ_ENABLE, sizeof(auth_sec_req_enable), &auth_sec_req_enable);
   le_bond_set_param(GAP_PARAM_BOND_SEC_REQ_REQUIREMENT, sizeof(auth_sec_req_flags), &auth_sec_req_flags);
   ......
}

调用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流程。

../../../../../_images/ATT_Insufficient_Authentication.png

ATT Insufficient Authentication

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的认证要求示例代码如下,对0其进行读或写操作时要求认证的链路。

/* client characteristic configuration 3*/
{
     (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 | GATT_PERM_WRITE)          /* wPermissions */
}

Service的security要求示例代码位于 hids_kb.c 中。

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供用户参考。

  1. Broadcaster

    • 发送advertisement

    • 不能建立connection

    • 示例APP: LE Broadcaster

  2. Observer

    • 对advertisement进行scan

    • 不能发起connection

    • 示例APP: LE Observer

  3. Peripheral

    • 发送advertisement

    • 作为peripheral角色建立一条LE链路

    • 示例APP: LE Peripheral

  4. Central

    • 对advertisement进行scan

    • 作为central角色发起connection

    • 示例APP: LE Central

  5. 多重角色

  6. 使用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

  7. 使用LE Advertising Extensions的Central

    • 在primary advertising channel (LE 1M PHY or/and LE 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

  8. Peripheral + Privacy

    • 发送advertisement

    • 作为peripheral角色建立一条LE链路

    • 当对端设备使用resolvable private address时,可以使用Filter Accept List

    • 示例APP: LE Peripheral Privacy

参考资料

  1. Bluetooth SIG. Core_v5.4 [M]. 2023, 190.