蓝牙协议栈(四、协议)

BTstack是一种模块化双模蓝牙堆栈,支持蓝牙基本速率/增强日期速率(BR / EDR)以及蓝牙低功耗(LE)。BR / EDR技术也称为经典蓝牙,可在专为高数据速率设计的设备之间提供强大的无线连接。相比之下,LE技术具有更低的吞吐量,但能耗更低,连接设置更快,并且能够并行连接更多设备。

无论是Classic还是LE,蓝牙设备都会实现一个或多个蓝牙配置文件。蓝牙配置文件指定如何使用一个或多个蓝牙协议来实现其目标。例如,每个蓝牙设备都必须实现通用访问配置文件(GAP),它定义了设备如何找到彼此以及它们如何建立连接。该配置文件主要使用主机控制器接口(HCI)协议,该协议是堆栈层次结构中的最低协议,它实现了蓝牙芯片组的命令接口。

除了GAP之外,流行的经典蓝牙示例将是可以通过串行端口配置文件(SPP)连接的外围设备。SPP基本上指定兼容设备应提供包含RFCOMM信道号的服务发现协议(SDP)记录,该记录将用于实际通信。

类似地,对于每个LE设备,除了GAP之外,还必须实现通用属性配置文件(GATT)配置文件。GATT建立在属性协议(ATT)之上,定义了一个设备如何与远程设备上的GATT服务进行交互。

到目前为止,BTstack最受欢迎的用途是可通过SPP(Android 2.0或更高版本)和GATT(Android 4.3或更高版本以及iOS 5或更高版本)连接的外围设备。如果外围设备和iOS设备之间需要更高的数据速率,可以使用Made for iPhone程序的iAP1和iAP2协议代替GATT。有关BTstack和MFi的信息,请直接与我们联系。

图下面描述的蓝牙协议和目前由BTstack实施的配置文件。在下文中,我们首先解释如何在BTstack中使用各种蓝牙协议。在下一章中,我们将介绍配置文件。

蓝牙协议栈(四、协议)_第1张图片

HCI - 主机控制器接口

HCI协议为蓝牙芯片组提供命令接口。在BTstack中,HCI实现还跟踪所有活动连接并处理更高层(L2CAP)数据包的碎片和重组。

请注意,应用程序很少需要自己发送HCI命令。相反,BTstack在GAP和自动使用HCI的更高级协议中提供便利功能。例如,要设置名称,请在启动之前调用gap_set_local_name()。应用程序中HCI命令的主要用途是在启动阶段配置尚未通过GAP API提供的特殊功能。以下部分介绍了如何发送自定义HCI命令。

定义自定义HCI命令模板

每个HCI命令被分配一个2字节的OpCode,用于唯一地标识不同类型的命令。OpCode参数分为两个字段,称为OpCode组字段(OGF)和OpCode命令字段(OCF),请参阅蓝牙规范 - 核心版本4.0,第2卷,E部分,第5.4章。

下面的清单显示了BTstack在文件src / hci.h中提供的OGF:

#define OGF_LINK_CONTROL 0x01

#define OGF_LINK_POLICY 0x02

#define OGF_CONTROLLER_BASEBAND 0x03

#define OGF_INFORMATIONAL_PARAMETERS 0x04

#define OGF_LE_CONTROLLER 0x08

#define OGF_BTSTACK 0x3d

#define OGF_VENDOR 0x3f

对于所有现有的蓝牙命令及其OCF,请参阅蓝牙规范 - 核心版本4.0,第2卷,E部分,第7章。

在HCI命令包中,OpCode后跟参数总长度和实际参数。可以使用OPCODE宏计算命令的OpCode。BTstack提供hci_cmd_t 结构作为紧凑格式来定义HCI命令包,参见下面的清单,并 在源代码中包含/ btstack / hci_cmd.h文件。

// Calculate combined ogf/ocf value.#define OPCODE(ogf, ocf) (ocf | ogf << 10)

 

// Compact HCI Command packet description.typedef struct {

uint16_t opcode;

const char *format;

} hci_cmd_t;

下面的清单说明了库中的hci_write_local_name HCI命令模板:

// Sets local Bluetooth name

const hci_cmd_t hci_write_local_name = {

OPCODE(OGF_CONTROLLER_BASEBAND, 0x13), "N"

// Local name (UTF-8, Null Terminated, max 248 octets)

};

