藍牙Beacon廣播數據包格式以及解析

来源:https://www.cnblogs.com/zfb132/archive/2019/07/25/11244184.html
-Advertisement-
Play Games

內容轉載自 "我的博客" 如果你只想找到如何用代碼解析各數據請點擊目錄"使用Java解析數據" @ "TOC" 1. 獲取原始藍牙廣播包 首先需要開啟 開發者選項 :不同Android手機打開此功能的方法基本一致,首先打開設置,然後找到系統版本號(例如MIUI系統的全部參數選項的MIUI版本),快速 ...


目錄

內容轉載自我的博客
如果你只想找到如何用代碼解析各數據請點擊目錄"使用Java解析數據"

@(文章目錄)

1. 獲取原始藍牙廣播包

首先需要開啟開發者選項:不同Android手機打開此功能的方法基本一致,首先打開設置,然後找到系統版本號(例如MIUI系統的全部參數選項的MIUI版本),快速連續點擊5次以上即可自動打開開發者選項;然後選擇"打開藍牙數據包日誌"功能,接著打開藍牙功能即可開始記錄數據包,日誌文件存放位置在不同的手機上略有不同;最後把日誌複製到電腦上等待處理

2. 安裝WireShark軟體

對於ubuntu系統來說,只需要輸入以下命令即可成功安裝:
sudo apt-get install wireshark
對於windows或其他系統來說,打開官網按照提示下載安裝即可

3. 分析Beacon廣播包數據

把日誌文件導入WireShark軟體,會自動識別為藍牙廣播包。首先需要瞭解藍牙數據包的主要格式:一個廣播包是由若幹個廣播單元AD Structure構成的。每個廣播單元的組成是:第一個位元組是長度值 length,表示接下來的 length個位元組是數據部分;數據部分的第一個位元組表示數據的類型AD Type,AD type非常關鍵,決定了AD Data的數據代表的是什麼以及怎麼解析,這是官網上面不同的值代表的數據類型 https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile ;剩下的length-1個位元組是真正的數據AD data。這裡需要特別註意的是,由於發送數據是從低位到高位依次發送,所以接收到的數據要反過來按位元組拼接。例如接收到的MAC為 8b 03 00 b0 01 c2,那麼實際的MAC為 c2:01:b0:00:03:8b
通過分析可以發現,藍牙設備會連續收到兩個來自(同一個)Beacon的廣播數據包,每個原始數據包都是59bytes,前一個主要包含MAC和設備名稱等信息,後一個主要包含UUID,txPower等信息。不妨認為前一個數據包為packetA,後一個為packetB。以下是一組實際數據

3.1 第一個數據包格式

網頁端配置

可以看到,軟體會自動把每個部分的數據進行解釋,如果你的英語水平可以的話,以下內容就不需要看了。數據包內容:

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
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

下麵是每個位元組對應的含義:

第一個位元組是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 代表接下的的數據的位元組數(長度),以下數據就是最重要的廣播數據了
-------------------------------------------
02 0a 00 代表的是Tx Power Level的信息,02 表示數據位元組數,0a 表示數據類型,00 表示功率水平(單位是dBm)
08 16 f0 ff 代表的是Service Data信息,08 表示數據位元組數,16 表示數據類型,ff f0 表示16bit UUID ,
64 27 11 4c b9 表示Service Data的具體信息
11 表示後面的數據的位元組數
09 表示數據類型
4d 69 6e 69 42 65 61 63 6f 6e 5f 30 30 39 30 37 表示設備的名稱,每一個位元組對應一個ASCII

如果你想查看WireShark軟體的解析名稱,可在附錄里瀏覽

3.2 第二個數據包格式

網頁端配置

此數據包才是最重要的,59bytes的數據包內容如下:

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
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

下麵是每個位元組對應的含義:

第一行數據同上,不再分析,重點分析第二行(也就是廣播數據部分)
--------------------------------------
02 表示接下來的數據有兩個位元組
01 表示數據類型,此處的類型是Flags
06 表示Flags的具體模式
1a 表示接下來的數據有26個位元組
ff 表示數據類型,此處是廠家特定字(Manufacturer specific)
4c 00 表示公司的ID,此處的004c代表蘋果公司
02 代表beacon標識位
15 表示接下來有22個位元組的數據
fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25 表示beacon UUID
27 11 是major的值,2711轉化為10進位是10001
4c b9 是minor的值,4cb9轉化為10進位是19641
c5 是txPower的補碼,計算可知原碼是-59

