回顾:
在本系列前面的文章中我们完成了发现BLE传感器并与之建立连接。现在只剩下从其中获取数据了,但是这并没有看起来那么简单。在这篇文章中我们将讨论GATT的特点以及如何促进主机与传感器之间的数据交换。
GATT服务器的结构:
在前面的文章看我们了解了传感器包含GATT服务器,我们也已经与GATT建立连接。GATT服务器包含一个或多个GATT服务,不同的GATT服务代表了可以进行交换的不同类型的数据。例如,在SensorTag中有不同的GATT服务分别代表着SensorTag中不同的传感器组件(湿度传感器,气压传感器,等)。
每个GATT服务包含一个或多个GATT特性。一个GATT特性是一个可以通过BLE进行传递的原子数据。一个GATT特性可以包含任意的数据,用一个类型标识符表示任意数据的类型。它也可以包含零个或多个GATT描述符。
GATT描述符包含了关于该GATT特性的原子数据,例如GATT特性值的单位,或者当GATT特征值变化时是否需要通知我们。
总之,GATT服务器可以包含一个或多个GATT服务;每个GATT服务可以包含一个或多个GATT特性;每个GATT特性可包含零个或更多个GATT描述符。
GATT服务、特征以及描述符的一个共同点是他们都是使用一个通用唯一标识符(UUID)标识。正如UUID名称所表现的那样,UUID是一个简单且唯的标识符,用来找到GATT服务、特征以及描述符。
SensorTag的UUIDs可在 GATT服务器参考文档 中找到的。这可能有点令人困惑,但它其实是很简单的。所有的SensorTag UUID 都是基于这种形式:F0000000-0451-4000-B000- 00000000。四个黑体字的数字是不同的GATT服务、特征、描述唯一不一样的地方。如果我们看看下面的湿度传感器,我们可以看到其UUID是为AA20(类型为GATT_PRIMARY_SERVICE_UUID)。此服务的完整的UUID是F000AA20-0451-4000-B000-00000000.
服务有两个特征:
第一个代表着实际的传感器数据,UUID为AA21,转换后完整的UUID为 F000AA21-0451-4000-B000- 00000000。这些数据为4个字节,表示的温度和湿度值:TempLSB:TempMSB:HumidityLSB:HumidityMSB 之后我们将看看该如何解码。该特征有一个描述符(GATT_CLIENT_CHAR_CFG_UUID 条目),在文档中不会有该特征的UUID定义。这是因为这是一个标准的UUID,因为它提供一个公共的功能而被共同使用,就是当该值改变时我们需要被通知到。我们将在后面深入讨论这一点。
第二个特征是一个需要我们设置其为打开状态的标志,UUID为AA22,完整的UUID为 F000AA22-0451-4000-B000- 00000000。还是那句话,我们将在适当的时候看到其使用方法。
我们有必要去解释一些关于UUID以及UUID怎么工作的事情,在某些情况下,他们可能不像在SensorTag上那样作为设备的显著的特征。在许多传感器上可能很多服务都拥有一个共同的UUID和结构,不同的厂商都坚持将执行相同的功能的传感器使用一个标准的GATT服务标准(例如有一个专门的心率仪GATT服务标准等)。Also, it is possible to include GATT service UUIDS in the device discovery phase to only match devices which offer specific services. 。不幸的是,SensorTag不支持这个,所以在这篇教程不涉及这一块。如果你有兴趣的话,可以通过调用 BluetoothAdapter#onStartLeScan() 方法的不同形式来实现。
那么,现在我们知道了服务是如何组织的,我们可以开始读取数据了,是吗?不幸的是,它不是那么简单的。记住,与GATT服务器的连接由两部分组成:传感器上的GATT服务器,和一个本地代理。虽然我们可能知道GATT服务,本地代理不知道,所以我们要让本地代理从传感器中获取其服务列表。这个通过使用 discoverservices() 方法实现:
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 状态将确保一旦服务发现完成我们的回调方法只执行一次。
下期预告:
一旦支持的服务被装载到当地的代理,我们就可以访问服务所包含的特征,我们将在本系列文章的总结中讨论这一点。
本文的源代码在这里 可以找到。