1. 程式人生 > >學習筆記之低功耗藍芽開發

學習筆記之低功耗藍芽開發

概述

ClassicBluetooth:學習筆記之經典藍芽開發

經典藍芽與低功耗(BLE)藍芽的區別

①經典藍芽:發現/搜尋裝置-->配對/繫結裝置-->建立連線-->資料通訊

②BLE藍芽:中央 VS 外圍裝置,中央裝置掃描,尋找廣播;外圍裝置發出廣播。二者通過GATT協議進行資料通訊互動

③只要有藍芽功能基本都支援經典藍芽,那麼BLE藍芽Google在Android 4.3才開始支援BLE API

④BLE有裝置角色的概念以及基於GATT協議來達到互動,至於經典藍芽的資料通訊,檢視android.bluetooth包下發現BluetoothServerSocket與BluetoothSocket你就大致知道了

⑤BLE與傳統的藍芽相比最大的優勢是功耗降低90%,同時傳輸距離增大(超過100米)、安全和穩定性提高(支援AES加密和CRC驗證)

iBeacon與BLE的區別

雖然iBeacon技術基於BLE,但是iBeacon的UUID和BLE的Service、Characteristic、Descriptor的UUID是沒關係,iBeacon的UUID是廣播的時候發出,是由Apple自己定義的標準,而Service、Characteristic、Descriptor必須是連上BLE終端後才得到,是BLE標準。

執行效果


許可權

在經典藍芽需要的許可權基礎上再加一個
 <uses-feature Android:name="android.hardware.bluetooth_le" android:required="true"/>
這裡的true標示手機如果不支援低功耗藍芽就直接不讓安裝,但是如果想讓你的app提供給那些不支援BLE的裝置,需要在manifest中包括上面程式碼並設定required="false",然後在執行時可以通過使用getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)確定BLE的可用性。

開啟藍芽

開啟方法與經典藍芽開啟方式相同,區別在於BLE通過getSystemService(Context.BLUETOOTH_SERVICE)得到BluetoothManager(API level 18),BluetoothManager.getAdapter()得到BluetoothAdapter。

發現/搜尋BLE裝置

通過呼叫BluetoothAdapter的startLeScan(BluetoothAdapter.LeScanCallback)搜尋BLE裝置。呼叫此方法時需要傳入 BluetoothAdapter.LeScanCallback引數。因此你需要實現 BluetoothAdapter.LeScanCallback介面,BLE裝置的搜尋結果將通過這個callback返回。如果你只需要搜尋指定UUID的外設,你可以呼叫 startLeScan(UUID[], BluetoothAdapter.LeScanCallback)方法。其中UUID陣列指定你的應用程式所支援的GATT Services的UUID。stopLeScan(BluetoothAdapter.LeScanCallback)停止搜尋BLE裝置,由於搜尋需要儘量減少功耗,因此在實際使用時需要注意。

onLeScan 方法在Android 5.0以下及Android 5.0及以上所執行的執行緒不同。

@Override
    public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
        if (Looper.myLooper() == Looper.getMainLooper()) {// Android 5.0 及以上
            addData(device, rssi, scanRecord);
        } else {
            runOnUiThread(new Runnable() {// Android 5.0 以下
                @Override
                public void run() {
                    addData(device, rssi, scanRecord);
                }
            });
        }
    }

處理髮現的BLE裝置,並展示在UI上

public void addData(BluetoothDevice device, int rssi, byte[] scanRecord) {
        if (device != null) {
            /**
             * 這裡呼叫ibeacon工具類封裝bieacon並轉換距離
             * 非ibeacon裝置可替換自己的業務
             */
            BleBuletoothDeviceBean mdb = IbeaconUtils.fromScanData(new BleBuletoothDeviceBean(), device, rssi, scanRecord);
            if (mainList.size() < 1) {
                mainList.add(mdb);
                lvAdapter.notifyDataSetChanged();
                return;
            }
            for (int i = 0; i < mainList.size(); i++) {
                BleBuletoothDeviceBean bbdb = mainList.get(i);
                if (bbdb.getType() == 1) {
                    if (bbdb.getDevice().getAddress().equals(device.getAddress())) {
                        bbdb.setDistance(mdb.getDistance());
                        bbdb.setRssi(mdb.getRssi());
                        bbdb.setTxPower(mdb.getTxPower());
                    }
                } else {
                    if (!bbdb.getDevice().getAddress().equals(mdb.getDevice().getAddress())) {
                        mainList.add(mdb);
                    }
                }
            }
            lvAdapter.notifyDataSetChanged();
        }
    }

