美文网首页通讯.网络连接Android
Android BLE低功耗蓝牙开发

Android BLE低功耗蓝牙开发

作者: Yestin | 来源:发表于2016-11-11 13:32 被阅读2325次

    最近做了一个智能硬件开发(针灸仪)的项目,有一部分涉及到低功耗蓝牙的开发,就是通过蓝牙和设备进行数据的交互,比如控制改设备的LED的开关,设备的开关机,设置设备的时间和温度等,下面就项目中遇到的坑一一说明:首先给出官网对于BLE开发的讲解,https://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#terms官方demo:https://github.com/googlesamples/android-BluetoothLeGatt,demo也比较好理解,主要是四个类,其中,DeviceControlActivity通过启动BluetoothLeService用来进行与蓝牙外围设备的交互。(注意,因为本人是将UI做了更改,并放到了fragment中,所以部分代码跟demo不一致,请主动忽略,关注蓝牙核心代码)

    BLE开发所需要的知识,通过官方demo,我们会发现很多service,点击service后,每个service下面是Characteristic,每个service和Characteristic都对应一个唯一的UUID。所以,在做BLE时候,首先你应该找出你的蓝牙外围设备uuid,不然会很头疼,这个UUID也可能是硬件给你的,也可以你自己试出来,当然自己试出来是个很烦的过程。自己试的方法就是根据demo,加上一份读写的协议,然后,排着点击,显示出来的蓝牙列表进行测试,看是否和协议对应。另外,BluetoothLeService类不用做太多的更改。

    一,蓝牙设备的扫描

    这一部分基本上很简单,只要设备上电以后,这部分代码执行后,便可以扫描出设备,并获得BluetoothDevice对象

        public void scanLeDevice(final boolean enable) {
            LogUtils.debug(TAG, "-----------开始扫描蓝牙=" + 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);
            }
        }
    

    上面代码为开始扫描周围已上电的设备,当发现设备后,BluetoothAdapter.LeScanCallback会执行onLeScan回调,将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, "---------获得设备" + device);
                        mLeDeviceListAdapter.addDevice(device);
                        mLeDeviceListAdapter.notifyDataSetChanged();
                    }
                });
            }
        }
    

    到此,我们便可以得到扫描到的蓝牙设备,但是目前仅仅是扫描到,并不代表已经连接上蓝牙设备。

    二,蓝牙设备的连接

    1,绑定service

        private final ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder service) {
                LogUtils.debug(TAG, "开始绑定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无法绑定了");
                mBluetoothLeService = null;
            }
        };
        
        
    
        public void mybindService(){
            LogUtils.debug(TAG, "---------开始执行onCreate---bindservice");
            Intent gattServiceIntent = new Intent(getActivity(), BluetoothLeService.class);
            getActivity().bindService(gattServiceIntent, mServiceConnection, getActivity().BIND_AUTO_CREATE);
        }
    

    2,连接蓝牙

        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())){
    
                        //暂时写死在这可以连接的BLE设备
                        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, "---字节数组为="+ 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);
            }
        }
    

    当连接上蓝牙后,我们会得到ACTION_GATT_CONNECTED的广播,然后是ACTION_GATT_SERVICES_DISCOVERED,这个时候我们需要对service进行初始化,以便能够读写数据,以下为初始化代码(注意,初始化时候我们需要用到读写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;
    
                    //写数据的服务和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为读和写数据的CHARACTER的UUID,如下,注意这四个UUID

    3,解析数据

    至此,如果顺利的话,我们就可以得到ACTION_DATA_AVAILABLE的广播,也就拿到了从蓝牙设备获得的byte数组,大多数协议里,每个字节代表一个命令。这里涉及到Java中byte值与int值的转换。因为Java中,所有的值都是singed性的,最高位为符号位,所以,大家请自行补下该部分的知识,对于有符号数,它的值相当于取补码,此处将不详述,

    上面为读数据,下面我们说写数据,比如,如下的开关机命令,

    // 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);
    

    这两行代码,是核心代码,但是,我们要进行字节数组的正确传递,这里,给大家贴出来几个很有可能用到的方法,

    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;
    }
    //打印字节数组
    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, "---发送到蓝牙的字节数组为="+ log);
        }
    }
    
    //数组倒序
    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 合并两个byte数组
    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;
    }
    

    写的比较简单,但是关键代码都有了,大家可以参考下!~

    相关文章

      网友评论

        本文标题:Android BLE低功耗蓝牙开发

        本文链接:https://www.haomeiwen.com/subject/xzlfpttx.html