Android低功耗藍牙總結

  • 2020 年 8 月 25 日
  • 筆記

聲明
這裡只列出重點原理內容,更加細節的內容請閱讀前面文章

首先要搞清楚一點,我們在 Android 中通過 SDK 獲得的藍牙廣播包是經過底層的 SDK 給我們處理過的,是一個長度為 62 的位元組數組。這個長度為 62 的位元組數組是怎麼來的呢?

想要搞清楚這個問題,首先我們要明白 iBeacon 向外發送的最原始的廣播包是什麼樣的?

首先我們要搞清楚一點,藍牙在向外發送數據的時候是分成兩個部分的一個就是普通的廣播包還有一個叫做應答包。這是藍牙協議的規定內容,針對於所有的藍牙設備(iBeacon 只是藍牙設備的一種)

  • 普通的廣播包格式是定義好的,長度為 30 byte

  • 應答包中的內容是可以由 藍牙的各個製造廠商自己向裡面放數據的。最大長度是 32 byte

需要注意的是,發送數據是從低位到高位一次發送,所以接收到的數據要返回來按位元組拼接,例如接收到的MAC為 8b 03 00 b0 01 c2,那麼實際的MAC為 c2:01:b0:00:03:8b

藍牙廣播包

首先我們來看一下第一個藍牙廣播包(來自 iBeacon 設備),一共 59 個位元組

04 3e 38 0d 01 13 00 01 8b 03 00 b0 01 c2 01 00 ff 7f af 00 00 00 00 00 00 00 00 00 1e	   29個位元組	
02 01 06 1a ff 4c 00 02 15 fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25 27 11 4c b9 c5  30個位元組

第一行的內容可以認為是藍牙廣播包中的附帶資訊,通過 Android SDK 是沒法看到的,第二行是對應我們 Android SDK 中收到的廣播包中的前一部分。

第一個位元組是HCI Packet Type,04表示這是HCI Event;剩下的58bytes則是HCI Event的具體內容
第二個位元組是EventCode,3e是此事件的程式碼;第三個位元組是Parameter Length,0x38(十進位56)表示後面數據長度56bytes
第四個位元組是SubEvent,0d表示這是LE Extended Advertising Report;第五個位元組是Num Reports,數值為01
1b 00這兩個位元組代表Event Type,由於發送數據都是按位元組發送以及從低位向高位發送,因此真實值是 001b
01 表示這是隨機設備地址
8b 03 00 b0 01 c2 是此設備的MAC,根據從低向高的發送規則,所以真實MAC是 c2:01:b0:00:03:8b
01 代表首要廣播信道的頻寬
00 代表次要廣播信道的頻寬,此處表示不使用次要信道
ff 表示廣播SID
7f 代表Tx Power的大小,此處是127dbm
af 代表RSSI的大小,此處是-81dbm
00 00 代表周期廣播間隔
00 代表直接地址類型,次數是公共設備地址
00 00 00 00 00 00 代表直接BD_ADDR
1e 代表接下的的數據的位元組數(長度),以下數據就是最重要的廣播數據了

上面的內容就是對應第一行的解釋了,其實 Android SDK 已經幫我們把這些數據中的部分內容解析出來,我們可以直接通過對應的 SDK 的方法來直接獲取。

下面我們再來看 真正意義上的廣播包

格式是這樣的:

一個廣播包是由若干個廣播單元 AD Structure 構成的。每個 AD Structure 的組成是:第一個位元組表示長度值 length,表示接下來的 length 個位元組是數據部分,數據部分的第一個位元組表示數據的類型 AD Type,AD Type 決定了下面的數據代表了什麼,關於每個數值代表的數據類型見官方文檔,剩下的 length – 1 個位元組表示真正的數據

02 01 06  

02 表示接下來的數據有兩個位元組  01 表示數據類型,此處類型是 Flags    06 就是具體的數值了 0x06 = 0000 0110  每一位都有不同的含義,見官方文檔			

1a ff 4c 00 02 15 fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25 27 11 4c b9 c5

1a 表示接下來的數據有 26 位元組

FF 表示數據類型,此處類型是 廠商自定義數據類型(這裡的廠商指的是蘋果公司,因為 iBeacon 是蘋果公司提出的)

4C 00 表示公司的 ID,此處的 004C 代表蘋果公司
02 15  Beacon 的標識位,必須是這樣的

fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25
表示 Beacon UUID 

27 11  是 major 的值

4C b9  是 minor 的值

C5 表示 Measured Power  表示的是此設備在 1 米處的 RSSI 值,用於距離測算

這段內容其實主要是蘋果公司在藍牙協議的基礎上定義的。

如果符合 1AFF4C000215 則說明此設備是 iBeacon 設備

藍牙應答包

04 3e 38 0d 01 1b 00 01 8b 03 00 b0 01 c2 01 00 ff 7f af 00 00 00 00 00 00 00 00 00 1e      29個位元組
02 0a 00 08 16 f0 ff 64 27 11 4c b9 11 09 4d 69 6e 69 42 65 61 63 6f 6e 5f 30 30 39 30 37   30個位元組
其中第一行與上面一樣,這裡不再介紹

