Tieto公司某蓝牙大牛写得《程序员》投稿文章
Android 4.2蓝牙介绍
蓝牙一词源于公元十世纪丹麦国王HaraldBlatand名字中的Blatand。Blatand的英文之意就是Blue tooth。这是因为这位让丹麦人引以为傲的国王酷爱吃蓝莓以至于牙龈都被染成蓝色。由于Blatand统一了丹麦和挪威,所以,作为无线通信技术的一种,蓝牙技术之所以取名Bluetooth可谓志向远大。不过,在以Android为代表的智能机出现以前,蓝牙在早期智能机甚至功能机中一直扮演着“鸡肋”的角色。那么,随着无线通信技术的快速发展以及Android的普及,蓝牙能给我们带来哪些新的变化呢?
本文将从蓝牙核心规范的发展历史、最具应用前景的几个蓝牙Profile以及Android 4.2中蓝牙实现情况等几个方面向读者介绍蓝牙技术。
一 蓝牙规范介绍
作为一种通用的无线通信技术,规范自然是蓝牙技术的核心。蓝牙规范可分为两个层次,如图1所示:
图1 蓝牙规范的层次结构
由图1可知蓝牙规范包括:
- Core Specification(核心规范),用于规定蓝牙设备必须实现的通用功能和协议层次。它由软件和硬件模块组成,两个模块之间的信息和数据通过主机控制接口(HCI)的解释才能进行传递。
- Profiles(蓝牙应用规范),它从应用场景的角度为蓝牙技术的使用制定了不同的规范。这也是和大众日常生活接触最多的一部分。蓝牙支持很多Profiles,下文将介绍几种使用最广泛的蓝牙应用规范。
1.1 蓝牙核心规范介绍
核心规范是蓝牙协议家族的基础,自蓝牙技术联盟(Bluetooth SIG,Special Interest Group)在1999年颁布蓝牙核心规范1.0版本以来,到目前为止蓝牙SIG一共发布了七个重要版本。每一个版本都促使蓝牙技术朝着更快、更安全、更省电的方向发展。表1所示为蓝牙核心规范[①]发展历史。
表1 蓝牙核心规范发展介绍
版本 | 规范发布日期 | 增强功能 |
0.7 | 1998年10月19日 | Baseband、LMP |
0.8 | 1999年1月21日 | HCI、L2CAP、RFCOMM |
0.9 | 1999年4月30日 | OBEX与IrDA的互通性 |
1.0 Draft | 1999年7月5日 | SDP、TCS |
1.0 A | 1999年7月26日 | |
1.0 B | 2000年10月1日 | 安全性,厂商设备之间连接兼容性 |
1.1 | 2001年2月22日 | IEEE 802.15.1 |
1.2 | 2003年11月5日 | 快速连接、自适应跳频、错误检测和流程控制、同步能力 |
2.0 + EDR | 2004年11月9日 | EDR传输率提升至2-3Mbps |
2.1 + EDR | 2007年7月26日 | 扩展查询响应、简易安全配对、暂停与继续加密、Sniff省电 |
3.0 + HS | 2009年4月21日 | 交替射频技术、802.11协议适配层、电源管理、取消了UMB的应用 |
4.0 +BLE | 2010年6月30日 | 低功耗物理层和链路层、AES加密、Attribute Protocol(ATT)、Generic Attribute Profile(GATT)、Security Manager(SM) |
表1中,
- EDR:全称为Enhanced Data Rate。通过提高多任务处理和多种蓝牙设备同时运行的能力,EDR使得蓝牙设备的传输速度可达3Mbps。
- HS:全称为High Speed。HS使得Bluetooth能利用WiFi作为传输方式进行数据传输,其支持的传输速度最高可达24Mbps。其核心是在802.11的基础上,通过集成802.11协议适配层,使得蓝牙协议栈可以根据任务和设备的不同,选择正确的射频。
- BLE:全称为Bluetooth Low Energy。蓝牙规范4.0最重要的一个特性就是低功耗。BLE使得蓝牙设备可通过一粒纽扣电池供电以维持续工作数年之久。很明显,BLE使得蓝牙设备在钟表、远程控制、医疗保健及运动感应器等市场具有极光明的应用场景。
虽然蓝牙4.0规范3年就发布,但目前使用最广泛的蓝牙核心规范版本还是3.0。智能手机中只有Iphone 4S,Iphone5,三星GallaxyS3、S4、Note2等少数设备支持蓝牙4.0。不过,Google已经在Android 4.3中添加了对4.0的支持。很明显,随着Android的持续推进和众多厂商的齐力支持,笔者估计在未来较短的一段时间内,蓝牙核心规范4.0将得到迅速普及。表2是经典蓝牙与低功耗蓝牙的一些区别:
表2 经典蓝牙与低功耗蓝牙的区别
技术规范 | 经典蓝牙(2.1 &3.0) | 低功耗蓝牙(4.0) |
无线电频率 | 2.4GHz | 2.4GHz |
距离 | 10米/100米 | 30米 |
数据速率 | 1-3Mbps | 1Mbps |
应用吞吐量 | 0.7-2.1Mbps | 0.2Mbps |
发送数据的总时间 | 100ms | <6ms |
耗电量 | 1 | 0.01至0.5 |
最大操作电流 | <30mA | <15mA(最高运行时为15 mA) |
主要用途 |
那么,蓝牙核心规范4.0有什么特别之处呢?蓝牙核心规范4.0的模块如图2所示:
图2 蓝牙核心规范4.0的模块
由图2可知,蓝牙核心规范4.0的模块增加了以下几个蓝牙低功耗组件。
- GATT表示服务器属性和客户端属性,描述了属性服务器中使用的服务层次,特点和属性。BLE设备使用它作为蓝牙低功耗应用规范的服务发现。
- ATT实现了属性客户端和服务器之间的点对点协议。ATT客户端给ATT服务器发送请命令。ATT服务器向ATT客户端发送回复和通知。
- SMP用于生成对等协议的加密密钥和身份密钥。SMP管理加密密钥和身份密钥的存储,它通过生成和解析设备的地址来识别蓝牙设备。
1.2 蓝牙应用规范[②]
蓝牙SIG根据不同的应用场景定义了不同的蓝牙应用规范,截止到现在,发布了40个蓝牙应用规范。本节介绍最常用的五个的蓝牙应用规范。
1.2.1 Advanced Audio Distribution Profile
Advanced Audio Distribution Profile 简称为A2DP(高质量音频分发规范)定义了如何将立体声质量的音频通过流媒体的方式从媒体源传输到接收器上。A2DP使用Asynchronous Connectionless Link(ACL,蓝牙异步传输)信道传输高质量音频内容,它依赖于Generic Audio/Video Distribution Profile(GAVDP,通用音频/视频分发规范)。A2DP必须支持低复杂度及Sub-bandCodec(SBC,低带宽编解码),可选支持MPEG1,2音频,MPEG2、4AAC。A2DP的应用场景如图4[1]所示:A2DP的应用场景如图3所示:
图3 A2DP的应用场景
由图3可知,A2DP有两种应用场景分别是播放和录音。
- 播放场景是具有蓝牙功能的播放器通过A2DP向蓝牙耳机或蓝牙立体声扬声器传送高质量音频。
- 录音场景是具有蓝牙功能的麦克风通过A2DP向蓝牙录音器传送高质量音频。
和A2DP相关的规范有Video Distribution Profile(VDP,视频分发规范),Audio/Video Remote Control Profile(AVRCP,音频/视频运程控制规范)。
1.2.2 Object Push Profile
OPP(对象推送规范)定义了推送服务器和客户端之间基于Generic Object Exchange Profile(GOEP,通用对象交换规范)进行对象交换的规范。OPP的应用场景如图4所示:
图4 OPP的应用场景
由图4可知,OPP主要用于手机与手机或者手机与电脑之间通过蓝牙进行文件操作。可交换的文件类型有电话本,备忘录,日程表等文本文件,还有视频,声音,图片,音乐等多媒体文件。
Wi-Fi Direct(WiFi直连)[③]和蓝牙OPP有相同的功能。WiFi直连是WiFi设备之间不需要无线路由器,直接进行对象交换。它的优点是传输距离长、速度快,缺点是功耗高。
1.2.3 Hands-Free Profile
HFP(HFP,免提规范)定义了蓝牙音频网关设备如何通过蓝牙免提设备拨打和接听电话。HFP的应用场景如图5所示:
图5 HFP的应用场景
由图5可知,HFP包括两个角色:
- Audio Gateway(AG,音频网关)和Hands-Free Unit(HF,免提设备)。AG是音频输入和输出的设备,典型的AG设备是手机。HF是执行音频网关的远程音频输入输出设备。
- HFP常见的场景是汽车上的车载套件,当车载套件和耳机通过蓝牙方式连接到手机时,通过无线蓝牙耳机拨打和接听电话。
和HFP相关的规范有Headset Profile(HSP,耳机规范),Phonebook Access Profile(PBAP,电话簿访问规范。
1.2.4 Heart Rate Profile
HRP(心率规范)定位与和医疗/健康相关的应用场景中,它使得蓝牙设备能与心率传感器交互。相关场景如图6所示:
图6 HRP的角色关系和应用场景
由图6可知:
- 左图是HRP定义的角色关系。HRP中有两个角色:心率感应器和收集器。心率感应器是GATT服务器,是测量心率的设备,它包含心率服务和设备信息服务,心率服务导出心率测量数据;收集器是GATT客户端,是从心率感应器接收心率测量数据和其它数据的设备。
- 右图是HRP的应用场景。心率规范用于让设备获得心率传感器的心率测量和其它数据。例如,护士或医生可以用心率传感器测量病人的心率,并把心率数据传到笔记本或手持设备上。
随着人口老龄化,医疗设备和医护人员资源不足,可以运用蓝牙健康规范实现远程医疗。笔者所在的Tieto公司在Android平台上运用心率规范开发了心率测量的原型程序,详细介绍请看视频http://www.youtube.com/watch?v=r_t-hstRgDs&feature=youtu.be。
和HRP相关的健康规范有Glucose Profile(GLP,血糖规范),Blood Pressure Profile(BLP,血压规范BLP),Health Thermometer Profile(HTP,健康体温计规范)。
1.2.5 Cycling Speed and Cadence Profile
CSCP(自行车速度和步调规范)让人们在骑自行车锻炼时跟踪速度和节奏。CSCP也基于GATT的规范。自行车速度和步调规范的角色关系和应用场景如图7所示:
图7 CSCP的角色关系和应用场景
由图7可知:
- 左图是CSCP的角色关系。CSCP定义了两个角色:自行车速度和步调感应器和收集器。CSC感应器是GATT服务器,向收集器报告车轮转速数据或轴转速数据。CSC感应器包含CSC服务和设备信息服务;收集器是GATT客户端,从CSC感应器接收自行车的速度和步调数据。
- 右图是CSCP的应用场景。传感器测量被广泛应用于运动和健身,通过传感器来监视和控制训练强调,以及在多个训练中衡量进展情况。自行车速度传感器和自行车踏频传感器是用户测量车轮速度或蹬踏节奏的设备。任何设备实现CSC规范可以与CSC传感器连接并接收数据。
和CSCP相关的规范有Running Speed and Cadence Profile(RSCS,跑步速度和步调规范)。
二 Android中的Bluetooth
Android 4.2之前,Google一直使用的是Linux官方蓝牙协议栈,即知名老牌开源项目BlueZ。BlueZ实际上是由高通公司在2001年5月基于GPL协议发布的一个开源项目,该项目仅发布一个月后就被Linux之父Linux Torvalds纳入了Linux内核,并做为Linux 2.4.6内核的官方蓝牙协议栈。随着Android设备的流行,BlueZ也得到了极大的完善和扩展。例如Android 4.1中BlueZ的版本升级为4.93,它支持蓝牙核心规范4.0,并实现了绝大部分的Profiles。
BlueZ现在正处于其巅峰时期,但好景不长。从Android 4.2即Jelly Bean开始,Google便在Android源码中推出了它和博通公司一起开发的BlueDroid以替代BlueZ。虽然因为时间及成熟度的原因,大部分手机厂商在Android 4.2中仍继续使用BlueZ。但据笔者了解,BlueZ的创始者,高通公司也将在基于其芯片的Android参考设计中去除BlueZ,并仅支持BlueDroid。
BlueZ的未来如何笔者姑且不论。不过,能让高通改弦易辙,BlueDroid自有其合理之处。相比BlueZ,BlueDroid最值得称道的地方就是其框架结构变得更为简洁和清晰。另外,借助HAL(Hardware Abstraction Layer,硬件抽象层),BlueDroid终于不再和dbus有任何瓜葛。图8所示为Android 4.2中BlueDroid的框架结构图[④]:
图8 Android 4.2BlueDroid框架结构图
由图8可知,Android4.2中BlueDroid框架包括以下几个部分:
- 应用程序通过android.bluetooth package下的API来调用系统的Bluetooth功能。
- 应用层空间增加了一个名为Bluetooth的App。它做为系统的bluetooth核心进程而存在。其内部将通过JNI来调用Bluetooth HAL层以完成各种蓝牙请求。
- Bluetooth HAL也属于Android 4.2新增模块,它由蓝牙核心规范硬件抽象层和蓝牙应用规范硬件抽象层组成。由于HAL层的隔离作用,上层代码可轻松移植到不同芯片平台。
- 作为整个蓝牙服务的核心,Bluetooth Stack模块则由Bluetooth Application Layer(缩写为BTA)和Bluetooth Embedded System(缩写为BTE)两大部分组成。BTA实现了蓝牙设备管理、状态管理及一些应用规范。而BTE则通过HCI与厂商蓝牙芯片交互以实现了蓝牙协议栈的通用功能和相关协议。另外,BTE还包括一个统一内核接口(GKI),蓝牙芯片厂商可借助GKI快速轻松得移植蓝牙协议栈到其他操作系统或手机平台上。
- Vendor Extentions(厂商扩展):开发者可以添加自定义扩展以实现厂商特定的模块和组件。
除了BlueDroid外,在今年的Google I/O大会,谷歌公司还宣布将于与苹果、微软和黑莓等公司共同支持Bluetooth Smart Ready(BSR,蓝牙智能就绪)和Bluetooth Smart(BS,蓝牙智能)技术。这项技术使蓝牙设备或应用可以非常容易地连接全球成千上万的蓝牙设备,蓝牙使用者的生活也因此变得更加简单。BSR和BS都是建立在蓝牙核心规范4.0和GATT应用规范。即将发布的Android 4.3(MR2)支持BSR技术,使得BS的开发者可以轻易地将其设备和应用与Android BSR设备进行连接和发布。蓝牙使用者运用BS的智能应用配件(如健康监控或医疗设备)收集数据,再传送到支持BSR设备(如智能手机或平板)上。
另外,蓝牙SIG也正在研发工具Bluetooth Application Accelerator(蓝牙应用加速器)。据可靠消息,该工具将随Android 4.3发布,并将帮助开发者在Android 4.3上快速开发蓝牙应用,从而加快相关产品的研发时间。
三 总结
本文对蓝牙核心规范、蓝牙应用规范以及Android 4.2中的蓝牙协议栈BlueDroid进行了一些简单介绍。
从笔者了解的情况来看,BlueDroid虽然对BlueZ大有取而代之的趋势,但现在它对蓝牙应用规范的支持还不够完善。例如BlueDroid仅支持AVRCP 1.0,而非最新的AVRCP 1.5。所以,国内某些芯片或手机厂商若能及早完成BlueZ相关模块到BlueDroid的移植工作,相信能帮助它们在竞争日趋白日化的移动世界中拔得先机。
另外,作为一种成熟、低功耗无线通信技术的先锋,蓝牙未来在可穿戴设备领域中也将扮演越来越重要的作用。那时,蓝牙或许就会真正像“牙齿”一样成为各种设备中不可或缺的一部分了。
[①]http://zh.wikipedia.org/wiki/Bluetooth
[②]详情可参考http://developer.bluetooth.org/TechnologyOverview/Pages/Profiles.aspx
[③]关于WFD,读者可参考http://blog.csdn.net/innost/article/details/8474683
[④]http://source.android.com/devices/bluetooth.html
android 4.0 BLE开发官方文档介绍
安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。相比传统的蓝牙,BLE更显著的特点是低功耗。这一优点使android App可以与具有低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备等。
关键术语和概念
- Generic Attribute Profile(GATT)—GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。
- Attribute Protocol(ATT)—GATT在ATT协议基础上建立,也被称为GATT/ATT。ATT对在BLE设备上运行进行了优化,为此,它使用了尽可能少的字节。每个属性通过一个唯一的的统一标识符(UUID)来标识,每个String类型UUID使用128 bit标准格式。属性通过ATT被格式化为characteristics和services。
- Characteristic 一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。
- Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。
- Service service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。
角色和责任
以下是Android设备与BLE设备交互时的角色和责任:
- 中央 VS 外围设备。 适用于BLE连接本身。中央设备扫描,寻找广播;外围设备发出广播。
- GATT 服务端 VS GATT 客户端。决定了两个设备在建立连接后如何互相交流。
为了方便理解,想象你有一个Android手机和一个用于活动跟踪BLE设备,手机支持中央角色,活动跟踪器支持外围(为了建立BLE连接你需要注意两件事,只支持外围设备的两方或者只支持中央设备的两方不能互相通信)。
当手机和运动追踪器建立连接后,他们开始向另一方传输GATT数据。哪一方作为服务器取决于他们传输数据的种类。例如,如果运动追踪器想向手机报告传感器数据,运动追踪器是服务端。如果运动追踪器更新来自手机的数据,手机会作为服务端。
在这份文档的例子中,android app(运行在android设备上)作为GATT客户端。app从gatt服务端获得数据,gatt服务端即支持Heart Rate Profile(心率配置)的BLE心率监测仪。但是你可以自己设计android app去扮演GATT服务端角色。更多信息见BluetoothGattServer。
BLE权限
为了在app中使用蓝牙功能,必须声明蓝牙权限BLUETOOTH。利用这个权限去执行蓝牙通信,例如请求连接、接受连接、和传输数据。
如果想让你的app启动设备发现或操纵蓝牙设置,必须声明BLUETOOTH_ADMIN权限。注意:如果你使用BLUETOOTH_ADMIN权限,你也必须声明BLUETOOTH权限。
在你的app manifest文件中声明蓝牙权限。
|
|
如果想声明你的app只为具有BLE的设备提供,在manifest文件中包括:
|
|
但是如果想让你的app提供给那些不支持BLE的设备,需要在manifest中包括上面代码并设置required="false"
,然后在运行时可以通过使用PackageManager.hasSystemFeature()
确定BLE的可用性。
|
|
设置BLE
你的app能与BLE通信之前,你需要确认设备是否支持BLE,如果支持,确认已经启用。注意如果<uses-feature.../>
设置为false,这个检查才是必需的。
如果不支持BLE,那么你应该适当地禁用部分BLE功能。如果支持BLE但被禁用,你可以无需离开应用程序而要求用户启动蓝牙。使用BluetoothAdapter两步完成该设置。
-
所有的蓝牙活动都需要蓝牙适配器。BluetoothAdapter代表设备本身的蓝牙适配器(蓝牙无线)。整个系统只有一个蓝牙适配器,而且你的app使用它与系统交互。下面的代码片段显示了如何得到适配器。注意该方法使用getSystemService()]返回BluetoothManager,然后将其用于获取适配器的一个实例。Android 4.3(API 18)引入BluetoothManager。
|
|
-
开启蓝牙
接下来,你需要确认蓝牙是否开启。调用isEnabled())去检测蓝牙当前是否开启。如果该方法返回
false
,蓝牙被禁用。下面的代码检查蓝牙是否开启,如果没有开启,将显示错误提示用户去设置开启蓝牙。
|
|
发现BLE设备
为了发现BLE设备,使用startLeScan())方法。这个方法需要一个参数BluetoothAdapter.LeScanCallback。你必须实现它的回调函数,那就是返回的扫描结果。因为扫描非常消耗电量,你应当遵守以下准则:
- 只要找到所需的设备,停止扫描。
- 不要在循环里扫描,并且对扫描设置时间限制。以前可用的设备可能已经移出范围,继续扫描消耗电池电量。
下面代码显示了如何开始和停止一个扫描:
|
|
如果你只想扫描指定类型的外围设备,可以改为调用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)),需要提供你的app支持的GATT services的UUID对象数组。
作为BLE扫描结果的接口,下面是BluetoothAdapter.LeScanCallback的实现。
|
|
注意:只能扫描BLE设备或者扫描传统蓝牙设备,不能同时扫描BLE和传统蓝牙设备。
连接到GATT服务端
与一个BLE设备交互的第一步就是连接它——更具体的,连接到BLE设备上的GATT服务端。为了连接到BLE设备上的GATT服务端,需要使用connectGatt( )方法。这个方法需要三个参数:一个Context对象,自动连接(boolean值,表示只要BLE设备可用是否自动连接到它),和BluetoothGattCallback调用。
|
|
连接到GATT服务端时,由BLE设备做主机,并返回一个BluetoothGatt实例,然后你可以使用这个实例来进行GATT客户端操作。请求方(Android app)是GATT客户端。BluetoothGattCallback用于传递结果给用户,例如连接状态,以及任何进一步GATT客户端操作。
在这个例子中,这个BLE APP提供了一个activity(DeviceControlActivity)来连接,显示数据,显示该设备支持的GATT services和characteristics。根据用户的输入,这个activity与BluetoothLeService通信,通过Android BLE API实现与BLE设备交互。
|
|
当一个特定的回调被触发的时候,它会调用相应的broadcastUpdate()辅助方法并且传递给它一个action。注意在该部分中的数据解析按照蓝牙心率测量配置文件规格进行。
|
|
返回DeviceControlActivity, 这些事件由一个BroadcastReceiver来处理:
|
|
读取BLE变量
你的android app完成与GATT服务端连接和发现services后,就可以读写支持的属性。例如,这段代码通过服务端的services和 characteristics迭代,并且将它们显示在UI上。
|
|
接收GATT通知
当设备上的特性改变时会通知BLE应用程序。这段代码显示了如何使用setCharacteristicNotification( )给一个特性设置通知。
|
|
如果对一个特性启用通知,当远程蓝牙设备特性发送变化,回调函数onCharacteristicChanged( ))被触发。
|
|
关闭客户端App
当你的app完成BLE设备的使用后,应该调用close( )),系统可以合理释放占用资源。
|
|
Android 蓝牙4.0 BLE 理解
本文简单结合两篇文章
http://blog.csdn.net/hellogv/article/details/24267685
http://blog.csdn.net/jimoduwu/article/details/21604215
在BLE协议中,有两个角色,周边(Periphery)和中央(Central),一个中央可以同时连接多个周边,但是一个周边某一时刻只能连接一个中央。但是不管是Periphery还是Central都是可以实现 GATT server 和 GATT client去传输数据,但是无法同时都是。
大概了解了概念后,看看Android BLE SDK的四个关键类(class):
a) BluetoothGattServer作为周边来提供数据;BluetoothGattServerCallback返回周边的状态。
b) BluetoothGatt作为中央来使用和处理数据;BluetoothGattCallback返回中央的状态和周边提供的数据。
因为我们讨论的是Android的BLE SDK,下面所有的BluetoothGattServer代表周边,BluetoothGatt代表中央。
一.创建一个周边(虽然目前周边API在Android手机上不工作,但还是看看)
a)先看看周边用到的class,蓝色椭圆
b)说明:
每一个周边BluetoothGattServer,包含多个服务Service,每一个Service包含多个特征Characteristic。
1.new一个特征:character = new BluetoothGattCharacteristic(
UUID.fromString(characteristicUUID),
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ);
2.new一个服务:service = new BluetoothGattService(UUID.fromString(serviceUUID),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
3.把特征添加到服务:service.addCharacteristic(character);
4.获取BluetoothManager:manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
5.获取/打开周边:BluetoothGattServer server = manager.openGattServer(this,
new BluetoothGattServerCallback(){...});
6.把service添加到周边:server.addService(service);
7.开始广播service:Google还没有广播Service的API,等吧!!!!!所以目前我们还不能让一个Android手机作为周边来提供数据。
二.创建一个中央(这次不会让你失望,可以成功创建并且连接到周边的)
a)先看看中央用到的class,蓝色椭圆
b)说明:
为了拿到中央BluetoothGatt,可要爬山涉水十八弯:
1.先拿到BluetoothManager:bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
2.再拿到BluetoothAdapt:btAdapter = bluetoothManager.getAdapter();
3.开始扫描:btAdapter.startLeScan( BluetoothAdapter.LeScanCallback);
4.从LeScanCallback中得到BluetoothDevice:public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {.....}
5.用BluetoothDevice得到BluetoothGatt:gatt = device.connectGatt(this, true, gattCallback);
终于拿到中央BluetoothGatt了,它有一堆方法(查API吧),调用这些方法,你就可以通过BluetoothGattCallback和周边BluetoothGattServer交互了。
本文来自http://blog.csdn.net/hellogv/ ,引用必须注明出处!
最近穿戴设备发展得很火,把相关技术也带旺了,其中一项是BLE(Bluetooth Low Energy)。BLE是蓝牙4.0的核心Profile,主打功能是快速搜索,快速连接,超低功耗保持连接和传输数据,弱点是数据传输速率低,由于BLE的低功耗特点,因此普遍用于穿戴设备。Android 4.3才开始支持BLE API,所以请各位客官把本文代码运行在蓝牙4.0和Android 4.3及其以上的系统,另外本文所用的BLE终端是一个蓝牙4.0的串口蓝牙模块。
PS:我的i9100刷了4.4系统后,竟然也能跟BLE蓝牙模块通信。
BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。一般来说,Characteristic是手机与BLE终端交换数据的关键,Characteristic有较多的跟权限相关的字段,例如PERMISSION和PROPERTY,而其中最常用的是PROPERTY,本文所用的BLE蓝牙模块竟然没有标准的Characteristic的PERMISSION。Characteristic的PROPERTY可以通过位运算符组合来设置读写属性,例如READ|WRITE、READ|WRITE_NO_RESPONSE|NOTIFY,因此读取PROPERTY后要分解成所用的组合(本文代码已含此分解方法)。
本文代码改自Android 4.3 Sample的BluetoothLeGatt,把冗余代码去掉,获取的BLE设备信息都通过Log,还有一些必要的读写蓝牙方法,应该算是简化到大家一看就可以懂了。本文代码可以到http://download.csdn.net/detail/hellogv/7228819下载。接下来贴出本文运行的结果,首先是连接BLE设备后,枚举出设备所有Service、Characteristic、Descriptor,并且手机会往Characteristic uuid=0000ffe1-0000-1000-8000-00805f9b34fb写入“send data->”字符串,BLE终端收到数据通过串口传到PC串口助手(见PC串口助手的截图):
04-21 18:28:25.465: E/DeviceScanActivity(12254): -->service type:PRIMARY
04-21 18:28:25.465: E/DeviceScanActivity(12254): -->includedServices size:0
04-21 18:28:25.465: E/DeviceScanActivity(12254): -->service uuid:00001800-0000-1000-8000-00805f9b34fb
04-21 18:28:25.465: E/DeviceScanActivity(12254): ---->char uuid:00002a00-0000-1000-8000-00805f9b34fb
04-21 18:28:25.465: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.465: E/DeviceScanActivity(12254): ---->char property:READ
04-21 18:28:25.465: E/DeviceScanActivity(12254): ---->char uuid:00002a01-0000-1000-8000-00805f9b34fb
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char property:READ
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char uuid:00002a02-0000-1000-8000-00805f9b34fb
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char property:READ|WRITE|
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char uuid:00002a03-0000-1000-8000-00805f9b34fb
04-21 18:28:25.470: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.475: E/DeviceScanActivity(12254): ---->char property:READ|WRITE|
04-21 18:28:25.475: E/DeviceScanActivity(12254): ---->char uuid:00002a04-0000-1000-8000-00805f9b34fb
04-21 18:28:25.475: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.475: E/DeviceScanActivity(12254): ---->char property:READ
04-21 18:28:25.475: E/DeviceScanActivity(12254): -->service type:PRIMARY
04-21 18:28:25.475: E/DeviceScanActivity(12254): -->includedServices size:0
04-21 18:28:25.475: E/DeviceScanActivity(12254): -->service uuid:00001801-0000-1000-8000-00805f9b34fb
04-21 18:28:25.480: E/DeviceScanActivity(12254): ---->char uuid:00002a05-0000-1000-8000-00805f9b34fb
04-21 18:28:25.480: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.480: E/DeviceScanActivity(12254): ---->char property:INDICATE
04-21 18:28:25.480: E/DeviceScanActivity(12254): -------->desc uuid:00002902-0000-1000-8000-00805f9b34fb
04-21 18:28:25.480: E/DeviceScanActivity(12254): -------->desc permission:UNKNOW
04-21 18:28:25.480: E/DeviceScanActivity(12254): -->service type:PRIMARY
04-21 18:28:25.480: E/DeviceScanActivity(12254): -->includedServices size:0
04-21 18:28:25.480: E/DeviceScanActivity(12254): -->service uuid:0000ffe0-0000-1000-8000-00805f9b34fb
04-21 18:28:25.480: E/DeviceScanActivity(12254): ---->char uuid:0000ffe1-0000-1000-8000-00805f9b34fb
04-21 18:28:25.480: E/DeviceScanActivity(12254): ---->char permission:UNKNOW
04-21 18:28:25.480: E/DeviceScanActivity(12254): ---->char property:READ|WRITE_NO_RESPONSE|NOTIFY|
04-21 18:28:25.490: E/DeviceScanActivity(12254): -------->desc uuid:00002902-0000-1000-8000-00805f9b34fb
04-21 18:28:25.490: E/DeviceScanActivity(12254): -------->desc permission:UNKNOW
04-21 18:28:25.490: E/DeviceScanActivity(12254): -------->desc uuid:00002901-0000-1000-8000-00805f9b34fb
04-21 18:28:25.490: E/DeviceScanActivity(12254): -------->desc permission:UNKNOW
04-21 18:28:26.025: E/DeviceScanActivity(12254): onCharRead BLE DEVICE read 0000ffe1-0000-1000-8000-00805f9b34fb -> 00
这里红字是由BluetoothGattCallback的onCharacteristicRead()回调而打出Log
以下Log是PC上的串口工具通过BLE模块发送过来,由BluetoothGattCallback的 onCharacteristicChanged()打出Log
04-21 18:30:18.260: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:18.745: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:19.085: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:19.350: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:19.605: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:19.835: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:20.055: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:20.320: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:20.510: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:20.735: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
04-21 18:30:21.000: E/DeviceScanActivity(12254): onCharWrite BLE DEVICE write 0000ffe1-0000-1000-8000-00805f9b34fb -> send data to phone
接下来贴出本文核心代码:
- public class DeviceScanActivity extends ListActivity {
- private final static String TAG = DeviceScanActivity.class.getSimpleName();
- private final static String UUID_KEY_DATA = "0000ffe1-0000-1000-8000-00805f9b34fb";
- private LeDeviceListAdapter mLeDeviceListAdapter;
- /**搜索BLE终端*/
- private BluetoothAdapter mBluetoothAdapter;
- /**读写BLE终端*/
- private BluetoothLeClass mBLE;
- private boolean mScanning;
- private Handler mHandler;
- // Stops scanning after 10 seconds.
- private static final long SCAN_PERIOD = 10000;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getActionBar().setTitle(R.string.title_devices);
- mHandler = new Handler();
- // Use this check to determine whether BLE is supported on the device. Then you can
- // selectively disable BLE-related features.
- if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
- Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
- finish();
- }
- // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
- // BluetoothAdapter through BluetoothManager.
- final BluetoothManager bluetoothManager =
- (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
- mBluetoothAdapter = bluetoothManager.getAdapter();
- // Checks if Bluetooth is supported on the device.
- if (mBluetoothAdapter == null) {
- Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
- finish();
- return;
- }
- //开启蓝牙
- mBluetoothAdapter.enable();
- mBLE = new BluetoothLeClass(this);
- if (!mBLE.initialize()) {
- Log.e(TAG, "Unable to initialize Bluetooth");
- finish();
- }
- //发现BLE终端的Service时回调
- mBLE.setOnServiceDiscoverListener(mOnServiceDiscover);
- //收到BLE终端数据交互的事件
- mBLE.setOnDataAvailableListener(mOnDataAvailable);
- }
- @Override
- protected void onResume() {
- super.onResume();
- // Initializes list view adapter.
- mLeDeviceListAdapter = new LeDeviceListAdapter(this);
- setListAdapter(mLeDeviceListAdapter);
- scanLeDevice(true);
- }
- @Override
- protected void onPause() {
- super.onPause();
- scanLeDevice(false);
- mLeDeviceListAdapter.clear();
- mBLE.disconnect();
- }
- @Override
- protected void onStop() {
- super.onStop();
- mBLE.close();
- }
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
- if (device == null) return;
- if (mScanning) {
- mBluetoothAdapter.stopLeScan(mLeScanCallback);
- mScanning = false;
- }
- mBLE.connect(device.getAddress());
- }
- private void scanLeDevice(final boolean enable) {
- if (enable) {
- // Stops scanning after a pre-defined scan period.
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mScanning = false;
- mBluetoothAdapter.stopLeScan(mLeScanCallback);
- invalidateOptionsMenu();
- }
- }, SCAN_PERIOD);
- mScanning = true;
- mBluetoothAdapter.startLeScan(mLeScanCallback);
- } else {
- mScanning = false;
- mBluetoothAdapter.stopLeScan(mLeScanCallback);
- }
- invalidateOptionsMenu();
- }
- /**
- * 搜索到BLE终端服务的事件
- */
- private BluetoothLeClass.OnServiceDiscoverListener mOnServiceDiscover = new OnServiceDiscoverListener(){
- @Override
- public void onServiceDiscover(BluetoothGatt gatt) {
- displayGattServices(mBLE.getSupportedGattServices());
- }
- };
- /**
- * 收到BLE终端数据交互的事件
- */
- private BluetoothLeClass.OnDataAvailableListener mOnDataAvailable = new OnDataAvailableListener(){
- /**
- * BLE终端数据被读的事件
- */
- @Override
- public void onCharacteristicRead(BluetoothGatt gatt,
- BluetoothGattCharacteristic characteristic, int status) {
- if (status == BluetoothGatt.GATT_SUCCESS)
- Log.e(TAG,"onCharRead "+gatt.getDevice().getName()
- +" read "
- +characteristic.getUuid().toString()
- +" -> "
- +Utils.bytesToHexString(characteristic.getValue()));
- }
- /**
- * 收到BLE终端写入数据回调
- */
- @Override
- public void onCharacteristicWrite(BluetoothGatt gatt,
- BluetoothGattCharacteristic characteristic) {
- Log.e(TAG,"onCharWrite "+gatt.getDevice().getName()
- +" write "
- +characteristic.getUuid().toString()
- +" -> "
- +new String(characteristic.getValue()));
- }
- };
- // Device scan callback.
- private BluetoothAdapter.LeScanCallback mLeScanCallback =
- new BluetoothAdapter.LeScanCallback() {
- @Override
- public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLeDeviceListAdapter.addDevice(device);
- mLeDeviceListAdapter.notifyDataSetChanged();
- }
- });
- }
- };
- private void displayGattServices(List<BluetoothGattService> gattServices) {
- if (gattServices == null) return;
- for (BluetoothGattService gattService : gattServices) {
- //-----Service的字段信息-----//
- int type = gattService.getType();
- Log.e(TAG,"-->service type:"+Utils.getServiceType(type));
- Log.e(TAG,"-->includedServices size:"+gattService.getIncludedServices().size());
- Log.e(TAG,"-->service uuid:"+gattService.getUuid());
- //-----Characteristics的字段信息-----//
- List<BluetoothGattCharacteristic> gattCharacteristics =gattService.getCharacteristics();
- for (final BluetoothGattCharacteristic gattCharacteristic: gattCharacteristics) {
- Log.e(TAG,"---->char uuid:"+gattCharacteristic.getUuid());
- int permission = gattCharacteristic.getPermissions();
- Log.e(TAG,"---->char permission:"+Utils.getCharPermission(permission));
- int property = gattCharacteristic.getProperties();
- Log.e(TAG,"---->char property:"+Utils.getCharPropertie(property));
- byte[] data = gattCharacteristic.getValue();
- if (data != null && data.length > 0) {
- Log.e(TAG,"---->char value:"+new String(data));
- }
- //UUID_KEY_DATA是可以跟蓝牙模块串口通信的Characteristic
- if(gattCharacteristic.getUuid().toString().equals(UUID_KEY_DATA)){
- //测试读取当前Characteristic数据,会触发mOnDataAvailable.onCharacteristicRead()
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mBLE.readCharacteristic(gattCharacteristic);
- }
- }, 500);
- //接受Characteristic被写的通知,收到蓝牙模块的数据后会触发mOnDataAvailable.onCharacteristicWrite()
- mBLE.setCharacteristicNotification(gattCharacteristic, true);
- //设置数据内容
- gattCharacteristic.setValue("send data->");
- //往蓝牙模块写入数据
- mBLE.writeCharacteristic(gattCharacteristic);
- }
- //-----Descriptors的字段信息-----//
- List<BluetoothGattDescriptor> gattDescriptors = gattCharacteristic.getDescriptors();
- for (BluetoothGattDescriptor gattDescriptor : gattDescriptors) {
- Log.e(TAG, "-------->desc uuid:" + gattDescriptor.getUuid());
- int descPermission = gattDescriptor.getPermissions();
- Log.e(TAG,"-------->desc permission:"+ Utils.getDescPermission(descPermission));
- byte[] desData = gattDescriptor.getValue();
- if (desData != null && desData.length > 0) {
- Log.e(TAG, "-------->desc value:"+ new String(desData));
- }
- }
- }
- }//
- }
- }
Android 蓝牙通信
Android 蓝牙传文件比较常见,但是官方也给出了基于蓝牙通讯做了个聊天室的sample,BluetoothChat。有兴趣的可以下载看下,很有意思。通讯那块用了特殊的BluetoothSocket。思路跟一般socket通讯一样。必须有服务端和客户端。sample有三个类:BluetoothChat,BluetoothChatService,DeviceListActivity。
BluetoothChat是主界面,可以看到聊天的内容,BluetoothChatService是功能类,实现了主要功能,但本身不是一个service,通过BluetoothChat的handle进行UI交互,DeviceListActivity是查找蓝牙设备的功能类
BluettohtChatService的实现逻辑是启动后就是服务端的在等待客户端来建立连接产生socket,当用户选了某个设备建立连接,那个设备就是客户端了,会把服务端的线程断掉。然后就利用BluetoothSocket通讯。用了状态模式来写,很有参考价值。不过很奇怪是一直有两个线程来等待设备接入,叫安全线程,不安全线程。功能一模一样都。
服务器端的实现
通过调用BluetoothAdapter的listenUsingRfcommWithServiceRecord(String, UUID)方法来获取BluetoothServerSocket(UUID用于客户端与服务器端之间的配对)
调用BluetoothServerSocket的accept()方法监听连接请求,如果收到请求,则返回一个BluetoothSocket实例(此方法为block方法,应置于新线程中)
如果不想在accept其他的连接,则调用BluetoothServerSocket的close()方法释放资源(调用该方法后,之前获得的BluetoothSocket实例并没有close。但由于RFCOMM一个时刻只允许在一条channel中有一个连接,则一般在accept一个连接后,便close掉BluetoothServerSocket)
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while ( true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
通过搜索得到服务器端的BluetoothService
调用BluetoothService的listenUsingRfcommWithServiceRecord(String, UUID)方法获取BluetoothSocket(该UUID应该同于服务器端的UUID)
调用BluetoothSocket的connect()方法(该方法为block方法),如果UUID同服务器端的UUID匹配,并且连接被服务器端accept,则connect()方法返回
注意:在调用connect()方法之前,应当确定当前没有搜索设备,否则连接会变得非常慢并且容易失败
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) { }
mmSocket = tmp;
}
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
try {
mmSocket.close();
} catch (IOException closeException) { }
return;
}
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
分别通过BluetoothSocket的getInputStream()和getOutputStream()方法获取InputStream和OutputStream
使用read(bytes[])和write(bytes[])方法分别进行读写操作
注意:read(bytes[])方法会一直block,知道从流中读取到信息,而write(bytes[])方法并不是经常的block(比如在另一设备没有及时read或者中间缓冲区已满的情况下,write方法会block)
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while ( true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main Activity to send data to the remote device */
public void write( byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main Activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}