它使用OGF_CONTROLLER_BASEBAND作为OGF,0x13作为OCF,并且具有一个格式为“N”的参数,表示空终止的UTF-8字符串。表下面列出BTstack支持的格式说明。检查其他预定义的HCI命令及其参数信息。


格式说明符说明1,2,3,4一至四字节值A 31字节广告数据B蓝牙基带地址D 8字节数据块E扩展查询信息240个八位字节H HCI连接句柄N名称最多248个字符,UTF8字符串,null终止P 16字节配对码,例如PIN码或链接键S服务记录(数据元素序列)


表:HCI命令参数支持的格式说明符。

基于模板发送HCI命令

您可以使用hci_send_cmd函数根据模板和参数列表发送HCI命令。但是,有必要检查输出数据包缓冲区是否为空,并且蓝牙模块已准备好接收下一个命令 - 大多数现代蓝牙模块仅允许发送单个HCI命令。这可以通过调用hci_can_send_command_packet_now()函数来完成 ,如果可以发送,则返回true。

下面的清单说明了如何使用HCI Write Local Name命令手动设置设备名称。

if (hci_can_send_packet_now(HCI_COMMAND_DATA_PACKET)){

hci_send_cmd(&hci_write_local_name, "BTstack Demo");

}

请注意,应用程序很少需要自己发送HCI命令。相反,BTstack在GAP和自动使用HCI的更高级协议中提供便利功能。

L2CAP - 逻辑链路控制和适配协议

L2CAP协议支持更高级别的协议复用和数据包分段。它为RFCOMM和BNEP协议提供了基础。对于BTstack正式支持的所有配置文件,不需要直接使用L2CAP。为了测试或开发自定义协议,能够访问和提供L2CAP服务是有帮助的。

访问远程设备上的L2CAP服务

L2CAP基于渠道的概念。通道是基带连接之上的逻辑连接。每个通道以多对一的方式绑定到单个协议。多个通道可以绑定到同一个协议,但是一个通道不能绑定到多个协议。多个通道可以共享相同的基带连接。

到远程设备上与L2CAP服务进行通信,本地蓝牙设备上的应用程序启动用的L2CAP层 l2cap_init功能,然后创建传出L2CAP信道使用一个远程设备的PSM l2cap_create_channel 功能。如果l2cap_create_channel函数尚不存在,它将启动新的基带连接。作为L2CAP创建信道功能的输入参数给出的分组处理器将被分配给新的输出L2CAP信道。此处理程序接收L2CAP_EVENT_CHANNEL_OPENED和L2CAP_EVENT_CHANNEL_CLOSED事件和L2CAP数据包,如下面的清单所示。

btstack_packet_handler_t l2cap_packet_handler;

 

void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

bd_addr_t event_addr;

switch (packet_type){

case HCI_EVENT_PACKET:

switch (hci_event_packet_get_type(packet)){

case L2CAP_EVENT_CHANNEL_OPENED:

l2cap_event_channel_opened_get_address(packet, &event_addr);

psm = l2cap_event_channel_opened_get_psm(packet);

local_cid = l2cap_event_channel_opened_get_local_cid(packet);

handle = l2cap_event_channel_opened_get_handle(packet);

if (l2cap_event_channel_opened_get_status(packet)) {

printf("Connection failed\n\r");

} else

printf("Connected\n\r");

}

break;

case L2CAP_EVENT_CHANNEL_CLOSED:

break;

...

}

case L2CAP_DATA_PACKET:

// handle L2CAP data packet

break;

...

}

}

 

void create_outgoing_l2cap_channel(bd_addr_t address, uint16_t psm, uint16_t mtu){

l2cap_create_channel(NULL, l2cap_packet_handler, remote_bd_addr, psm, mtu);

}

 

void btstack_setup(){

...

l2cap_init();

}

提供L2CAP服务

要提供L2CAP服务,本地蓝牙设备上的应用程序必须初始化L2CAP层并使用l2cap_register_service注册该服务 。从那以后,它可以等待传入的L2CAP连接。应用程序可以通过分别调用l2cap_accept_connection 和l2cap_deny_connection函数来接受或拒绝传入连接。

如果接受连接并且传入的L2CAP信道成功打开,则L2CAP服务可以使用l2cap_send向连接的设备发送和接收L2CAP数据包。

