android專題-藍芽掃描、連線、讀寫

語言: CN / TW / HK

android專題-藍芽掃描、連線、讀寫

概念

外圍裝置

可以被其他藍芽裝置連線的外部藍芽裝置,不斷廣播自身的藍芽名及其資料,如小米手環、共享單車、藍芽體重秤

中央裝置

可以搜尋並連線周邊的外圍裝置,並與之進行資料讀寫通訊,如手機

日常生活中常見的場景是手機app通過藍芽開啟共享單車,手機app通過藍芽獲取藍芽體重秤的體重結果,這時候共享單車、藍芽體重秤就稱為外圍裝置,而手機就稱為中央裝置

經典藍芽BT

泛指支援藍芽協議在4.0以下的模組,一般用於資料量比較大的傳輸,如:語音、音樂等較高資料量的傳輸。經典藍芽模組又可細分為:傳統藍芽和高速藍芽模組。傳統藍芽模組在2004年推出,主要代表是支援藍芽2.1協議的模組,在智慧手機爆發的時期得到了廣泛的使用。高速藍芽模組在2009年推出,速率提高到約24Mbps,傳輸速率是經典藍芽的八倍,可以輕鬆的應用於錄影機到電視、PC到PMP、UMPC到印表機之間的資料傳輸。

低功耗藍芽BLE

是指支援藍芽協議4.0或者以上的模組,也被稱為BLE模組,最大的特點就是成本和功耗的降低,可以應用於實時性要求較高的產品當中,比如:智慧家居類(藍芽鎖、藍芽燈)、感測裝置的資料傳送(血壓計、溫度感測器)、消費類電子(電子煙、遙控玩具)等。

目前市面上大部分的藍芽都是4.0以上的低功耗藍芽,經典藍芽已經很少見到了。

通訊過程

一個外圍裝置可以釋出多個服務service,每個服務可以包含多個特徵值characteristic,每個特徵值都有他的屬性,例如長度(size),許可權(permission),值(value),描述(descriptor),讀寫通訊都是通過Characteristic進行的。
每個service、characteristic都含有一個對應的UUID,通過和外圍裝置藍芽約定UUID來進行讀寫通訊。

整個通訊流程為:

建立藍芽例項
搜尋掃描外圍裝置
連線外圍裝置
獲取外圍裝置的服務service
獲取服務的特徵characteristic
從外圍裝置讀取資料,即讀資料
給外圍裝置傳送資料寫資料,即寫資料
斷開連線

常用業務API

1.判斷當前藍芽是否已經開啟,如果沒有開啟提示使用者開啟
2.實時掃描周邊藍芽,獲取藍芽名給使用者選擇
3.解析藍芽廣播資料處理業務
4.監聽外圍裝置傳送給app的資料,處理對應業務
5.app傳送資料給外圍裝置以處理業務



Android常用的第三方藍芽框架:okbleAndroid-BluetoothKit

以okble為例:

package com.wrs.project.module.app.common.bluetooth;
import android.content.Context;
import android.util.Log;

import com.a1anwang.okble.client.core.OKBLEDevice;
import com.a1anwang.okble.client.core.OKBLEDeviceImp;
import com.a1anwang.okble.client.core.OKBLEDeviceListener;
import com.a1anwang.okble.client.core.OKBLEOperation;
import com.a1anwang.okble.client.scan.BLEScanResult;
import com.a1anwang.okble.client.scan.DeviceScanCallBack;
import com.a1anwang.okble.client.scan.OKBLEScanManager;
import com.a1anwang.okble.common.OKBLECharacteristicModel;
import com.a1anwang.okble.common.OKBLEServiceModel;
import com.wrs.project.module.app.common.AppMgr;

import java.util.List;

public class Bluetooth implements DeviceScanCallBack, OKBLEDeviceListener{
   
   
    private OKBLEScanManager scanManager;
    private OKBLEDevice okbleDevice;// 當前連線的藍芽裝置
    private OKBLECharacteristicModel writeCharacteristicModel; // 藍芽可寫的Characteristic
    private Context context = AppMgr.context;

    private String tag = "Bluetooth";

    public Bluetooth() {
   
   
        scanManager = new OKBLEScanManager(context);
        scanManager.setScanCallBack(this);
    }

    /**
     * 掃描到藍芽裝置
     * @param device
     * @param rssi
     */
    @Override
    public void onBLEDeviceScan(BLEScanResult device, int rssi) {
   
   
        Log.e(tag, "掃描到藍芽裝置:" + device.toString());
        String localName = device.getCompleteLocalName();
        if (null != localName && localName.startsWith("ABC-")) {
   
   
            stopScanBluetooth();
            connectBluetoothDevice(device);
        }
    }

