回顾

在本系列的前两篇文章中,我们已经了解了一些关于Bluetooth LE的背景并建立一个简单的Activity / Service框架。   在这篇文章中,我们将探讨Bluetooth LE的细节以及蓝牙设备查找的一些问题。

扫描并发现蓝牙设备

蓝牙设备的发现是十分简单的,它是一个在蓝牙可见范围内查找设备的过程。首先我们要做的就是在Manifest中添加必要的权限,否则我们将在一开始就碰壁。我们需要的权限是android.permission.BLUETOOTH(一般蓝牙使用)和android.permission.BLUETOOTH_ADMIN(额外的任务,如蓝牙发现)。

在我们深入之前,值得说明的是BleService 将作为一个状态机,在不同的状态执行不同的任务。这些状态中,我们首先要考虑的是扫描状态。当BleService 接收到一个 MSG_START_SCAN  消息后进入扫描状态:

private static class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
BleService service = mService.get();
if (service != null) {
switch (msg.what) {
.
.
.
case MSG_START_SCAN:
service.startScan();
Log.d(TAG, "Start Scan");
break;
default:
super.handleMessage(msg);
}
}
}
}

startScan()方法开始扫描:

public class BleService extends Service implements
BluetoothAdapter.LeScanCallback {
private final Map<String,BluetoothDevice> mDevices =
new HashMap<String, BluetoothDevice>(); public enum State {
UNKNOWN,
IDLE,
SCANNING,
BLUETOOTH_OFF,
CONNECTING,
CONNECTED,
DISCONNECTING
}
private BluetoothAdapter mBluetooth = null;
private State mState = State.UNKNOWN;
.
.
.
private void startScan() {
mDevices.clear();
setState(State.SCANNING);
if (mBluetooth == null) {
BluetoothManager bluetoothMgr = (BluetoothManager)
getSystemService(BLUETOOTH_SERVICE);
mBluetooth = bluetoothMgr.getAdapter();
}
if (mBluetooth == null || !mBluetooth.isEnabled()) {
setState(State.BLUETOOTH_OFF);
} else {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mState == State.SCANNING) {
mBluetooth.stopLeScan(
BleService.this);
setState(State.IDLE);
}
}
}, SCAN_PERIOD);
mBluetooth.startLeScan(this);
}
}
}

首先,我们需要确保手机上的蓝牙已启用,如果没有则提示用户打开它。这个过程也很简单。首先我们需要获取 BluetoothService 实例,这是一个Android系统服务。通过 BluetoothService 对象可以得到一个代表了设备上蓝牙无线电的 BluetoothAdapter 对象的一个实例。然后我们可以做一个非空判断,之后调用 isenabled() 方法来确定蓝牙是否打开。如果是,那么我们就可以下面的操作了,但如果不是则设置适当的状态 (State.BLUETOOTH_OFF)。当状态时变化将发送一个消息给此Service的所有客户端(相应的Activity为):

public class BleActivity extends Activity {
private final int ENABLE_BT = 1;
.
.
.
private void enableBluetooth() {
Intent enableBtIntent =
new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, ENABLE_BT);
} @Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
if(requestCode == ENABLE_BT) {
if(resultCode == RESULT_OK) {
//Bluetooth connected, we may continue
startScan();
} else {
//The user has elected not to turn on
//Bluetooth. There's nothing we can do
//without it, so let's finish().
finish();
}
} else {
super.onActivityResult(requestCode,
resultCode, data);
}
} private void startScan() {
mRefreshItem.setEnabled(false);
mDeviceList.setDevices(this, null);
mDeviceList.setScanning(true);
Message msg = Message.obtain(null,
BleService.MSG_START_SCAN);
if (msg != null) {
try {
mService.send(msg);
} catch (RemoteException e) {
Log.w(TAG,
"Lost connection to service", e);
unbindService(mConnection);
}
}
}
}

为了提示用户打开蓝牙,我们可以使用系统服务来完成,这确保了在不同平台上用户的体验是一致的。虽然在程序中也可以打开蓝牙,但提示用户是推荐的方法。这真的是很容易的,我们通过适当的方式(BluetoothAdapter  的7-9行)启动另一个Activity 来提醒用户我们想做什么,在那个Activity结束的时候接收返回结果(onactivityresult()方法)。

到目前为止还没有什么具体的Bluetooth LE 相关的内容,但这些都是标准蓝牙需要经历的步骤。

我们要做的下一件事是扫描Bluetooth LE设备。这其实是很容易的,因为 BluetoothAdapter 类正好包含一个startLeScan()方法!该方法接收一个BluetoothAdapter.LeScanCallback 的实例作为参数,该参数会在扫描过程中接收回调:

public class BleService extends Service implements
BluetoothAdapter.LeScanCallback
private static final String DEVICE_NAME = "SensorTag";
.
.
.
private void startScan() {
mDevices.clear();
setState(State.SCANNING);
if (mBluetooth == null) {
BluetoothManager bluetoothMgr = (BluetoothManager)
getSystemService(BLUETOOTH_SERVICE);
mBluetooth = bluetoothMgr.getAdapter();
}
if (mBluetooth == null || !mBluetooth.isEnabled()) {
setState(State.BLUETOOTH_OFF);
} else {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (mState == State.SCANNING) {
mBluetooth.stopLeScan(
BleService.this);
setState(State.IDLE);
}
}
}, SCAN_PERIOD);
mBluetooth.startLeScan(this);
}
} @Override
public void onLeScan(final BluetoothDevice device,
int rssi, byte[] scanRecord) {
if (device != null && !mDevices.containsValue(device) &&
device.getName() != null &&
device.getName().equals(DEVICE_NAME)) {
mDevices.put(device.getAddress(), device);
Message msg = Message.obtain(null,
MSG_DEVICE_FOUND);
if (msg != null) {
Bundle bundle = new Bundle();
String[] addresses = mDevices.keySet()
.toArray(new String[mDevices.size()]);
bundle.putStringArray(KEY_MAC_ADDRESSES,
addresses);
msg.setData(bundle);
sendMessage(msg);
}
Log.d(TAG, "Added " + device.getName() + ": " +
device.getAddress());
}
}
}

关于onstartlescan()很重要的一点是,它只管一件事--开始扫描。我们必须停止扫描。根据需求,一旦发现设备便停止扫描是没错的,但在我们的例子中我们要扫描一个固定的时间,所以我们使用 postdelayed() 在未来的某个时间点调用 stoplescan() 停止扫描。

在扫描的过程中每次蓝牙适配器接收来自BLE设备的广播信息 onLeScan() 方法都会被调用。当蓝牙设备处于广播模式的时候通常每秒会发出十次广播信息,所以在扫描的工程中我们必须小心地只回应新的设备。我们通过维护一个已发现设备的映射表(由他们的MAC地址做映射,这在后面很有用)来实现,在扫描过程中如果 onLeScan() 方法被调用,我们检查是否知道这个设备。

另外我们还需要过滤出那些我们感兴趣的设备,这通常是基于某种特征(在之后的文章中会有更多的介绍)。 SensorTag 文档 表明蓝牙名称为SensorTag,所以我们应当匹配设备名称为”SensorTag”的设备。

这里值得一提的是, the host needs to scan while sensors are advertising their presence. This is the heart of security on BLE – sensors must be told by the user to advertise their presence before they can be discovered. (又不知道该怎么翻译了,罪过罪过)一旦蓝牙被发现并建立起主设备与传感器之间的相互信任关系,那么之后主设备就可以直接连接到传感器而无需将其置入广播模式下,although this behaviour any vary on different sensors 。

每当我们发现一个新设备,我们将它加入到映射表(Map)中,同时以字符串数组封装所有已发现设备的MAC地址,并把字符串数组加入到一个 MSG_DEVICE_FOUND  消息中发送给Activity。

还需说明的是我们的Service运行在UI线程中,但我们真的不需要担心它会阻塞UI线程。调用 Bluetooth LE 扫描的操作是异步的,我们开启了一个后台线程去执行扫描并通过onLeScan() 方法回调。如果我们没在onLeScan()方法中做任何复杂的工作,我们就不需要担心后台线程所做的任何事情。

Activity还作为一个状态机,利用BleService的状态改变UI。BleService是否处于SCANNING状态决定了Activity的“刷新”菜单是否可用,BleService的状态也决定了是否切换到显示设备列表的Fragment(当发现设备的时候)。每当Activity收到MSG_DEVICE_FOUND 消息时候都会更新已发现设备的列表。我不想在这里解释所有的UI代码,因为他们和BLE并不相关,但如果你想看,源代码 是公开的。

我们运行上述代码后开始扫描设备,如果周边有SensorTag蓝牙设备,他将显示在设备列表中:

下期预告

现在我们完成了基本的设备扫描查找工作,接下来我们需要做的是连接到一个或多个我们已经发现的传感器,我们将在下一篇文章中讨论。

本文的源代码在这里 可以找到。