下面的清单 提供了L2CAP服务示例代码。

void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

bd_addr_t event_addr;

switch (packet_type){

case HCI_EVENT_PACKET:

switch (hci_event_packet_get_type(packet)){

case L2CAP_EVENT_INCOMING_CONNECTION:

local_cid = l2cap_event_incoming_connection_get_local_cid(packet);

l2cap_accept_connection(local_cid);

break;

case L2CAP_EVENT_CHANNEL_OPENED:

l2cap_event_channel_opened_get_address(packet, &event_addr);

psm = l2cap_event_channel_opened_get_psm(packet);

local_cid = l2cap_event_channel_opened_get_local_cid(packet);

handle = l2cap_event_channel_opened_get_handle(packet);

if (l2cap_event_channel_opened_get_status(packet)) {

printf("Connection failed\n\r");

} else

printf("Connected\n\r");

}

break;

case L2CAP_EVENT_CHANNEL_CLOSED:

break;

...

}

case L2CAP_DATA_PACKET:

// handle L2CAP data packet

break;

...

}

}

 

void btstack_setup(){

...

l2cap_init();

l2cap_register_service(NULL, packet_handler, 0x11,100);

}

发送L2CAP数据

由于完整的内部BTstack输出数据包缓冲区,或者如果蓝牙模块中的ACL缓冲区变满,即,如果应用程序的发送速度快于数据包可以通过空中传输,则L2CAP数据包的发送可能会失败。

建议不要直接调用l2cap_send来调用 l2cap_request_can_send_now_event(cahnnel_id),它将尽快触发L2CAP_EVENT_CAN_SEND_NOW。在l2cap_request_can_send_now_event函数返回之前,可能会发生事件是通过数据包处理程序接收的。L2CAP_EVENT_CAN_SEND_NOW表示可以进行发送的信道ID。

请注意,只有收到活动才能保证发送数据包。从包处理程序返回后,BTstack可能需要发送自己。

LE数据通道

LE数据通道的完整标题实际上是LE连接导向通道,具有LE信用流控制模式。在此模式下,数据作为服务数据单元(SDU)发送,可以大于单个HCI LE ACL数据包。

LE数据通道类似于传统L2CAP通道,但也提供类似于RFCOMM通道的基于信用的流量控制。除非使用蓝牙核心4.2规范的LE数据包扩展,否则LE ACL数据包的最大包大小为27个字节。为了发送更大的分组,每个分组将被分成多个ACL LE分组并在接收侧重新组合。

由于可以同时发送多个SDU并且可以交错发送各个ACL LE分组,因此BTstack需要每个信道的专用接收缓冲区,在创建信道或接受信道时必须通过该缓冲区。类似地,在发送SDU时,提供给l2cap_le_send_data的数据必须保持有效,直到收到L2CAP_EVENT_LE_PACKET_SENT。

当创建接受传入的传出连接时,initial_credits允许向远程端提供固定数量的信用。使用l2cap_le_provide_credits可以随时提供更多信用。如果L2CAP_LE_AUTOMATIC_CREDITS被使用,BTstack自动提供信用根据需要-有效交易为了方便,流量控制功能。

API的其余部分类似于L2CAP:

  • l2cap_le_register_service和l2cap_le_unregister_service用于管理本地服务。

  • l2cap_le_accept_connection和l2cap_le_decline_connection用于接受或拒绝传入连接请求。

  • l2cap_le_create_channel创建传出连接。

  • l2cap_le_can_send_now检查是否可以立即调度数据包进行传输。

  • l2cap_le_request_can_send_now_event尽快请求L2CAP_EVENT_LE_CAN_SEND_NOW事件。

  • l2cap_le_disconnect关闭连接。

RFCOMM - 射频通信协议

射频通信(RFCOMM)协议通过L2CAP协议提供仿真串口并重新组装。它是串行端口配置文件和用于电信的其他配置文件的基础,如头设置配置文件,免提配置文件,对象交换(OBEX)等。

没有RFCOMM数据包边界

由于RFCOMM模拟串行端口,因此不会保留数据包边界。

在大多数操作系统上,RFCOMM / SPP将被建模为允许写入字节块的管道。操作系统和蓝牙堆栈可以以任何合适的方式自由缓冲和分块这些数据。因此,在您的BTstack应用程序中,您将以相同的顺序接收这些数据,但不能保证它如何被分段为多个块。