如果你想查看WireShark軟體的解析名稱,可在附錄里瀏覽

3.3 Android程式開發中的藍牙廣播包

上面所講解的都是Android 系統中的藍牙廣播包的格式,是最底層的數據包格式,如果我們是在開發OS 的話才可能會接觸解析這些數據。對於只是進行普通應用程式開發的我們來說,只需要處理已經被Android 系統一次解析之後的數據包,這個數據包才是我們開發應用程式時遇到的數據記錄。  
對於Android 開發中,系統會把packetB中的第二行數據(30bytes,長度不定)和packetA(30bytes)中的第二行數據連接在一起,最後總的數據長度為62bytes,不夠的話用0填充,如下所示:
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 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 00 00

4. 使用Java解析各數據

以下是主要解析代碼,相容Android P最新版本和Android 較低版本:

//Android Lollipop 版本以上的掃描回調函數
private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        super.onScanResult(callbackType, result);
        final BluetoothDevice device = result.getDevice();
        final int rssi = result.getRssi();
        final byte[] scanRecord = result.getScanRecord().getBytes();
        // 判斷Activity是否已經退出
        if (mainActivity == null) {
            return;
        }
        mainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String name = device.getName();
                if (name == null) {
                    //Log.d(TAG, "onLeScan: 此條數據被過濾---"+mac);
                    return;
                }
                Log.d(TAG, "onScanResult: 開始處理廣播數據");
                handleScanResult(device, rssi, scanRecord);
            }
        });
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
    }

    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
        Log.e(TAG, "啟動掃描失敗");
    }
};

// Android KITKAT 版本以下的掃描回調函數
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
        // 判斷Activity是否已經退出
        if (mainActivity == null) {
            return;
        }
        mainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String name = device.getName();
                String mac = device.getAddress();
                // 官方提供的過濾UUID方法存在問題,此處自己先根據MAC過濾
                if (name == null || mac == null || !Arrays.asList(beaconMAC).contains(mac)) {
                    //Log.d(TAG, "onLeScan: 此條數據被過濾---"+mac);
                    return;
                }
                Log.d(TAG, "onLeScan: 開始處理廣播數據");
                handleScanResult(device, rssi, scanRecord);
            }
        });
    }
};

// 處理廣播數據
private void handleScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
    int startIndex = 2;
    boolean patternFound = false;
    Log.d(TAG, "handleScanResult: "+bytesToHex(scanRecord));
    // 尋找是否存在beacon以及有效數據的起始索引
    while (startIndex <= 5) {
        if (((int) scanRecord[startIndex + 2] & 0xff) == 0x02
                && ((int) scanRecord[startIndex + 3] & 0xff) == 0x15) {
            patternFound = true;
            break;
        }
        startIndex++;
    }
    // 如果找到
    if (patternFound) {
        String ibeaconName = device.getName();
        String mac = device.getAddress();
        Log.d(TAG, "onLeScan: 搜索到一個Beacon設備" + mac);
        Log.d(TAG, "onLeScan: 它的名字是" + ibeaconName);
        String data = parseBLEData(scanRecord, startIndex);
        Log.d(TAG, "onLeScan: RSSI=" + rssi);
        String result = "Beacon設備名稱:" + ibeaconName + "  MAC:" + mac + "\n";
        double distance = rssi2distance(rssi);
        String dis = String.format("%.2f", distance);
        result = result + data + "    RSSI:" + rssi + " ( " + dis + " )";
        Log.d(TAG, result);
    } else {
        String name = device.getName();
        if (name == null) {
            //Log.d(TAG, "onLeScan: 搜索到一個普通藍牙設備,它的MAC是"+device.getAddress());
        } else {
            //Log.d(TAG, "onLeScan: 搜索到一個普通藍牙設備,它的名字是"+name);
        }
    }
}

// 根據RSSI計算距離
public double rssi2distance(int rssi) {
    int iRssi = Math.abs(rssi);
    // 發射端和接收端相隔1米時的信號強度
    int A = 59;
    // 環境雜訊衰減因數
    double n = 2.0;
    double power = (iRssi - A) / (10 * n);
    return Math.pow(10, power);
}