連線BLE裝置

低功耗藍芽(BLE)連線都是建立在 GATT (Generic Attribute Profile) 協議之上。GATT 是一個在藍芽連線之上的傳送和接收很短的資料段的通用規範,這些很短的資料段被稱為屬性(Attribute)。具體的內容檢視GATT Profile簡介,用一張圖來概括GATT的結構

連線GATT

連線GATT,連線結果非同步通過onConnectionStateChange回撥
public boolean connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            Log.e(TAG, "BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        if (!mBluetoothDeviceAddress.equals("") && address.equals(mBluetoothDeviceAddress)
                && !Gatt()) {
            Log.e(TAG, "Trying to use an existing mBluetoothGatt for connection.");
            if (mBluetoothGatt.connect()) {
                return true;
            } else {
                return false;
            }
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            Log.e(TAG, "Device not found.  Unable to connect.");
            return false;
        }
        mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
        Log.e(TAG, "Trying to create a new connection.");
        mBluetoothDeviceAddress = address;
        return true;
    }
GATT回撥監聽:連線更改和服務發現,mgl是自己實現的監聽回撥,用於UI介面的業務
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {//成功連線後發現服務的嘗試
                if (mgl != null)
                    mgl.onConnect(gatt);
                Log.e(TAG, "Connected to GATT server.");
                Log.e(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//斷開
                if (mgl != null) mgl.onDisconnect(gatt);
                Log.e(TAG, "Disconnected from GATT server.");
            }
        }
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {//services被發現
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (mgl != null) {
                    mgl.onServiceDiscover(gatt);
                }
            } else {
                Log.e(TAG, "onServicesDiscovered received: " + status);
            }
        }
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (mgl != null)//characteristic被讀到
                mgl.onCharacteristicRead(gatt, characteristic, status);
        }
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (mgl != null)//characteristic被寫入
                mgl.onCharacteristicWrite(gatt, characteristic, status);
        }
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
				Log.e(TAG, "onCharacteristicChanged:come in ");
        }
    };

Gatt的服務列表

當服務被發現時,呼叫getservices()獲取Gatt的服務列表
/**
     * 檢索的連線裝置支援的GATT的服務列表。只有在成功完成後才能呼叫該函式。
     *
     * @return
     */
    public List<BluetoothGattService> getSupportedGattServices() {
        if (Gatt()) return null;
        return mBluetoothGatt.getServices();
    }
