Android使用BLE(低功耗藍牙,Bluetooth Low Energy)

背景

在學習BLE的過程當中,積累了一些心得的DEMO,放到Github,造成本文。感興趣的同窗能夠下載到源代碼。
github: https://github.com/vir56k/bluetoothDemohtml

什麼是BLE(低功耗藍牙)

BLE(Bluetooth Low Energy,低功耗藍牙)是對傳統藍牙BR/EDR技術的補充。
儘管BLE和傳統藍牙都稱之爲藍牙標準,且共享射頻,可是,BLE是一個徹底不同的技術。
BLE不具有和傳統藍牙BR/EDR的兼容性。它是專爲小數據率、離散傳輸的應用而設計的。
通訊距離上也有改變,傳統藍牙的傳輸距離幾十米到幾百米不等,BLE則規定爲100米。android

低功耗藍牙特色

功耗低
鏈接更快,無需配對
*異步通信git

常見兩種藍牙模式

普通藍牙鏈接(2.0)
BLE(藍牙4.0)github

關鍵術語和概念

Generic Attribute Profile(GATT)—GATT配置文件是一個通用規範,用於在BLE鏈路上發送和接收被稱爲「屬性」的數據塊。目前全部的BLE應用都基於GATT。 藍牙SIG規定了許多低功耗設備的配置文件。配置文件是設備如何在特定的應用程序中工做的規格說明。注意一個設備能夠實現多個配置文件。例如,一個設備可能包括心率監測儀和電量檢測。
Attribute Protocol(ATT)—GATT在ATT協議基礎上創建,也被稱爲GATT/ATT。ATT對在BLE設備上運行進行了優化,爲此,它使用了儘量少的字節。每一個屬性經過一個惟一的的統一標識符(UUID)來標識,每一個String類型UUID使用128 bit標準格式。屬性經過ATT被格式化爲characteristics和services。
Characteristic 一個characteristic包括一個單一變量和0-n個用來描述characteristic變量的descriptor,characteristic能夠被認爲是一個類型,相似於類。
Descriptor Descriptor用來描述characteristic變量的屬性。例如,一個descriptor能夠規定一個可讀的描述,或者一個characteristic變量可接受的範圍,或者一個characteristic變量特定的測量單位。
*Service service是characteristic的集合。例如,你可能有一個叫「Heart Rate Monitor(心率監測儀)」的service,它包括了不少characteristics,如「heart rate measurement(心率測量)」等。你能夠在bluetooth.org 找到一個目前支持的基於GATT的配置文件和服務列表。服務器

角色和責任

如下是Android設備與BLE設備交互時的角色和責任:app

中央 VS 外圍設備。 適用於BLE鏈接自己。中央設備掃描,尋找廣播;外圍設備發出廣播。
GATT 服務端 VS GATT 客戶端。決定了兩個設備在創建鏈接後如何互相交流。異步

爲了方便理解,想象你有一個Android手機和一個用於活動跟蹤BLE設備,手機支持中央角色,活動跟蹤器支持外圍(爲了創建BLE鏈接你須要注意兩件事,只支持外圍設備的兩方或者只支持中央設備的兩方不能互相通訊)。
當手機和運動追蹤器創建鏈接後,他們開始向另外一方傳輸GATT數據。哪一方做爲服務器取決於他們傳輸數據的種類。例如,若是運動追蹤器想向手機報告傳感器數據,運動追蹤器是服務端。若是運動追蹤器更新來自手機的數據,手機會做爲服務端。
在這份文檔的例子中,android app(運行在android設備上)做爲GATT客戶端。app從gatt服務端得到數據,gatt服務端即支持Heart Rate Profile(心率配置)的BLE心率監測儀。可是你能夠本身設計android app去扮演GATT服務端角色ide

設備對BLE的支持

分爲兩種狀況
* 目標設備是否支持BLE
* Android手機是否支持BLE函數

