______________________________________________________________
Android BLE 框架发布,功能全面,简单易用:
https://github.com/a1anwang/okble
______________________________________________________________
上期讲过android手机可以模拟BLE设备来进行广播,有兴趣的同学可以再去看一下,有助于理解BLE设备广播数据的分析《android BLE Peripheral 手机模拟设备发出BLE广播 BluetoothLeAdvertiser》 《android BLE 手机模拟iBeacon发出广播》
好了,本期重点是 在广播的基础上 可以被其他手机通过BLE的api进行连接,也就是 手机完全模拟成一个BLE设备。
在BLE协议中,有两个角色,周边(Peripheral)和中央(Central)
开发过BLE的app的同学对 BluetoothGatt都不陌生,给设备发送数据,读取数据都是通过BluetoothGatt, BluetoothGatt其实就是中央, BLE设备就是周边来提供数据。 那么 周边 对应的类是 BluetoothGattServer ,注意不是BluetoothGattService.
直接进入主题,创建BluetoothGattServer :
public class MainActivity extends Activity { BluetoothAdapter bluetoothAdapter; BluetoothGattServer gattServer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); gattServer = bluetoothManager.openGattServer(this, gattServerCallback); } private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() { @Override public void onConnectionStateChange(final BluetoothDevice device, int status, int newState) { super.onConnectionStateChange(device, status, newState); LogUtils.e(TAG, " onConnectionStateChange:" + status + " newState:" + newState + " devicename:" + device.getName() + " mac:" + device.getAddress()); } @Override public void onServiceAdded(int status, BluetoothGattService service) { super.onServiceAdded(status, service); LogUtils.e(TAG, " onServiceAdded status:" + status+" service:"+service.getUuid().toString()); } @Override public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { super.onCharacteristicReadRequest(device, requestId, offset, characteristic); LogUtils.e(TAG, " onCharacteristicReadRequest requestId:" + requestId + " offset:" + offset + " characteristic:" + characteristic.getUuid().toString()); } @Override public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); LogUtils.e(TAG, " onCharacteristicWriteRequest requestId:" + requestId + " preparedWrite:" + preparedWrite + " responseNeeded:" + responseNeeded + " offset:" + offset + " value:" + OKBLEDataUtils.Bytes2HexString(value) + " characteristic:" + characteristic.getUuid().toString()); } @Override public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) { super.onDescriptorReadRequest(device, requestId, offset, descriptor); LogUtils.e(TAG, " onCharacteristicReadRequest requestId:" + requestId + " offset:" + offset + " descriptor:" + descriptor.getUuid().toString()); } @Override public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); LogUtils.e(TAG, " onDescriptorWriteRequest requestId:" + requestId + " preparedWrite:" + preparedWrite + " responseNeeded:" + responseNeeded + " offset:" + offset + " value:" + OKBLEDataUtils.Bytes2HexString(value) + " characteristic:" + descriptor.getUuid().toString()); } @Override public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { super.onExecuteWrite(device, requestId, execute); LogUtils.e(TAG, " onExecuteWrite requestId:" + requestId + " execute:" + execute); } @Override public void onNotificationSent(BluetoothDevice device, int status) { super.onNotificationSent(device, status); LogUtils.e(TAG, " onNotificationSent status:" + status); } @Override public void onMtuChanged(BluetoothDevice device, int mtu) { super.onMtuChanged(device, mtu); LogUtils.e(TAG, " onMtuChanged mtu:" + mtu); } }; }
有一个回调 BluetoothGattServerCallback,里面很多回调方法大家应该很眼熟,但是有点区别,和BluetoothGattCallback很像,因为这是成对存在的嘛。
代码写到这里呢,如果把手机开启BLE广播的话(开启方法见上面提到的文章),其他手机是可以扫描到并连接这个模拟设备的,会走到回调:onConnectionStateChange BLE重点不是连接,还是连接之后的数据通信,通信大家都知道特征值BluetoothGattCharacteristic, 没错,那么我们现在来给这个模拟设备加上特征值,特征值是属于BluetoothGattService的,那么我们先创建BluetoothGattService:
BluetoothGattService service1 = new BluetoothGattService(UUID.fromString("0000ccc0-0000-1000-8000-00805f9b34fb"), BluetoothGattService.SERVICE_TYPE_PRIMARY); BluetoothGattCharacteristic characteristic1 = new BluetoothGattCharacteristic( UUID.fromString("0000ccc1-0000-1000-8000-00805f9b34fb"), BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE); service1.addCharacteristic(characteristic1); gattServer.addService(service1);
这里创建了 uuid为ccc0的service, uuid为ccc1的characteristic,然后把 characteristic添加进service里面。
new BluetoothGattCharacteristic() 有3个参数,第一个参数就是uuid,第二个是property属性,例子里填的的是PROPERTY_WRITE,表示可写,也就是手机是可以向这个特征值发送数据的, 第三个参数permission权限,填的是PERMISSION_WRITE,表示有权限写入,其实我一直没理解透为什么还要个permission,如果不想被写入 不要PROPERTY_WRITE可写不就行了嘛。 如果你想能正确写入数据到模拟设备里,这里的permission参数必须填PERMISSION_WRITE,如果你填PERMISSION_READ等其他数据,你write数据下去的时候回调会告诉你失败,无权写入,这个待会我都会一一分析。
这样就完整的创建出带有特征值的BluetoothGattService,熟悉BLE开发的同学还知道,每个BluetoothGattService可以拥有多个BluetoothGattCharacteristic,再加一个特征值:
BluetoothGattCharacteristic characteristic2 = new BluetoothGattCharacteristic( UUID.fromString("0000ccc2-0000-1000-8000-00805f9b34fb"), BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ); service1.addCharacteristic(characteristic2); gattServer.addService(service1);
这样又给BluetoothGattService加了一个特征值,同样的,permission参数必须填PERMISSION_READ,不然读取会回调失败。这样可读可写的特征值都有了(有同学提到了通知,这个下面重点介绍)。接下来我们用2台手机,一台作为中央(手机1号),一台模拟周边(手机2号)来看一下这个运行过程:
为了排版,我用了一张长图片:
好的,读和写的操作很类似,上面的一个过程 详细表示了中央和周围2个角色的运行。
那么再讲一下 notify/indicate
熟悉BLE开发的同学已经知道,使用notify/indicate功能,连接成功之后需要开启 notify/indicate才可以,这个开启的过程其实是对 特征值characteristic下的2902这个描述符descriptor的value 进行设置。
我先上一段 开启notify/indicate 的代码:
private boolean setNotificationOrIndication(boolean enable, BluetoothGattCharacteristic characteristic) { if (characteristic == null) { LogUtils.e(TAG, "setNotificationOrIndication failed, characteristic is null"); return false; } if((characteristic.getProperties()&BluetoothGattCharacteristic.PROPERTY_NOTIFY)==0 &&(characteristic.getProperties()&BluetoothGattCharacteristic.PROPERTY_INDICATE)==0){ LogUtils.e(TAG, "setNotificationOrIndication failed, characteristic has no notification or indication function"); return false; } if (!isConnected()) { LogUtils.e(TAG, "setNotificationOrIndication failed, device not connected"); return false; } if (mBluetoothGatt == null) { LogUtils.e(TAG, "setNotificationOrIndication failed, mBluetoothGatt is null"); return false; } if (!mBluetoothGatt.setCharacteristicNotification(characteristic, enable)) { LogUtils.e(TAG, "setNotificationOrIndication failed"); return false; } BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (clientConfig == null) { LogUtils.e(TAG, "setNotificationOrIndication failed,clientConfig is null"); return false; } byte[] configValue=null; if(enable){ if((characteristic.getProperties()&BluetoothGattCharacteristic.PROPERTY_NOTIFY)!=0){ configValue=BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; }else if((characteristic.getProperties()&BluetoothGattCharacteristic.PROPERTY_INDICATE)!=0){ configValue=BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; } }else{ configValue=BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE; } boolean b = clientConfig.setValue(configValue); if (!b) { LogUtils.e(TAG, "setNotificationOrIndication failed,clientConfig setValue failed"); return false; } b = mBluetoothGatt.writeDescriptor(clientConfig); LogUtils.e(TAG, "setNotificationOrIndication:" + b); return b; }
开启 notification/indication 的代码里我们可以看到 首先获取 了特征值的 uuid为2902的这个BluetoothGattDescriptor
为什么是2902?其实这就是BLE协议的规定,2902就是专门用来描述特征值是否拥有notify/indicate的功能的。
然后给这个descriptor 设置了value :
打开notification 对应的value为 BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
打开indication对饮的value为BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
关闭对应的value均为BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
我们来看一下这些常量对应的源码:
好了,不多BB,直接给我们的模拟设备(手机2)加上一个拥有notification功能的特征值:
public class MainActivity extends AppCompatActivity { private String TAG = "MainActivity"; Handler handler = new Handler(); BluetoothAdapter bluetoothAdapter; BluetoothLeAdvertiser mBluetoothLeAdvertiser; BluetoothGattServer gattServer; TextView tv_status; BluetoothManager bluetoothManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); gattServer = bluetoothManager.openGattServer(this, gattServerCallback); BluetoothGattCharacteristic characteristic1 = new BluetoothGattCharacteristic( UUID.fromString("0000ccc1-0000-1000-8000-00805f9b34fb"), BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE); BluetoothGattCharacteristic characteristic2 = new BluetoothGattCharacteristic( UUID.fromString("0000ccc2-0000-1000-8000-00805f9b34fb"), BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ); BluetoothGattService service1 = new BluetoothGattService(UUID.fromString("0000ccc0-0000-1000-8000-00805f9b34fb"), BluetoothGattService.SERVICE_TYPE_PRIMARY); service1.addCharacteristic(characteristic1); service1.addCharacteristic(characteristic2); gattServer.addService(service1); BluetoothGattCharacteristic characteristic3 = new BluetoothGattCharacteristic( UUID.fromString("0000bbb1-0000-1000-8000-00805f9b34fb"), BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ , BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ ); characteristic3.addDescriptor(new BluetoothGattDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"), BluetoothGattDescriptor.PERMISSION_WRITE)); final BluetoothGattService service2 = new BluetoothGattService(UUID.fromString("0000bbb0-0000-1000-8000-00805f9b34fb"), BluetoothGattService.SERVICE_TYPE_PRIMARY); service2.addCharacteristic(characteristic3); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); gattServer.addService(service2); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
这里面有几个注意点:
1.我新建了一个uuid为bbb0的服务service2,一个 uuid为bbb1的特征值characteristic3 ,然后给bbb1这个特征值添加了 2902这个Descriptor,必须添加这个Descriptor。
2.在 new bbb1 这个characteristic3 的时候,第二个property参数我填的是:
BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ没错,使用|(或)运算,熟悉BLE开发的同学遇到过 有些特征值 同时拥有读/写/通知等组合功能, 我这里这样写就可以让bbb1这个特征值同时拥有这3个操作,可以自由组合。 第三个 permission参数 填的是
BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ
也是使用|(或)运算,既然bbb1同时可读可写,那么permission也需要把2个加上,不然会权限不够,读写失败。
3.在添加了 第一个 service1之后,我用线程睡眠了1000毫秒,然后才添加的service2,这是我在测试中发现连续添加service,有时候会有部分service添加失败,没有回调onServiceAdded,这个跟 连续进行读写特征值,有些操作失败一样 应该是一个道理(虽然读写特征值的源码里有busy这个boolean值可以明确的看出来蓝牙操作事个耗时操作,需要排队进行, 但是我在gattserver源码里没有找到连续添加失败的原因,也没有busy这个变量,但我想原理应该也是一样的吧)。
那么用手机1(中央)连接手机2(周边),然后打开通知,进行数据传递,前面打开通知的过程跟上面的读写过程差的不多:
手机1 调用 setNotificationOrIndication 会像2902的descriptor写入数据,然后手机2会收到回调onDescriptorWriteRequest:
int i = 0; @Override public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) { super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value); LogUtils.e(TAG, " onDescriptorWriteRequest requestId:" + requestId + " preparedWrite:" + preparedWrite + " responseNeeded:" + responseNeeded + " offset:" + offset + " value:" + OKBLEDataUtils.Bytes2HexString(value) + " characteristic:" + descriptor.getUuid().toString()); gattServer.sendResponse(device, requestId,0, offset, value); new Thread(new Runnable() { @Override public void run() { while (isConnected(device)) { try { Thread.sleep(1000); notifyData(device, "bbb1", new byte[]{(byte) i}, false); i++; } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } private void notifyData(final BluetoothDevice device, String uuid, byte[] value, final boolean confim) { final String entireUUID = CommonUUIDUtils.Common_UUID_String_xxxx.replace("xxxx", uuid.toLowerCase()); BluetoothGattCharacteristic characteristic = null; for (BluetoothGattService service : gattServer.getServices()) { for (BluetoothGattCharacteristic mcharacteristic : service.getCharacteristics()) { if (mcharacteristic.getUuid().toString().equals(entireUUID)) { characteristic = mcharacteristic; break; } } } if (characteristic != null) { characteristic.setValue(value); gattServer.notifyCharacteristicChanged(device, characteristic, confim); } }
这里代码收到onDescriptorWriteRequest回调后,也是用了gattServer.sendResponse(device, requestId,0, offset, value);进行回复,然后手机1就会收到回调:public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) 表示开启完成。
然后手机2(周边) 里,我用了一个线程,来循环发送数据,数据发送后手机2会收到回调:
@Override public void onNotificationSent(BluetoothDevice device, int status) { super.onNotificationSent(device, status); LogUtils.e(TAG, " onNotificationSent status:" + status); }
,手机1会收到回调:
@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); LogUtils.e(TAG," onCharacteristicChanged characteristic:"+characteristic.getUuid().toString() +" value:"+OKBLEDataUtils.Bytes2HexString(characteristic.getValue())); }
然后手机1收到的数据打印结果如下:
07-03 17:34:11.955 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:00 07-03 17:34:12.950 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:01 07-03 17:34:14.030 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:02 07-03 17:34:15.067 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:03 07-03 17:34:15.987 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:04 07-03 17:34:17.031 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:05 07-03 17:34:18.111 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:06 07-03 17:34:19.034 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:07 07-03 17:34:20.026 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:08 07-03 17:34:21.147 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:09 07-03 17:34:22.076 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:0A 07-03 17:34:23.070 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:0B 07-03 17:34:24.067 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:0C 07-03 17:34:25.150 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:0D 07-03 17:34:26.110 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:0E 07-03 17:34:27.110 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:0F 07-03 17:34:28.112 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:10 07-03 17:34:29.192 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:11 07-03 17:34:30.147 12132-12147/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:12 07-03 17:34:31.192 12132-12580/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:13 07-03 17:34:32.190 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:14 07-03 17:34:33.187 12132-12148/com.a1anwang.mybledemo E/OKBLEDevice: onCharacteristicChanged characteristic:0000bbb1-0000-1000-8000-00805f9b34fb value:15
好了,notification过程介绍完毕,indication跟这个差不多。
其他注意点:
1. 周边(手机2)APP在每次重新运行时,模拟出的BLE设备的MAC地址都是随机变化的,也就是说 手机2每次重新运行这个模拟BLE设备的app的时候,手机1在进行扫描的时候,扫描到的 模拟设备(手机2)的MAC地址都是不一样的。其实更准确的说法:手机2每次使用BluetoothLeAdvertiser.startAdvertising() 开启模拟BLE广播的时候,这个模拟的BLE设备的MAC地址都是不一样的。
而,在周边BluetoothGattServer的回调onConnectionStateChange里的 BluetoothDevice,这个蓝牙设备是指 中央设备即手机1,这个device的MAC地址不会随着APP的重启而改变,只会随着 手机蓝牙开关的重启而改变(重启手机?蓝牙也会重启,当然也改变)
private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() { @Override public void onConnectionStateChange(final BluetoothDevice device, int status, int newState) { super.onConnectionStateChange(device, status, newState); //这个device是中央设备, mac地址会 因为 中央(手机)蓝牙重启而变化 LogUtils.e(TAG, " onConnectionStateChange:" + status + " newState:" + newState + " devicename:" + device.getName() + " mac:" + device.getAddress()); }
2.周边(手机2)在被连接成功后 如果调用BluetoothLeAdvertiser.stopAdvertising 停止BLE广播的话,会造成断线,这一点和我们连接真正的智能硬件时体验不一样。
3.周边(手机2)在被 中央设备(手机1的APP)连接后,周边设备的广播还是在继续,依然可以被其他 手机的APP或者 手机1的其他的APP连接并通信
最后附上 手机模拟BLE设备BluetoothGattServer 的使用代码:http://pan.baidu.com/s/1nvQaoit
原文地址《android BLE Peripheral BluetoothGattServer 手机模拟设备,可以被连接 通信》
同时拥有读/写/通知等组合功能,我觉得不应该是直接相加,因为相加后没法拆开啊
应该用 | 合并,然后用 & 可以拆开,+和 | 可能结果一样(这种情况刚好正确),但也可能不一样
01 + 10 = 01 | 10 = 11
11 + 10 = 101
11 | 10 = 11