// 位元組數據轉為Hex: 1 byte = 8bit = 兩個16進位數字
public String bytesToHex(byte[] src) {
    StringBuilder stringBuilder = new StringBuilder("");
    for (int i = 0; i < src.length; i++) {
        int v = src[i] & 0xFF;
        String hv = Integer.toHexString(v);
        if (hv.length() < 2) {
            stringBuilder.append(0);
        }
        stringBuilder.append(hv);
    }
    return stringBuilder.toString();
}

// 格式化UUID
public String parseUUID(String data) {
    String uuid = "";
    if (data.length() == 32) {
        uuid = data.substring(0, 8) + "-"
                + data.substring(8, 12) + "-"
                + data.substring(12, 16) + "-"
                + data.substring(16, 20) + "-"
                + data.substring(20);
    } else {
        showTips(getString(R.string.toast_uuid_not_found));
    }
    return uuid;
}

// 解析BLE數據
public String parseBLEData(byte[] scanRecord, int startIndex) {
    // uuid的長度是16bytes
    byte[] uuidBytes = new byte[16];
    System.arraycopy(scanRecord, startIndex + 4, uuidBytes, 0, 16);
    String hexString = bytesToHex(uuidBytes);
    // beacon的UUID值
    String uuid = parseUUID(hexString);
    // beacon的Major值
    int major = (scanRecord[startIndex + 20] & 0xff) * 0x100
            + (scanRecord[startIndex + 21] & 0xff);
    // ibeacon的Minor值
    int minor = (scanRecord[startIndex + 22] & 0xff) * 0x100
            + (scanRecord[startIndex + 23] & 0xff);

    int txPower = (scanRecord[startIndex + 24]);
    Log.d(TAG, "onLeScan: 它的UUID是" + uuid + ",txPower是" + txPower);
    Log.d(TAG, "onLeScan: major=" + major + ",minor=" + minor);
    return "UUID:" + uuid + "\nmajor:" + major + "    minor:" + minor + "\ntxPower:" + txPower;
}

以上就是主要代碼

5. 附錄

5.1 第一個數據包的內容以及解析

數據包內容:

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
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

下麵是每個位元組對應的含義(WireShark軟體的解析):

Bluetooth HCI H4
    [Direction: Rcvd (0x01)]
    HCI Packet Type: HCI Event (0x04)
Bluetooth HCI Event - LE Meta
    Event Code: LE Meta (0x3e)
    Parameter Total Length: 56
    Sub Event: LE Extended Advertising Report (0x0d)
    Num Reports: 1
    Event Type: 0x001b, Connectable, Scannable, Scan Response, Legacy, Data Status: Complete
        .... .... .... ...1 = Connectable: True
        .... .... .... ..1. = Scannable: True
        .... .... .... .0.. = Directed: False
        .... .... .... 1... = Scan Response: True
        .... .... ...1 .... = Legacy: True
        .... .... .00. .... = Data Status: Complete (0x0)
        0000 0000 0... .... = Reserved: 0x000
    Peer Address Type: Random Device Address (0x01)
    BD_ADDR: c2:01:b0:00:03:8b (c2:01:b0:00:03:8b)
    Primary PHY: LE 1M (0x01)
    Secondary PHY: No packets on the secondary advertising channel (0x00)
    Advertising SID: 0xff (not available)
    TX Power: 127dBm (not available)
    RSSI: -81dBm
    Periodic Advertising Interval: 0x0000 (no periodic advertising)
    Direct Address Type: Public Device Address (0x00)
    Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)
    Data Length: 30
    Advertising Data
        Tx Power Level
            Length: 2
            Type: Tx Power Level (0x0a)
            Power Level (dBm): 0
        Service Data - 16 bit UUID
            Length: 8
            Type: Service Data - 16 bit UUID (0x16)
            UUID 16: Unknown (0xfff0)
            Service Data: 6427114cb9
        Device Name: MiniBeacon_00907
            Length: 17
            Type: Device Name (0x09)
            Device Name: MiniBeacon_00907

5.2 第二個數據包的內容以及解析

數據包內容:

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
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

下麵是每個位元組對應的含義(WireShark軟體的解析):

