谈谈蓝牙4.0(BLE)模块与安卓的数据交互

开发蓝牙4.0也快两个月了,给我的感受还是颇多的。
我开发的是与TI的蓝牙模块CC2540交互的安卓端蓝牙软件,对于安卓都不是很熟悉的我,是一个不小的挑战。
废话不多说,直接谈项目。
我用的是google官方的源码,它本身把很多基本框架都搭好了。直接就能运行,当然前提是在Android Studio上,如果是Eclipse上还需要稍作改动。我们先来看下代码。它主要有两个Avtivity和一个Service组成,顾名思义,两个Activity的用途分别为设备扫描与设备管理。
这里我不准备介绍基本的BLE知识(自行百度即可),我只是说一下一些需要注意的地方,特别是我百度不到的好多东西,希望跟大家分享一下。
一开始的软件已经可以查看Service和Characteristic的UUID,不过只能接收消息,原因大概是这个Sample当初设计的时候就只负责接收。当然要发送也简单,只要在BluetoothLeService中加writeCharacteristic方法。

public boolean writeCharacteristic(BluetoothGattCharacteristic charac,String message){
        //check mBluetoothGatt is available
        if (mBluetoothGatt == null) {
            Log.e(TAG, "lost connection");
            return false;
        }
        charac.setValue(message.getBytes());
        boolean status = mBluetoothGatt.writeCharacteristic(charac);
        return status;
}

写数据比较简单,直接调用这个方法即可。当然,这里也有个陷阱,我们发现,如果你在cc2540中定义了Byte字节的长度,那么在charac.setValue()中的数组长度要跟其相同,不然模块是收不到的。
我们发现在很多地方都用到了BluetoothGattCharacteristic这个类,即特性,BLE就是靠改变特性值来传递数据的,那么,这些BluetoothGattCharacteristic是从哪里得到的呢?我们知道,BluetoothGattCharacteristic是BluetoothGattService中的属性,我们要得到BluetoothGattCharacteristic,就要先得到BluetoothGattService。那要怎么样得到BluetoothGattService呢?

        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                // Attempts to discover services after successful connection.
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

这里先简单见一下回调,开发过安卓的朋友们应该了解,在安卓开发中,某个动作可能会出发后续的动作,这个被触发的动作就是回调。这里onConnectionStateChange函数就是在连接状态改变后会触发的回调,我们看到mBluetoothGatt.discoverServices()这里执行了发现服务的方法,那么在发现完服务呢?没错,它同样触发回调。

@Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else { 
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

它会调用onServicesDiscovered(BluetoothGatt gatt, int status)方法,在这里它会发出一个广播broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED)。值得注意的是,在这里,BluetoothLeService与DeviceControlActivity交互的方式是广播。我们看到broadcastUpdate方法,在BluetoothLeService中。

    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        sendBroadcast(intent);
    }

    private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
        }
        sendBroadcast(intent);
    }

这里有两个broadcastUpdate,但效果是相似的,他们都调用sendBroadcast(intent),至于sendBroadcast方法,就是底层的一些实现了,这里不用管他。我们知道了,在发现了服务之后,BluetoothLeService就sendBroadcast。那么,这个Broadcast传到哪里呢?答案是这里。

    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            mBluetoothLeService.getSupportedGattServices();
         }
        }
    }

在DeviceControlActivity中有个onReceive方法,就是接受广播的地方。他通过参数intent的值判断广播的内容。ACTION_GATT_SERVICES_DISCOVERED这个参数表示发现服务。这里再调用getSupportedGattServices就可以获得服务啦。然后特性什么的就都知道了。
目前为止我们解决了往特性里写值的问题,那么,如何读取模块发来的消息呢?我们来说说具体的接收流程。
我们首先要明确的是,安卓端接收消息的被动的,它只能被动地告知要接收消息,而告诉他的人就是模块。这里我们说一下特性,特性有自身的一些属性,其实一个服务的每个特性都有自己独立的作用,这个可以自己定义,主要有可写,可读,还有专门用来通知的,即Notification,在模块改变了特性值,他得让手机知道,那么他就通过有Notification功能的特性发出通知。那么,如果模块的Service如果有好多特性,他怎么知道往哪个特性中发通知呢?所以,我们必须先告知模块我们通过那个特性接收通知,代码如下。

public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                              boolean enabled) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }

        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                UUID.fromString(DeviceControlActivity.NotificationCharacDescripter));
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mBluetoothGatt.writeDescriptor(descriptor);  

        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

    }

这里设置了BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE,相当于告知模块,我们从这个特性接受通知。然后,如果模块改变了其他特性值,他就会通过这个特性通知我们去读取。
在我们接收到通知后,安卓触发回调onCharacteristicChanged,并广播通知DeviceControlActivity去接收消息。
这里我们再看一下broadcastUpdate函数,这里调用的是private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic)。

private void broadcastUpdate(final String action,
                                 final BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
        }
        sendBroadcast(intent);
    }

上面的代码中BluetoothLeService把接收的数据保存到了EXTRA_DATA里面。

if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
    intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
}

DeviceControlActivity再通过这里接收广播并取出数据,完成数据接收。
好了,这大概就是安卓发送接收数据的过程。有问题欢迎指正!

你可能感兴趣的:(蓝牙4-0)