如果您需要保留通过RFCOMM发送具有特定大小的数据包的概念,最简单的方法是在数据前加上2或4字节长度字段,然后在接收端重建数据包。

请注意,由于BTstack的“无缓冲”策略,BTstack将立即发送传出的RFCOMM数据并隐式保留数据包边界,即它将数据作为单个RFCOMM数据包发送到单个L2CAP数据包中,该数据包将在一个片。虽然这将在两个BTstack实例之间保持不变,但依靠实现细节而不是如上所述为数据添加前缀并不是一个好主意。

RFCOMM流量控制

RFCOMM具有强制性的基于信用的流量控制。这意味着建立RFCOMM连接的两个设备使用信用来跟踪可以向每个设备发送多少RFCOMM数据包。如果设备没有剩余(传出)信用,则无法发送另一个RFCOMM数据包,必须暂停传输。在连接建立期间,提供初始信用。BTstack跟踪两个方向的积分数量。如果没有可用的传出信用,RFCOMM发送功能将返回错误,您可以稍后再试。对于传入数据,BTstack通过不同的功能提供有和没有自动信用管理的信道和服务,分别创建/注册它们。如果学分管理是自动的,当需要依赖ACL流控制时提供新的信用 - 这仅在没有传输大量数据和/或仅使用一个物理连接时才有用。如果信用管理是手动的,则应用程序提供信用,以便它可以明确地管理其接收缓冲区。

访问远程设备上的RFCOMM服务

到远程设备上与RFCOMM服务进行通信,本地蓝牙设备上的应用程序启动用的RFCOMM层rfcomm_init功能,然后创建使用远程设备上的传出RFCOMM信道到一个给定的服务器信道rfcomm_create_channel功能。该 rfcomm_create_channel功能将发起针对RFCOMM多路复用器一个新的L2CAP连接,如果它不存在。该频道将自动为远程端提供足够的积分。要手动提供信用,您必须通过调用rfcomm_create_channel_with_initial_credits来创建RFCOMM连接- 请参阅手动信用分配部分。

作为RFCOMM创建信道功能的输入参数给出的分组处理程序将被分配给新的输出信道。该处理程序接收RFCOMM_EVENT_CHANNEL_OPENED和RFCOMM_EVENT_CHANNEL_CLOSED事件以及RFCOMM数据包,如下面的清单所示。

void rfcomm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

switch (packet_type){

case HCI_EVENT_PACKET:

switch (hci_event_packet_get_type(packet)){

case RFCOMM_EVENT_CHANNEL_OPENED:

if (rfcomm_event_open_channel_complete_get_status(packet)) {

printf("Connection failed\n\r");

} else {

printf("Connected\n\r");

}

break;

case RFCOMM_EVENT_CHANNEL_CLOSED:

break;

...

}

break;

case RFCOMM_DATA_PACKET:

// handle RFCOMM data packets

return;

}

}

 

void create_rfcomm_channel(uint8_t packet_type, uint8_t *packet, uint16_t size){

rfcomm_create_channel(rfcomm_packet_handler, addr, rfcomm_channel);

}

 

void btstack_setup(){

...

l2cap_init();

rfcomm_init();

}

提供RFCOMM服务

要提供RFCOMM服务,本地蓝牙设备上的应用程序必须首先启动L2CAP和RFCOMM层,然后使用rfcomm_register_service注册该服务。从那时起,它可以等待传入的RFCOMM连接。应用程序可以通过分别调用rfcomm_accept_connection和rfcomm_deny_connection函数来接受或拒绝传入连接 。如果接受连接并且传入的RFCOMM信道成功打开,则RFCOMM服务可以使用rfcomm_send将RFCOMM数据包发送到连接的设备,并通过rfcomm_register_service 调用提供的数据包处理程序接收数据包。

下面的清单提供了RFCOMM服务示例代码。

void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