    /**
     * 掃描失敗
     * @param code
     */
    @Override
    public void onFailed(int code) {
   
   

    }

    @Override
    public void onStartSuccess() {
   
   

    }

    /**
     * 藍芽是否已經開啟
     * @return
     */
    public boolean bluetoothIsEnable() {
   
   
        if (null != scanManager) {
   
   
            scanManager.bluetoothIsEnable();
        }
        return false;
    }

    /**
     * 關閉手機藍芽
     */
    public void disableBluetooth() {
   
   
        if (null != scanManager) {
   
   
            scanManager.disableBluetooth();
        }
    }

    /**
     * 開啟手機藍芽
     */
    public void enableBluetooth() {
   
   
        if (null != scanManager) {
   
   
            scanManager.enableBluetooth();
        }
    }

    /**
     * 開始掃描藍芽
     */
    public void startScanBluetooth() {
   
   
        if (null != scanManager) {
   
   
            if (!scanManager.isScanning()) {
   
   
                scanManager.startScan();
            }
        }
    }

    /**
     * 停止掃描藍芽
     */
    public void stopScanBluetooth() {
   
   
        if (null != scanManager) {
   
   
            if (scanManager.isScanning()) {
   
   
                scanManager.stopScan();
            }
        }
    }

    /**
     * 斷開藍芽連線
     */
    public void disConnect() {
   
   
        if (null != okbleDevice) {
   
    // 如果當前已經連線其他裝置,先斷開連線
            okbleDevice.removeDeviceListener(this);
            okbleDevice.disConnect(false);
            okbleDevice = null;
        }
    }

    /**
     * 傳送資料給藍芽裝置
     * @param data
     */
    public void writeData(byte[] data) {
   
   
        if (null != okbleDevice && null != writeCharacteristicModel && null != data && data.length > 0) {
   
   
            okbleDevice.addWriteOperation(writeCharacteristicModel.getUuid(), data, new OKBLEOperation.WriteOperationListener() {
   
   
                @Override
                public void onWriteValue(byte[] value) {
   
   
                    Log.e(tag, "藍芽寫資料成功");
                }

                @Override
                public void onFail(int code, String errMsg) {
   
   
                    Log.e(tag, "藍芽寫資料失敗:" + code + " " + errMsg);
                }

                @Override
                public void onExecuteSuccess(OKBLEOperation.OperationType type) {
   
   

                }
            });
        } else {
   
   
            Log.e(tag, "藍芽寫資料失敗: 藍芽沒有連線或沒發現可寫Characteristic");
        }
    }

    public void connectBluetoothDevice(BLEScanResult device) {
   
   
        // 先斷開當前連線
        disableBluetooth();
        okbleDevice = new OKBLEDeviceImp(context, device);
        okbleDevice.addDeviceListener(this);
        okbleDevice.connect(true);//true表示連線斷開後OKBLE的會自動重連
    }

    /**
     * 藍芽裝置連線成功
     * @param deviceTAG
     */
    @Override
    public void onConnected(String deviceTAG) {
   
   
        Log.e(tag, "裝置連線成功 " + deviceTAG);

        // 連上藍芽後,獲取藍芽的WriteCharacteristic用後面給藍芽裝置傳送資料,獲取藍芽的ReadCharacteristic用來監聽藍芽裝置傳送過來的資料
        List<OKBLEServiceModel> serviceModels = okbleDevice.getServiceModels();
        if (null != serviceModels && serviceModels.size() > 0) {
   
   
            for (int i = 0; i < serviceModels.size(); i++) {
   
   
                OKBLEServiceModel serviceModel = serviceModels.get(i);
                String serviceUUID = serviceModel.getUuid();
                if (serviceUUID.startsWith("aaaaaaa-")) {
   
    // 匹配找到讀寫的服務
                    List<OKBLECharacteristicModel> characteristicModels = serviceModel.getCharacteristicModels();
                    if (null != characteristicModels && characteristicModels.size() > 0) {
   
   
                        for (int j = 0; j < characteristicModels.size(); j++) {
   
   
                            OKBLECharacteristicModel characteristicModel = characteristicModels.get(j);
                            String characteristicUUID = characteristicModel.getUuid();
                            if (characteristicUUID.startsWith("bbbbbbbbb") && characteristicModel.isCanWrite() && characteristicModel.isCanWriteNoResponse()) {
   
    // 匹配找到寫的Characteristic
                                findWriteCharacteristic(characteristicModel);
                            } else if (characteristicUUID.startsWith("8653000b-") && characteristicModel.isCanNotify()) {
   
    // 匹配找到讀的Characteristic
                                findReadCharacteristic(characteristicModel);
                            }
                        }
                    }
                    break;
                }
            }
        }
    }

