继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android 蓝牙4.0 BLE 串口模块最佳实践

刘某人
关注TA
已关注
手记 9
粉丝 472
获赞 529

我在慕课网的Android新课核心知识点如下:
Android X/音视频开发/社交匹配算法/即时通信/语音识别/App优化/安全加固
手把手完成商业级社交App开发进阶Android高级工程师

很多同学对BLE感兴趣,确实,作为主流的蓝牙协议,它还是很有竞争性的,但是BLE没有硬件也不好调试,所以我买了一块开发板来给大家写这篇文章,讲解各种细节和思路,希望你看完这篇文章能对BLE有一个更加清晰的认识。

一.蓝牙模块

首先介绍一下这块开发板,型号是HC-08,相关开发包和工具我也会在文末给大家提供,模块采用 TI 的 CC2540F256 芯片,配置 256K 字节空间,支持 AT 指令,用户可根据 需要更改角色(主、从模式)以及串口波特率、设备名称等参数,使用灵活。
图片描述

再说一下指令,也就是协议,其实就是约定的字段,和接口文档类似,我们来看下一些基本的指令吧:
图片描述

大家看下大概能明白就行,指令很多,就不一一列举出来了,连接BLE设备然后通信,这其实和我们所认知的C/S架构是一样的,不过,模块是支持身份互相转换的,我们默认即手机为从设备,模块为主设备,如图:
图片描述
所以只要建立连接,然后按照协议去收发即可了,那么首先,我们需要来进行一些模块的初始化动作,先将模块插入USB,指示灯亮起,并且电脑识别到设备即可,然后打开HID传串口小助手,如图:

图片描述

在这一步我们应该做的就是点击选择模块选择合适的模块,比如我这里选择的就是HC-08,然后紧接着就可以测试了,如下图,我们首先点击了一个测试指令,实际上他发送的就是AT,然后返回OK,说明我们的串口通信没问题,对吧,如图:

图片描述

那么接下来就要设置波特率了,这里输入115200点击设置即可,可以看到控制台返回OK115200,NONE字样,首先OK115200是成功的意思,大家都知道,然后你可以看到下表中其实有一个模块波特率设置失败的提示,这其实是我们已经是115200波特率了,你也可以点击模块波特率查询查看,NONE的意思见下图:

图片描述

我们查看协议文档,可知最后的是校验位代号,而NONE代表无校验。

图片描述

接着我们再来设置下蓝牙名称,输入LGL后点击设置即可,同样的控制台也输出设置成功了。

图片描述

对比一下协议你就清楚我们三方的关系了,现在你最起码会觉得这玩意其实没有想象中的那么难对吧,那么我们就可以来开始我们的Android实践了。

图片描述

二.认识BLE API

我们做一个抽丝剥茧的应用,首先是搜索,对吧,搜索到对应的BLE设备,比如我们这个蓝牙模块之后建立通信,然后开始协议的发送和接口,所以我们的开发步骤:

1.搜索设备
2.连接设备
3.发送指令

那么你肯定得懂他的方法和回调,而之前的蓝牙方法是不支持BLE的,低功耗蓝牙的传输方面是有自己的一套理论体系,且听我慢慢分析

1.权限

在BLE之前,我们只需要添加两条权限就够了

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

但是现在还需要增加几条权限

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />

分别是定位权限和声明BLE的标记,如果没有这些权限是无法使用BLE的,Android6.0还需要动态申请运行时权限

2.判断支持性

我们需要检查运行设备是否能支持BLE,这就刚好用到了刚才在清单文件定义的标志位了

//判断是否支持BLE
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, "设备不支持BLE", Toast.LENGTH_SHORT).show();
    finish();
}

有些设备是不支持BLE的

3.BluetoothAdapter

本地蓝牙适配器,可以从中获取到本地蓝牙的相关信息,比如名称,地址等,我们可以通过两种方式来获取本地适配器

//1.获取本地蓝牙适配器
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//2.获取本地蓝牙适配器
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

可以看到,我们可以通过实例化BluetoothManager来获取,也可以通过BluetoothAdapter的静态方法getDefaultAdapter来获取。

如果翻阅BluetoothManager的源码你就会发现,实际上getAdapter中获取的适配器对象就是BluetoothManager 初始化的时候调用BluetoothAdapter.getDefaultAdapter()获取的,所以两者并没有什么区别。

图片描述

4.BluetoothDevice

蓝牙设备类,代表远程端的设备信息

5.BluetoothManager

蓝牙的管理类,我们一般用他来获取我们本地的BluetoothAdapter。

6.BluetoothLeScanner

蓝牙的搜索类,负责搜索和停止搜索。

7.ScanCallback

BluetoothLeScanner搜索的结果回调,可以获取远程设备

8.BluetoothGatt

BLE协议的执行者,通过他可以连接设备,发现设备以及收发数据,而他其实是继承自BluetoothProfile,这个类就是一套通用的数据交互规范类,我们通过BluetoothGatt就可以发送和接收数据了。

9.BluetoothGattService

Gatt服务

10.BlueBluetoothGattCharacteristic

Gatt的特性

11.BluetoothGattDescriptor

Gatt描述

12.BluetoothGattCallback

Gatt回调

//连接状态发生变化
//newState:
//连接成功:BluetoothProfile.STATE_CONNECTED
//连接失败:BluetoothProfile.STATE_DISCONNECTED
onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
//UUID搜索服务成功回调
onServicesDiscovered(BluetoothGatt gatt, int status)
//读取数据
//status:成功:BluetoothGatt.GATT_SUCCESS
onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
//写入数据
//status:成功:BluetoothGatt.GATT_SUCCESS
onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
//收到数据
onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

三.初始化