switch (packet_type){

case HCI_EVENT_PACKET:

switch (hci_event_packet_get_type(packet)){

case RFCOMM_EVENT_INCOMING_CONNECTION:

rfcomm_channel_id = rfcomm_event_incoming_connection_get_rfcomm_cid(packet);

rfcomm_accept_connection(rfcomm_channel_id);

break;

case RFCOMM_EVENT_CHANNEL_OPENED:

if (rfcomm_event_open_channel_complete_get_status(packet)){

printf("RFCOMM channel open failed.");

break;

}

rfcomm_channel_id = rfcomm_event_open_channel_complete_get_rfcomm_cid(packet);

mtu = rfcomm_event_open_channel_complete_get_max_frame_size(packet);

printf("RFCOMM channel open succeeded, max frame size %u.", mtu);

break;

case RFCOMM_EVENT_CHANNEL_CLOSED:

printf("Channel closed.");

break;

...

}

break;

case RFCOMM_DATA_PACKET:

// handle RFCOMM data packets

return;

...

}

...

}

 

void btstack_setup(){

...

l2cap_init();

rfcomm_init();

rfcomm_register_service(packet_handler, rfcomm_channel_nr, mtu);

}

减慢RFCOMM数据接收速度

RFCOMM基于信用的流量控制可用于调整,即将RFCOMM数据减慢到您的处理速度。对于传入数据,BTstack提供有和没有自动信用管理的渠道和服务。如果信用管理是自动的,则在需要时依靠ACL流控制提供新信用。这仅在传输的数据不多和/或仅使用一个物理连接时才有用。见下面的清单。

void btstack_setup(void){

...

// init RFCOMM

rfcomm_init();

rfcomm_register_service(packet_handler, rfcomm_channel_nr, 100);

}

如果信用管理是手动的,则应用程序提供信用,以便它可以明确地管理其接收缓冲区,请参见下面的清单。

当收到的RFCOMM数据无法立即处理时,建议使用手动信用管理。在SPP流控制示例中,借助于周期性计时器模拟接收数据的延迟处理。要提供新的学分,请使用RFCOMM频道ID和学分数调用rfcomm_grant_credits函数,如下面的清单所示。

void btstack_setup(void){

...

// init RFCOMM

rfcomm_init();

// reserved channel, mtu=100, 1 credit

rfcomm_register_service_with_initial_credits(packet_handler, rfcomm_channel_nr, 100, 1);

}

void processing(){

// process incoming data packet

...

// provide new credit

rfcomm_grant_credits(rfcomm_channel_id, 1);

}

请注意,提供单一信用有效地将基于信用的(滑动窗口)流量控制减少到停止和等待流量控制,从而大大限制了数据吞吐量。从好的方面来说,它允许最小的内存占用。如果可能,应使用多个RFCOMM缓冲区以避免暂停,而发送方必须等待新的信用。

发送RFCOMM数据

传出的数据包,包括命令和数据,都没有在BTstack中排队。本节介绍了此设计决策对发送数据的影响,以及为什么它没有听起来那么糟糕。

与输出缓冲器的数量无关,分组生成必须适应远程接收器和/或最大链路速度。因此,只有在可以发送数据包时才能生成数据包。有了这个假设,单输出缓冲器设计不会产生额外的限制。在下文中,我们将展示如何使用它来调整RFCOMM发送速率。

当需要发送数据包时,请调用rcomm_request_can_send_now 并等待接收RFCOMM_EVENT_CAN_SEND_NOW事件以发送数据包,如下面的清单所示。

请注意,只有收到活动才能保证发送数据包。从包处理程序返回后,BTstack可能需要发送自己。

void prepare_data(uint16_t rfcomm_channel_id){

...

// prepare data in data_buffer

rfcomm_request_can_send_now_event(rfcomm_channel_id);

}

 

void send_data(uint16_t rfcomm_channel_id){

rfcomm_send(rfcomm_channel_id, data_buffer, data_len);

// packet is handed over to BTstack, we can prepare the next one

prepare_data(rfcomm_channel_id);

}

 

void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

switch (packet_type){

case HCI_EVENT_PACKET:

switch (hci_event_packet_get_type(packet)){

...

case RFCOMM_EVENT_CAN_SEND_NOW:

rfcomm_channel_id = rfcomm_event_can_send_now_get_rfcomm_cid(packet);

send_data(rfcomm_channel_id);

break;

...

}

...

}

}

}

优化发送RFCOMM数据

当通过rfcomm_send发送RFCOMM数据时,BTstack需要将数据从用户提供的缓冲区复制到传出缓冲区。这需要用户数据的附加缓冲区以及复制操作。

