Bluetooth LE(低功耗蓝牙) - 第六部分(完)
在本系列前面的文章中我们已经了解了,在我们从一个TI SensorTag中获取温度和湿度数据之前,我们需要经历的各种步骤。在本系列中的最后一篇文章,我们将完成注册并接收SensorTag的通知,并接收温度和湿度数据。
接收数据:
现在,本地的代理组件知道了传感器所提供的服务,我们可以开始使用这些服务了。为了使用它们,我们首先需要获得服务,然后是该服务所包含的特征,最后是特征的描述符。
一个GATT服务表现为一个 BluetoothGattService 对象,我们需要通过适当的UUID从 BluetoothGatt 实例中获得;一个GATT特征表示为一个 BluetoothGattCharacteristic 对象,我们可以通过适当的UUID从 BluetoothGattService 中得到;一个GATT描述符表现为一个 BluetoothGattDescriptor 对象,我们可以通过适当的UUID从BluetoothGattCharacteristic 对象中获得:

private static final UUID UUID_HUMIDITY_SERVICE =
UUID.fromString("f000aa20-0451-4000-b000-000000000000");
private static final UUID UUID_HUMIDITY_DATA =
UUID.fromString("f000aa21-0451-4000-b000-000000000000");
private static final UUID UUID_HUMIDITY_CONF =
UUID.fromString("f000aa22-0451-4000-b000-000000000000");
private static final UUID UUID_CCC =
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
.
.
.
private void subscribe(BluetoothGatt gatt) {
BluetoothGattService humidityService =
gatt.getService(UUID_HUMIDITY_SERVICE);
if(humidityService != null) {
BluetoothGattCharacteristic humidityCharacteristic =
humidityService.getCharacteristic(UUID_HUMIDITY_DATA);
BluetoothGattCharacteristic humidityConf =
humidityService.getCharacteristic(UUID_HUMIDITY_CONF);
if(humidityCharacteristic != null && humidityConf != null) {
BluetoothGattDescriptor config =
humidityCharacteristic.getDescriptor(UUID_CCC);
if(config != null) {
}
}
}
}

为了使示例代码清晰,我已经移除了所有的错误信息展示代码,但在实际的应用程序中你应该考虑如何将错误信息通知给用户。
从 UUID_HUMIDITY_DATA 特征中读取数据一件简单的事,只需调用gatt.readCharacteristic(humidityCharacteristic) 方法,并在BluetoothGattCallback 的实现类上复写onReadCharacteristic 方法(再次说明,这是一个异步执行的操作,所以可以在UI线程上调用readCharacteristic() 方法)。这样一次性的操作是没什么问题的,但我们应用程序要不断监测温度和湿度值,并将其报告给用户。相比注册监听特征值的发生,通过调用readCharacteristic() 定期轮询会更有效(译者注:感觉说反了,大家可以看看原文,不知道是不是我翻译错了: Instead we can get notifications whenever the value changes, which is much more efficient that polling and performing a readCharacteristic() periodically.)。
注册监听特征值变化的通知我们要做三件事。首先我们必须把传感器打开,使SensorTag 开始收集适当的数据(实际上如果我们使用readCharacteristic() 方法,同样要打开传感器)。然后我们必须注册传感器以及我们需要通知的本地代理。这一步是特别针对于SensorTag的,其他设备可能不需要这样,或者他们可能需要另一种方法。
为了打开传感器,我们必须写一个 0×01 值到 UUID_HUMIDITY_CONF 特性;为了收到通知,我们首先必须调用BluetoothGatt 实例的setCharacteristicNotification() 方法,然后写一个 {0x00,0x01}(两个字节)的值到 UUID_CCC 描述符。正如我们前面所提到的那样,UUID_CCC描述符不是SensorTag特定的(就像其他所有的UUID一样),这是一个标准关闭或打开通知的UUID。
到目前为止,我们已经极大地受益于BLE的异步性API,因为我们不需要担心从UI线程调用它。不幸的是,它使我们面临一些小问题。我们希望下面的代码会做我们所要求的事,但是运行它不会开始收到通知:
gatt.setCharacteristicNotification(humidityCharacteristic, true);
humidityConf.setValue(ENABLE_SENSOR);
gatt.writeCharacteristic(humidityConf);
config.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(config);
原因是我们项传感器写入值的操作是异步执行的,那不会起作用,我们必须等待一个写操作完后才可以开始下一个。如果我们不能等待,在第一个值正在写入的过程中尝试写入第二个值,那么第二值就会丢失。要克服他并不困难,我们可以维护一个写队列,当我们收到前一个写操作已经完成的通知后触发下一个写操作:

private static final byte[] ENABLE_SENSOR = {0x01};
private static final Queue<Object> sWriteQueue =
new ConcurrentLinkedQueue<Object>();
private static boolean sIsWriting = false;
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
.
.
.
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.v(TAG, "onCharacteristicWrite: " + status);
sIsWriting = false;
nextWrite();
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status) {
Log.v(TAG, "onDescriptorWrite: " + status);
sIsWriting = false;
nextWrite();
}
}
.
.
.
private void subscribe(BluetoothGatt gatt) {
BluetoothGattService humidityService =
gatt.getService(UUID_HUMIDITY_SERVICE);
if(humidityService != null) {
BluetoothGattCharacteristic humidityCharacteristic =
humidityService.getCharacteristic(UUID_HUMIDITY_DATA);
BluetoothGattCharacteristic humidityConf =
humidityService.getCharacteristic(UUID_HUMIDITY_CONF);
if(humidityCharacteristic != null &&
humidityConf != null) {
BluetoothGattDescriptor config =
humidityCharacteristic.getDescriptor(UUID_CCC);
if(config != null) {
gatt.setCharacteristicNotification(
humidityCharacteristic, true);
humidityConf.setValue(ENABLE_SENSOR);
write(humidityConf);
config.setValue(
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
write(config);
}
}
}
}
private synchronized void write(Object o) {
if(sWriteQueue.isEmpty() && !sIsWriting) {
doWrite(o);
} else {
sWriteQueue.add(o);
}
}
private synchronized void nextWrite() {
if(!sWriteQueue.isEmpty() && !sIsWriting) {
doWrite(sWriteQueue.poll());
}
}
private synchronized void doWrite(Object o) {
if(o instanceof BluetoothGattCharacteristic) {
sIsWriting = true;
mGatt.writeCharacteristic(
(BluetoothGattCharacteristic)o);
} else if(o instanceof BluetoothGattDescriptor) {
sIsWriting = true;
mGatt.writeDescriptor((BluetoothGattDescriptor) o);
} else {
nextWrite();
}
}

所以在加入这个简单的写队列之后,我们只需要在我们的BluetoothGattCallback 实现类中加入相应的回调方法以开启监听通知事件。

private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
.
.
.
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.v(TAG, "onCharacteristicChanged: " +
characteristic.getUuid());
if(characteristic.getUuid().equals(UUID_HUMIDITY_DATA)) {
int t = shortUnsignedAtOffset(characteristic, 0);
int h = shortUnsignedAtOffset(characteristic, 2);
t = t - (t % 4);
h = h - (h % 4);
float humidity = (-6f) + 125f * (h / 65535f);
float temperature = -46.85f +
175.72f/65536f * (float)t;
Log.d(TAG, "Value: " + humidity + ":" + temperature);
Message msg = Message.obtain(null, MSG_DEVICE_DATA);
msg.arg1 = (int)(temperature * 100);
msg.arg2 = (int)(humidity * 100);
sendMessage(msg);
}
}
}
private static Integer shortUnsignedAtOffset(
BluetoothGattCharacteristic characteristic, int offset) {
Integer lowerByte = characteristic.getIntValue(
BluetoothGattCharacteristic.FORMAT_UINT8, offset);
Integer upperByte = characteristic.getIntValue(
BluetoothGattCharacteristic.FORMAT_UINT8, offset + 1);
return (upperByte << 8) + lowerByte;
}