02 0a 00 
02 表示接下來的數據長度 2 個位元組
0a 表示數據類型 這裡表示 Tx Power Level   取值範圍是 -127 到 127 dBm
00 表示 0 dBm

08 16 f0 ff 64 27 11 4c b9
08 表示數據長度 
16 表示 Service Data 由 Service UUID 和 service 數據組成 前兩個位元組是 UUID 後面是數據
f0ff 是 Service UUID 
64 27 11 4c b9  是數據

11 09 4d 69 6e 69 42 65 61 63 6f 6e
11 表示數據長度
09 表示設備完整的名字
4d 69 6e 69 42 65 61 63 6f 6e  就是設備名字的 ASSIC 碼了 對應 MiniBeacon
M   i n  i  B  e  a  c  o  n 

5f 30 30 39 30 37
這幾個數據就是 Beacon 開發者隨便亂加入的數據了,不符合協議內容

Android 中接受到的廣播包

上面我們分別分析了藍牙原始數據包中的廣播包和應答包,其實對於 iBeacon 來說廣播包中的大多數內容其確定的,只有 UUID Major Minor 會有變化。而且每個位置所代表的作用都已經被 蘋果公司 定義好了。如果想要 iBeacon 發出的數據包有更多的內容,那麼我們就可以在應答包中做文章了,應答包是有 32 個位元組的。我們只需要按照協議的內容嚮應答包中添加數據就可以了。

對於 Android 客戶端,通過 Scanresult.getScanRecord().getBytes() 獲得的廣播包是 62 個位元組,它把上面原始數據包中的內容提取出來了,只保留了第二行內容。就是 藍牙廣播包第二行(30 byte) + 藍牙應答包第二行(最多 32 byte,數目不確定),如果位數不夠的話就用 0 補充。

所以我們現在就可以很好根據獲得的 byte[] 數組來解析廣播包了。

// 現在就獲得廣播包了
byte[] result = ScanResult.getScanRecord().getBytes();
// UUID  包含 result[9] 和 result[24]
result[9]---result[24];
// Major
result[25]  result[26]
// Minor
result[27]   result[28]    
// Measured Power
result[29] 

// 一般我們都是直接會先把 廣播包轉成 16 進位的格式然後來截取
String uuid = broadcast.substring(18, 50);   

// 至於後面應答包的內容就要根據具體的廣播包格式來進行解析了,比如你們公司的硬體開發人員把電池電量放入了裡面,那麼你們就約定好放在什麼位置,到時候你直接取就可以了。    
關於 ScanResult 中的方法

這幾個方法所獲得內容都不是直接從 Android 中收到的廣播(ScanResult.getScanRecord().getBytes())中解析出來的,而是從原始數據包中解析的。

getTxPower 獲取傳輸功率,如果這個 iBeacon 不支援的話,那麼結果就是 127

後面這幾個方法作用不大,關鍵看設備是否支援

關鍵方法

ScanRecord 中的這幾個方法就很重要的,這幾個方法都和我們收到的廣播包有關係。

比如:如果應答包中對 Tx Power Level 進行了設置我們就可以通過 getTxPowerLevel() 來直接獲取。比如上面例子中的廣播包,通過調用方法 getTxPowerLevel() 就可以得到 0

其他方法類似,只要你的應答包中數據的格式正確,就可以解析出來。

舉例說明:

比如 Android 端收到的廣播包是:

0201061AFF4C0002150123456789ABCDEF0123456789ABCDEF00000007C5   廣播包

020A00    0303F1FF  0E16F1FF6400000007AC233F66C401   070965526F7574650000  應答包

getTxPowerLevel() 返回 0 因為在應答包中有正確的格式數據 020A00

getServiceData() 也會返回值,因為在應答包中有對應的數據 0E16F1FF6400000007AC233F66C401

0E 表示數據長度
16 表示類型  此處表示 Service Data - 16-bit UUID (不僅僅是 UUID 還帶有數據) 前兩個位元組表示 UUID 後面是數據
F1FF 表示 UUID

6400000007AC233F66C401  表示數據  

Map<ParcelUuid, byte[]>  getServiceData() 返回的值就是用 UUID 和 數據作為鍵值對的形式
此處返回的 Map 集合中的內容是  注意:變化的 UUID 其餘位數不會變化,如果廣播包中 UUID 不是 F1FF,那麼只需要對應替換就可以了
ParcelUuid = ParcelUuid.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
byte[] 就是數據部分對應的位元組數值

List<ParcelUuid> getServiceUuids() 方法對應的就是應答包中的數據 0303F1FF 由於只出現一次,所以 list 的 size就只有一個就是 F1FF 對應的 ParcelUuid 就是 ParcelUuid.fromString("0000fff1-0000-1000-8000-00805f9b34fb");

同樣的下面幾個方法也是對 Android 端收到的 62 byte 的廣播包中數據的解析所得

String getDeviceName() 獲得是名字 需要廣播包中有對應的數據 070965526F7574650000

SpareArray<byte[]> getManufacturerSpecificaData() 獲取的製造商的數據,對應 4C000215

byte[] getManufacturerSpecificData(int manufacture) 根據製造商程式碼(4c 對應的十進位)獲得byte[] (0215)

數據類型對應表

還是有一些欄位翻譯過來不夠精細,詳細見官方文檔://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/