为避免这种情况,可以直接将用户数据写入传出缓冲区。

获取RFCOMM_CAN_SEND_NOW事件时,调用rfcomm_reserve_packet_buffer来锁定发送操作的缓冲区。然后,你可以问多少字节你可以发送rfcomm_get_max_frame_size并获得一个指向BTstack的缓冲区 rfcomm_get_outgoing_buffer。现在,您可以填充该缓冲区,最后使用rfcomm_send_prepared发送数据。

SDP - 服务发现协议

SDP协议允许宣布服务并发现远程蓝牙设备提供的服务。

创建并公布SDP记录

BTstack包含一个完整的SDP服务器,允许注册SDP记录。SDP记录是存储在数据元素序列(DES)中的SDP属性{ID,值}对的列表。属性ID是一个16位数字,该值可以是其他简单类型,如整数或字符串,或者本身可以包含其他DES。

要为SPP服务创建SDP记录,可以使用指向缓冲区的指针调用 spp_create_sdp_record来存储记录,服务器通道编号和记录名称。

对于其他类型的记录,您可以使用数据元素de_ functions 来使用其他函数。清单[sdpCreate]显示了如何创建包含两个SDP属性的SDP记录。首先,创建DES,然后将服务记录句柄和服务类ID列表属性添加到其中。通过调用de_add_number函数两次添加Service Record Handle属性:第一次添加0x0000作为属性ID,第二次添加实际记录句柄(此处为0x1000)作为属性值。Service Class ID List属性的ID为0x0001,它需要一个UUID列表作为属性值。要创建列表,请执行de_push_sequence被称为“打开”子DES。返回的指针用于向此子DES添加元素。添加所有UUID后,使用de_pop_sequence “关闭”子DES 。

要注册SDP记录,请使用指向它的指针调用sdp_register_service。SDP记录可以存储在FLASH中,因为BTstack只存储指针。请注意,缓冲区需要持久化(例如全局存储,从堆或FLASH中动态分配),不能用于创建另一个SDP记录。

查询远程SDP服务

BTstack提供SDP客户端来查询远程设备的SDP服务。SDP客户端API显示在此处。所述sdp_client_query功能发起L2CAP连接到远程服务器SDP。在连接时,发送具有服务搜索模式和属性ID列表的服务搜索属性请求。服务搜索属性查询的结果包含服务记录列表 ,每个都包含请求的属性。这些记录由SDP解析器处理。解析器通过已注册的回调传递SDP_PARSER_ATTRIBUTE_VALUE和SDP_PARSER_COMPLETE事件。SDP_PARSER_ATTRIBUTE_VALUE事件逐字节地传递属性值。

除此之外,您还可以实现特定的SDP查询。例如,BTstack提供RFCOMM服务名称和频道号的查询。例如,如果要连接到远程SPP服务,则需要此信息。该查询通过已注册的回调提供所有匹配的RFCOMM服务,包括其名称和通道号,以及查询完成事件,如下面的清单所示。

bd_addr_t remote = {0x04,0x0C,0xCE,0xE4,0x85,0xD3};

 

void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){

if (packet_type != HCI_EVENT_PACKET) return;

 

uint8_t event = packet[0];

switch (event) {

case BTSTACK_EVENT_STATE:

// bt stack activated, get started

if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING){

sdp_client_query_rfcomm_channel_and_name_for_uuid(remote, 0x0003);

}

break;

default:

break;

}

}

 

static void btstack_setup(){

...

// init L2CAP

l2cap_init();

l2cap_register_packet_handler(packet_handler);

}

 

void handle_query_rfcomm_event(sdp_query_event_t * event, void * context){

sdp_client_query_rfcomm_service_event_t * ve;

 

switch (event->type){

case SDP_EVENT_QUERY_RFCOMM_SERVICE:

ve = (sdp_client_query_rfcomm_service_event_t*) event;

printf("Service name: '%s', RFCOMM port %u\n", ve->service_name, ve->channel_nr);

break;

case SDP_EVENT_QUERY_COMPLETE:

report_found_services();

printf("Client query response done with status %d. \n", ce->status);

break;

}

}

 

int main(void){

hw_setup();

btstack_setup();

 

// register callback to receive matching RFCOMM Services and

// query complete event

sdp_client_query_rfcomm_register_callback(handle_query_rfcomm_event, NULL);

 

// turn on!

hci_power_control(HCI_POWER_ON);

// go!

btstack_run_loop_execute();

return 0;

}