Bluetooth HCI H4
    [Direction: Rcvd (0x01)]
    HCI Packet Type: HCI Event (0x04)
Bluetooth HCI Event - LE Meta
    Event Code: LE Meta (0x3e)
    Parameter Total Length: 56
    Sub Event: LE Extended Advertising Report (0x0d)
    Num Reports: 1
    Event Type: 0x0013, Connectable, Scannable, Legacy, Data Status: Complete
        .... .... .... ...1 = Connectable: True
        .... .... .... ..1. = Scannable: True
        .... .... .... .0.. = Directed: False
        .... .... .... 0... = Scan Response: False
        .... .... ...1 .... = Legacy: True
        .... .... .00. .... = Data Status: Complete (0x0)
        0000 0000 0... .... = Reserved: 0x000
    Peer Address Type: Random Device Address (0x01)
    BD_ADDR: c2:01:b0:00:03:8b (c2:01:b0:00:03:8b)
    Primary PHY: LE 1M (0x01)
    Secondary PHY: No packets on the secondary advertising channel (0x00)
    Advertising SID: 0xff (not available)
    TX Power: 127dBm (not available)
    RSSI: -81dBm
    Periodic Advertising Interval: 0x0000 (no periodic advertising)
    Direct Address Type: Public Device Address (0x00)
    Direct BD_ADDR: 00:00:00_00:00:00 (00:00:00:00:00:00)
    Data Length: 30
    Advertising Data
        Flags
            Length: 2
            Type: Flags (0x01)
            000. .... = Reserved: 0x0
            ...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0)
            .... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0)
            .... .1.. = BR/EDR Not Supported: true (0x1)
            .... ..1. = LE General Discoverable Mode: true (0x1)
            .... ...0 = LE Limited Discoverable Mode: false (0x0)
        Manufacturer Specific
            Length: 26
            Type: Manufacturer Specific (0xff)
            Company ID: Apple, Inc. (0x004c)
            Data: 0215fda50693a4e24fb1afcfc6eb0764782527114cb9c5
                [Expert Info (Note/Undecoded): Undecoded]
                    [Undecoded]
                    [Severity level: Note]
                    [Group: Undecoded]

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 可以將以下代碼保存為backup.bat,添加計劃任務即可。 也可直接在cmd命令中複製單條語句執行,註意修改為自己的電腦路徑。 說明:--skip-lock-tables 如出現Can’t open file when using LOCK TABLES錯誤提示,可能是許可權不足導致,這裡我們在上述 ...
  • 1、創建臨時表的方法 方法一、select * into #臨時表名 from 你的表; 方法二、 create table #臨時表名(欄位1 約束條件,欄位2 約束條件,.....)create table ##臨時表名(欄位1 約束條件,欄位2 約束條件,.....) 註:以上的#代表局部臨時 ...
  • 瞭解了什麼是kafka( https://www.cnblogs.com/tree1123/p/11226880.html)以後 學習Kafka核心之消費者,kafka的消費者經過幾次版本變化,特別容易混亂,所以一定要搞清楚是哪個版本再研究。 一、舊版本consumer 只有舊版本(0.9以前)才有 ...
  • 總耗時: [SQL] CALL insert_batch();受影響的行: 1時間: 873.795s ...
  • 關於mapreduce的一些註意細節 如果把mapreduce程式打包放到了liux下去運行, 命令java –cp xxx.jar 主類名 如果報錯了,說明是缺少相關的依賴jar包 用命令hadoop jar xxx.jar 類名因為在集群機器上用 hadoop jar xx.jar mr.wc. ...
  • MySql 嚴格模式 [TOC] MySQL的sql_mode合理設置 sql model 常用來解決下麵幾類問題 sql_mode常用值 ONLY_FULL_GROUP_BY NO_AUTO_VALUE_ON_ZERO STRICT_TRANS_TABLES NO_ZERO_IN_DATE NO_ ...
  • 文本組件(text)負責顯示文本和定義顯示樣式,下表為text常見屬性 ...
  • 版權聲明:本文為xing_star原創文章,轉載請註明出處! 本文同步自http://javaexception.com/archives/174 ViewPager作為RecyclerView的itemView出現的刷新不顯示的問題 Google搜索關鍵詞 recyclerview viewpag ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...