android专题-蓝牙扫描、连接、读写
概念
外围设备
可以被其他蓝牙设备连接的外部蓝牙设备,不断广播自身的蓝牙名及其数据,如小米手环、共享单车、蓝牙体重秤
中央设备
可以搜索并连接周边的外围设备,并与之进行数据读写通讯,如手机
日常生活中常见的场景是手机app通过蓝牙开启共享单车,手机app通过蓝牙获取蓝牙体重秤的体重结果,这时候共享单车、蓝牙体重秤就称为外围设备,而手机就称为中央设备
经典蓝牙BT
泛指支持蓝牙协议在4.0以下的模块,一般用于数据量比较大的传输,如:语音、音乐等较高数据量的传输。经典蓝牙模块又可细分为:传统蓝牙和高速蓝牙模块。传统蓝牙模块在2004年推出,主要代表是支持蓝牙2.1协议的模块,在智能手机爆发的时期得到了广泛的使用。高速蓝牙模块在2009年推出,速率提高到约24Mbps,传输速率是经典蓝牙的八倍,可以轻松的应用于录像机到电视、PC到PMP、UMPC到打印机之间的资料传输。
低功耗蓝牙BLE
是指支持蓝牙协议4.0或者以上的模块,也被称为BLE模块,最大的特点就是成本和功耗的降低,可以应用于实时性要求较高的产品当中,比如:智能家居类(蓝牙锁、蓝牙灯)、传感设备的数据发送(血压计、温度传感器)、消费类电子(电子烟、遥控玩具)等。
目前市面上大部分的蓝牙都是4.0以上的低功耗蓝牙,经典蓝牙已经很少见到了。
通讯过程
一个外围设备可以发布多个服务service,每个服务可以包含多个特征值characteristic,每个特征值都有他的属性,例如长度(size),权限(permission),值(value),描述(descriptor),读写通讯都是通过Characteristic进行的。
每个service、characteristic都含有一个对应的UUID,通过和外围设备蓝牙约定UUID来进行读写通讯。
整个通讯流程为:
创建蓝牙实例 搜索扫描外围设备 连接外围设备 获取外围设备的服务service 获取服务的特征characteristic 从外围设备读取数据,即读数据 给外围设备发送数据写数据,即写数据 断开连接常用业务API
1.判断当前蓝牙是否已经开启,如果没有开启提示用户开启
2.实时扫描周边蓝牙,获取蓝牙名给用户选择
3.解析蓝牙广播数据处理业务
4.监听外围设备发送给app的数据,处理对应业务
5.app发送数据给外围设备以处理业务
Android常用的第三方蓝牙框架:okble、Android-BluetoothKit
以okble为例:
package com.wrs.project.module.app.common.bluetooth;
import android.content.Context;
import android.util.Log;
import com.a1anwang.okble.client.core.OKBLEDevice;
import com.a1anwang.okble.client.core.OKBLEDeviceImp;
import com.a1anwang.okble.client.core.OKBLEDeviceListener;
import com.a1anwang.okble.client.core.OKBLEOperation;
import com.a1anwang.okble.client.scan.BLEScanResult;
import com.a1anwang.okble.client.scan.DeviceScanCallBack;
import com.a1anwang.okble.client.scan.OKBLEScanManager;
import com.a1anwang.okble.common.OKBLECharacteristicModel;
import com.a1anwang.okble.common.OKBLEServiceModel;
import com.wrs.project.module.app.common.AppMgr;
import java.util.List;
public class Bluetooth implements DeviceScanCallBack, OKBLEDeviceListener{
private OKBLEScanManager scanManager;
private OKBLEDevice okbleDevice;// 当前连接的蓝牙设备
private OKBLECharacteristicModel writeCharacteristicModel; // 蓝牙可写的Characteristic
private Context context = AppMgr.context;
private String tag = "Bluetooth";
public Bluetooth() {
scanManager = new OKBLEScanManager(context);
scanManager.setScanCallBack(this);
}
@Override
public void onBLEDeviceScan(BLEScanResult device, int rssi) {
Log.e(tag, "扫描到蓝牙设备:" + device.toString());
String localName = device.getCompleteLocalName();
if (null != localName && localName.startsWith("ABC-")) {
stopScanBluetooth();
connectBluetoothDevice(device);
}
}
@Override
public void onFailed(int code) {
}
@Override
public void onStartSuccess() {
}
public boolean bluetoothIsEnable() {
if (null != scanManager) {
scanManager.bluetoothIsEnable();
}
return false;
}
public void disableBluetooth() {
if (null != scanManager) {
scanManager.disableBluetooth();
}
}
public void enableBluetooth() {
if (null != scanManager) {
scanManager.enableBluetooth();
}
}
public void startScanBluetooth() {
if (null != scanManager) {
if (!scanManager.isScanning()) {
scanManager.startScan();
}
}
}
public void stopScanBluetooth() {
if (null != scanManager) {
if (scanManager.isScanning()) {
scanManager.stopScan();
}
}
}
public void disConnect() {
if (null != okbleDevice) { // 如果当前已经连接其他设备,先断开连接
okbleDevice.removeDeviceListener(this);
okbleDevice.disConnect(false);
okbleDevice = null;
}
}
public void writeData(byte[] data) {
if (null != okbleDevice && null != writeCharacteristicModel && null != data && data.length > 0) {
okbleDevice.addWriteOperation(writeCharacteristicModel.getUuid(), data, new OKBLEOperation.WriteOperationListener() {
@Override
public void onWriteValue(byte[] value) {
Log.e(tag, "蓝牙写数据成功");
}
@Override
public void onFail(int code, String errMsg) {
Log.e(tag, "蓝牙写数据失败:" + code + " " + errMsg);
}
@Override
public void onExecuteSuccess(OKBLEOperation.OperationType type) {
}
});
} else {
Log.e(tag, "蓝牙写数据失败: 蓝牙没有连接或没发现可写Characteristic");
}
}
public void connectBluetoothDevice(BLEScanResult device) {
// 先断开当前连接
disableBluetooth();
okbleDevice = new OKBLEDeviceImp(context, device);
okbleDevice.addDeviceListener(this);
okbleDevice.connect(true);//true表示连接断开后OKBLE的会自动重连
}
@Override
public void onConnected(String deviceTAG) {
Log.e(tag, "设备连接成功 " + deviceTAG);
// 连上蓝牙后,获取蓝牙的WriteCharacteristic用后面给蓝牙设备发送数据,获取蓝牙的ReadCharacteristic用来监听蓝牙设备发送过来的数据
List<OKBLEServiceModel> serviceModels = okbleDevice.getServiceModels();
if (null != serviceModels && serviceModels.size() > 0) {
for (int i = 0; i < serviceModels.size(); i++) {
OKBLEServiceModel serviceModel = serviceModels.get(i);
String serviceUUID = serviceModel.getUuid();
if (serviceUUID.startsWith("aaaaaaa-")) { // 匹配找到读写的服务
List<OKBLECharacteristicModel> characteristicModels = serviceModel.getCharacteristicModels();
if (null != characteristicModels && characteristicModels.size() > 0) {
for (int j = 0; j < characteristicModels.size(); j++) {
OKBLECharacteristicModel characteristicModel = characteristicModels.get(j);
String characteristicUUID = characteristicModel.getUuid();
if (characteristicUUID.startsWith("bbbbbbbbb") && characteristicModel.isCanWrite() && characteristicModel.isCanWriteNoResponse()) { // 匹配找到写的Characteristic
findWriteCharacteristic(characteristicModel);
} else if (characteristicUUID.startsWith("8653000b-") && characteristicModel.isCanNotify()) { // 匹配找到读的Characteristic
findReadCharacteristic(characteristicModel);
}
}
}
break;
}
}
}
}
@Override
public void onDisconnected(String deviceTAG) {
}
@Override
public void onReadBattery(String deviceTAG, int battery) {
}
@Override
public void onReceivedValue(String deviceTAG, String uuid, byte[] value) {
}
@Override
public void onWriteValue(String deviceTAG, String uuid, byte[] value, boolean success) {
}
@Override
public void onReadValue(String deviceTAG, String uuid, byte[] value, boolean success) {
}
@Override
public void onNotifyOrIndicateComplete(String deviceTAG, String uuid, boolean enable, boolean success) {
}
private void findWriteCharacteristic(OKBLECharacteristicModel characteristic) {
if (null != okbleDevice && null != characteristic) {
writeCharacteristicModel = characteristic;
}
}
private void findReadCharacteristic(OKBLECharacteristicModel characteristic) {
if (null != okbleDevice && null != characteristic) {
String uuid = characteristic.getUuid();
boolean enableNotifyEnable = okbleDevice.isNotifyEnabled(uuid);
if (enableNotifyEnable) {
Log.e(tag, "打开读属性成功");
} else {
okbleDevice.addNotifyOrIndicateOperation(uuid, true, new OKBLEOperation.NotifyOrIndicateOperationListener() {
@Override
public void onFail(int code, String errMsg) {
Log.e(tag, "打开读属性失败");
}
@Override
public void onExecuteSuccess(OKBLEOperation.OperationType type) {
Log.e(tag, "打开读属性成功");
}
@Override
public void onNotifyOrIndicateComplete() {
Log.e(tag, "打开读属性成功");
}
});
}
}
}
}
蓝牙广播数据包解析
广播包有两种: 广播包 (Advertising Data)和 响应包 (Scan Response),其中广播包是每个设备必须广播的,而响应包是可选的。
每个包都是 31 字节,分为有效数据和无效数据两部分。
有效数据部分 :包含若干个广播数据单元,称为 AD Structure 。
AD Structure 的组成是:
第一个字节是长度值 Len ,表示接下来的 Len 个字节是数据部分。
数据部分的第一个字节表示数据的类型 AD Type ,剩下的 Len - 1 个字节是真正的数据 AD data 。其中 AD type 非常关键,决定了 AD Data 的数据代表的是什么和怎么解析。
无效数据部分 :因为广播包的长度必须是 31 个 byte,如果有效数据部 分不到 31 自己,剩下的就用 0 补全。这部分的数据是无效的,解释的时候,忽略即可。在 Android 中,系统会把这两个数据拼接在一起,返回一个 62 字节的数组。
例如:
第一个 字节代表广播数据单元的长度 ,02 转为10进制就是 2代表其数据长度为2 , 而数据单元的第一个字节代表类型 。
01 代表 代表物理连接功能为普通发现模式 06代表其数据类容
紧接着下一个数据单元:
0B代表数据长度为11 ,数据类型为 02 即Serviceuuid代表是非完整的16bit uuid, 所以紧接着的后10位就是其uuid。
接下来就是下一个数据单元
首位是13转为二进制就是19,其长度就是19,类型就是09 ,代表设备名称,30-》字符0,65代表字符e,61代表字符a,73代表字符s,79代表字符y,4E代表N,65代表e,57代表W,44代表D,43代表C,53代表S ,00 代表字符null,01代表字符soh(SOH是序始字符(Start Of Header),它表示标题的开始),56代表V ,31代表字符1,2E代表字符.,30代表字符0,44代表D所有其设备名称就是0easyNewDCS V1.0D。
接下来的一个数据单元长度是5,广播类型12 连接间隔范围,有四个字节,接下来数据长度是02,类型是0A代表信号强度 剩余都是00000都是补位的无效数据。
广播数据类型:
(1)Flags: TYPE = 0x01。这个数据用来标识设备 LE 物理连接的功能。DATA 是 0 到多个字节的 Flag 值,每个 bit 上用 0 或者 1 来表示是否为 True。如果有任何一个 bit 不为 0,并且广播包是可连接的,就必须包含此数据。各 bit 的定义如下: bit 0: LE 有限发现模式 bit 1: LE 普通发现模式 bit 2: 不支持 BR/EDR bit 3: 对 Same Device Capable(Controller) 同时支持 BLE 和 BR/EDR bit 4: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR bit 5…7: 预留
(2)Service UUID: 广播数据中一般都会把设备支持的 GATT Service 广播出来,用来告诉外面本设备所支持的 Service。有三种类型的 UUID:16 bit, 32bit, 128 bit。广播中,每种类型类型有有两个类别:完整和非完整的。这样就共有 6 种 AD Type。
非完整的 16 bit UUID 列表: TYPE = 0x02;
完整的 16 bit UUID 列表: TYPE = 0x03;
非完整的 32 bit UUID 列表: TYPE = 0x04;
完整的 32 bit UUID 列表: TYPE = 0x05;
非完整的 128 bit UUID 列表: TYPE = 0x06;
完整的 128 bit UUID 列表: TYPE = 0x07;
(3) Local Name: 设备名字,DATA 是名字的字符串。 Local Name 可以是设备的全名,也可以是设备名字的缩写,其中缩写必须是全名的前面的若干字符。 设备全名: TYPE = 0x08 设备简称: TYPE = 0x09
(4)TX Power Level: TYPE = 0x0A,表示设备发送广播包的信号强度。DATA 部分是一个字节,表示 -127 到 + 127 dBm。
(5) 带外安全管理(Security Manager Out of Band):TYPE = 0x11。DATA 也是 Flag,每个 bit 表示一个功能: bit 0: OOB Flag,0 表示没有 OOB 数据,1 表示有 bit 1: 支持 LE bit 2: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR bit 3: 地址类型,0 表示公开地址,1 表示随机地址 。
(6)外设(Slave)连接间隔范围:TYPE = 0x12。数据中定义了 Slave 最大和最小连接间隔,数据包含 4 个字节:
前 2 字节:定义最小连接间隔,取值范围:0x0006 ~ 0x0C80,而 0xFFFF 表示未定义; 后 2 字节:定义最大连接间隔,同上,不过需要保证最大连接间隔大于或者等于最小连接间隔。
(7) 服务搜寻:外围设备可以要请中心设备提供相应的 Service。其数据定义和前面的 Service UUID 类似:
16 bit UUID 列表: TYPE = 0x14
32 bit UUID 列表: TYPE = 0x??
128 bit UUID 列表: TYPE = 0x15
(8) Service Data: Service 对应的数据。
16 bit UUID Service: TYPE = 0x16, 前 2 字节是 UUID,后面是 Service 的数据;
32 bit UUID Service: TYPE = 0x??, 前 4 字节是 UUID,后面是 Service 的数据;
128 bit UUID Service: TYPE = 0x??, 前 16 字节是 UUID,后面是 Service 的数据;
(9) 公开目标地址:TYPE = 0x17,表示希望这个广播包被指定的目标设备处理,此设备绑定了公开地址,DATA 是目标地址列表,每个地址 6 字节。
(10) 随机目标地址:TYPE = 0x18,定义和前一个类似,表示希望这个广播包被指定的目标设备处理,此设备绑定了随机地址,DATA 是目标地址列表,每个地址 6 字节。
(11) Appearance:TYPE = 0x19,DATA 是表示了设备的外观。
(12) 厂商自定义数据: TYPE = 0xFF,厂商自定义的数据中,前两个字节表示厂商 ID,剩下的是厂商自己按照需求添加,里面的数据内容自己定义。
项目源码:https://codechina.csdn.net/android1/projectbasic
上篇:android专题-数据库Room 目录 下篇: Android专题-常用第三方框架