在前面兩篇文章中,曉東和大傢一起分析瞭android是如何向藍牙發送掃描命令的,這篇文章我們將繼續來看,藍牙在收到掃描命令之後是如何向android上層反饋搜索到的設備以及上層對這些搜索到的設備是如何進行進一步處理的。
7、inquiry
result event的分析
Inquiry result的意思大概就是在收到inquiryresponse的時候會從controller回應這個event上來,需要註意的是一個event可能會有多個response,並不是一個response對應一個event。
7.1 inquiry resultevent在spec中的定義
它在spec中的格式如下所示:
vcD4KCjxwPsbk1tC497j2ss7K/bXE0uLLvMjnz8KjujwvcD4KPHA+PGltZyBzcmM9″https://www.aiwalls.com/uploadfile/Collfiles/20131211/20131211141435254.jpg” alt=”\”>
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 dev_class[0] | (info->dev_class[1] <dev_class[2] <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 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 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 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 <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,下面我們隨便想想都可以猜到,肯定就是上層顯示搜索到的設備瞭。
若您覺得該文章對您有幫助,請在下面用鼠標輕輕按一下“頂”,哈哈~~·