Android BLE低功耗藍(lán)牙開(kāi)發(fā)

最近做了一個(gè)智能硬件開(kāi)發(fā)(針灸儀)的項(xiàng)目,有一部分涉及到低功耗藍(lán)牙的開(kāi)發(fā),就是通過(guò)藍(lán)牙和設(shè)備進(jìn)行數(shù)據(jù)的交互,比如控制改設(shè)備的LED的開(kāi)關(guān),設(shè)備的開(kāi)關(guān)機(jī),設(shè)置設(shè)備的時(shí)間和溫度等,下面就項(xiàng)目中遇到的坑一一說(shuō)明:首先給出官網(wǎng)對(duì)于BLE開(kāi)發(fā)的講解,https://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#terms官方demo:https://github.com/googlesamples/android-BluetoothLeGatt,demo也比較好理解,主要是四個(gè)類(lèi),其中,DeviceControlActivity通過(guò)啟動(dòng)BluetoothLeService用來(lái)進(jìn)行與藍(lán)牙外圍設(shè)備的交互。(注意,因?yàn)楸救耸菍I做了更改,并放到了fragment中,所以部分代碼跟demo不一致,請(qǐng)主動(dòng)忽略,關(guān)注藍(lán)牙核心代碼)

BLE開(kāi)發(fā)所需要的知識(shí),通過(guò)官方demo,我們會(huì)發(fā)現(xiàn)很多service,點(diǎn)擊service后,每個(gè)service下面是Characteristic,每個(gè)service和Characteristic都對(duì)應(yīng)一個(gè)唯一的UUID。所以,在做BLE時(shí)候,首先你應(yīng)該找出你的藍(lán)牙外圍設(shè)備uuid,不然會(huì)很頭疼,這個(gè)UUID也可能是硬件給你的,也可以你自己試出來(lái),當(dāng)然自己試出來(lái)是個(gè)很煩的過(guò)程。自己試的方法就是根據(jù)demo,加上一份讀寫(xiě)的協(xié)議,然后,排著點(diǎn)擊,顯示出來(lái)的藍(lán)牙列表進(jìn)行測(cè)試,看是否和協(xié)議對(duì)應(yīng)。另外,BluetoothLeService類(lèi)不用做太多的更改。

一,藍(lán)牙設(shè)備的掃描

這一部分基本上很簡(jiǎn)單,只要設(shè)備上電以后,這部分代碼執(zhí)行后,便可以掃描出設(shè)備,并獲得BluetoothDevice對(duì)象

    public void scanLeDevice(final boolean enable) {
        LogUtils.debug(TAG, "-----------開(kāi)始掃描藍(lán)牙=" + enable);
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    stopScanOuter();
                }
            }, SCAN_PERIOD);
            LogUtils.debug("----------startLeScan--");
            mScanning = true;
            mBluetoothAdapter.startLeScan(this);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(this);
        }
    }

上面代碼為開(kāi)始掃描周?chē)焉想姷脑O(shè)備,當(dāng)發(fā)現(xiàn)設(shè)備后,BluetoothAdapter.LeScanCallback會(huì)執(zhí)行onLeScan回調(diào),將BluetoothDevice返回,

    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("MyDeviceFragment");
        if(dcfrag != null && dcfrag.isVisible()) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    LogUtils.debug(TAG, "---------獲得設(shè)備" + device);
                    mLeDeviceListAdapter.addDevice(device);
                    mLeDeviceListAdapter.notifyDataSetChanged();
                }
            });
        }
    }

到此,我們便可以得到掃描到的藍(lán)牙設(shè)備,但是目前僅僅是掃描到,并不代表已經(jīng)連接上藍(lán)牙設(shè)備。

二,藍(lán)牙設(shè)備的連接

1,綁定service

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            LogUtils.debug(TAG, "開(kāi)始綁定service onServiceConnected"+device+"---name="+device.getName()+"--address="+device.getAddress());
            mDeviceAddress = device.getAddress();
            mDeviceName = device.getName();

            mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
            if (!mBluetoothLeService.initialize()) {
                Log.i(TAG, "Unable to initialize Bluetooth");
            }
            // Automatically connects to the device upon successful start-up initialization.
            mBluetoothLeService.connect(mDeviceAddress);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            LogUtils.debug(TAG, "--------onServiceDisconnected service無(wú)法綁定了");
            mBluetoothLeService = null;
        }
    };
    
    
    public void mybindService(){
        LogUtils.debug(TAG, "---------開(kāi)始執(zhí)行onCreate---bindservice");
        Intent gattServiceIntent = new Intent(getActivity(), BluetoothLeService.class);
        getActivity().bindService(gattServiceIntent, mServiceConnection, getActivity().BIND_AUTO_CREATE);
    }