處理gatt返回的資料並填充展示(一個gatt包含多個service;一個sercice包含多個Characteristics;一個Characteristics包含多個Descriptors)
private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        for (BluetoothGattService gattService : gattServices) {
            //-----Service的欄位資訊-----//
            Log.e(TAG, "========Service的欄位資訊========");
            Log.e(TAG, "-->service type:" + gattService.getType());
            Log.e(TAG, "-->includedServices size:" + gattService.getIncludedServices().size());
            Log.e(TAG, "-->service uuid:" + gattService.getUuid().toString());
            HashMap<String, String> hmServiceData = new HashMap<String, String>();
            hmServiceData.put(LIST_NAME, GattUtils.lookup(gattService.getUuid().toString(), "serviceName==null"));
            hmServiceData.put(LIST_UUID, gattService.getUuid().toString());
            gattServiceData.add(hmServiceData);
            ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>();
            //-----Characteristics的欄位資訊-----//
            for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                Log.e(TAG, "========Characteristics的欄位資訊========");
                Log.e(TAG, "---->char uuid:" + gattCharacteristic.getUuid());
                Log.e(TAG, "---->char permission:" + gattCharacteristic.getPermissions());
                Log.e(TAG, "---->char property:" + gattCharacteristic.getProperties());
                if (gattCharacteristic.getValue() != null && gattCharacteristic.getValue().length > 0) {
                    Log.e(TAG, "---->char value:" + new String(gattCharacteristic.getValue()));
                }
                charas.add(gattCharacteristic);
                HashMap<String, String> hmCharaData = new HashMap<String, String>();
                hmCharaData.put(LIST_NAME, GattUtils.lookup(gattCharacteristic.getUuid().toString(), 
				"characteristicsName==null"));
                hmCharaData.put(LIST_UUID, gattCharacteristic.getUuid().toString());
                gattCharacteristicGroupData.add(hmCharaData);
                //-----Descriptors的欄位資訊-----//
                List<BluetoothGattDescriptor> descriptors = gattCharacteristic.getDescriptors();
                for (BluetoothGattDescriptor gattDescriptor : descriptors) {
                    Log.e(TAG, "========Descriptors的欄位資訊========");
                    Log.e(TAG, "-------->desc uuid:" + gattDescriptor.getUuid());
                    Log.e(TAG, "-------->desc permission:" + gattDescriptor.getPermissions());
                    if (gattDescriptor.getValue() != null && gattDescriptor.getValue().length > 0) {
                        Log.e(TAG, "-------->desc value:" + new String(gattDescriptor.getValue()));
                    }
                }
            }
            bleGattCharacteristicList.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
        }
        updateUi(2);
    }

讀取Characteristic資料

觸發點選事件,gatt呼叫readCharacteristic()
@Override
    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
        if (bleGattCharacteristicList.size() > 0) {
            BluetoothGattCharacteristic bgc = bleGattCharacteristicList.get(groupPosition).get(childPosition);
            int properties = bgc.getProperties();
            if ((properties | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                //如果在特性上有一個活動通知,請首先清除它,這樣它就不會更新使用者介面上的資料欄位.
                if (mNotifyCharacteristic != null) {
                    gc.setCharacteristicNotification(mNotifyCharacteristic, false);
                    mNotifyCharacteristic = null;
                }
                gc.readCharacteristic(bgc);
            }
            if ((properties | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                mNotifyCharacteristic = bgc;
                gc.setCharacteristicNotification(bgc, true);
            }
        }
        return false;
    }
在onCharacteristicRead()監聽讀取內容,並展示在UI上。
 /**
     * BLE終端資料被讀的事件
     */
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
        Log.e(TAG, "調onCharacteristicRead");
        if (status == BluetoothGatt.GATT_SUCCESS) {
            final String text = GattUtils.bytesToHexString(characteristic.getValue());
            final String wid = characteristic.getUuid().toString();
            Log.e(TAG, "BLE終端資料被讀的事件onCharRead " + gatt.getDevice().getName()
                    + " read "
                    + characteristic.getUuid().toString()
                    + " -> "
                    + (text.equals("") ? "data=null" : text));
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    data_value.setText(text.equals("") ? "read==null" : text);
                    writerId_value.setText(wid.equals("") ? "writer==null" : wid);
                    writeId = wid;
                }
            });
        } else {
            Log.e(TAG, "沒有讀到");
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(BleGattActivity.this, "沒有讀到data", Toast.LENGTH_SHORT).show();
                }
            });
        }

    }

資料寫入Characteristic

設定寫入的內容,gatt呼叫writeCharacteristic()
public void writeSomeData() {
        //可以跟藍芽模組串列埠通訊的Characteristic
        Log.e(TAG, "寫入的Characteristic:" + mNotifyCharacteristic.getUuid().toString());
        //接受Characteristic被寫的通知,收到藍芽模組的資料後會觸發mOnDataAvailable.onCharacteristicWrite()
        gc.setCharacteristicNotification(mNotifyCharacteristic, true);
        String str = "9";
        //設定資料內容
        boolean data = mNotifyCharacteristic.setValue(str);
        //往藍芽模組寫入資料
        boolean flag = gc.writeCharacteristic(mNotifyCharacteristic);
        Log.e(TAG, "設定資料是否成功:"+data);
        Log.e(TAG, "寫入資料是否成功:"+flag);
    }