目標設備是否支持要看具體目標設備的狀況,請參考硬件提供商和說明書。
通常狀況下Android4.3之後的手機具備藍牙模塊的話都會支持BLE,具體能夠再代碼中判斷。post

爲了在app中使用藍牙功能,必須聲明藍牙權限BLUETOOTH。利用這個權限去執行藍牙通訊,例如請求鏈接、接受鏈接、和傳輸數據。
若是想讓你的app啓動設備發現或操縱藍牙設置,必須聲明BLUETOOTH_ADMIN權限。注意:若是你使用BLUETOOTH_ADMIN權限,你也必須聲明BLUETOOTH權限。
在你的app manifest文件中聲明藍牙權限。

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

若是想聲明你的app只爲具備BLE的設備提供,在manifest文件中包括:

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

代碼中判斷手機是否支持BLE特性:

// 使用此檢查肯定BLE是否支持在設備上,而後你能夠有選擇性禁用BLE相關的功能
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

在Android中使用BLE

1.獲取 BluetoothAdapter

全部的藍牙活動都須要藍牙適配器。BluetoothAdapter表明設備自己的藍牙適配器(藍牙無線)。整個系統只有一個藍牙適配器,並且你的app使用它與系統交互。

//使用getSystemService()返回BluetoothManager,而後將其用於獲取適配器的一個實例。
// 初始化藍牙適配器
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

2.開啓藍牙

調用isEnabled())去檢測藍牙當前是否開啓。若是該方法返回false,藍牙被禁用。下面的代碼檢查藍牙是否開啓,若是沒有開啓,將顯示錯誤提示用戶去設置開啓藍牙

// 確保藍牙在設備上能夠開啓
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
   Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
   startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

3.搜索藍牙設備

爲了發現BLE設備,使用startLeScan())方法。這個方法須要一個參數BluetoothAdapter.LeScanCallback。你必須實現它的回調函數,那就是返回的掃描結果。由於掃描很是消耗電量,你應當遵照如下準則:
只要找到所需的設備,中止掃描。
不要在循環裏掃描,而且對掃描設置時間限制。之前可用的設備可能已經移出範圍,繼續掃描消耗電池電量。

public void cancelDiscovery() {
        if(isDiscovering) {
            isDiscovering = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

    public boolean isDiscovering() {
        return isDiscovering;
    }

    public void startDiscovery() {
        mBluetoothAdapter.startLeScan(mLeScanCallback);
        isDiscovering = true;
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                cancelDiscovery();
                if (getCallback() != null)
                    getCallback().onDiscoveryComplete();
            }
        }, SCAN_PERIOD);

    }


    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            if (getCallback() != null)
                getCallback().onDeviceFound(device);
        }
    };

GATT鏈接

搜索結束後,咱們可獲得一個搜索結果 BluetoothDevice ,它表示搜到的藍牙設備
1.調用

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

能夠創建一個GATT鏈接,它須要一個 回調mGattCallback 參數。
2.在回調方法的 onConnectionStateChange 中,咱們能夠經過 status 判斷是否GATT鏈接成功
3.在GATT鏈接創建成功後,咱們調用 mBluetoothGatt.discoverServices() 方法 發現GATT服務。
若是搜到服務將會觸發onServicesDiscovered回調

// Implements callback methods for GATT events that the app cares about.  For example,
    // connection change and services discovered.
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {

                Log.e(TAG, "Connected to GATT server.");
                // Attempts to discover services after successful connection.
                Log.e(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.e(TAG, "Disconnected from GATT server.");
                setState(ConnectionState.STATE_NONE);
                if (getConnectionCallback() != null)
                    getConnectionCallback().onConnectionLost();
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.e(TAG, "onServicesDiscovered received:  SUCCESS");
                initCharacteristic();
                try {
                    Thread.sleep(200);//延遲發送,不然第一次消息會不成功
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (getConnectionCallback() != null)
                    getConnectionCallback().onConnected(mBluetoothDevice.getName());
                setState(ConnectionState.STATE_CONNECTED);
            } else {
                Log.e(TAG, "onServicesDiscovered error falure " + status);
                setState(ConnectionState.STATE_NONE);

                if (getConnectionCallback() != null)
                    getConnectionCallback().onConnectionLost();
            }

        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.e(TAG, "onCharacteristicWrite status: " + status);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            Log.e(TAG, "onDescriptorWrite status: " + status);
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
            Log.e(TAG, "onDescriptorRead status: " + status);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            Log.e(TAG, "onCharacteristicRead status: " + status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic);
            readCharacteristic(characteristic);
        }
    };