2,連接藍(lán)牙

    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_GATT_CONNECTED");
                mConnected = true;

                LogUtils.debug(TAG, "--------ACTION_GATT_CONNECTED devicename"+mDeviceName);
                Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ConnectFragment");
                if((dcfrag != null && dcfrag.isVisible())){

                    //暫時(shí)寫(xiě)死在這可以連接的BLE設(shè)備
                    if(mDeviceName == null || !deviceFilter(mDeviceName)){
                        connectService.switchFragment(false, mDeviceName);
                    }else{
                        connectService.switchFragment(true, mDeviceName);
                    }
                }

            } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_GATT_DISCONNECTED");
                mConnected = false;
                Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ConnectFragment");
                if((dcfrag != null && dcfrag.isVisible()))
                    connectService.switchFragment(false, mDeviceName);

                Fragment contfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ControlorFragment");
                if((contfrag != null && contfrag.isVisible())){
                    transfertoControler.closeButton(false, false);
                }
            } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_GATT_SERVICES_DISCOVERED");
                initMoxibustionService(
                        mBluetoothLeService.getSupportedGatteService(
                                SampleGattAttributes.SERVIECE_NOTIFY_DATA));

                initMoxibustionService(
                        mBluetoothLeService.getSupportedGatteService(
                                SampleGattAttributes.SERVIECE_WRITE_DATA));

                // Show all the supported services and characteristics on the user interface.
//                displayGattServices(mBluetoothLeService.getSupportedGattServices());
            } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_DATA_AVAILABLE");
//                byte[] data = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
//                StringBuilder stringBuilder = new StringBuilder(data.length);
//                for(byte byteChar : data)
//                    stringBuilder.append(String.format("0x%02X ", byteChar));
//                String log = stringBuilder.toString();
//                LogUtils.debug(TAG, "---字節(jié)數(shù)組為="+ log);
                parsedata(intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA));
            }  else if (BluetoothLeService.ACTION_DATA_SEND_CONFIRM.equals(action)) {
                // ECHO from android
                LogUtils.debug(TAG, "write ok!");
            }


        }
    };
    public void myConnetService(){
        getActivity().registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
        LogUtils.debug(TAG, "------mBluetoothLeService  myConnetService" + mBluetoothLeService);
        if (mBluetoothLeService != null) {
            final boolean result = mBluetoothLeService.connect(mDeviceAddress);
            LogUtils.debug(TAG, "Connect request result=" + result);
        }
    }

當(dāng)連接上藍(lán)牙后,我們會(huì)得到ACTION_GATT_CONNECTED的廣播,然后是ACTION_GATT_SERVICES_DISCOVERED,這個(gè)時(shí)候我們需要對(duì)service進(jìn)行初始化,以便能夠讀寫(xiě)數(shù)據(jù),以下為初始化代碼(注意,初始化時(shí)候我們需要用到讀寫(xiě)service的UUID)

    private void initMoxibustionService(BluetoothGattService gattService) {
        String uuid = "";
        if (gattService == null)
        {
            LogUtils.debug(TAG, "gattService is null");
            return;
        }
        List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
        for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
            uuid = gattCharacteristic.getUuid().toString();
            if (SampleGattAttributes.CHARACTER_NOTIFY_DATA.substring(0,8).equals(uuid.substring(0, 8))) {
                mNotifyCharacteristic = gattCharacteristic;
                mBluetoothLeService.setCharacteristicNotification(
                        mNotifyCharacteristic, true);
                LogUtils.debug(TAG, "NOTIFY_DATA");
                LogUtils.debug(TAG, "getProperties()=" + mNotifyCharacteristic.getProperties());
            } else if (SampleGattAttributes.CHARACTER_WRITE_DATA.substring(0,8).equals(uuid.subSequence(0, 8))) {
//                mCommandCharacteristic = gattCharacteristic;

                //寫(xiě)數(shù)據(jù)的服務(wù)和characteristic
                mCommandCharacteristic = mBluetoothLeService.getSupportedGatteService(SampleGattAttributes.SERVIECE_WRITE_DATA)
                        .getCharacteristic(UUID.fromString(SampleGattAttributes.CHARACTER_WRITE_DATA));


                LogUtils.debug(TAG, "WRITE_CMD");
                LogUtils.debug(TAG, "getProperties()=" + mCommandCharacteristic.getProperties());
                mCommandCharacteristic.setWriteType(
                        BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                LogUtils.debug(TAG, "getProperties()=" + mCommandCharacteristic.getProperties());
            }
        }
    }

CHARACTER_NOTIFY_DATA和CHARACTER_WRITE_DATA為讀和寫(xiě)數(shù)據(jù)的CHARACTER的UUID,如下,注意這四個(gè)UUID

