xml地图|网站地图|网站标签 [设为首页] [加入收藏]

智能家电

当前位置:美高梅游戏网站 > 智能家电 > Android BLE低功耗蓝牙5.0开垦

Android BLE低功耗蓝牙5.0开垦

来源:http://www.gd-chuangmei.com 作者:美高梅游戏网站 时间:2019-09-10 13:43
  • BLE 与经典蓝牙的区别
  • BLE 的 Kotlin 下实践

最近做了一个智能硬件开发(针灸仪)的项目,有一部分涉及到低功耗蓝牙的开发,就是通过蓝牙和设备进行数据的交互,比如控制改设备的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不一致,请主动忽略,关注蓝牙核心代码)

经典蓝牙(Classic Bluetooth)& 低功耗蓝牙(Bluetooth Low Energy)

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

  • 经典蓝牙可以用与数据量比较大的传输,如语音,音乐,较高数据量传输等。

  • BLE 特点就如其名,功耗更低的同时,对数据包做出了限制。所以适用于实时性要求比较高,但是数据速率比较低的产品,如鼠标,键盘,传感设备的数据发送等。

一,蓝牙设备的扫描

这一部分基本上很简单,只要设备上电以后,这部分代码执行后,便可以扫描出设备,并获得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();
                }
            });
        }
    }

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

蓝牙 4.0 支持单模和双模两种部署方式,其中单模即是我们说的 BLE,而双模指的是 Classic Bluetooth + BLE 。实际上,BLE 和经典蓝牙的使用等各方面都像是没有关联的两个东西,甚至因为 BLE 的通讯机制不同,所以是不能向下兼容的;经典蓝牙则可以兼容到蓝牙 3.0 / 2.1。

二,蓝牙设备的连接

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;
}

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

经典蓝牙

参考官方文档,因为有中文文档,所以只要看这一篇文档就可以应付一般的开发了。

最重要的是这一次项目里的硬件貌似不能支持经典蓝牙,所以并没有实践的机会。

BLE

同样,有条件一定要去看官方文档,然而这一次并没有中文版,或许可以找一些国内大佬们翻译的版本。还有就是大佬 JBD 写的 Android BLE 蓝牙开发入门 ,而且还用 RxJava 封装成一个库可以直接调用:RxBLE ,是真的厉害,不妨去学习学习。

  • 概念与常用 API

UUID:每个服务和特征都会有唯一的 UUID ,由硬件决定。服务:蓝牙设备中可以定义多个服务,相当于功能的集合。特征(Characteristic):一个服务可以包含多个特征,可以通过 UUID 获取到对应的特征的实例,通过这个实例就可以向蓝牙设备发送 / 读取数据。

BluetoothDeivce:调用 startLeScan()获取该实例,用于连接设备。BluetoothManager:蓝牙管理器,调用 getSystemService() 获取,用于获取蓝牙适配器和管理所有和蓝牙相关的东西。BluetoothAdapter:蓝牙适配器,通过 BluetoothManager 获取,用于打开蓝牙、开始扫描设备等操作。BluetoothGatt:通用属性协议, 定义了BLE通讯的基本规则,就是通过把数据包装成服务和特征的约定过程。BluetoothGattCallback:一个回调类,非常重要而且会频繁使用,用于回调 GATT 通信的各种状态和结果。BluetoothGattService:服务,通过 BluetoothGatt 实例调用 getService 获取BluetoothGattCharacteristic:特征,通过 BluetoothGattService 实例调用 getCharacteristic 获取,是 GATT 通信中的最小数据单元。BluetoothGattDescriptor:特征描述符,对特征的额外描述,包括但不仅限于特征的单位,属性等。

  • 声明权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <!-- Android 5.0 及以上需要添加 GPS 权限 --><uses-feature android:name="android.hardware.location.gps" /><!-- Android 6.0 及以上需要添加定位权限 --><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  • 初始化
fun initBluetoothAdapter(){ val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter //如果蓝牙没有打开则向用户申请 if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) bluetoothAdapter.enable()}
  • 扫描设备与停止扫描
var mDevice : BluetoothDevice ?= null//扫描结果的回调,开始扫描后会多次调用该方法val mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord -> //通过对比设备的 mac 地址获取需要的实例 if(device.address == "50:F1:4A:A1:77:00"){ mDevice = device }}//开始扫描之前判断是否开启了蓝牙,enable 为 false 可以停止扫描fun scanLeDeviceWithBLE(enable:Boolean = true){ if (mBluetoothAdapter == null) initBluetoothAdapter() if (mBluetoothAdapter?.isEnabled as Boolean){ mBluetoothAdapter?.enable() } if { mScanning = true mBluetoothAdapter?.startLeScan(mLeScanCallback) TimeUtilWithoutKotlin.Delay(8,TimeUnit.SECONDS).setTodo { mBluetoothAdapter?.stopLeScan(mLeScanCallback) mScanning = false } }else { //停止扫描,在连接设备时最好调用 stopLeScan() mBluetoothAdapter?.stopLeScan(mLeScanCallback) mScanning = false }}

其实 startLeScan() 已经被声明为过时,所以开始扫描还有其他的方法:

private fun startDiscover() { //这种方法需要注册接收广播,获取扫描结果。 val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() bluetoothAdapter?.startDiscovery()}//注册广播,监听 BluetoothDevice.ACTION_FOUND 获取扫描结果private inner class DeviceReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action = intent.action if (BluetoothDevice.ACTION_FOUND.equals { val device = intent .getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) Log.e("Service","device: ${device.address}") } }}
  • 连接蓝牙设备此时已经获取到了蓝牙设备的实例:mDevice,开始连接

本文由美高梅游戏网站发布于智能家电,转载请注明出处:Android BLE低功耗蓝牙5.0开垦

关键词: