Android-低功耗藍牙(BLE)-客戶端(主機/中心設備)和服務端(從機/外圍設備)


一.Android 低功耗藍牙(BLE)的API簡介

從Android 4.3(API 18)才支持低功耗藍牙(Bluetooth Low Energy, BLE)的核心功能,
BLE藍牙協議是GATT協議, BLE相關類不多, 全都位於android.bluetooth包和android.bluetooth.le包的幾個類:
android.bluetooth.
  .BluetoothGattService  包含多個Characteristic(屬性特征值), 含有唯一的UUID作為標識
  .BluetoothGattCharacteristic  包含單個值和多個Descriptor, 含有唯一的UUID作為標識
  .BluetoothGattDescriptor  對Characteristic進行描述, 含有唯一的UUID作為標識
  
  .BluetoothGatt   客戶端相關
  .BluetoothGattCallback  客戶端連接回調     
  .BluetoothGattServer  服務端相關
  .BluetoothGattServerCallback 服務端連接回調

android.bluetooth.le.
  .AdvertiseCallback  服務端的廣播回調
  .AdvertiseData  服務端的廣播數據
  .AdvertiseSettings 服務端的廣播設置
  .BluetoothLeAdvertiser 服務端的廣播
  
  .BluetoothLeScanner  客戶端掃描相關(Android5.0新增)
  .ScanCallback  客戶端掃描回調
  .ScanFilter 客戶端掃描過濾
  .ScanRecord 客戶端掃描結果的廣播數據
  .ScanResult 客戶端掃描結果
  .ScanSettings 客戶端掃描設置
  
BLE設備分為兩種設備: 客戶端(也叫主機/中心設備/Central), 服務端(也叫從機/外圍設備/peripheral)
客戶端的核心類是 BluetoothGatt
服務端的核心類是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE數據的核心類是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor

二.低功耗藍牙(BLE)-手機同時作為BLE客戶端和BLE服務端,讀寫Characteristic數據

完整源碼: https://github.com/lifegh/Bluetooth

bt_client.png

 

bt_server.png

 

1.藍牙權限

BLE權限增加了BEL支持檢查,其它與上篇的經典藍牙相同,不再寫了
(1).在manifest中添加權限



(2).在Activity中設置藍牙
// 檢查是否支持BLE藍牙
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Util.toast(this, "本機不支持低功耗藍牙!");
    finish();
    return;
}

2.BLE客戶端(也叫主機/中心設備/Central)

(1).掃描BLE設備(不包含經典藍牙)

注意: BLE設備地址是動態變化(每隔一段時間都會變化),而經典藍牙設備是出廠就固定不變了!
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 下面使用Android5.0新增的掃描API,掃描返回的結果更友好,比如BLE廣播數據以前是byte[] scanRecord,而新API幫我們解析成ScanRecord類
// 舊API是BluetoothAdapter.startLeScan(...)
final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(mScanCallback);
mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        bluetoothLeScanner.stopScan(mScanCallback); //停止掃描
        isScanning = false;
    }
}, 3000);

// 掃描結果Callback
private final ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {、
        BluetoothDevice dev = result.getDevice() 獲取BLE設備信息
        // result.getScanRecord() 獲取BLE廣播數據
    }
};

(2).建立連接

// 獲取掃描設備,建立連接
closeConn();
BluetoothDevice dev = result.getDevice()
mBluetoothGatt = dev.connectGatt(BleClientActivity.this, false, mBluetoothGattCallback); // 連接藍牙設備

// BLE中心設備連接外圍設備的數量有限(大概2~7個),在建立新連接之前必須釋放舊連接資源,否則容易出現連接錯誤133
private void closeConn() {
    if (mBluetoothGatt != null) {
        mBluetoothGatt.disconnect();
        mBluetoothGatt.close();
    }
}
    
// 與服務端連接的Callback
public BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        BluetoothDevice dev = gatt.getDevice();
        Log.i(TAG, String.format("onConnectionStateChange:%s,%s,%s,%s", dev.getName(), dev.getAddress(), status, newState));
        if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
            isConnected = true;
            gatt.discoverServices(); //啟動服務發現
        } else {
            isConnected = false;
            closeConn();
        }
        logTv(String.format(status == 0 ? (newState == 2 ? "與[%s]連接成功" : "與[%s]連接斷開") : ("與[%s]連接出錯,錯誤碼:" + status), dev));
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        Log.i(TAG, String.format("onServicesDiscovered:%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), status));
        if (status == BluetoothGatt.GATT_SUCCESS) { //BLE服務發現成功
            // 遍歷獲取BLE服務Services/Characteristics/Descriptors的全部UUID
            for (BluetoothGattService service : gatt.getServices()) {
                StringBuilder allUUIDs = new StringBuilder("UUIDs={nS=" + service.getUuid().toString());
                for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                    allUUIDs.append(",nC=").append(characteristic.getUuid());
                    for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors())
                        allUUIDs.append(",nD=").append(descriptor.getUuid());
                }
                allUUIDs.append("}");
                Log.i(TAG, "onServicesDiscovered:" + allUUIDs.toString());
                logTv("發現服務" + allUUIDs);
            }
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        UUID uuid = characteristic.getUuid();
        String valueStr = new String(characteristic.getValue());
        Log.i(TAG, String.format("onCharacteristicRead:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status));
        logTv("讀取Characteristic[" + uuid + "]:n" + valueStr);
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        UUID uuid = characteristic.getUuid();
        String valueStr = new String(characteristic.getValue());
        Log.i(TAG, String.format("onCharacteristicWrite:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status));
        logTv("寫入Characteristic[" + uuid + "]:n" + valueStr);
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        UUID uuid = characteristic.getUuid();
        String valueStr = new String(characteristic.getValue());
        Log.i(TAG, String.format("onCharacteristicChanged:%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr));
        logTv("通知Characteristic[" + uuid + "]:n" + valueStr);
    }

    @Override
    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        UUID uuid = descriptor.getUuid();
        String valueStr = Arrays.toString(descriptor.getValue());
        Log.i(TAG, String.format("onDescriptorRead:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status));
        logTv("讀取Descriptor[" + uuid + "]:n" + valueStr);
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        UUID uuid = descriptor.getUuid();
        String valueStr = Arrays.toString(descriptor.getValue());
        Log.i(TAG, String.format("onDescriptorWrite:%s,%s,%s,%s,%s", gatt.getDevice().getName(), gatt.getDevice().getAddress(), uuid, valueStr, status));
        logTv("寫入Descriptor[" + uuid + "]:n" + valueStr);
    }
};

(3).傳輸數據(讀寫CHARACTERISTIC和DESCRIPTOR)

注意:
    1.每次讀寫數據最多20個字節,如果超過,只能分包
    2.連續頻繁讀寫數據容易失敗,讀寫操作間隔最好200ms以上,或等待上次回調完成后再進行下次讀寫操作!
// 讀取數據成功會回調->onCharacteristicChanged()
public void read(View view) {
    BluetoothGattService service = getGattService(BleServerActivity.UUID_SERVICE);
    if (service != null) {
        BluetoothGattCharacteristic characteristic = service.getCharacteristic(BleServerActivity.UUID_CHAR_READ_NOTIFY);//通過UUID獲取可讀的Characteristic
        mBluetoothGatt.readCharacteristic(characteristic);
    }
}

// 寫入數據成功會回調->onCharacteristicWrite()
public void write(View view) {
    BluetoothGattService service = getGattService(BleServerActivity.UUID_SERVICE);
    if (service != null) {
        String text = mWriteET.getText().toString();
        BluetoothGattCharacteristic characteristic = service.getCharacteristic(BleServerActivity.UUID_CHAR_WRITE);//通過UUID獲取可寫的Characteristic
        characteristic.setValue(text.getBytes()); //單次最多20個字節
        mBluetoothGatt.writeCharacteristic(characteristic);
    }
}

// 獲取Gatt服務
private BluetoothGattService getGattService(UUID uuid) {
    BluetoothGattService service = mBluetoothGatt.getService(uuid);
    if (service == null)
        Util.toast(this, "沒有找到服務UUID=" + uuid);
    return service;
}

(4).設置通知,實時監聽CHARACTERISTIC變化

// Characteristic變化會回調->onCharacteristicChanged()
BluetoothGattService service = getGattService(BleServerActivity.UUID_SERVICE);
if (service != null) {
    // 設置Characteristic通知
    BluetoothGattCharacteristic characteristic = service.getCharacteristic(BleServerActivity.UUID_CHAR_READ_NOTIFY);//通過UUID獲取可通知的Characteristic
    mBluetoothGatt.setCharacteristicNotification(characteristic, true);

    // 向Characteristic的Descriptor屬性寫入通知開關,使藍牙設備主動向手機發送數據
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(BleServerActivity.UUID_DESC_NOTITY);
    // descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);//和通知類似,但服務端不主動發數據,只指示客戶端讀取數據
    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    mBluetoothGatt.writeDescriptor(descriptor);
}

3.BLE服務端(也叫從機/外圍設備/peripheral)

public static final UUID UUID_SERVICE = UUID.fromString("10000000-0000-0000-0000-000000000000"); //自定義UUID
public static final UUID UUID_CHAR_READ_NOTIFY = UUID.fromString("11000000-0000-0000-0000-000000000000");
public static final UUID UUID_DESC_NOTITY = UUID.fromString("11100000-0000-0000-0000-000000000000");
public static final UUID UUID_CHAR_WRITE = UUID.fromString("12000000-0000-0000-0000-000000000000");
private BluetoothLeAdvertiser mBluetoothLeAdvertiser; // BLE廣播
private BluetoothGattServer mBluetoothGattServer; // BLE服務端

@Override
protected void onCreate(Bundle savedInstanceState) {
    ......
    BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    // BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    // ============啟動BLE藍牙廣播(廣告) =================================================================================
    //廣播設置(必須)
    AdvertiseSettings settings = new AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //廣播模式: 低功耗,平衡,低延遲
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //發射功率級別: 極低,低,中,高
            .setConnectable(true) //能否連接,廣播分為可連接廣播和不可連接廣播
            .build();
    //廣播數據(必須,廣播啟動就會發送)
    AdvertiseData advertiseData = new AdvertiseData.Builder()
            .setIncludeDeviceName(true) //包含藍牙名稱
            .setIncludeTxPowerLevel(true) //包含發射功率級別
            .addManufacturerData(1, new byte[]{23, 33}) //設備廠商數據,自定義
            .build();
    //掃描響應數據(可選,當客戶端掃描時才發送)
    AdvertiseData scanResponse = new AdvertiseData.Builder()
            .addManufacturerData(2, new byte[]{66, 66}) //設備廠商數據,自定義
            .addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服務UUID
    //      .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服務數據,自定義
            .build();
    mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
    mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);

    // 注意:必須要開啟可連接的BLE廣播,其它設備才能發現並連接BLE服務端!
    // =============啟動BLE藍牙服務端=====================================================================================
    BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
    //添加可讀+通知characteristic
    BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY,
            BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
    characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE));
    service.addCharacteristic(characteristicRead);
    //添加可寫characteristic
    BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE,
            BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
    service.addCharacteristic(characteristicWrite);
    if (bluetoothManager != null)
        mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
    mBluetoothGattServer.addService(service);
}

// BLE廣播Callback
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
        logTv("BLE廣播開啟成功");
    }

    @Override
    public void onStartFailure(int errorCode) {
        logTv("BLE廣播開啟失敗,錯誤碼:" + errorCode);
    }
};

// BLE服務端Callback
private BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() {
    @Override
    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
        Log.i(TAG, String.format("onConnectionStateChange:%s,%s,%s,%s", device.getName(), device.getAddress(), status, newState));
        logTv(String.format(status == 0 ? (newState == 2 ? "與[%s]連接成功" : "與[%s]連接斷開") : ("與[%s]連接出錯,錯誤碼:" + status), device));
    }

    @Override
    public void onServiceAdded(int status, BluetoothGattService service) {
        Log.i(TAG, String.format("onServiceAdded:%s,%s", status, service.getUuid()));
        logTv(String.format(status == 0 ? "添加服務[%s]成功" : "添加服務[%s]失敗,錯誤碼:" + status, service.getUuid()));
    }

    @Override
    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
        Log.i(TAG, String.format("onCharacteristicReadRequest:%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, offset, characteristic.getUuid()));
        String response = "CHAR_" + (int) (Math.random() * 100); //模擬數據
        mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());// 響應客戶端
        logTv("客戶端讀取Characteristic[" + characteristic.getUuid() + "]:n" + response);
    }

    @Override
    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
        // 獲取客戶端發過來的數據
        String requestStr = new String(requestBytes);
        Log.i(TAG, String.format("onCharacteristicWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, characteristic.getUuid(),
                preparedWrite, responseNeeded, offset, requestStr));
        mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);// 響應客戶端
        logTv("客戶端寫入Characteristic[" + characteristic.getUuid() + "]:n" + requestStr);
    }

    @Override
    public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
        Log.i(TAG, String.format("onDescriptorReadRequest:%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, offset, descriptor.getUuid()));
        String response = "DESC_" + (int) (Math.random() * 100); //模擬數據
        mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes()); // 響應客戶端
        logTv("客戶端讀取Descriptor[" + descriptor.getUuid() + "]:n" + response);
    }

    @Override
    public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
        // 獲取客戶端發過來的數據
        String valueStr = Arrays.toString(value);
        Log.i(TAG, String.format("onDescriptorWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, descriptor.getUuid(),
                preparedWrite, responseNeeded, offset, valueStr));
        mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);// 響應客戶端
        logTv("客戶端寫入Descriptor[" + descriptor.getUuid() + "]:n" + valueStr);

        // 簡單模擬通知客戶端Characteristic變化
        if (Arrays.toString(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).equals(valueStr)) { //是否開啟通知
            final BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        SystemClock.sleep(3000);
                        String response = "CHAR_" + (int) (Math.random() * 100); //模擬數據
                        characteristic.setValue(response);
                        mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
                        logTv("通知客戶端改變Characteristic[" + characteristic.getUuid() + "]:n" + response);
                    }
                }
            }).start();
        }
    }

    @Override
    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
        Log.i(TAG, String.format("onExecuteWrite:%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, execute));
    }

    @Override
    public void onNotificationSent(BluetoothDevice device, int status) {
        Log.i(TAG, String.format("onNotificationSent:%s,%s,%s", device.getName(), device.getAddress(), status));
    }

    @Override
    public void onMtuChanged(BluetoothDevice device, int mtu) {
        Log.i(TAG, String.format("onMtuChanged:%s,%s,%s", device.getName(), device.getAddress(), mtu));
    }
};

簡書: https://www.jianshu.com/p/8ac31a5070d4

CSDN: https://blog.csdn.net/qq_32115439/article/details/80643906

GitHub博客: http://lioil.win/2018/06/10/Android-BLE.html

Coding博客: http://c.lioil.win/2018/06/10/Android-BLE.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM