在这系列中的前几篇文章里,我们探索了低耗蓝牙传感器并学习了如何建立连接。剩下的问题就是如何从传感器中获得一些确切的数据,但这并不是像看起来那样直接。在这篇文章里,我们继续探讨GATT特征并学习它是如何在主机和传感器之间进行数据交换的。
在前一篇文章里,我们了解了传感器设备是如何包含了一个GATT服务器的,并且我们还发布了一个连接的方法。GATT服务器包含了一个或多个GATT服务,这些GATT服务代表了不同类型的可被交换的数据。例如,在SensorTag中包含了不同的GATT服务代表着在SensorTag中的每一个不同的传感器组件(湿度传感器、气压传感器等)。
每个GATT服务包含着一个或更多GATT特征。一个GATT特征是一个能够通过BLE传输的数据的原子块。一个GATT特征包含了任意数据和能够表明该数据类型的标识。它还能不包含或者包含更多的GATT描述。
一个GATT描述包含着关于特征的元数据,例如特征值的单位,或者当特征值改变时是否需要通知。
简而言之,一个GATT服务器可以包含一个或者更多GATT服务;每一个GATT服务可以包含一个或者更多的GATT特征;一个GATT特征可以不包含或者包含更多的GATT描述。
在GATT服务,特征和描述中一个共同点是他们都用一个通用唯一标识符(UUID)来辨认。正如UUID的名字所示,一个UUID是一个简单唯一的标识,它被用来检索GATT服务,特征或描述。
对于SensorTag来说这写UUID可以在GATT服务器参考文档中找到。当第一次看的时候可能会有一点困惑,但它确实非常直接。所有的SensorTag UUID都有基本值: F0000000-__0451__-4000-B000-00000000
其中四位加粗的数字是唯一会变的部分来区分不同的GATT服务,特征或者描述。如果我们看湿度传感器,我们能看到它的UUID是AA20(在黄色的行处标记着GATT_PRIMARY_SERVICE_UUID)。这个服务的UUID的全写为F000AA20-0451-4000-B000-00000000
。
这个服务有两个特征:
第一个特征代表着确切的传感器数据,UUID为AA21,全写为 F000AA21-0451-4000-B000-00000000
。数据为4bytes代表着温度值和湿度值:温度最低有效位:温度最低有效位:湿度最低有效位:湿度最高有效位——我们会稍后研究如何解码这些数据。这些特征同样也有一个描述(GATT_CLIENT_CHAR_CFG_UUID
条目),它在文档里没有定义明确的UUID。这是因为这是一个标准的UUID,它为一个公共功能服务,所以它很常用,这是为了注册当这个值变化时我们需要一个通知。同样,我们会在后面更加深入的探讨。
第二个特征是一个标记,我们需要设定这个标记来将特定的传感器打开。它的UUID为AA22,全写为F000AA22-0451-4000-B000-00000000
。同样,我们会在适当的时候介绍该如何实际使用这个标记。
为了进一步解释UUID这里很有必要多说一句,在某些情况下,特别是它们在SensorTag上时,它们不是设备。在许多传感器中可能有带有共同UUID的服务,和被不同制造商支持的结构,这些制造商都为执行相同功能的传感器坚持一个标准的服务标准(例如心率检测仪有标准的GATT服务定义)。同样,在设备发现阶段加入GATT服务UUID也是可能的,这样能够匹配提供特定服务的设备。不幸的是SensorTag并不支持这个功能,所以我在这里不能讨论更多内容。对于那些对此感兴趣的人,可以通过调用不同形式的BluetoothAdapter#onStartLeScan()
来完成。
现在我们了解了服务是如何被组织的,我们可就以直接开始读取数据了吗?不幸的是这并不是那样简单。还记得GATT服务器连接由两部分组成:在传感器上的GATT服务器,和一个本地代理吗?我们或许知道服务而不知道本地代理,所以我们需要引导它来检索传感器上的服务的列表。这可以用discoverServices()
方法完成:
BleService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
; html-script: false ] private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); Log.v(TAG, "Connection State Changed: " + (newState == BluetoothProfile.STATE_CONNECTED ? "Connected" : "Disconnected")); if (newState == BluetoothProfile.STATE_CONNECTED) { setState(State.CONNECTED); gatt.discoverServices(); } else { setState(State.IDLE); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if(status == BluetoothGatt.GATT_SUCCESS) { Log.v(TAG, "onServicesDiscovered: " + status); } } } |
这又是一个异步方法,所以我们可以大胆的在UI线程中调用它。我们需要定义一个确切的回调方法,以便在当发现服务完成时调用。在回调方法中检查状态值是非常重要的,因为有时它会在服务检测完成之前被调用。检查GATT_SUCCESS
值会确保我们只执行了一遍在服务检测实际完成时。
支持的服务一旦被加载到本地代理中,我们可以实际访问到它所包含的特征,我们会在这一系列的文章中继续讨论。
这篇文章中的源码。