作者 gordon1986
這篇文章我們將繼續來看,藍牙在收到掃描命令之後是如何向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]位來表示。
主要服務類
主要和次要設備類用於定義Bluetooth SIG成員希望與其應用程序相關聯的設備通用系列。不應僅根據主要或次要設備類的分配而作出有關任何應用的特定功能或特性的假設。
位数 | 主要服務類 |
---|---|
13 | 有限可發現模式[Ref #1] |
14 | (保留) |
15 | (保留) |
16 | 定位(位置標示) |
17 | 網路(LAN、Ad hoc ...) |
18 | 渲染(打印、揚聲器 ...) |
19 | 捕捉(掃描儀、麥克風 ...) |
20 | 對象傳輸(v-Inbox、v-Folder ...) |
21 | 音频(揚聲器、麥克風、耳機服務 ...) |
22 | 電話(無線電話、調制調解器、耳機服務 ...) |
23 | 信息(WEB 服務器、WAP 服務器) |
主要設備類表
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
|
所有其他保留值
|
ref#2 次要設備是根據主要設備再進行判斷的,我就不一一列出來了,具體可以參見藍牙官網: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);
- }
沒有留言:
張貼留言