GATT特征中的数据包括温度值(位于第0个字节和第1个字节)和湿度值(位于第2个字节和第3个字节)。然后我们去掉任何不必要的状态位。计算方法直接来自SensorTag 文档。
为了将这些数据展示在界面上,我们使用 Message 的 arg1和arg2 两个成员变量,在将其转换为所需的Int值之前我们先乘100。之后,我们将除以100以返回一个float值。就像以前一样,在此不会展示UI的代码,但在源代码中会有。
如果我们将程序运行起来,我们可以看到显示处理的摄氏温度以及湿度百分比 【视频在youtube 上,需要穿越才能看到哦】:
Bluetooth LE(低功耗蓝牙) - 第六部分(完)的更多相关文章
- Bluetooth LE(低功耗蓝牙) - 第一部分
前言 在写这篇文章的时候,谷歌刚刚发布了Android Wear ,摩托罗拉也发布了 Moto 360 智能手表.Android Wear的API还是相当基本的,是很好的文档材料,而且还会不断的更新, ...
- Bluetooth LE(低功耗蓝牙) - 第三部分
回顾 在本系列的前两篇文章中,我们已经了解了一些关于Bluetooth LE的背景并建立一个简单的Activity / Service框架. 在这篇文章中,我们将探讨Bluetooth LE的细节 ...
- Bluetooth LE(低功耗蓝牙) - 第二部分
回顾 在前面的文章中我们介绍了Bluetooth LE的背景也说明了我们在本系列文章中将要开发什么,但是还没有实际的代码.我们将在这篇文章中纠正这一点,我们将通过定义 Service/Activity ...
- Bluetooth LE(低功耗蓝牙) - 第四部分
回顾 在本系列前几篇文章中我们完成了BLE设备的发现 , 为我们的app通过BLE显示从TI SensorTag设备中获取到环境温度和湿度的工作打下了基础.在这篇文章中我们将着眼于连接到我们所发现的S ...
- Bluetooth LE(低功耗蓝牙) - 第五部分
回顾: 在本系列前面的文章中我们完成了发现BLE传感器并与之建立连接.现在只剩下从其中获取数据了,但是这并没有看起来那么简单.在这篇文章中我们将讨论GATT的特点以及如何促进主机与传感器之间的数据交换 ...
- Bluetooth Low Energy——蓝牙低功耗
Android4.3(API级别18)引入内置平台支持BLE的central角色,同时提供API和app应用程序用来发现设备,查询服务,和读/写characteristics.与传统蓝牙(Classi ...
- 低功耗之战!ANT VS Bluetooth LE
利用近距离无线通信技术将手机及可穿戴式传感器终端等与智能电话连接起来,实现新的功能.最近,以此为目标的行动正在展开.其中备受关注的近距离无线方式是“ANT”和“Bluetooth LE”.为了在各种便 ...
- BLE——低功耗蓝牙(Bluetooth Low Energy)
1.简介 以下蓝牙协议特指低功耗蓝牙协议. 蓝牙协议是由SIG制定并维护的通信协议,蓝牙协议栈是蓝牙协议的具体实现. 各厂商都根据蓝牙协议实现了自己的一套函数库——蓝牙协议栈,所以不同厂商的蓝牙协议栈 ...
- Android使用BLE(低功耗蓝牙,Bluetooth Low Energy)
背景 在学习BLE的过程中,积累了一些心得的DEMO,放到Github,形成本文.感兴趣的同学可以下载到源代码. github: https://github.com/vir56k/bluetooth ...
随机推荐
- Xcode4快速Doxygen文档注释 — 简明图文教程
转自:http://blog.csdn.net/totogo2010/article/details/9100767 准备2个文件: 文件一,ThisService.app 文件二,Doxygen.r ...
- c#不重复的排序方法
public int getRandom(int num) { Thread.Sleep(5); // Random ro = new Random(unchecked((int)DateTime.N ...
- “System.Transactions.Diagnostics.DiagnosticTrace”的类型初始值设定项引发异常。
今天在项目中用log4net,App.config文件中增加了configSections节点,程序运行报错“System.Transactions.Diagnostics.DiagnosticTra ...
- 将应用程序中的一些参数写到xml配置文件中
最近碰到一个问题,需要将程序中的一些基本参数写到xml文件中,虽然网上有好多现成的代码,但是觉得对xml不熟悉,果断就研究了一下.先说一下大体思路吧,我设计了一个用来读取和回填的类,然后定义了一个接口 ...
- SQL循环+游标
/****** Script for SelectTopNRows command from SSMS ******/use DB declare @id bigint DECLARE cur ...
- wordpress 后台显示空白现象
简单的说两句,出现此种现象的因素可能在于主题或者插件再或者是因为(恶意)插件(误更改)更改了某个重要的文件出现错误.本次我遇到的是插件的错误,具体是什么错误,我也没有去深究,重要的是结果! 使用排查的 ...
- 读懂IL代码(三)
由于要写毕业论文的缘故,最近比较没有时间写,总是要抽出时间抽出时间.诶,这样的生活比较烦躁. 这一篇主要写委托.类.方法的IL代码,一一来说明. 委托:搞过C#的都应该清楚,委托实际上是一个类.编译器 ...
- c#数组的交集,差集,并集
, , , , }; , , , , }; // 差集 var z1 = x.Except(y); foreach (var i in z1) { Console.Write(i + " & ...
- wamp使用方法【总】
Apache与php配置:我们把php-5.2.9-Win32.zip解压到C盘根目录下,把文件夹名字改成PHP,这样方便一下. 1. 找到PHP目录下的“php.ini-dist”或者“php.in ...
- JavaScript中Ajax的get和post请求
AJAX = 异步 JavaScript和XML(Asynchronous JavaScript and XML) 作用:在不重新加载整个网页的情况下,对网页的某部分进行更新. 两种请求方式: 1 ...