在onCharacteristicWrite()監聽寫入的結果,並展示在UI上。
@Override
    public void onCharacteristicWrite(BluetoothGatt gatt, 
	final BluetoothGattCharacteristic characteristic, final int status) {
        Log.e(TAG, "調onCharacteristicWrite");
        Log.e(TAG, "收到BLE終端寫入資料回撥onCharWrite " + gatt.getDevice().getName()
                + " write "
                + characteristic.getUuid().toString()
                + " -> "
                + GattUtils.bytesToHexString(characteristic.getValue()));
        if (status == BluetoothGatt.GATT_SUCCESS) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    writer_in_value.setText(GattUtils.bytesToHexString(characteristic.getValue()));
                }
            });
        } else if (status == BluetoothGatt.GATT_FAILURE) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    writer_in_value.setText("寫入失敗==" + status);
                }
            });
        } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    writer_in_value.setText("沒有寫入的許可權==" + status);
                }
            });
        } else {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    writer_in_value.setText("寫入異常狀態==" + status);
                }
            });
        }
    }
用一張圖來描述流程
GATT連線過程Log日誌 02-17 13:52:04.809 26265-26265/com.lhj.classic.bluetooth E/GattConnectControl: Trying to create a new connection.
02-17 13:52:04.809 26265-26265/com.lhj.classic.bluetooth E/flag: 開始連線gatt=true
02-17 13:52:08.794 26265-26282/com.lhj.classic.bluetooth E/GattConnectControl: Connected to GATT server.
02-17 13:52:08.798 26265-26282/com.lhj.classic.bluetooth E/GattConnectControl: Attempting to start service discovery:true
02-17 13:52:10.657 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: 搜尋到BLE終端服務的事件
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ========Service的欄位資訊========
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -->service type:0
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -->includedServices size:0
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -->service uuid:00001801-0000-1000-8000-00805f9b34fb
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ========Characteristics的欄位資訊========
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ---->char uuid:00002a05-0000-1000-8000-00805f9b34fb
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ---->char permission:0
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ---->char property:34
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ========Descriptors的欄位資訊========
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -------->desc uuid:00002902-0000-1000-8000-00805f9b34fb
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -------->desc permission:0
02-17 13:52:14.428 26265-26283/com.lhj.classic.bluetooth E/BleGattActivity: 調onCharacteristicRead
02-17 13:52:14.430 26265-26283/com.lhj.classic.bluetooth E/BleGattActivity: BLE終端資料被讀的事件onCharRead null read 00002a02-0000-1000-8000-00805f9b34fb -> 00
02-17 13:52:15.917 26265-26265/com.lhj.classic.bluetooth E/BleGattActivity: 寫入的Characteristic:00002a02-0000-1000-8000-00805f9b34fb
02-17 13:52:15.942 26265-26265/com.lhj.classic.bluetooth E/BleGattActivity: true=======true
02-17 13:52:15.980 26265-26282/com.lhj.classic.bluetooth E/BleGattActivity: 調onCharacteristicWrite
02-17 13:52:15.985 26265-26282/com.lhj.classic.bluetooth E/BleGattActivity: 收到BLE終端寫入資料回撥onCharWrite null write 00002a02-0000-1000-8000-00805f9b34fb -> 39
02-17 13:52:18.821 26265-26283/com.lhj.classic.bluetooth E/GattConnectControl: Disconnected from GATT server
當然還有其它資訊的讀取與寫入,如writeDescriptor,readDescriptor,readRemoteRssi等與之對應的就有onDescriptorRead,onDescriptorWrite,onReadRemoteRssi。 注意靈活使用(取決於BLE裝置的通訊方式)setCharacteristicNotification(characteristic, enabled:設定當指定characteristic值變化時,發出通知。

小結

實戰才是真理,專案已經上傳至GitHub,專案包含經典藍芽與BLE以及ibeacon,請自行閱讀需要的模組。

以上便關於低功耗藍芽的主要內容,如果有任何不妥之處,還請各位指出。