BNEP - 蓝牙网络封装协议

BNEP协议用于通过标准网络协议(如TCP,IPv4或IPv6)传输控制和数据包。它建立在L2CAP之上,它指定最小L2CAP MTU为1691字节。

接收BNEP活动

要接收BNEP事件,请使用bnep_register_packet_handler注册数据包处理程序 。

访问远程设备上的BNEP服务

要连接到远程BNEP服务,您需要知道其UUID。可以通过针对PAN简档的SDP查询来查询该组可用UUID。有关详细信息,请参阅PAN配置文件部分。使用远程UUID,您可以使用bnep_connect 函数创建连接。成功或失败时,您将收到BNEP_EVENT_CHANNEL_OPENED。

成功打开连接后,您可以发送和接收以太网数据包。在使用bnep_send发送以太网帧之前,bnep_can_send_packet_now需要返回true。通过已注册的数据包处理程序接收以太网帧,数据包类型为 BNEP_DATA_PACKET。

BTstack BNEP实现分别使用bnep_set_net_type_filter和 bnep_set_multicast_filter支持网络协议过滤器和多播过滤器。

最后,要关闭BNEP连接,可以调用bnep_disconnect。

提供BNEP服务

要提供BNEP服务,请使用提供的服务UUID和最大帧大小调用bnep_register_service。

一个BNEP_EVENT_INCOMING_CONNECTION事件将标志着传入的连接建立。此时,您可以开始发送和接收以太网数据包,如上一节所述。

发送以太网数据包

与L2CAP和RFOMM类似,如果蓝牙模块中的输出数据包缓冲区或ACL缓冲区已满,则直接通过BNEP发送以太网数据包可能会失败。

当需要发送以太网数据包时,请调用bnep_request_can_send_now 并在收到BNEP_EVENT_CAN_SEND_NOW事件时发送数据包。

ATT - 属性协议

ATT客户端使用ATT协议来读取和写入存储在ATT服务器上的属性值。此外,ATT服务器可以通知客户端属性值的变化。属性具有句柄,类型和一组属性。

通用属性(GATT)配置文件基于ATT构建,并将ATT属性的更高级别组织提供给GATT服务和GATT特征。在BTstack中,完整的ATT客户端功能包含在GATT客户端中。有关更多信息,请参阅GATT客户。

在服务器端,一个或多个GATT配置文件提前转换为相应的ATT属性数据库,并由att_server 实现提供。根据客户端请求,ATT服务器自动提供常量数据。要接收动态数据,例如特征值,应用程序需要注册读取和/或写入回调。此外,还可以发送通知和指示。有关更多信息,请参阅GATT服务器上的部分 。

SMP - 安全管理器协议

SMP协议允许设置经过身份验证和加密的LE连接。在初始化和配置之后,SMP自己处理与安全相关的功能,但在需要来自主应用程序或用户的反馈时发出事件。SMP协议的两个主要任务是:绑定和身份解析。

LE Legacy配对和LE安全连接

蓝牙Core V4.0中引入的原始配对算法在初始配对期间出现攻击者时不提供安全性。为了解决这个问题,Bluetooth Core V4.2规范引入了新的 LE Secure Connections方法,同时将原始方法称为LE Legacy Pairing。

BTstack支持两种配对方法。为了使更多的安全LE安全连接方法,ENABLE_LE_SECURE_CONNECTIONS需要加以界定btstack_config.h。

LE安全连接基于椭圆曲线Diffie-Hellman(ECDH)算法进行密钥交换。一开始,会生成一个新的公钥/私钥对。在配对期间,基于本地密钥对和远程公钥生成长期密钥(LTK)。为了便于创建这样的密钥对和计算LTK,Bluetooth Core V4.2规范为蓝牙控制器引入了适当的命令。

作为不提供这些原语的控制器的替代方案,BTstack通过BSD-2-Clause许可的micro-ecc库在软件中提供相关的加密功能。

使用LE安全连接时,外设必须将LTK存储在非易失性存储器中。

初始化

要激活安全管理器,请调用sm_init()。

