[Android源码解析]蓝牙扫描结果反馈的分析
在前面两篇文章中,晓东和大家一起分析了android是如何向蓝牙发送扫描命令的,这篇文章我们将继续来看,蓝牙在收到扫描命令之后是如何向android上层反馈搜索到的设备以及上层对这些搜索到的设备是如何进行进一步处理的。
7、inquiry result event的分析
Inquiry result的意思大概就是在收到inquiryresponse的时候会从controller回应这个event上来,需要注意的是一个event可能会有多个response,并不是一个response对应一个event。
7.1 inquiry resultevent在spec中的定义
它在spec中的格式如下所示:
其中各个参数的意思如下:
Num_Responses:表示response的设备数目。上文我们有提到一个event是可以包含多个response的,这里就是表明究竟有多少个response的。
BD_ADDR:这个很好理解,就是每个response的bd addr。是按照顺序存储的,每个占6byte。
Page scan repetition mode:这个其实大家也不要太关心,大概的意思就是page的时候采取的策略.他们具体的差别在于:
R0对连接的时间要求很严格,并且pagingdevice需要有很好的clock,在这种模式下,别的连接是完全没有机会进来的。当然总的来说,他的功耗也是最大的.
R1可以理解为连接时间要求还是很严格(和R0接近),但是设备没有足够好的蓝牙clock,就会建议使用这个模式,在这种模式下,别的设备时有机会连接上来的,当然功耗就没有R0大了,可以说他是R0和R1之间的一个模式。
R2 就是连时间要求都不是那么的严格了。其他就更不谈了,功耗当然也不是很大的。
Reserved1和2这两个都是在早起的spec中才看到,v1.1之前。现在4.1都要出来了,所以我们就不用管了。
Class of device:这个表示设备的类型。他分为主要设备类和次要设备类。其中主要设备类是其中的[12:8]位来表示,次要设备类是其中的[7:2]位来表示。
主要设备类表
12 | 11 | 10 | 9 | 8 | 主要设备类 |
0 | 0 | 0 | 0 | 0 | 其他 [Ref #2] |
0 | 0 | 0 | 0 | 1 | 计算机(台式机、笔记本、PDA、organizer ....) |
0 | 0 | 0 | 1 | 0 | 电话(手机、无绳、支付电话、调制解调器 ...) |
0 | 0 | 0 | 1 | 1 | LAN/网络接入点 |
0 | 0 | 1 | 0 | 0 | 音频/视频(耳机、扬声器、立体声、视频显示、VCR..... |
0 | 0 | 1 | 0 | 1 | 配件(鼠标、游戏杆、键盘 .....) |
0 | 0 | 1 | 1 | 0 | 成像(打印、扫描仪、相机、显示 ...) |
0 | 0 | 1 | 1 | 1 | 可穿戴 |
0 | 1 | 0 | 0 | 0 | 玩具 |
0 | 1 | 0 | 0 | 1 | 健康 |
1 | 1 | 1 | 1 | 1 | 未分类:未指定设备代码 |
X | X | X | X | X | 所有其他保留值 |
次要设备是根据主要设备再进行判断的,我就不一一列出来了,具体可以参见蓝牙官网:https://www.bluetooth.org/zh-cn/specification/assigned-numbers/baseband
Clock_offset:表示master和slave之间的clock的偏差,有了这个值可以加快master和slave之间page的时间。
7.2inquiry result event具体的实现代码
static inline void inquiry_result(int index, int plen, void *ptr){ struct dev_info *dev = &devs[index]; //表示response的设备数目 uint8_t num = *(uint8_t *) ptr++; int i; /* Skip if it is not in Inquiry state */ //这里就是首先会检查是否在discov inq的state if (get_state(index) != DISCOV_INQ) return; for (i = 0; i < num; i++) { //inquiry info就是返回那些参数了 inquiry_info *info = ptr; //得到device of class的值 uint32_t class = info->dev_class[0] | (info->dev_class[1] << 8) | (info->dev_class[2] << 16); //这个是没有rssi的信息的,所以传入0,最后一个是ext的data,这里也是没有的,具体见7.5分析 btd_event_device_found(&dev->bdaddr, &info->bdaddr, class, 0, NULL); ptr += INQUIRY_INFO_SIZE; }}
在这里我们先暂停一下,我们发现这里还有个rssi参数,以及data参数,在这个event中都是没有传入的,难道还有什么event是有这些参数的,你猜对了,所以这里我插播另外几个用来表示inquiry result的event。它们分别是inquiry result with rssi event以及extended inquiryresulte event.
7.3 inquiry result withrssi event的介绍
这个event比inquiryresult event所表露的信息更多一点,他在spec中的定义如下:
我们可以很清晰地看出来,inquiryresult有的东西他都有(少了一个reserved,其实我们不会关注啦),在最后他还多了一个参数就是rssi。这个参数意思是:
Rssi:他是用来表示信号强度的,值越高越好。一般我们认为低于-90就是信号不好了。
注:若是想controller能返回这个event,需要通过writeinquiry mode cmd中吧inquiry mode参数设为0x01.
可以想象,他在android的代码中的实现和上面的差别就在于把那个rssi的null换成对应的event返回值即可。大家可以自己去看。
7.4 extended inquiryresult event介绍
这个event和上面两个event大同小异,差别在于它有多了一点信息,就是extended inquiry response。
该参数的介绍如下:
Extended Inquiry Response一共是由240byte组成,分为significant部分和non-significant部分。significant部分由一系列的EIRdata构成,每个EIR data由一个1byte的长度域和这个长度的data域组成。其中,n byte的长度用来表示EIR data的type,剩余的长度减去n就是真正的EIR data。Non significant部分就是用来补充significant部分剩余的byte的,他必须全部是0。其实就是我们检测到长度域为0的情况就应该不会再往下看了。
一般而言,EIR data可以包括设备名字,TX的power level,serviceclass UUID,以及设备制造商的一些数据等等。我们希望host能够控制这个数据不要超过240byte
看到这里,你不禁会问,这个nbyte的EIR datatype究竟是怎么回事,这个n又是什么,一般而言,有以下 EIR Data type比较重要。
1)Service Class UUIDs
这里会有三种UUID有可能返回,他们分别是16bit,32bit以及128bit的uuids。需要注意的是EIRData type不仅看以表示究竟是16bit,32bit还是126bit,还可以表示,这个UUID的列表是否是complete还是说仍有没有显示的UUID。他的type value见下表:
所以,还是蛮清晰的吧,若是type value的值是05,则表示是一个32bit的service UUIDS,并且是一个complete list的value。
2)Local Name
这个data是用来表示设备的名字的,有两种类型设备的名字,一种就是complete的,也就是通常意义上的设备名字,另外一种则是shorten,表示名字太长,这里没有全部显示出来。需要host再发送remote name request去获得这个名字。它的type value如下表:
也就是value type是0x08就是shortened的名字,还是很好理解的。
3)flags
这个type主要用来表示一些LE和BR/EDR的内容,具体如下:
需要注意的是在BR/EDR的通道上,我们不能回应BR/EDRNOT Support。当然上面两个LE Limited和LE General Discoverable Mode在BR/EDR中是不响应的。忽略即可。总的来说这个flags用得不是很多。
4)Manufacture Specific Data
这个就是厂商的一些特殊的data,我们只需要知道他的value是0xff即可。还有就是这个type的开始2个byte需要些对应的厂商在sig中的编号。
5)TX Power Level
他的格式如下表所示,有了这个和上文的RSSI,我们就可以用来判断path loose了。简单的一个应用就是我们通过手机的蓝牙来实现电脑的自动锁屏和解锁,比如说我离电脑较远,我就锁上屏幕,回来了之后就把屏幕解锁。
当然,有必要说明的是,这个值其实并不是那么可靠,只能说我们只能用来参考罢了。
大概比较常见的就是这几个EIRData Type了,大家知道后我们看代码就清晰多了。
注:在host的实现中,并不需要对每一个EIRData type都解析,若是不能解析就跳过去,看下一个data的内容即可。
这个event在android中的代码处理如下:
//把扫描到的设备信息加入到found device中去 btd_event_device_found(&dev->bdaddr, &info->bdaddr, class, info->rssi, info->data);
和上面的差别就在于又多传入了一个eir data的指针进去了,也就是解析这个data了。
7.5 btd_event_device_found的分析
这个地方就是对上述的7.2~7.4中的各个event的统一处理函数了。他的代码分析如下:
void btd_event_device_found(bdaddr_t *local, bdaddr_t *peer, uint32_t class, int8_t rssi, uint8_t *data){ struct btd_adapter *adapter; //local是本机的bdaddr, peer是搜索到的bdaddr, class是class of device的值 //找到对应的adapter adapter = manager_find_adapter(local); if (!adapter) { error("No matching adapter found"); return; } //把最新看到的设备加入到lastseen文件中 update_lastseen(local, peer); //写入到classes文件中 write_remote_class(local, peer, class); //若有EIR Data内容,写入到eir文件中 if (data) write_remote_eir(local, peer, data); //找到device后的一系列操作 adapter_update_found_devices(adapter, peer, class, rssi, data);}void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr, uint32_t class, int8_t rssi, uint8_t *data){ struct remote_dev_info *dev, match; struct eir_data eir_data; char *alias, *name; gboolean legacy, le; name_status_t name_status; int err; memset(&eir_data, 0, sizeof(eir_data)); //解析eir data,详细见7.5.1 err = eir_parse(&eir_data, data); if (err < 0) { error("Error parsing EIR data: %s (%d)", strerror(-err), -err); return; } //若是有name,并且同时是complete的,我们就写入到names if (eir_data.name != NULL && eir_data.name_complete) write_device_name(&adapter->bdaddr, bdaddr, eir_data.name); /* Device already seen in the discovery session ? */ memset(&match, 0, sizeof(struct remote_dev_info)); bacpy(&match.bdaddr, bdaddr); match.name_status = NAME_ANY; //看found device列表里面有没有这个设备 dev = adapter_search_found_devices(adapter, &match); if (dev) { //若是有,则把这个设备从oor device列表中remove adapter->oor_devices = g_slist_remove(adapter->oor_devices, dev); //看rssi有没有变化,若有变化,到done,根据rssi重新排序 if (dev->rssi != rssi) goto done; //把eir data free掉 eir_data_free(&eir_data); return; } /* New device in the discovery session */ //得到保存的名字,eir data在上面已经更新过了 name = read_stored_data(&adapter->bdaddr, bdaddr, "names"); //flags就是le和bredr的一些内容判断 if (eir_data.flags < 0) { le = FALSE; legacy = pairing_is_legacy(&adapter->bdaddr, bdaddr, data, name); //没有name,并且支持name resolv,则置为NAME——required,就是待会会发remote name request出去 if (!name && main_opts.name_resolv && adapter_has_discov_sessions(adapter)) name_status = NAME_REQUIRED; else //否则就不会requiry name name_status = NAME_NOT_REQUIRED; } else { le = TRUE; legacy = FALSE; name_status = NAME_NOT_REQUIRED; } //找到aliases,就是别名 alias = read_stored_data(&adapter->bdaddr, bdaddr, "aliases"); if (!eir_data.name_complete) { //若是没有name complete,则也会发name request name_status = NAME_REQUIRED; } //一个新的found的device dev = found_device_new(bdaddr, le, name, alias, class, legacy, name_status, eir_data.flags); //释放name和alias空间 free(name); free(alias); //加入到found devices列表中 adapter->found_devices = g_slist_prepend(adapter->found_devices, dev);done: //重新赋值rssi dev->rssi = rssi; //根据rssi进行排序 adapter->found_devices = g_slist_sort(adapter->found_devices, (GCompareFunc) dev_rssi_cmp); //把同样的uuid去除掉 g_slist_foreach(eir_data.services, remove_same_uuid, dev); g_slist_foreach(eir_data.services, dev_prepend_uuid, dev); //上报device found,见7.5.2 adapter_emit_device_found(adapter, dev); //释放eir data的空间 eir_data_free(&eir_data);}
7.5.1 eir data的解析
其实在上面我们了解了eirdata的组成之后,对于解析的方法也就水到渠成了。看一下下面的代码,我们就会发现其实不难。
int eir_parse(struct eir_data *eir, uint8_t *eir_data){ uint16_t len = 0; size_t total; size_t uuid16_count = 0; size_t uuid32_count = 0; size_t uuid128_count = 0; uint8_t *uuid16 = NULL; uint8_t *uuid32 = NULL; uint8_t *uuid128 = NULL; uuid_t service; char *uuid_str; unsigned int i; eir->flags = -1; /* No EIR data to parse */ if (eir_data == NULL) return 0; while (len < HCI_MAX_EIR_LENGTH - 1) { uint8_t field_len = eir_data[0]; /* Check for the end of EIR */ if (field_len == 0) break; switch (eir_data[1]) { //看eir data的type来进行处理 case EIR_UUID16_SOME: case EIR_UUID16_ALL: uuid16_count = field_len / 2; uuid16 = &eir_data[2]; break; case EIR_UUID32_SOME: case EIR_UUID32_ALL: uuid32_count = field_len / 4; uuid32 = &eir_data[2]; break; case EIR_UUID128_SOME: case EIR_UUID128_ALL: uuid128_count = field_len / 16; uuid128 = &eir_data[2]; break; case EIR_FLAGS: eir->flags = eir_data[2]; break; case EIR_NAME_SHORT: case EIR_NAME_COMPLETE: //保存名字到eir name中,还有name——complete位来表示时short还是complete if (g_utf8_validate((char *) &eir_data[2], field_len - 1, NULL)) eir->name = g_strndup((char *) &eir_data[2], field_len - 1); else eir->name = g_strdup(""); eir->name_complete = eir_data[1] == EIR_NAME_COMPLETE; break; } len += field_len + 1; eir_data += field_len + 1; } /* Bail out if got incorrect length */ if (len > HCI_MAX_EIR_LENGTH) return -EINVAL; total = uuid16_count + uuid32_count + uuid128_count; //没有uuid的解析,我们就直接返回 /* No UUIDs were parsed, so skip code below */ if (!total) return 0; /* Generate uuids in SDP format (EIR data is Little Endian) */ //eir->services的构建,和sdp的格式类似 service.type = SDP_UUID16; for (i = 0; i < uuid16_count; i++) { uint16_t val16 = uuid16[1]; val16 = (val16 << 8) + uuid16[0]; service.value.uuid16 = val16; uuid_str = bt_uuid2string(&service); eir->services = g_slist_append(eir->services, uuid_str); uuid16 += 2; }……}
7.5.2 device found的上报
该函数的主要工作就是把刚刚反馈上来的设备信息回报到上层。
void adapter_emit_device_found(struct btd_adapter *adapter, struct remote_dev_info *dev){ struct btd_device *device; char peer_addr[18], local_addr[18]; const char *icon, *paddr = peer_addr; dbus_bool_t paired = FALSE; dbus_int16_t rssi = dev->rssi; char *alias; size_t uuid_count; ba2str(&dev->bdaddr, peer_addr); ba2str(&adapter->bdaddr, local_addr); //找到对应的device device = adapter_find_device(adapter, paddr); //检查device是否已经配对 if (device) paired = device_is_paired(device); /* The uuids string array is updated only if necessary */ //device services的uuid count uuid_count = g_slist_length(dev->services); //重新赋值一下dev的uuid count变量 if (dev->services && dev->uuid_count != uuid_count) { g_strfreev(dev->uuids); dev->uuids = strlist2array(dev->services); dev->uuid_count = uuid_count; } //若是le的设备,我们暂时不关注 if (dev->le) { gboolean broadcaster; if (dev->flags & (EIR_LIM_DISC | EIR_GEN_DISC)) broadcaster = FALSE; else broadcaster = TRUE; emit_device_found(adapter->path, paddr, "Address", DBUS_TYPE_STRING, &paddr, "RSSI", DBUS_TYPE_INT16, &rssi, "Name", DBUS_TYPE_STRING, &dev->name, "Paired", DBUS_TYPE_BOOLEAN, &paired, "Broadcaster", DBUS_TYPE_BOOLEAN, &broadcaster, "UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count, NULL); return; } //根据对应的class来返回对应的字符信息,就是那个主设备 icon = class_to_icon(dev->class); //若是没有别名,就把name拷贝过去,若是连名字都没有,就是地址了 if (!dev->alias) { if (!dev->name) { alias = g_strdup(peer_addr); g_strdelimit(alias, ":", '-'); } else alias = g_strdup(dev->name); } else alias = g_strdup(dev->alias); //通过Devicefound来向上层回报一系列的信息 emit_device_found(adapter->path, paddr, "Address", DBUS_TYPE_STRING, &paddr, "Class", DBUS_TYPE_UINT32, &dev->class, "Icon", DBUS_TYPE_STRING, &icon, "RSSI", DBUS_TYPE_INT16, &rssi, "Name", DBUS_TYPE_STRING, &dev->name, "Alias", DBUS_TYPE_STRING, &alias, "LegacyPairing", DBUS_TYPE_BOOLEAN, &dev->legacy, "Paired", DBUS_TYPE_BOOLEAN, &paired, "UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count, NULL); g_free(alias);}至此, bluez 这边搜索到一个设备的信息处理就全部结束了,它向上层回报了一个 device found 的 signal ,下面我们随便想想都可以猜到,肯定就是上层显示搜索到的设备了。
若您觉得该文章对您有帮助,请在下面用鼠标轻轻按一下“顶”,哈哈~~·
更多相关文章
- Android提高第十三篇之探秘蓝牙隐藏API
- 实时监控Android设备网络封包
- android 简单按键修改
- Android(安卓)各版本新特性
- Android(安卓)TTS学习——独特你的名字
- Android中关于dip和px以及转换的总结(重点推荐文章)
- Android培训---运行你的应用程序
- Android设备唯一码的获取
- android的语言切换-应用程序 根据 不同的语言 加载不同的 res