    @Override
    public void onDisconnected(String deviceTAG) {
   
   

    }

    @Override
    public void onReadBattery(String deviceTAG, int battery) {
   
   

    }

    /**
     * 接收到藍芽傳送的資料
     * @param deviceTAG
     * @param uuid
     * @param value
     */
    @Override
    public void onReceivedValue(String deviceTAG, String uuid, byte[] value) {
   
   

    }

    @Override
    public void onWriteValue(String deviceTAG, String uuid, byte[] value, boolean success) {
   
   

    }

    @Override
    public void onReadValue(String deviceTAG, String uuid, byte[] value, boolean success) {
   
   

    }

    @Override
    public void onNotifyOrIndicateComplete(String deviceTAG, String uuid, boolean enable, boolean success) {
   
   

    }

    private void findWriteCharacteristic(OKBLECharacteristicModel characteristic) {
   
   
        if (null != okbleDevice && null != characteristic) {
   
   
            writeCharacteristicModel = characteristic;
        }
    }

    private void findReadCharacteristic(OKBLECharacteristicModel characteristic) {
   
   
        if (null != okbleDevice && null != characteristic) {
   
   
            String uuid = characteristic.getUuid();
            boolean enableNotifyEnable = okbleDevice.isNotifyEnabled(uuid);
            if (enableNotifyEnable) {
   
   
                Log.e(tag, "開啟讀屬性成功");
            } else {
   
   
                okbleDevice.addNotifyOrIndicateOperation(uuid, true, new OKBLEOperation.NotifyOrIndicateOperationListener() {
   
   

                    @Override
                    public void onFail(int code, String errMsg) {
   
   
                        Log.e(tag, "開啟讀屬性失敗");

                    }

                    @Override
                    public void onExecuteSuccess(OKBLEOperation.OperationType type) {
   
   
                        Log.e(tag, "開啟讀屬性成功");

                    }

                    @Override
                    public void onNotifyOrIndicateComplete() {
   
   
                        Log.e(tag, "開啟讀屬性成功");
                    }
                });

            }

        }
    }
}

藍芽廣播資料包解析

廣播包有兩種: 廣播包 (Advertising Data)和 響應包 (Scan Response),其中廣播包是每個裝置必須廣播的,而響應包是可選的。
每個包都是 31 位元組,分為有效資料和無效資料兩部分。

有效資料部分 :包含若干個廣播資料單元,稱為 AD Structure 。

AD Structure 的組成是:
第一個位元組是長度值 Len ,表示接下來的 Len 個位元組是資料部分。
資料部分的第一個位元組表示資料的型別 AD Type ,剩下的 Len - 1 個位元組是真正的資料 AD data 。其中 AD type 非常關鍵,決定了 AD Data 的資料代表的是什麼和怎麼解析。
無效資料部分 :因為廣播包的長度必須是 31 個 byte,如果有效資料部 分不到 31 自己,剩下的就用 0 補全。這部分的資料是無效的,解釋的時候,忽略即可。在 Android 中,系統會把這兩個資料拼接在一起,返回一個 62 位元組的陣列。
在這裡插入圖片描述
例如:
在這裡插入圖片描述





第一個 位元組代表廣播資料單元的長度 ,02 轉為10進位制就是 2代表其資料長度為2 , 而資料單元的第一個位元組代表型別 。
01 代表 代表物理連線功能為普通發現模式 06代表其資料類容
緊接著下一個資料單元:
0B代表資料長度為11 ,資料型別為 02 即Serviceuuid代表是非完整的16bit uuid, 所以緊接著的後10位就是其uuid。
接下來就是下一個資料單元
首位是13轉為二進位制就是19,其長度就是19,型別就是09 ,代表裝置名稱,30-》字元0,65代表字元e,61代表字元a,73代表字元s,79代表字元y,4E代表N,65代表e,57代表W,44代表D,43代表C,53代表S ,00 代表字元null,01代表字元soh(SOH是序始字元(Start Of Header),它表示標題的開始),56代表V ,31代表字元1,2E代表字元.,30代表字元0,44代表D所有其裝置名稱就是0easyNewDCS V1.0D。
接下來的一個數據單元長度是5,廣播型別12 連線間隔範圍,有四個位元組,接下來資料長度是02,型別是0A代表訊號強度 剩餘都是00000都是補位的無效資料。