發現服務 (觸發onServicesDiscovered)

在發現服務後,會觸發 GATT回調的onServicesDiscovered 方法,咱們須要在這裏初始化咱們的操做,包括:
1 查看服務。或者便利查找指定的(和目標硬件UUID符合的)服務。
2 得到指定服務的特徵 characteristic1
3 訂閱「特徵」發生變化的通知」

public void initCharacteristic() {
        if (mBluetoothGatt == null) throw new NullPointerException();
        List<BluetoothGattService> services = mBluetoothGatt.getServices();
        Log.e(TAG, services.toString());
        BluetoothGattService service = mBluetoothGatt.getService(uuidServer);
        characteristic1 = service.getCharacteristic(uuidChar1);
        characteristic2 = service.getCharacteristic(uuidChar2);

        final String uuid = "00002902-0000-1000-8000-00805f9b34fb";
        if (mBluetoothGatt == null) throw new NullPointerException();
        mBluetoothGatt.setCharacteristicNotification(characteristic1, true);
        BluetoothGattDescriptor descriptor = characteristic1.getDescriptor(UUID.fromString(uuid));
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBluetoothGatt.writeDescriptor(descriptor);
    }

訂閱「特徵」發生變化的通知」

調用 mBluetoothGatt.setCharacteristicNotification() 方法,傳入一個特徵 characteristic 對象。
當這個特徵裏的數據發生變化(接收到數據了),會觸發 回調方法的 onCharacteristicChanged 方法。咱們在這個回調方法中讀取數據。

final String uuid = "00002902-0000-1000-8000-00805f9b34fb";
            if (mBluetoothGatt == null) throw new NullPointerException();
            mBluetoothGatt.setCharacteristicNotification(characteristic1, true);
            BluetoothGattDescriptor descriptor = characteristic1.getDescriptor(UUID.fromString(uuid));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(descriptor);

讀取數據

GATT的回調中有 onCharacteristicChanged 方法,咱們在這裏能夠得到接收的數據

@Override
            public void onCharacteristicChanged(BluetoothGatt gatt,
                                                BluetoothGattCharacteristic characteristic) {
                Log.e(TAG, "onCharacteristicChanged characteristic: " + characteristic);
                readCharacteristic(characteristic);
            }

調用 characteristic.getValue() 方法,得到字節

public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
                    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
                        Log.e(TAG, "BluetoothAdapter not initialized");
                        return;
                    }
                    mBluetoothGatt.readCharacteristic(characteristic);
                    byte[] bytes = characteristic.getValue();
                    String str = new String(bytes);
                    Log.e(TAG, "## readCharacteristic, 讀取到: " + str);
                    if (getConnectionCallback() != null)
                        getConnectionCallback().onReadMessage(bytes);
                }

寫入數據

寫入數據時,咱們須要先得到特徵,特徵存在於服務內,通常在發現服務的 onServicesDiscovered 時,查找到特徵對象。

public void write(byte[] cmd) {
        Log.e(TAG, "write:" + new String(cmd));
        synchronized (LOCK) {
            characteristic2.setValue(cmd);
            characteristic2.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
            mBluetoothGatt.writeCharacteristic(characteristic2);

            if (getConnectionCallback() != null)
                getConnectionCallback().onWriteMessage(cmd);
        }
    }

關閉藍牙鏈接

public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}

#參考 https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

相關文章
相關標籤/搜索