1.整体架构
1.1 名词解释
HAL :HardwareAbstraction Layer
Btif :Bluetoothinterface
Bta :Bluetoothapplication
Btu :Bluetoothuper layer
Bte :Bluetoothembedded layer
Btm :Bluetooth devicemanager
CO : callout\CI: call in
HF : HandsfreeProfile
HH :HID HostProfile
HL :HealthDevice Profile
AV :audio\vidio
Ag :audiogateway
Ar :audio/videoregistration
Gattc :GATT client
Gatts :GATT server
BLE :Bluetooth Low Energy
1.2 架构图
应用层到协议层:
协议层:
2.辅助类数据结构
2.1 BT HAL的实现(Bluetooth.h,Bluetooth.c)
hw_module_t :模块类型
每个模块都需要自定义自己的模块类型。
hw_module_methods_t:打开该模块类型的方法
它是hw_module_t结构体的一个属性,通过该方法获取“向上提供方法接口的设备类型”
hw_device_t :设备类型,向上提供的接口
通过hw_module_methods_t结构体中open函数获得设备类型。
2.2 主要的工具数据结构
包含了许多蓝牙协议栈自定义的数据结构,例如:alloctor(内存分配工具)、hashmap、list、array、fixed_queue、config、buffer、socket、thread、alarm、eager_reader、wacklock、samphore等,保存在system\bt\osi\路径下。
2.2.1 Thread.c
线程数据结构贯穿了整个协议栈,目前可以发现几个比较重要的线程,manageme
-nt_thread(协议栈管理线程)、bt_jni_workqueue_thread(jni工作线程)、bt_workqueue_thread(蓝牙协议栈中心工作线程)、hci_thread(hci层工作线程)等。
蓝牙协议栈自定义线程的数据结构如下:
struct thread_t{
bool is_joined; //是否正在被等待结束
pthread_t pthread; //线程实例
pid_t tid; //线程ID
char name[THREAD_NAME_MAX + 1]; //线程名
reactor_t *reactor; //用于工作队列在线程中注册反应堆
fixed_queue_t *work_queue; //工作线程
};
线程工具数据结构方法介绍:
1)thread_new_sized为线程结构体分配内存空间,
参数:(const char *name,size_t work_queue_capacity),name指线程名,work_q
-ueue_capacity代表工作队列的长度。
返回值:thread_t * 即返回线程结构体指针。
2)thread_free 终止并回收线程
3)thread_post 向线程工作队列发送消息。
参数:(thread_t *thread,thread_fn func, void *context)
Func代表消息执行函数,context代表消息执行参数,两者组成一个work_item_t结构体并放置到线程工作队列中,等待执行。
4)thread_stop 终止线程
5)run_thread 线程执行函数,在执行时注册并启动了reactor反应堆,进入工作队列中的消息会通过reactor反应堆分发出去。
6)work_queue_read_cb工作队列消息执行回调,每进去一个消息会通过该回调将消息从工作队列中退出并执行该消息。
2.2.2 Reactor.c
使用I/O多路复用epoll模型构建的消息反应堆。
Epoll 相关函数如下:
int epoll_create(int size); 创建epoll监听fd,此处fd是一个fd数组,fd可能是一个socket端口,文件输入输出流,eventfd等I/O流,协议栈中使用的是eventfd。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);添加或者删除一个fd到监听fd数组中。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);监听fd数组的I/O变化并提醒。
Eventfd是一个事件通知,可以创建一个fd,阻塞监听read,以及写入write数据到fd。
比如说当一个队列有消息入队时,使用eventfd写入1到fd中,并且此时fd已经被epoll监听,这样就实现了epoll监听消息入队并将消息分发执行。事实上协议栈中的Reactor消息反应堆模型就是这样搭建的。任何一个固定队列(Fixed_queue)都包含了一个出队fd(dequeue_sem),当有消息入队,会通过向fd中写入数据来通知epoll。任何一个固定队列都可以注册到一个线程的Reactor中,返回一个reactor_object_t结构体。
Reactor结构体如下:
struct reactor_t{
int epoll_fd; //该反应堆监听的epoll fd数组
int event_fd; //控制reactor终止的fd
pthread_mutex_t list_lock; //无效队列锁
list_t *invalidation_list; //当前无效的reactor_object队列
pthread_t run_thread; //reactor依赖的线程
bool is_running; //标志线程函数是否在执行
bool object_removed; //是否有reactor_object被移除
};
Reactor_object_t结构体如下:
structreactor_object_t {
int fd; //需要监听的fd,比如队列出队fd。
void *context; //消息回调函数的上下文,比如队列
reactor_t *reactor; //该reactor object所注册到的reactor
pthread_mutex_t lock; //reactorobject锁
void (*read_ready)(void *context); //当fd可读时的消息回调,比如队列有消息入队
void (*write_ready)(void *context); //当fd可写时的消息回调
};
Reactor结构体方法介绍如下:
1)reactor_new 为reactor分配内存空间,并初始化结构体,包括创建一个epoll,并创建event_fd用于控制reactor的终止。
2)reactor_register为注册一个新的reactor object到reactor中。
参数:(reactor_t*reactor,int fd, void *context, void (*read_ready)(void *context), void (*write_ready)(void *context)) reactor代表reactorobject要注册的reactor,fd代表要监听的fd,context代表回调函数的上下文,read_ready代表fd可读时的回调,write_ready代表可写时的回调。
3)reactor_unregister为从reactor中注销一个reactor object。
4)reactor_start代表开始执行reactor。
5)run_reactor代表reactor运行函数,在该函数中会进行循环epoll_wait监听所有的fd并分发消息,当收Reactor 的event_fd时终止该循环。
2.2.3 Fixed_queue.c
由列表(List)数据结构实现的可以注册出队消息信号到reactor的固定队列。
fixed_queue_t结构体如下:
typedef structfixed_queue_t {
list_t *list; //列表
semaphore_t *enqueue_sem; //入队信号量
semaphore_t *dequeue_sem; //出队信号量
pthread_mutex_t lock; //队列锁
size_t capacity; //容量
reactor_object_t *dequeue_object;//注册出队信号的reactorobject结构体
fixed_queue_cb dequeue_ready; //出队消息回调
void *dequeue_context; //回调参数
} fixed_queue_t;
入队和出队信号量实际就是一个fd,队列可以将此fd注册到一个线程的reactor中进行监听,当监听到消息时进行消息回调。
Fixed_queue结构体方法介绍如下:
1)fixed_queue_new为固定队列分配内存空间并进行初始化
2)fixed_queue_is_empty队列是否为空
3)fixed_queue_enqueue入队,入队时置出队信号量为1,表示有消息进入队列。
4)fixed_queue_dequeue出队,出队时置入队信号量为1,表示此时允许消息入队。
5)fixed_queue_register_dequeue注册出队信号到一个线程的Reactor中进行监听。
参数(fixed_queue_t*queue, reactor_t *reactor, fixed_queue_cb ready_cb, void *context),queue代表要注册的队列,reactor代表一个线程的reactor,ready_cb代表出队消息的回调,context代表回调参数。
6)fixed_queue_unregister_dequeue将队列从reactor中注销。
Thread与Reactor和FixedQueue共同构成了具有反应堆的工作线程,具体工作过程如下:
线程的创建主要包括reactor的创建,线程的运行包括reactor的注册以及运行过程,进入线程的消息封装work_item结构体,通过thread_post入队,等待队列执行该work_item。
2.2.4 Eager_reader.c
该数据结构用于监听vendor(controller)串口的输入事件,并分发到注册在该reader上的线程(即Hci线程)中去。
Eager_reader结构体如下定义:
structeager_reader_t {
int bytes_available_fd; //使用eventfd来记录信道所收到的字节流总长度
int inbound_fd; //代表监听哪一个信道的有输入事件
const allocator_t *allocator; //内存分配器
size_t buffer_size; //接收一次输入字节流的缓冲区大小
fixed_queue_t *buffers; //reader从信道中读取的若干“字节流缓冲区”列表
data_buffer_t *current_buffer; //临时存放一个字节流缓冲区的数据
thread_t *inbound_read_thread; //用于监听和读取信道数据的线程
reactor_object_t *inbound_read_object; //注册到输入线程 reactor中的reactorobject对象
reactor_object_t *outbound_registration; //注册bytes_available_fd到输出线程reactor 中的reactorobject对象
eager_reader_cb outbound_read_ready; //告知输出线程缓冲区已经有数据的回调
void *outbound_context; //回调参数
};
Eager_reader结构体方法介绍如下:
1)eager_reader_new为Eager_reader结构体分配内存空间并进行初始化,创建信道监听线程。
参数:(int fd_to_read,const allocator_t *allocator, size_t buffer_size, size_t max_buffer_count,const char *thread_name)
fd_to_read代表需要监听的信道,allocator代表内存分配器,buffer_size代表一个字节流缓冲区的大小,max_buffer_count代表缓冲区的最多个数,thread_name代表监听信道的线程名。
2)eager_reader_register在eager_reader注册一个线程,用于eager_reader分发所接收到的数据,这里的线程是Hci线程。
参数:(eager_reader_t*reader, reactor_t *reactor, eager_reader_cb read_cb, void *context) reader代表需要注册的reader,reactor代表注册线程的reactor,read_cb代表注册在reader中“已经有数据可读”的回调,context代表回调参数。
3)eager_reader_read从eager_reader中的buffers中读取数据。
参数:(eager_reader_t*reader, uint8_t *buffer, size_t max_size)reader代表信道数据的reader,buffer代表读取数据存放的buffer,max_size代表需要读取数据的大小。
4)inbound_data_waiting用于reader内部线程从信道中读取数据存放到buffers中,并写入buffers中buffer的个数到bytes_available_fd中,通知上层线程(Hci_thread)reader中已经有数据。
5)has_byte用于检测eager_reader_read检测当前reader是否已经从信道中读取到了数据。
Eager_reader工作流程如下:
Eager_reader主要的工作就是监听controller端串口是否有数据输入,当有数据输入就可以上报到Hci_layer线程,并提供读取数据的方法。
3.使能过程
3.1 协议栈初始化
协议栈初始化主要工作是初始化stackManager,创建stackManager工作线程,stackManager作为蓝牙协议栈的管理者,提供管理接口,主要工作即为初始化(init_stack)、打开(start_up_stack_async)、关闭(shut_down_stack_async)、清除协议栈(clean_up_stack)。每个操作会将消息发送到工作线程的工作队列中,在工作线程中执行。
init_stack进而会打开JNI工作线程(通过AttachCurrentThread将该线程连接到jvm中),该工作线程用于将底层消息回调(HAL_CBACK)到上层,sBluetoothCallbacks注册的回调。
3.2 协议栈使能
协议栈使能主要工作如下:
1.打开Hci_layer的工作线程hci_thread,并向controller层下发芯片上电使能控制命令(BT_VND_PWR_ON)。
2.打开Btu层蓝牙协议栈工作中心线程(bt_workqueue_thread),该中心线程有两个主要的工作队列,即btu_bta_msg_queue(接收Bta层的消息)以及btu_hci_msg_queue(接收Hci层的消息)。Bta层消息放置到btu_bta_msg_queue中,工作线程将消息下发到Hci_layer,Hci_layer已控制命令或者数据的形式发送到Controller端,等待Controller端消息返回。Hci_layer将返回的消息放置到btu_hci_msg_queue中,工作线程将消息返回到Bta层。
3.在bt_workqueue_thread中进行了核心协议栈(btm、l2cp、sdp、gatt、btm_ble)的初始化以及profile协议(RFCOMM、PAN、A2DP、...)初始化。
4.对btm_cb进行初始化,btm管理着整个“蓝牙管理”数据(ACL、PowerManagement、Device control、BLE Device controllers、Inquiry、SCO Management、Security Manag
-ement)。
5.bta_sys_hw状态机的转换(从bta_sys_hw_off状态到bta_sys_hw_on状态)。
6.通过命令读取本地controller一些配置信息,比如所支持的命令集和服务等。
7.向controller下发一些初始化的命令,比如设置查询参数的一些命令等。
8.上报本机Property的信息以及已配对Property的信息。
4.HCI层的实现
4.1 HCI协议
蓝牙系统的协议模型如下图所示。从图中可以看出,HCI是位于蓝牙系统的L2CAP(逻辑链路控制与适配协议)层和LMP(链路管理协议)层之间的一层协议。HCI为上层协议提供了进入LM的统一接口和进入基带的统一方式。在HCI的主机(Host)和HCI主机控制器(Host Controller)之间会存在若干传输层,这些传输层是透明的,只需完成传输数据的任务,不必清楚数据的具体格式。
HCI是通过包的方式来传送数据、命令和事件的,所有在主机和主机控制器之间的通信都以包的形式进行。包括每个命令的返回参数都通过特定的事件包来传输。HCI有数据、命令和事件三种包,其中数据包是双向的,命令包只能从主机发往主机控制器,而事件包始终是主机控制器发向主机的。主机发出的大多数命令包都会触发主机控制器产生相应的事件包作为响应。
命令包分为七种类型:
* 链路控制命令;
* 链路政策和模式命令;
* 主机控制和基带命令;
* 信息命令;
* 状态命令;
* 测试命令
* 低功耗相关命令
事件包也可分为四种类型:
* 通用事件,包括命令完成包(CommandComplete)和命令状态包(Command Status);
* 测试事件;
* 出错时发生的事件,如产生丢失(FlushOccured)和数据缓冲区溢出(Data Buffer Overflow)。
* 低功耗相关事件,比如广播报告(LEAdvertising Report Event)和连接建立成功(LE Connection Complete Event)
数据包则可分为ACL和SCO的数据包。
各种包的格式如下:
4.2 HCI层在BlueDroid中的代码实现
Hci层相关代码包括如下几个重要部分:
Hci_layer.c:维护了hci层的主线程(hci_thread),提供了传送和接收命令和数据的接口。
Packet_fragmenter.c:对Hci包进行分解和重组的工具。
Hci_hal_h4.c:打开hci到vendor的串口(信道),处理下发数据到vendor和从vendor接收数据的一层。
Eager_reader.c:监听vendor某个串口(信道)的数据可读事件,并提供读取数据方法。
Hcicmd.c:向上提供发送命令的接口。
4.2.1 HCI数据结构
typedef struct hci_t {
//发送低电量命令接口
void (*send_low_power_command)(low_power_command_t command);
//处理后期加载事件
void (*do_postload)(void);
//高层线程注册事件分发器
data_dispatcher_t *event_dispatcher;
//高层接收ACL数据的队列
void (*set_data_queue)(fixed_queue_t *queue);
//发送带有回调的命令包
void (*transmit_command)(
BT_HDR *command,
command_complete_cb complete_callback,
command_status_cb status_cb,
void *context
);
//发送命令包并等待状态信号量
future_t *(*transmit_command_futured)(BT_HDR *command);
// 发送数据包
void (*transmit_downward)(data_dispatcher_type_t type, void *data);
} hci_t;
4.2.2 HCI层的使能过程
主要包括HCI工作线程的启动、各个工作队列的创建、各个定时器的创建、分包和组包工具(packet_fragmenter)的初始化、hal的初始化、低电量管理模块的初始化、vendor的开启和配置、芯片上电命令的下发。
使能过程Hci_layer与vendor部分(以Marlin芯片为例)时序图如下:
4.2.3 HCI层的包下发
包的下发主要使用transmit_command和transmit_downward接口,前者发送命令包,是带有命令完成和状态回调函数的。后者发送数据包,不带有回调函数。发送包的拆包使用到了packet_fragmenter的fragment_and_dispatch函数。
包的接收和分发分为了事件包和数据包两类,事件包的分发需要上层线程注册了事件分发器,数据包的分发需要上层在Hci层设置数据队列。接收包的组包使用到了packet_fragmenter的reassemble_and_dispatch函数,包的监听使用到了eager_reader。
包下发的流程图如下:
Hal_h4里会将发送包的串口打开,使用write方法将包写入串口。Vendor端对串口的实际上使用的是socket。
里面比较重要的部分是对acl数据的分包过程,代码如下:
//获取controller支持的包的最大长度
uint16_tmax_data_size =
SUB_EVENT(packet->event) ==LOCAL_BR_EDR_CONTROLLER_ID ?
controller->get_acl_data_size_classic() :
controller->get_acl_data_size_ble();
uint16_t max_packet_size = max_data_size +HCI_ACL_PREAMBLE_SIZE;
uint16_t remaining_length = packet->len;
//根据当前的connection ID生成continue connection id
uint16_t continuation_handle;
STREAM_TO_UINT16(continuation_handle,stream);
continuation_handle =APPLY_CONTINUATION_FLAG(continuation_handle);
while (remaining_length > max_packet_size){
// 保证每次发送的acl包均不超过最大尺寸
stream = packet->data +packet->offset;
STREAM_SKIP_UINT16(stream);
UINT16_TO_STREAM(stream, max_data_size);
packet->len = max_packet_size;
//发送已经分解出的包
callbacks->fragmented(packet, false);
packet->offset += max_data_size;
remaining_length -= max_data_size;
packet->len = remaining_length;
// 生成继续包的hci头部
stream = packet->data +packet->offset;
UINT16_TO_STREAM(stream,continuation_handle);
UINT16_TO_STREAM(stream, remaining_length -HCI_ACL_PREAMBLE_SIZE);
// Apparently L2CAP can set layer_specificto a max number of segments to transmit
if (packet->layer_specific) {
packet->layer_specific--;
if (packet->layer_specific == 0) {
packet->event =MSG_HC_TO_STACK_L2C_SEG_XMIT;
callbacks->transmit_finished(packet,false);
return;
}
}
}
其中生成continueconnection id方法如下:
#define APPLY_CONTINUATION_FLAG(handle)(((handle) & 0xCFFF) | 0x1000)
该方法根据acl数据格式决定的,如下:
PB Flag代表是否是后续包。
4.2.2 HCI层的包接收和分发
包的接收需要上层注册下去事件分发器和数据接收队列,在bte_main_boot_entry阶段设置,如下:
voidbte_main_boot_entry(void)
{
module_init(get_module(INTEROP_MODULE));
hci = hci_layer_get_interface();
if (!hci)
LOG_ERROR(LOG_TAG, "%s could not gethci layer interface.", __func__);
btu_hci_msg_queue =fixed_queue_new(SIZE_MAX);
if (btu_hci_msg_queue == NULL) {
LOG_ERROR(LOG_TAG, "%s unable to allocatehci message queue.", __func__);
return;
}
//事件分发器的队列和数据接收的队列均使用btu_hci_msg_queue
data_dispatcher_register_default(hci->event_dispatcher,btu_hci_msg_queue);
hci->set_data_queue(btu_hci_msg_queue);
module_init(get_module(STACK_CONFIG_MODULE));
}
包的接收和分发流程图如下:
其中包拼装过程会先提取出preamble部分,从preamble部分中可以得到包数据部分的长度,根据长度可以拼装整个包的数据。如下部分代码:
case PREAMBLE:
incoming->preamble[incoming->index] = byte;
incoming->index++;
incoming->bytes_remaining--;
if (incoming->bytes_remaining == 0){
// For event and sco preambles, thelast byte we read is the length
incoming->bytes_remaining = (type== DATA_TYPE_ACL) ? RETRIEVE_ACL_LENGTH(incoming->preamble) : byte;
size_t buffer_size = BT_HDR_SIZE +incoming->index + incoming->bytes_remaining;
incoming->buffer = (BT_HDR*)buffer_allocator->alloc(buffer_size);
if (!incoming->buffer) {
LOG_ERROR(LOG_TAG, "%s errorgetting buffer for incoming packet of type %d and size %zd", __func__,type, buffer_size);
// Can't read any more of thiscurrent packet, so jump out
incoming->state =incoming->bytes_remaining == 0 ? BRAND_NEW : IGNORE;
break;
}
// Initialize the buffer
incoming->buffer->offset = 0;
incoming->buffer->layer_specific = 0;
incoming->buffer->event =outbound_event_types[PACKET_TYPE_TO_INDEX(type)];
memcpy(incoming->buffer->data,incoming->preamble, incoming->index);
incoming->state =incoming->bytes_remaining > 0 ? BODY : FINISHED;
}
break;
case BODY:
incoming->buffer->data[incoming->index] = byte;
incoming->index++;
incoming->bytes_remaining--;
size_t bytes_read =hal->read_data(type, (incoming->buffer->data + incoming->index),incoming->bytes_remaining);
incoming->index += bytes_read;
incoming->bytes_remaining -=bytes_read;
incoming->state =incoming->bytes_remaining == 0 ? FINISHED : incoming->state;
break;
对于过长的acl数据包需要进行包的重组,主要是根据acl包中的PB Flag标志位进行重组,如果当前是起始部分并且是不完整的,则生成一个部分包(partial_packet)放到hash map里,等下次收到它的后续部分进行拼装,拼装完毕后就分发出去,代码见packet_fragmenter的reassemble_and_dispatch函数,代码过长这里不再举例出来。
5.传统蓝牙扫描过程
关键字解读:Inquiry译为查询,代表单次查询附近可见设备;scan译为扫描,代表扫描来自其他BR / EDR控制器的页面尝试和/或询问请求;discovery译为发现,代表发现对端设备的一些特性,比如名称和支持服务;search译为搜索,代表查询和发现整个过程。
5.1涉及到的关键HCI命令
1)WriteInquiry Mode Command
设置查询模式,主要有三种查询模式,分别为标准查询模式、结果带有Rssi的查询模式、结果带有Rss或者带有拓展结果的查询模式。在7.0系统中查询使用第三种模式。
2)WriteCurrent IAC LAP Command
设置Inquiry AccessCodes(查询访问代码)。7.0系统中默认使用general_inq_lap {0x9e,0x8b,0x33}。
3)WriteInquiry Scan Activity Command
设置周期性查询的间隔(interval)大小和周期(window)大小。
4)PeriodicInquiry Mode Command
代表进入周期性查询周围可见设备。
5)Write ScanEnable Command
代表当前扫描模式,包括如下几种扫描模式:
0x00 No Scansenabled. Default.不可被连接,不可被发现。
0x01 InquiryScan enabled.Page Scan disabled.可以被发现,不可以被连接。
0x02 InquiryScan disabled.Page Scan enabled.不可被发现,可以被连接。
0x03 InquiryScan enabled.Page Scan enabled.可以被发现,可以被连接。
6)Set EventFilter Command
设置查询过滤器,具体包括两种过滤类型,一是按设备类型过滤,二是按设备地址过滤。
7)InquiryCommand
进入查询周围设备状态。
8)InquiryResult Event
查询到设备的返回结果。
9)ExtendedInquiry Result Event
带有拓展信息的查询返回结果。
10)InquiryComplete Event
查询完成事件。
具体命令的格式和意义见蓝牙核心协议Core4.0-volume2-part E。
5.2查询状态机
查询过程涉及到几个状态,在协议栈层使用二维数组(table)形成状态机,包括如下几个状态。
/* state table*/
consttBTA_DM_ST_TBL bta_dm_search_st_tbl[] = {
bta_dm_search_idle_st_table,
bta_dm_search_search_active_st_table,
bta_dm_search_search_cancelling_st_table,
bta_dm_search_disc_active_st_table
};
各个状态形式如下:
/* 闲置状态 */
const UINT8bta_dm_search_idle_st_table[][BTA_DM_SEARCH_NUM_COLS] =
{
/* Event Action1 Action 2 Next State*/
/* API_SEARCH*/ {BTA_DM_API_SEARCH, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/*API_SEARCH_CANCEL */ {BTA_DM_SEARCH_CANCEL_NOTIFY, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* API_SEARCH_DISC*/ {BTA_DM_API_DISCOVER, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/* INQUIRY_CMPL*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* REMT_NAME_EVT*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* SDP_RESULT_EVT*/ {BTA_DM_FREE_SDP_DB, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* SEARCH_CMPL_EVT*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* DISCV_RES_EVT*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/*API_DI_DISCOVER_EVT */ {BTA_DM_API_DI_DISCOVER, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE}
#if BLE_INCLUDED ==TRUE
/*DISC_CLOSE_TOUT_EVT */ ,{BTA_DM_CLOSE_GATT_CONN, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE}
#endif
};
/* 搜索活跃状态 */
const UINT8bta_dm_search_search_active_st_table[][BTA_DM_SEARCH_NUM_COLS] =
{
/* Event Action 1 Action 2 NextState */
/* API_SEARCH*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/* API_SEARCH_CANCEL*/ {BTA_DM_API_SEARCH_CANCEL, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING},
/* API_SEARCH_DISC*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/* INQUIRY_CMPL*/ {BTA_DM_INQUIRY_CMPL, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/* REMT_NAME_EVT*/ {BTA_DM_REMT_NAME, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/* SDP_RESULT_EVT*/ {BTA_DM_SDP_RESULT, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/* SEARCH_CMPL_EVT*/ {BTA_DM_SEARCH_CMPL, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* DISCV_RES_EVT*/ {BTA_DM_SEARCH_RESULT, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE},
/*API_DI_DISCOVER_EVT */ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE}
#if BLE_INCLUDED ==TRUE
/*DISC_CLOSE_TOUT_EVT */ ,{BTA_DM_CLOSE_GATT_CONN, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_ACTIVE}
#endif
};
/* 正在取消搜索状态 */
const UINT8bta_dm_search_search_cancelling_st_table[][BTA_DM_SEARCH_NUM_COLS] =
{
/* Event Action 1 Action 2 NextState */
/* API_SEARCH*/ {BTA_DM_QUEUE_SEARCH, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING},
/*API_SEARCH_CANCEL */ {BTA_DM_SEARCH_CLEAR_QUEUE, BTA_DM_SEARCH_CANCEL_NOTIFY, BTA_DM_SEARCH_CANCELLING},
/* API_SEARCH_DISC*/ {BTA_DM_QUEUE_DISC, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING},
/* INQUIRY_CMPL*/ {BTA_DM_SEARCH_CANCEL_CMPL, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* REMT_NAME_EVT*/ {BTA_DM_SEARCH_CANCEL_TRANSAC_CMPL, BTA_DM_SEARCH_CANCEL_CMPL, BTA_DM_SEARCH_IDLE},
/* SDP_RESULT_EVT*/ {BTA_DM_SEARCH_CANCEL_TRANSAC_CMPL, BTA_DM_SEARCH_CANCEL_CMPL, BTA_DM_SEARCH_IDLE},
/* SEARCH_CMPL_EVT*/ {BTA_DM_SEARCH_CANCEL_TRANSAC_CMPL,BTA_DM_SEARCH_CANCEL_CMPL, BTA_DM_SEARCH_IDLE},
/* DISCV_RES_EVT*/ {BTA_DM_SEARCH_CANCEL_TRANSAC_CMPL, BTA_DM_SEARCH_CANCEL_CMPL, BTA_DM_SEARCH_IDLE},
/*API_DI_DISCOVER_EVT */ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING}
#if BLE_INCLUDED ==TRUE
/*DISC_CLOSE_TOUT_EVT */,{BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING}
#endif
};
/* 发现活跃状态 */
const UINT8bta_dm_search_disc_active_st_table[][BTA_DM_SEARCH_NUM_COLS] =
{
/* Event Action 1 Action 2 NextState */
/* API_SEARCH*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/*API_SEARCH_CANCEL */{BTA_DM_SEARCH_CANCEL_NOTIFY, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING},
/* API_SEARCH_DISC*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/* INQUIRY_CMPL*/ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/* REMT_NAME_EVT*/ {BTA_DM_DISC_RMT_NAME, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/* SDP_RESULT_EVT*/ {BTA_DM_SDP_RESULT, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/* SEARCH_CMPL_EVT*/ {BTA_DM_SEARCH_CMPL, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},
/* DISCV_RES_EVT*/ {BTA_DM_DISC_RESULT, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},
/*API_DI_DISCOVER_EVT */ {BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE}
#if BLE_INCLUDED ==TRUE
/*DISC_CLOSE_TOUT_EVT */ ,{BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE}
#endif
};
数组的一维坐标代表某个事件,二维坐标分别代表事件驱动的action1,action2,以及next state。具体意思就是当来了一个事件时,需要经过一个或两个action动作之后进入下一个状态。
现在已经知道,状态是由事件驱动的,下面带有拓展查询结果的查询过程为例,结合状态机讲解一个完整的查询过程。
1.BTA_DM_API_SEARCH事件->进入bta_dm_search_search_active_st_table状态->执行bta_dm_search_start。
2.在该状态,首先设置查询过滤器,之后下发查询命令进入查询状态。
3.在查询状态会将查询到的结果记录到协议栈数据库中并且上报到上层。
4.重复步骤3直到到达查询总时间,BTA_DM_INQUIRY_CMPL_EVT事件->保持在bta_dm_search_search_active_st_table->状态执行bta_dm_inq_cmpl。
5.bta_dm_inq_cmpl会判断当前是否查询到设备,如果没有进入步骤6,如果查询到设备,则触发BTA_DM_DISCOVERY_RESULT_EVT事件->执行bta_dm_search_result(此过程中发现discovery到的设备名称会上报到上层)->判断是否还有设备,如有执行bta_dm_discover_next_device->上报设备名称到上层,如果仍然有则继续进行bta_dm_discover_next_device,如果所有设备发现完成,进入步骤6。
6.触发BTA_DM_SEARCH_CMPL_EVT事件->进入bta_dm_search_idle_st_table状态。
这里只涉及到了bta_dm_search_idle_st_table,bta_dm_search_search_active_st_ta
-ble两个状态。bta_dm_search_search_cancelling_st_table用于取消搜索过程。bta_dm_search_disc_active_st_table则用于发现对端支持的服务过程,对应SDP过程,具体讲解放到SDP章节中。
5.3关键数据结构
5.3.1 Bta层
//查询控制块
/* DM searchcontrol block */
和查询设备主要相关的属性用注释标出。
typedef struct
{
tBTA_DM_SEARCH_CBACK * p_search_cback; //btif 层注册的事件回调
tBTM_INQ_INFO *p_btm_inq_info; //从btm层设备数据库中获取到的当前设备,见tBTM_INQ_INFO数据结构解释
tBTA_SERVICE_MASK services;
tBTA_SERVICE_MASK services_to_search;
tBTA_SERVICE_MASK services_found;
tSDP_DISCOVERY_DB * p_sdp_db;
UINT16 state;
BD_ADDR peer_bdaddr; //当前对端蓝牙地址
BOOLEAN name_discover_done; //发现当前设备名称的操作是否完成
BD_NAME peer_name; //当前对端设备名称
alarm_t *search_timer;
UINT8 service_index;
tBTA_DM_MSG *p_search_queue; /* search or discovercommands during search cancel stored here */
BOOLEAN wait_disc;
BOOLEAN sdp_results;
tSDP_UUID uuid;
UINT8 peer_scn;
BOOLEAN sdp_search;
BOOLEAN cancel_pending; /* inquiry cancel is pending */
tBTA_TRANSPORT transport;
#if ((defined BLE_INCLUDED) &&(BLE_INCLUDED == TRUE))
tBTA_DM_SEARCH_CBACK * p_scan_cback;
#if ((defined BTA_GATT_INCLUDED) &&(BTA_GATT_INCLUDED == TRUE))
tBTA_GATTC_IF client_if;
UINT8 num_uuid;
tBT_UUID *p_srvc_uuid;
UINT8 uuid_to_search;
BOOLEAN gatt_disc_active;
UINT16 conn_id;
UINT8 * p_ble_rawdata;
UINT32 ble_raw_size;
UINT32 ble_raw_used;
alarm_t *gatt_close_timer; /* GATT channel close delay timer */
BD_ADDR pending_close_bda; /* pending GATT channel remote device address */
#endif
#endif
} tBTA_DM_SEARCH_CB;
5.3.2 Btm层
//设备信息,从Btm层数据库中读取
/* This is theinquiry response information held in its database by BTM, and available
** to applicationsvia BTM_InqDbRead, BTM_InqDbFirst, and BTM_InqDbNext.
*/
typedef struct
{
tBTM_INQ_RESULTS results; //设备详细信息
BOOLEAN appl_knows_rem_name; /* set by application if it knows the remotename of the peer device.This is later used by application to determine ifremote name request is required to be done. Having the flag here avoidduplicate store of inquiry results */
#if ( BLE_INCLUDED == TRUE)
UINT16 remote_name_len;
tBTM_BD_NAME remote_name;
UINT8 remote_name_state;
UINT8 remote_name_type;
#endif
} tBTM_INQ_INFO;
//设备详细信息,查询设备的回复信息
/* These are thefields returned in each device's response to the inquiry. It
** is returned inthe results callback if registered.
*/
typedef struct
{
UINT16 clock_offset;
BD_ADDR remote_bd_addr;
DEV_CLASS dev_class;
UINT8 page_scan_rep_mode;
UINT8 page_scan_per_mode;
UINT8 page_scan_mode;
INT8 rssi; /* Set to BTM_INQ_RES_IGNORE_RSSIif not valid */
UINT32 eir_uuid[BTM_EIR_SERVICE_ARRAY_SIZE];
BOOLEAN eir_complete_list;
#if (BLE_INCLUDED == TRUE)
tBT_DEVICE_TYPE device_type;
UINT8 inq_result_type;
UINT8 ble_addr_type;
tBTM_BLE_EVT_TYPE ble_evt_type;
UINT8 flag;
#endif
} tBTM_INQ_RESULTS;
//btm 层inq变量
typedef struct
{
tBTM_CMPL_CB *p_remname_cmpl_cb;
#define BTM_EXT_RMT_NAME_TIMEOUT_MS (40 *1000) /* 40 seconds */
alarm_t *remote_name_timer;
UINT16 discoverable_mode;
UINT16 connectable_mode;
UINT16 page_scan_window;
UINT16 page_scan_period;
UINT16 inq_scan_window;
UINT16 inq_scan_period;
UINT16 inq_scan_type;
UINT16 page_scan_type; /* currentpage scan type */
tBTM_INQ_TYPE scan_type;
BD_ADDR remname_bda; /* Name of bd addr for active remote namerequest */
#define BTM_RMT_NAME_INACTIVE 0
#define BTM_RMT_NAME_EXT 0x1 /* Initiated through API */
#define BTM_RMT_NAME_SEC 0x2 /* Initiated internally by securitymanager */
#define BTM_RMT_NAME_INQ 0x4 /* Remote name initiated internally byinquiry */
BOOLEAN remname_active; /* State ofa remote name request by external API */
tBTM_CMPL_CB *p_inq_cmpl_cb; //扫描完成回调
tBTM_INQ_RESULTS_CB *p_inq_results_cb; //扫描结果回调
tBTM_CMPL_CB *p_inq_ble_cmpl_cb; /*completion callback exclusively for LE Observe*/
tBTM_INQ_RESULTS_CB *p_inq_ble_results_cb;/*results callback exclusivelyfor LE observe*/
tBTM_CMPL_CB *p_inqfilter_cmpl_cb; /* Called (if notNULL) after inquiry filter completed*/
UINT32 inq_counter; /* Counterincremented each time an inquiry completes */
/*Used for determining whether or not duplicate devices */
/*have responded to the same inquiry */
tINQ_BDADDR *p_bd_db; /* Pointer to memory that holdsbdaddrs */
UINT16 num_bd_entries; /* Number of entries in database */
UINT16 max_bd_entries; /* Maximumnumber of entries that can be stored */
tINQ_DB_ENT inq_db[BTM_INQ_DB_SIZE]; //查询到的设备数据库
tBTM_INQ_PARMS inqparms; /* Containsthe parameters for the current inquiry */
tBTM_INQUIRY_CMPL inq_cmpl_info; /* Status and number of responses from the last inquiry */
UINT16 per_min_delay; /* Currentperiodic minimum delay */
UINT16 per_max_delay; /* Currentperiodic maximum delay */
BOOLEAN inqfilt_active; //是否正在设置inq event filter
UINT8 pending_filt_complete_event;/* to take care of btm_event_filter_complete corresponding to */
/* inquiry that has been cancelled*/
UINT8 inqfilt_type; /* Contains the inquiry filter type (BD ADDR, COD, or Clear) */
#define BTM_INQ_INACTIVE_STATE 0
#define BTM_INQ_CLR_FILT_STATE 1 /* Currently clearing the inquiry filter preceeding the inquiry request*/
/*(bypassed if filtering is not used) */
#define BTM_INQ_SET_FILT_STATE 2 /* Sets the new filter (or turns off filtering) in this state */
#define BTM_INQ_ACTIVE_STATE 3 /* Actual inquiry or periodic inquiry is in progress */
#define BTM_INQ_REMNAME_STATE 4 /* Remote name requests are active */
UINT8 state; /* Currentstate that the inquiry process is in */
UINT8 inq_active; /* Bit Maskindicating type of inquiry is active */
BOOLEAN no_inc_ssp; /* TRUE, to stop inquiry on incomingSSP */
#if (defined(BTA_HOST_INTERLEAVE_SEARCH)&& BTA_HOST_INTERLEAVE_SEARCH == TRUE)
btm_inq_state next_state; /*interleaving state to determine nextmode to be inquired*/
#endif
} tBTM_INQUIRY_VAR_ST;
Btm 层inquiry变量包含了查询的所有信息,包括各种回调函数、数据库(实际上是链表)、查询参数信息、以及状态信息等,几个比较关键的信息已经高亮出。
p_inq_results_cb查询结果的回调,p_inq_cmpl_cb查询完成回调,当向Hci层下发查询命令时会注册这两个回调。
p_inqfilter_cmpl_cb设置过滤器完成的回调,当向Hci层下发设置查询过滤器命令时注册的回调。
inq_counter用于记录当前查询的次数,用于判断某次查询时是否接收到重复设备。
num_bd_entries表示当前在数据库中保存的设备实体的个数。
inq_db[BTM_INQ_DB_SIZE]表示设备实体数据库,用于保存查询到设备。
Inqparms用于保存查询命令的参数信息。
inqfilt_type代表当前过滤器的类型,包括按地址、按类型、以及无过滤器。
State代表当前所处于的状态,包括如下四种状态:
#defineBTM_INQ_INACTIVE_STATE 0
#defineBTM_INQ_CLR_FILT_STATE 1 /* Currently clearing the inquiry filter preceeding the inquiry request */(bypassed if filtering is not used) */
#defineBTM_INQ_SET_FILT_STATE 2 /* Sets the new filter (or turns offfiltering) in this state */
#defineBTM_INQ_ACTIVE_STATE 3 /* Actual inquiry or periodic inquiry is inprogress */
#defineBTM_INQ_REMNAME_STATE 4 /* Remote name requests are active */
inq_active代表哪种查询类型属于活跃状态,包括如下几种:
#define BTM_INQUIRY_INACTIVE 0x0 /* no inquiry in progress */
#defineBTM_GENERAL_INQUIRY_ACTIVE BTM_GENERAL_INQUIRY /* ageneral inquiry is in progress */
#defineBTM_LIMITED_INQUIRY_ACTIVE BTM_LIMITED_INQUIRY /* alimited inquiry is in progress */
#defineBTM_PERIODIC_INQUIRY_ACTIVE 0x8 /* aperiodic inquiry is active */
#defineBTM_SSP_INQUIRY_ACTIVE 0x4 /* SSP is active, so inquiry is disallowed(work around for FW bug) */
#defineBTM_LE_GENERAL_INQUIRY_ACTIVE BTM_BLE_GENERAL_INQUIRY /* ageneral inquiry is in progress */
#defineBTM_LE_LIMITED_INQUIRY_ACTIVE BTM_BLE_LIMITED_INQUIRY /* alimited inquiry is in progress */
5.3.3 查询时序图
6.传统蓝牙配对过程
(待续...)
---------------------
作者:zcc450959507
来源:CSDN
原文:https://blog.csdn.net/zcc450959507/article/details/79402406
版权声明:本文为博主原创文章,转载请附上博文链接!