廣播資料型別:
(1)Flags: TYPE = 0x01。這個資料用來標識裝置 LE 物理連線的功能。DATA 是 0 到多個位元組的 Flag 值,每個 bit 上用 0 或者 1 來表示是否為 True。如果有任何一個 bit 不為 0,並且廣播包是可連線的,就必須包含此資料。各 bit 的定義如下: bit 0: LE 有限發現模式 bit 1: LE 普通發現模式 bit 2: 不支援 BR/EDR bit 3: 對 Same Device Capable(Controller) 同時支援 BLE 和 BR/EDR bit 4: 對 Same Device Capable(Host) 同時支援 BLE 和 BR/EDR bit 5…7: 預留
(2)Service UUID: 廣播資料中一般都會把裝置支援的 GATT Service 廣播出來,用來告訴外面本裝置所支援的 Service。有三種類型的 UUID:16 bit, 32bit, 128 bit。廣播中,每種型別型別有有兩個類別:完整和非完整的。這樣就共有 6 種 AD Type。
非完整的 16 bit UUID 列表: TYPE = 0x02;
完整的 16 bit UUID 列表: TYPE = 0x03;
非完整的 32 bit UUID 列表: TYPE = 0x04;
完整的 32 bit UUID 列表: TYPE = 0x05;
非完整的 128 bit UUID 列表: TYPE = 0x06;
完整的 128 bit UUID 列表: TYPE = 0x07;
(3) Local Name: 裝置名字,DATA 是名字的字串。 Local Name 可以是裝置的全名,也可以是裝置名字的縮寫,其中縮寫必須是全名的前面的若干字元。 裝置全名: TYPE = 0x08 裝置簡稱: TYPE = 0x09
(4)TX Power Level: TYPE = 0x0A,表示裝置傳送廣播包的訊號強度。DATA 部分是一個位元組,表示 -127 到 + 127 dBm。
(5) 帶外安全管理(Security Manager Out of Band):TYPE = 0x11。DATA 也是 Flag,每個 bit 表示一個功能: bit 0: OOB Flag,0 表示沒有 OOB 資料,1 表示有 bit 1: 支援 LE bit 2: 對 Same Device Capable(Host) 同時支援 BLE 和 BR/EDR bit 3: 地址型別,0 表示公開地址,1 表示隨機地址 。
(6)外設(Slave)連線間隔範圍:TYPE = 0x12。資料中定義了 Slave 最大和最小連線間隔,資料包含 4 個位元組:
前 2 位元組:定義最小連線間隔,取值範圍:0x0006 ~ 0x0C80,而 0xFFFF 表示未定義; 後 2 位元組:定義最大連線間隔,同上,不過需要保證最大連線間隔大於或者等於最小連線間隔。
(7) 服務搜尋:外圍裝置可以要請中心裝置提供相應的 Service。其資料定義和前面的 Service UUID 類似:
16 bit UUID 列表: TYPE = 0x14
32 bit UUID 列表: TYPE = 0x??
128 bit UUID 列表: TYPE = 0x15
(8) Service Data: Service 對應的資料。
16 bit UUID Service: TYPE = 0x16, 前 2 位元組是 UUID,後面是 Service 的資料;
32 bit UUID Service: TYPE = 0x??, 前 4 位元組是 UUID,後面是 Service 的資料;
128 bit UUID Service: TYPE = 0x??, 前 16 位元組是 UUID,後面是 Service 的資料;
(9) 公開目標地址:TYPE = 0x17,表示希望這個廣播包被指定的目標裝置處理,此裝置綁定了公開地址,DATA 是目標地址列表,每個地址 6 位元組。
(10) 隨機目標地址:TYPE = 0x18,定義和前一個類似,表示希望這個廣播包被指定的目標裝置處理,此裝置綁定了隨機地址,DATA 是目標地址列表,每個地址 6 位元組。
(11) Appearance:TYPE = 0x19,DATA 是表示了裝置的外觀。
(12) 廠商自定義資料: TYPE = 0xFF,廠商自定義的資料中,前兩個位元組表示廠商 ID,剩下的是廠商自己按照需求新增,裡面的資料內容自己定義。
























專案原始碼:https://codechina.csdn.net/android1/projectbasic
上篇:android專題-資料庫Room 目錄 下篇: Android專題-常用第三方框架