标准蓝牙设备
扫描经典蓝牙设备1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89final BluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bm.getAdapter();
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled())
{
Toast.makeText(this,"please open bluttooth",Toast.LENGTH_LONG).show();
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 0);
}
//获取已经绑定的蓝牙
Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
//遍历已绑定设备
if (devices.size() > 0) {
for (BluetoothDevice bluetoothDevice : devices) {
String info = bluetoothDevice.getName() + ":"
+ bluetoothDevice.getAddress() + "\n\n";
Log.d("m4bln",info);
}
}
/*
//扫描标准蓝牙设备
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
if (mBluetoothAdapter.startDiscovery()){
Log.d("jiyuanyuan","开始扫描");
}
//标准蓝牙设备的receiver
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
switch (intent.getAction()) {
case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
Toast.makeText(getApplicationContext(), "正在搜索蓝牙设备,搜索时间大约一分钟", Toast.LENGTH_SHORT).show();
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
Toast.makeText(getApplicationContext(), "搜索蓝牙设备结束", Toast.LENGTH_SHORT).show();
break;
case BluetoothDevice.ACTION_FOUND:
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d("jiyuanyuan",bluetoothDevice.getAddress()+bluetoothDevice.getName());
if (bluetoothDevice.getAddress().equals("1C:52:16:4E:7C:86")){
//78:62:56:D4:A2:3E
Log.d("jiyuanyuan","链接 QCY");
//bluetoothDevice.createBond();
try {
Boolean returnValue = false;
Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
Log.d("BlueToothTestActivity", "开始配对");
returnValue = (Boolean) createBondMethod.invoke(bluetoothDevice);
Log.d("BlueToothTestActivity", "配对返回:"+returnValue.toString());
}catch (Exception e) {
e.printStackTrace();
}
}else {
Log.d("jiyuanyuan","不相信");
}
break;
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
int status = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
if (status == BluetoothDevice.BOND_BONDED) {
Toast.makeText(getApplicationContext(), "已连接", Toast.LENGTH_SHORT).show();
} else if (status == BluetoothDevice.BOND_NONE) {
Toast.makeText(getApplicationContext(), "未连接", Toast.LENGTH_SHORT).show();
}
else if(status == BluetoothDevice.BOND_BONDING){
Toast.makeText(getApplicationContext(), "配对中", Toast.LENGTH_SHORT).show();
}
break;
}
}
};
参考
https://developer.android.google.cn/guide/topics/connectivity/bluetooth.html
BLE设备
BLE(Bluetooh Low Energy)蓝牙低能耗技术是短距离、低成本、可互操作性的无线技术,它利用许多智能手段最大限度地降低功耗。BLE设备有效传输距离被提升到了100米以上,同时只需要一颗纽扣电池就可以工作数年之久。
Android 4.3 (API level 18)内置了对BLE设备的支持,允许APP实现发现BLE设备、查找服务以及信息传输。
GAP
GAP(Generic Access Profile),它用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。
- 设备角色
GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central)。
外围设备:一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。
中心设备:中心设备相对比较强大,用来连接其他外围设备。例如手机等。 广播数据
在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。
这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。广播流程
外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。
几个概念
GATT
通过BLE连接,读写属性类数据的Profile通用规范,现在所有的BLE应用Profile都是基于GATT的。它定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信
GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。
GATT 事务是建立在嵌套的Profiles, Services 和 Characteristics之上的的,如下图所示:
http://jlog.qiniudn.com/microcontrollers_GattStructure.png
Profile
Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。Service
Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。(官方的Service https://www.bluetooth.com/specifications/gatt/services)
官方通过了一些标准 Service。以 Heart Rate Service为例,可以看到它的官方通过 16 bit UUID 是 0x180D,包含 3 个 Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point,并且定义了只有第一个是必须的,它是可选实现的。Characteristic
Characteristic在 GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的 X/Y/Z 三轴值。
与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。你可以免费使用 Bluetooth SIG 官方定义的标准 Characteristic (https://www.bluetooth.com/specifications/gatt/characteristics),使用官方定义的,可以确保 BLE 的软件和硬件能相互理解。当然,你可以自定义 Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。
举个例子, Heart Rate Measurement Characteristic,这是上面提到的 Heart Rate Service 必需实现的 Characteristic,它的 UUID 是 0x2A37。它的数据结构是,开始 8 bit 定义心率数据格式(是UINT8 还是 UINT16?),接下来就是对应格式的实际心率数据。
实际上,和 BLE 外设打交道,主要是通过 Characteristic。你可以从 Characteristic 读取数据,也可以往 Characteristic 写数据。这样就实现了双向的通信。所以你可以自己实现一个类似串口(UART)的 Sevice,这个 Service 中包含两个 Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。
Android BLE API
BluetoothAdapter
代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作,一个Android系统只有一个BluetoothAdapter,通过BluetoothManager获取。1
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
BluetoothDevice
扫描后发现可连接的设备,获取已经连接的设备。1
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
BluetoothGattCallback
对已经连接上设备的某些操作后返回的结果的回调1
2
3
4BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback(){
//实现回调方法,根据业务做相应处理
};
BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(this, false, bluetoothGattCallback);BluetoothGatt
通过BluetoothGatt可以连接设备(connect),发现服务(discoverServices),并把相应地属性返回到BluetoothGattCallback,可以看成蓝牙设备从连接到断开的生命周期。
扫描BLE设备
进行BLE设备扫描时,除了蓝牙权限外,还需要申请定位权限并打开GPS(这个非常坑)。扫描周边的BLE设备代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39//获取BluetoothAdapter
final BluetoothManager bm = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bm.getAdapter();
//判断蓝牙是否开启
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled())
{
Toast.makeText(this,"please open bluttooth",Toast.LENGTH_LONG).show();
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 0);
}
//扫描BLE设备
mScanCallback = new LeScanCallback();
BluetoothLeScanner mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
System.out.println("开始扫描BLE设备。。。");
mBluetoothLeScanner.startScan(mScanCallback);
private class LeScanCallback extends ScanCallback{
@Override
public void onScanResult(int callbackType, ScanResult result) {
if(result != null){
System.out.println("扫面到设备:" + result.getDevice().getName() + " " + result.getDevice().getAddress());
//此处,我们尝试连接MI 设备
mTargetDeviceName ="MI Band 2";
if (result.getDevice().getName() != null && mTargetDeviceName.equals(result.getDevice().getName())) {
//扫描到我们想要的设备后,立即停止扫描
result.getDevice().connectGatt(MainActivity.this, false, mGattCallback);
mBluetoothLeScanner.stopScan(mScanCallback);
}
}
else{
System.out.println("result为空");
}
}
};
BLE设备连接
两个设备通过BLE通信,首先需要建立GATT连接,这里我们讲的是Android设备作为client端,连接GATT Server。连接GATT Server,需要调用BluetoothDevice的connectGatt()方法,此函数带三个参数:Context、autoConnect(boolean)和 BluetoothGattCallback 对象。调用后返回BluetoothGatt对象,它是GATT profile的封装,通过这个对象,我们就能进行GATT Client端的相关操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback(){
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorRead(gatt, descriptor, status);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
super.onReadRemoteRssi(gatt, rssi, status);
}
@Override
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
super.onMtuChanged(gatt, mtu, status);
}
};
BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(this, false, bluetoothGattCallback);
//以下为获得Gatt后的相关操作对应的响应方法
//notification to onCharacteristicChanged;
bluetoothGatt.setCharacteristicNotification(characteristic, true);
//readCharacteristic to onCharacteristicRead;
bluetoothGatt.readCharacteristic(characteristic);
//writeCharacteristic to onCharacteristicWrite;
bluetoothGatt.wirteCharacteristic(mCurrentcharacteristic);
//connect and disconnect to onConnectionStateChange;
bluetoothGatt.connect();
bluetoothGatt.disconnect();
//readDescriptor to onDescriptorRead;
bluetoothGatt.readDescriptor(descriptor);
//writeDescriptor to onDescriptorWrite;
bluetoothGatt.writeDescriptor(descriptor);
//readRemoteRssi to onReadRemoteRssi;
bluetoothGatt.readRemoteRssi();
//executeReliableWrite to onReliableWriteCompleted;
bluetoothGatt.executeReliableWrite();
//discoverServices to onServicesDiscovered;
bluetoothGatt.discoverServices();
数据解析
数据包格式如下图
数据包有两种:广播包(Advertising Data)和响应包(Scan Response),其中广播包是每个设备必须广播的,而响应包是可选的。
每个包都是 31 字节,数据包中分为有效数据(significant)和无效数据(non-significant)两部分。
- 有效数据部分:
包含若干个广播数据单元,称为AD Structure。如图中所示,AD Structure的组成是:第一个字节是长度值Len,表示接下来的Len个字节是数据部分。数据部分的第一个字节表示数据的类型AD Type,剩下的Len - 1个字节是真正的数据AD data。其中AD type非常关键,决定了AD Data的数据代表的是什么和怎么解析 - 无效数据部分:
因为广播包的长度必须是31字节,如果有效数据部分不到31字节,剩下的就用0补齐,这部分的数据是无效的,解析的时候,直接忽略即可。
根据如下数据包,举例说明LBE数据包的解析:1
02 01 06 14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 06 08 48 45 54 2D 35 09 03 E7 FE 12 FF 0F 18 0A 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
根据解析规则,可分成如下部分:
广播数据(前31字节)
02 01 06 14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 06 08 48 45 54 2D 35响应数据
09 03 E7 FE 12 FF 0F 18 0A 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00有效数据
02 01 06 14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 06 08 48 45 54 2D 35 09 03 E7 FE 12 FF 0F 18 0A 18- 无效数据
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
其中的有效数据又可分为如下几个数据单元:
02 01 06
14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23
06 08 48 45 54 2D 35
09 03 E7 FE 12 FF 0F 18 0A 18
根据上面AD Type定义(所有的 AD type 的定义在文档中)分别解析如下:
第一组数据告诉我们该设备属于LE普通发现模式,不支持BR/EDR;
第二组数据告诉我们该数据为厂商自定义数据,一般是必须解析的,可根据协议规则进行解析获取对应的所需信息;
第三组数据告诉我们该设备的简称为HET-5,其中对应的字符是查找ASSIC表得出的;
第四组数据告诉我们UUID为E7FE-12FF-0F18-0A18(此处有疑,类型03表示的是16位的UUID,对应的两个字节,而此处有8个字节,估计是设备烧录时把字节位数理解为了字符位数导致的问题).