Bluetooth LE(低功耗蓝牙) - 第三部分的更多相关文章

  1. Bluetooth LE(低功耗蓝牙) - 第一部分

    前言 在写这篇文章的时候,谷歌刚刚发布了Android Wear ,摩托罗拉也发布了 Moto 360 智能手表.Android Wear的API还是相当基本的,是很好的文档材料,而且还会不断的更新, ...

  2. Bluetooth LE(低功耗蓝牙) - 第二部分

    回顾 在前面的文章中我们介绍了Bluetooth LE的背景也说明了我们在本系列文章中将要开发什么,但是还没有实际的代码.我们将在这篇文章中纠正这一点,我们将通过定义 Service/Activity ...

  3. Bluetooth LE(低功耗蓝牙) - 第四部分

    回顾 在本系列前几篇文章中我们完成了BLE设备的发现 , 为我们的app通过BLE显示从TI SensorTag设备中获取到环境温度和湿度的工作打下了基础.在这篇文章中我们将着眼于连接到我们所发现的S ...

  4. Bluetooth LE(低功耗蓝牙) - 第六部分(完)

    在本系列前面的文章中我们已经了解了,在我们从一个TI SensorTag中获取温度和湿度数据之前,我们需要经历的各种步骤.在本系列中的最后一篇文章,我们将完成注册并接收SensorTag的通知,并接收 ...

  5. Bluetooth LE(低功耗蓝牙) - 第五部分

    回顾: 在本系列前面的文章中我们完成了发现BLE传感器并与之建立连接.现在只剩下从其中获取数据了,但是这并没有看起来那么简单.在这篇文章中我们将讨论GATT的特点以及如何促进主机与传感器之间的数据交换 ...

  6. 低功耗蓝牙UUID三种格式转换

    熟悉BLE技术同学应该对UUID不陌生,服务.特征值.描述都是有UUID格式定义. 蓝牙广播中对服务UUID格式定义都有三种16 bit UUID.32 bit UUID.128 bit UUID. ...

  7. BLE Hacking:使用Ubertooth one扫描嗅探低功耗蓝牙

    0×00 前言 低功耗蓝牙(Low Energy; LE),又视为Bluetooth Smart或蓝牙核心规格4.0版本.其特点具备节能.便于采用,是蓝牙技术专为物联网(Internet of Thi ...

  8. Bluetooth Low Energy——蓝牙低功耗

    Android4.3(API级别18)引入内置平台支持BLE的central角色,同时提供API和app应用程序用来发现设备,查询服务,和读/写characteristics.与传统蓝牙(Classi ...

  9. 低功耗之战!ANT VS Bluetooth LE

    利用近距离无线通信技术将手机及可穿戴式传感器终端等与智能电话连接起来,实现新的功能.最近,以此为目标的行动正在展开.其中备受关注的近距离无线方式是“ANT”和“Bluetooth LE”.为了在各种便 ...

随机推荐

  1. HTTP基础:URL格式、 HTTP请求、响应、消息

    HTTP URL 格式: http://host[:port][abs_path] 其中http表示要通过HTTP协议来定位网络资源. host表示合法的Internet主机域名或IP地址(以点分十进 ...

  2. html 之前学习响应式的笔记

    响应式的设计,根据用户设备的不同,用户屏幕大小不同,提供不同的网页设计http://mediaqueri.es/PhoneGap 使用2,如何模拟手机设备chome 浏览器 在32以上设备检测用 de ...

  3. ASP.NET MVC 自我总结的便捷开发实例

    前言 工作了这么久了,接触ASP.NET MVC已经很久了,一直都想总结一下它的一些实用的,经常使用的一些技巧,但是因为一直都很懒,也不想总结,所以一直都没有好好写出来,趁着现在有这种冲劲,那么就先把 ...

  4. Android学习之旅:五子棋

    在学完了Android的基础之后,我开始尝试着写一些小项目练练手,同时进一步巩固自己的基础知识,而我选的的第一个项目就是做一个简单的人人对战的五子棋小游戏. 首先,我们要新建一个自定义控件类Panel ...

  5. delphi 截取指定符号之间的字符串-随机读取

    unit Unit1; interface uses  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Syste ...

  6. sqlserver2008安装出现跨语言

    我在安装sqlserver2008的时候出现了一个问题,安装到一半的时候出现 跨语言安装失败 ,我细细的查了下问题,我装的安装语言绝对没有错的吧,然后我后退几步又是同样的错误,最后我把镜像重新加载到虚 ...

  7. MySQL常用函数 转载

    一.数学函数ABS(x)                    返回x的绝对值BIN(x) 返回x的二进制(OCT返回八进制,HEX返回十六进制)CEILING(x)                返 ...

  8. Windows phone 之Interaction.Triggers的使用

    两个步骤:1.添加以下两个程序集System.Windows.InteractivityMicrosoft.Expression.Interactions 2.添加xmlns:i="clr- ...

  9. 开发错误日志之No matching bean of type [xxx] found for dependency

    No matching bean of type [org.springframework.data.mongodb.core.MongoTemplate] found for dependency ...

  10. Linux 信号量互斥编程

    所谓信号量,其实就是一个数字.内核给这个数字赋予一定的含义,让它等于不同的值时所表示的意义不同.这样就可以用它来标示某种资源是否正被使用.信号的分类其实挺多的,主要还是二值和计数器.这里讨论二值 现在 ...