安卓蓝牙4.0BLE 通信

来源:互联网 发布:软件测试技术语言 编辑:程序博客网 时间:2024/06/05 03:13

            安卓蓝牙4.0BLE通信之体重称

          最近正在做一个关于手机跟蓝牙体重称之间数据交互的工程,因为之前没接触过蓝牙开发,所以浪费了不少时间,但是经过查资料和大神们的文章,终于在一周后完工了,现在总结下这个demo,先来介绍下关于蓝牙BLE有关的知识。

        什么是BLE?

        BLE是蓝牙4.0的核心Profile,主打功能是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低,由于BLE的低功耗特点,因此普遍用于穿戴设备。Android 4.3才开始支持BLE API,所以请各位客官把本文代码运行在蓝牙4.0和Android 4.3及其以上的系统

      BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多Descriptor,一个Descriptor包含一个Value一般来说,Characteristic是手机与BLE终端交换数据的关键,两者就是通过改变Characteristic的值来实现数据交互的Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTYCharacteristic的PROPERTY可以通过位运算符组合来设置读写属性,例如READ|WRITE、READ|WRITE_NO_RESPONSE|NOTIFY,因此读取PROPERTY后要分解成所用的组合。

   下面直接贴代码,边贴核心代码边解释。

     上面是我的项目目录结构,BluetothLeClass是提取的蓝牙工具类,Conversion与Utils是转码的工具类,ProgressDialogsUtils是对话框工具类,LeDeviceListAdapter是设备ListView的Adapter,activity_main.xml的视图如下:

       点击蓝牙搜索按钮后,会查找蓝牙设备,然后将搜索到的设备显示在ListView上,再通过点击列表项,去连接设备,再发数据给设备然后处理设备发回的回调信息,下面粘一下核心代码:

            这是按钮点击事件的方法,主要功能是判断手机是否支持蓝牙4.0,开启蓝牙和设置发现设备和数据交互的回调:

 public void onClick(View v) {        switch (v.getId()) {            case R.id.btn_search_bluetooth:                openBluetooth();                break;        }    }    private void openBluetooth() {        //判断手机是否支持蓝牙4.0        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {            Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();            finish();        }        final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);        mBluetoothAdapter = bluetoothManager.getAdapter();        if (mBluetoothAdapter == null) {            Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();            finish();            return;        }        //开启蓝牙        mBluetoothAdapter.enable();        mBLE = new BluetoothLeClass(this, mHandler);        if (!mBLE.initialize()) {            Log.e(TAG, "Unable to initialize Bluetooth");            finish();        }        //发现BLE终端的Service时回调        mBLE.setOnServiceDiscoverListener(mOnServiceDiscover);        //收到BLE终端数据交互的事件        mBLE.setOnDataAvailableListener(mOnDataAvailable);        mLeDeviceListAdapter = new LeDeviceListAdapter(this);        ls_setup.setAdapter(mLeDeviceListAdapter);        scanLeDevice(true);    }

          这是发现设备的回调接口:

  private BluetoothLeClass.OnServiceDiscoverListener mOnServiceDiscover = new BluetoothLeClass.OnServiceDiscoverListener() {        @Override        public void onServiceDiscover(BluetoothGatt gatt) {            Log.w(TAG, "onServiceDiscover");            //gattServices=mBLE.getSupportedGattServices();            displayGattServices(mBLE.getSupportedGattServices());            Log.e(TAG, "found");        }    };

       这是数据交互的回调接口:

/**     * 收到BLE终端数据交互的事件     */    private BluetoothLeClass.OnDataAvailableListener mOnDataAvailable = new BluetoothLeClass.OnDataAvailableListener() {        /**         * BLE终端数据被读的事件         */        @Override        public void onCharacteristicRead(BluetoothGatt gatt,                                         BluetoothGattCharacteristic characteristic, int status) {            Log.w(TAG, "onServiceDiscover");            if (status == BluetoothGatt.GATT_SUCCESS)                Log.e(TAG, "onCharRead " + gatt.getDevice().getName()                        + " read "                        + characteristic.getUuid().toString()                        + " -> "                        + Utils.bytesToHexString(characteristic.getValue()))                        ;        }        /**         * 收到BLE终端写入数据回调         */        public void onCharacteristicWrite(BluetoothGatt gatt,                                          BluetoothGattCharacteristic characteristic) {            mProgressDialogUtils.dismissProgressDialog();            Log.e(TAG, "onCharWrite " + gatt.getDevice().getName()                            + " write "                            + characteristic.getUuid().toString()                            + " -> "                            + "结果是:" + Utils.bytesToHexString(characteristic.getValue())            );            if (gatt.getDevice().getName().equalsIgnoreCase("Chipsea-BLE")) {                getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(10, 14));            } else {                getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(4, 8));            }        }

displayGattServices方法主要是输出搜索到的设备中的各种Services,Characteristic,和Descriptor的各种信息,同时设置Charicteristic被写的通知,往BLE设备上写数据,
和读出数据

private void displayGattServices(List<BluetoothGattService> gattServices) {        Log.w(TAG, "displayGattServices");        if (gattServices == null) return;        for (BluetoothGattService gattService : gattServices) {            //-----Service的字段信息-----//            int type = gattService.getType();            Log.e(TAG, "-->service type:" + Utils.getServiceType(type));            Log.e(TAG, "-->includedServices size:" + gattService.getIncludedServices().size());            Log.e(TAG, "-->service uuid:" + gattService.getUuid());            //-----Characteristics的字段信息-----//            List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();            for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {                Log.e(TAG, "---->char uuid:" + gattCharacteristic.getUuid());                int permission = gattCharacteristic.getPermissions();                Log.e(TAG, "---->char permission:" + Utils.getCharPermission(permission));                int property = gattCharacteristic.getProperties();                Log.e(TAG, "---->char property:" + Utils.getCharPropertie(property));                byte[] data = gattCharacteristic.getValue();                if (data != null && data.length > 0) {                    Log.e(TAG, "---->char value:" + Utils.bytesToHexString(data));                }                //UUID_KEY_DATA是可以跟蓝牙模块串口通信的Characteristic                //if (gattCharacteristic.getUuid().toString().equals(UUID_KEY_DATA)) {                //当测试读取前Characteristic数据,会触发mOnDataAvailable.onCharacteristicRead()                //接受Characteristic被写的通知,收到蓝牙模块的数据后会触发mOnDataAvailable.onCharacteristicWrite()                mBLE.setCharacteristicNotification(gattCharacteristic, true);                //设置数据内容                //gattCharacteristic.setValue("A5,00,19,AF,50,5A,19");                //attCharacteristic.setValue(Conversion.HexString2Bytes("A5019AF505A19"));                byte[] b = {(byte) 0xA5, (byte) 0x00, (byte) 0x19, (byte) 0xAF, (byte) 0x50, (byte) 0x5A, (byte) 0x19};                gattCharacteristic.setValue(b);                //往蓝牙模块写入数据                mBLE.writeCharacteristic(gattCharacteristic);                mHandler.postDelayed(new Runnable() {                    @Override                    public void run() {                        mBLE.readCharacteristic(gattCharacteristic);                    }                }, 500);                Log.e(TAG, "转换是C:" + Utils.bytesToHexString(b));                //}                //-----Descriptors的字段信息-----//                List<BluetoothGattDescriptor> gattDescriptors = gattCharacteristic.getDescriptors();                for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) {                    Log.e(TAG, "-------->desc uuid:" + gattDescriptor.getUuid());                    int descPermission = gattDescriptor.getPermissions();                    Log.e(TAG, "-------->desc permission:" + Utils.getDescPermission(descPermission));                    byte[] desData = gattDescriptor.getValue();                    if (desData != null && desData.length > 0) {                        Log.e(TAG, "-------->desc value:" + Utils.bytesToHexString(desData));                    }                }            }        }//    }

setValue()为写数据关键,参数为设备上规定的APP到BLE设备上的协议内容。必须设置数据改变通知监听:mBLE.setCharacteristicNotification(gattCharacteristic, true);不然特征值改变也收不到回调信息,具体的通知方法为:

    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,                                              boolean enabled) {        if (mBluetoothAdapter == null || mBluetoothGatt == null) {            Log.w(TAG, "BluetoothAdapter not initialized");            return;        }        List<BluetoothGattDescriptor> gattDescriptors = characteristic.getDescriptors();        for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) {            gattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);            mBluetoothGatt.writeDescriptor(gattDescriptor);//写数据回调关键            mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);        }    }

谨记,只有mBluetoothGatt.writeDescriptor(gattDescriptor);才能保证可准确收到BLE设备往特征值写入数据的监听,具体原理我也不是特别清楚,只知道删除这句话将收不到监听方法。

下面来介绍下当BLE设备往特征值写入数据时的回调方法:

 public void onCharacteristicWrite(BluetoothGatt gatt,                                          BluetoothGattCharacteristic characteristic) {            mProgressDialogUtils.dismissProgressDialog();            Log.e(TAG, "onCharWrite " + gatt.getDevice().getName()                            + " write "                            + characteristic.getUuid().toString()                            + " -> "                            + "结果是:" + Utils.bytesToHexString(characteristic.getValue())            );            if (gatt.getDevice().getName().equalsIgnoreCase("Chipsea-BLE")) {                getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(10, 14));            } else {                getWeight(gatt, Utils.bytesToHexString(characteristic.getValue()).substring(4, 8));            }        }

当设备写入数据时,将对话框取消掉,因为我连接的体重秤有两种,协议的内容也不同,第一张设备中是去回调值的第10到14位,而第二种是取4到8位,所以我根据设备名称做了个判断,getWeight()方法主要是转码和计算体重值:

private void getWeight(BluetoothGatt gatt, String s) {            int a = Integer.parseInt(s, 16);            if (mt == null) {                mt = new MyThread();                mt.start();                Log.e(TAG, "当前线程为current:" + mt.currentThread() + cout++);            }            if (gatt.getDevice().getName().equalsIgnoreCase("Chipsea-BLE")) {                double w = a;                weight = w / 10;            } else {                double w = a;                weight = w / 100;            }        }    };

这里我通过handle与Thead的方法,当体重秤的数据改变时,我不断地刷新界面上的体重值,具体代码为:

class MyThread extends Thread {        @Override        public void run() {            super.run();            while (finish) {                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                Message msg = new Message();                Bundle bundle = new Bundle();                bundle.putDouble("weight", weight);                msg.setData(bundle);                msg.what = 1;                mHandler.sendMessage(msg);            }        }    }

private void intHandle() {        mHandler = new Handler() {            @Override            public void handleMessage(Message msg) {                super.handleMessage(msg);                switch (msg.what) {                    case 1:                        Bundle b = msg.getData();                        setWeight(b.getDouble("weight"));                        if (weight == 0.0) {                            mHandler.removeCallbacks(mt);                            mt = null;                        }                        break;                    case 2:                        Log.e(TAG, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~handlemessage" + i++);                        mProgressDialogUtils.dismissProgressDialog();                        mProgressDialogUtils.showProgressDialog(MainActivity.this, "连接成功,正常测量您的体重,请勿离开称体!");                        break;                    case 3:                        mProgressDialogUtils.dismissProgressDialog();                        mProgressDialogUtils.showProgressDialogWithButton(MainActivity.this, "连接失败!", "确定");                        break;                }            }        };    }

最后的结果为:




时间比较赶,所以逻辑有点混乱,思路不是很清晰,所以提供源码给各位小伙伴参考,谢谢!

源码地址:点击打开链接











0 0
原创粉丝点击