3,解析數(shù)據(jù)

至此,如果順利的話,我們就可以得到ACTION_DATA_AVAILABLE的廣播,也就拿到了從藍(lán)牙設(shè)備獲得的byte數(shù)組,大多數(shù)協(xié)議里,每個(gè)字節(jié)代表一個(gè)命令。這里涉及到Java中byte值與int值的轉(zhuǎn)換。因?yàn)镴ava中,所有的值都是singed性的,最高位為符號(hào)位,所以,大家請(qǐng)自行補(bǔ)下該部分的知識(shí),對(duì)于有符號(hào)數(shù),它的值相當(dāng)于取補(bǔ)碼,此處將不詳述,

上面為讀數(shù)據(jù),下面我們說(shuō)寫(xiě)數(shù)據(jù),比如,如下的開(kāi)關(guān)機(jī)命令,

// 01 10 01 01 92 07 17
public void controlpower(boolean isOpen){
    if (mCommandCharacteristic == null) return;
    if (mBluetoothLeService == null) return;
    byte[] setDataAfter;
    if(isOpen){
        byte[] setDataBefore = {0x01, 0x10, 0x01, 0x01};
        byte[] trans = inttobyte(integrityCheck(setDataBefore));
        byte[] transV = Arrays.copyOfRange(trans, 2, 4);
        byte[] setData = byteMerger(setDataBefore, reverse(transV));
        byte[] dd = {0x17};
        setDataAfter = byteMerger(setData, dd);
        LogUtils.debug(TAG, "---setDataAfter[4]="+setDataAfter[4]+",setDataAfter[5]="+setDataAfter[5]);
    }else{
        byte[] setDataBefore = {0x01, 0x10, 0x01, 0x00};
        byte[] trans = inttobyte(integrityCheck(setDataBefore));
        byte[] transV = Arrays.copyOfRange(trans, 2, 4);
        byte[] setData = byteMerger(setDataBefore, reverse(transV));
        byte[] dd = {0x17};
        setDataAfter = byteMerger(setData, dd);
        printDataHex(setDataAfter);
    }

    mCommandCharacteristic.setValue(setDataAfter);
    mBluetoothLeService.writeCharacteristic(mCommandCharacteristic);

}
    mCommandCharacteristic.setValue(setDataAfter);
    mBluetoothLeService.writeCharacteristic(mCommandCharacteristic);

這兩行代碼,是核心代碼,但是,我們要進(jìn)行字節(jié)數(shù)組的正確傳遞,這里,給大家貼出來(lái)幾個(gè)很有可能用到的方法,

CRC算法Java版:

//crc java
public int integrityCheck(byte[] bytes) {
    int wCrc = 0xffff;
    for (byte srcData : bytes) {
        int data = byteToInt(srcData);
        for(int j = 0; j < 8; j++) {
            if ((((wCrc & 0x8000) >> 8) ^ ((data << j) & 0x80)) != 0) {
                wCrc = (wCrc << 1) ^ 0x1021;
            } else {
                wCrc = wCrc << 1;
            }
        }
    }

    wCrc = (wCrc << 8) | (wCrc >> 8 & 0xff);
    return wCrc & 0xffff;
}
public static int byteToInt(byte b) {
    return b & 0xff;
}

// int to byte
public static byte[] inttobyte(int value) {
    byte b0 = (byte) ((value >> 24) & 0xFF);
    byte b1 = (byte) ((value >> 16) & 0xFF);
    byte b2 = (byte) ((value >> 8) & 0xFF);
    byte b3 = (byte) (value & 0xFF);
    byte[] bytes = { b0, b1, b2, b3 };
    return bytes;
}
//打印字節(jié)數(shù)組
public void printDataHex(byte[] data) {
    if(SENTLOG){
        StringBuilder stringBuilder = new StringBuilder(data.length);
        for(byte byteChar : data)
            stringBuilder.append(String.format("0x%02X ", byteChar));
        String log = stringBuilder.toString();
        LogUtils.debug(TAG, "---發(fā)送到藍(lán)牙的字節(jié)數(shù)組為="+ log);
    }
}

//數(shù)組倒序
public byte[] reverse(byte[] rt){
    for (int i = 0; i < rt.length / 2; i++) {
        byte temp = rt[i];
        rt[i] = rt[rt.length - 1 - i];
        rt[rt.length - 1 - i] = temp;
    }
    return rt;
}

//java 合并兩個(gè)byte數(shù)組
public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
    byte[] byte_3 = new byte[byte_1.length+byte_2.length];
    System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
    System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
    return byte_3;
}

寫(xiě)的比較簡(jiǎn)單,但是關(guān)鍵代碼都有了,大家可以參考下!~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容