对于初始化,我们会分为两部分,第一部分是给搜索做准备的,第二部分是给连接做准备的,我们先来看下搜索的初始化,如下代码:

图片描述

我们首先判断是否支持BLE,再到获取本地蓝牙适配器,以及判断蓝牙是否打开。

四.搜索与停止搜索

我们在初始化中获取到了BluetoothLeScanner的对象就可以实现搜索和停止搜索

图片描述

从图中的代码可以看到,我写了一个scanBleDev的方法,如果传true,则startScan,反之则stopScan,这里我在搜索的时候做了一个定时关闭的处理,这是因为蓝牙的搜索实际上是非常耗电的,如果不及时关闭,则会消耗设备的电量,所以我们会规定一段时间后停止搜索,而他们所传递的都是同一个回调mScanCallback

五.搜索结果处理

ScanCallback的回调onScanResult中会有参数ScanResult返回,我们来看下这个类的参数:
图片描述

我们可以看到,mDevice就是我们所需要的远程设备,并且我们还能获取到比如mRssi信号,mEventType事件类型等参数,而mDevice则是我们熟悉的BluetoothDevice,我们可以通过他获取想要的信息,如蓝牙名称,蓝牙地址等。

六.搜索实现

我们现在就可以去实现搜索了,我在menu中新建了两个item,效果如图:

图片描述

然后在内部通过RecyclerView实现搜索结果的列表,来看下对应的代码

1.Menu的创建
图片描述

2.数据源的填充
图片描述

来看下实现效果:
图片描述

七.连接设备

现在我们的列表已经搜索出来了,我们就可以根据地址去连接了,所以当我们点击某个Item的时候我们就跳转到另一个收发消息的界面:
图片描述

我们的主要数据还是蓝牙地址,我们可以通过远程设备去连接,如图:
图片描述

这样我们就能得到一个mBluetoothGatt对象以及一个mGattCallback回调了,我们的数据传输之旅就从这里开始了。

八.GattCallback

我们一起的起点是从这里开始,终点亦是从这里开始,先来看下第一个回调吧。
图片描述

当我们调用connect并且成功开始连接之后,onConnectionStateChange就能收到回调了,并且其中有一个参数newState就是用来判断状态的

  • 连接成功 BluetoothProfile.STATE_CONNECTED
  • 连接失败 BluetoothProfile.STATE_DISCONNECTED

当我们里连接成功之后,就可以调用discoverServices去搜索服务,那么在我们的onServicesDiscovered中就能接到通知,这个时候我们就要去从众多服务中拿到我们的BluetoothGattCharacteristic对象了,只有拿到了它,我们才能去发送消息,来看代码:
图片描述

这就是比较关键的代码了,我们通过Gatt客户端是可以获取到一个BluetoothGattService

的List,然后去遍历之后得到的BluetoothGattService再去获取对应BluetoothGattCharacteristic的List,然后再去根据他的UUID是否与我们初定的UUID想匹配,这个UUID是模块的:

//模块的UUID
public static String HEART_RATE_MEASUREMENT = "0000ffe1-0000-1000-8000-00805f9b34fb";

如果想匹配就可以确定他对应的BluetoothGattCharacteristic特性则使我们需要的数据对象了,现在我们还有两个回调,对应的就是读写的操作了

九.指令的读写

我们读写操作分为三部曲

  1. 发送数据
  2. 发送数据结果回调
  3. 接收数据

首先是发送数据,也就是我们的发送按钮的点击事件
图片描述

我们每次发送只支持20字节,所以我们需要分包
图片描述

最终封装我们从onServicesDiscovered中获取到的BluetoothGattCharacteristic对象由Gatt客户端来进行数据的写入,也就是

//发送数据
mBluetoothGatt.writeCharacteristic(target_chara);

那么我们的二部曲则是发送数据结果回调,来看下代码
图片描述

当你发送成功,也就是status == BluetoothGatt.GATT_SUCCESS的时候,我就向UI写入一个发送成功,那么现在还有三部曲,也就是我们的收,收的话也简单:
图片描述

在onCharacteristicChanged中就能得到values值,这样我们的通信就畅通无阻了。

十.测试

我们现在来测试一下:

需求:手机App连接蓝牙模块,然后发送一个Hello,串口接收到之后返回一个 Hi Android.

这个需求很简单吧,我们来看下运行效果

图片描述

首先连接成功,然后发送一个Hello,也显示发送成功,这说明onCharacteristicWrite的status=BluetoothGatt.GATT_SUCCESS,那么来看下我们的串口

图片描述

串口也收到了这个Hello,现在我们发送一个Hi Android回去

图片描述

可以看到onCharacteristicChanged已经收到了,并且UI上也显示了

图片描述

那么串口是如何发送消息的呢?

图片描述

在输入框中输入指令,然后点击数据发送的按钮即可。

十一.注意事项

1.本次案例是基于蓝牙窗口通信的Android案例,在实际开发过程中,串口端是有自己的协议的,比如我Android端发送AT,则串口返回OK

2.关于数据字节码这块,首先发送字节是需要分包的,这个要注意下,其次是接收的字节,我只是简单的转换成String,数字和字母是没问题的,如果你想支持中文,还需要另外的解码,因为字节是以十六进制进行发送的,网上也能找到对应的资料。

最后来张模块的真机图片吧:

图片描述

源码&PPT&蓝牙模块资料包
https://pan.baidu.com/s/1epSh2ScCSbhq6xmStywTQw
密码:t04q

我在慕课网的Android新课核心知识点如下:
Android X/音视频开发/社交匹配算法/即时通信/语音识别/App优化/安全加固
手把手完成商业级社交App开发进阶Android高级工程师

如果觉得文章不错,点个赞呗~ ❥(^_-)

打开App,阅读手记
3人推荐
发表评论
随时随地看视频慕课网APP