如果您正在创建产品,则还应使用固定的随机16字节编号调用sm_set_ir()和 sm_set_er()以创建IR和ER密钥种子。如果可能,请为每个设备使用唯一的随机数,而不是从产品序列号或类似内容中获取。由BLE外设生成的加密密钥最终将从ER密钥种子中导出。有关导出不同密钥的更多详细信息,请参阅 蓝牙规范 - 蓝牙核心V4.0,第3卷,第G部分,5.2.2。如果使用私有的,可解析的蓝牙地址,则IR密钥用于标识设备。

组态

要从安全管理器接收事件,必须进行回调。如何注册此数据包处理程序取决于您的应用程序配置。

当att_server用于提供GATT / ATT服务时,att_server 将自身注册为Security Manager数据包处理程序。然后,应用程序通过att_server数据包处理程序接收安全管理器事件 。

如果未使用att_server,则可以通过调用sm_register_packet_handler直接向安全管理器注册数据包处理程序 。

BTstack中的默认SMP配置应尽可能开放:

  • 接受所有短期密钥(STK)生成方法,

  • 接受7..16字节的加密密钥大小,

  • 期望没有认证要求,

  • 不支持LE安全连接,和

  • IO功能设置为IO_CAPABILITY_NO_INPUT_NO_OUTPUT。

您可以分别通过调用以下函数来配置这些项:

  • sm_set_accepted_stk_generation_methods

  • sm_set_encryption_key_size_range

  • sm_set_authentication_requirements:添加SM_AUTHREQ_SECURE_CONNECTION标志以启用LE安全连接

  • sm_set_io_capabilities

身份解决

身份解析是使用其身份解析(IR)密钥将私有的,可解析的蓝牙地址与先前配对的设备进行匹配的过程。建立LE连接后,BTstack会自动尝试解析此设备的地址。在此查找期间,BTstack将发出以下事件:

  • SM_EVENT_IDENTITY_RESOLVING_START用于标记查找的开始,

然后:

  • 查找成功时SM_EVENT_IDENTITY_RESOLVING_SUCCEEDED,或

  • 查找失败时SM_EVENT_IDENTITY_RESOLVING_FAILED。

用户互动

根据身份验证要求,IO功能,可用的OOB数据和启用的STK生成方法,BTstack将以事件的形式请求来自应用程序的反馈:

  • SM_EVENT_JUST_WORKS_REQUEST:请求用户接受Just Works配对

  • SM_EVENT_PASSKEY_INPUT_NUMBER:请求用户输入密钥

  • SM_EVENT_PASSKEY_DISPLAY_NUMBER:向用户显示密钥

  • SM_EVENT_NUMERIC_COMPARISON_REQUEST:向用户显示密钥并请求确认

要停止绑定过程,应调用sm_bonding_decline。否则,可以调用sm_just_works_confirm或sm_passkey_input。

绑定过程之后,如果之前显示了Just Works请求或密钥,则会发出SM_EVENT_JUST_WORKS_CANCEL,SM_EVENT_PASSKEY_DISPLAY_CANCEL或SM_EVENT_NUMERIC_COMPARISON_CANCEL以更新用户界面。

Keypress通知

作为Bluetooth Core V4.2规范的一部分,具有键盘但没有显示器的设备可以发送按键通知以提供更好的用户反馈。在BTstack中,sm_keypress_notification()函数用于发送通知。BTstack通过SM_EVENT_KEYPRESS_NOTIFICATION事件接收通知。

LE安全连接的交叉传输密钥推导

在双模式配置中,BTstack通过链路密钥转换功能h6自动从LE LTK生成BR / EDR链路密钥。然后将其存储在链接密钥db中。

要从BR / EDR链路密钥导出LE LTK,蓝牙控制器需要通过NIST P-256椭圆曲线支持安全连接,并且LE安全连接需要通过LE传输建立。BTstack目前不支持通过LE Transport连接LE安全连接。

带LE Legacy配对的带外数据

通过为两个设备提供一种通过某种奇特的方法获取预共享秘密16字节密钥的方式,可以使LE Legacy配对安全。在大多数情况下,这不是一个选项,特别是因为像iOS这样的流行操作系统没有提供指定它的方法。在某些应用中,蓝牙链路的两侧是一起开发的,这可以提供一个可行的选择。

要提供OOB数据,可以使用sm_register_oob_data_callback注册OOB数据回调 。

你可能感兴趣的:(蓝牙协议栈手册详解)