GATT简介
蓝牙分为经典蓝牙和低功耗蓝牙(BLE),我们常用的蓝牙遥控器就是低功耗蓝牙
低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。
GATT全称Generic Attribute Profile(直译即:通用属性协议),是一个在蓝牙连接之上的发送和接收较短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。
GATT的结构如下图:
- gatt是多个service的集合,gatt包含多个不同的service
- service下包含多个不同的Charcteristic(特征)
- Charcteristic又包含value和Descriptor
客户端
前言 客户端的是通过从特定的服务(BluetoothGattService)里面获取特性(BluetoothGattCharacteristic) 客户端 BluetoothGatt --- 通过此类发送消息 BluetoothGattService -- 特定服务 BluetoothGattCharacteristic -- 特定字符 BluetoothGattCallback -- 回调监听 第一个维度: 怎么获取? 1.我们怎么获取BluetoothGatt? BluetoothGatt gatt = BluetoothDevice.connectGatt(context, false , bluetoothGattCallback); 2.怎么从BluetoothGatt中获取BluetoothGattService 1)先启动发现服务:gatt.discoverServices(); 2)再从bluetoothGattCallback.onServicesDiscovered的回调方法中调用 gatt.getServices()通过特定uuid找特定服务。例如: for (BluetoothGattService service : gatt.getServices()) { Log.d(TAG, "service uuid " + service.getUuid() + " type "+service.getType()); if (service.getUuid().toString().equals(serviceUUID)) {//客户端默认一个uuid bluetoothGattService = service; } } 3.怎么从BluetoothGattService中获取BluetoothGattCharacteristic 从2中找到了BluetoothGattService,就可以在通过uuid找对应的BluetoothGattCharacteristic 例如。bluetoothGattService.getCharacteristic(uuid) 或者 for (BluetoothGattCharacteristic characteristic : bluetoothGattService.getCharacteristics()) { Log.d(TAG, "characteristic uuid "+characteristic.getUuid()+" type "+characteristic.getProperties()); if (characteristic.getUuid().toString().equals(readUUID)) { //读特征 readCharacteristic = characteristic; } else if (characteristic.getUuid().toString().equals(writeUUID)) { //写特征 writeCharacteristic = characteristic; } } 第二个维度: 目的干什么? 我们的目的是把数据发出去,而发数据的关键是BluetoothGattCharacteristic 发数据的工具是BluetoothGatt 例如 客户端主动要求的: BluetoothGatt.writeCharacteristic 回调于 bluetoothGattCallback.onCharacteristicWrite BluetoothGatt.readCharacteristic 回调于 bluetoothGattCallback.onCharacteristicRead 客户端端被动接收的: 按照以往的思路,先要设置监听对象 BluetoothGatt.setCharacteristicNotification 设置需要监听的BluetoothGattCharacteristic 回调于 bluetoothGattCallback.onCharacteristicRead 注意: 如果你想监听几个,就需要设置几个BluetoothGattCharacteristic
服务端
关键类
BluetoothGattServer --- 发送数据的关键类
BluetoothGattService -- ble特定服务
BluetoothGattCharacteristic -- ble特定字符
BluetoothGattServerCallback -- 回调监听
BluetoothLeAdvertiser --- 广播ble
AdvertiseData --- 广播所带数据
AdvertiseSettings --- 广播属性设置
简单来说:
1.如果我们需要我们的ble service被监听到,就需要时时广播
2.广播需要分两步走:
a.先广播:BluetoothLeAdvertiser.startAdvertising
发送的广播被客户端扫描的时候接收到
b.再启动服务:BluetoothGattServer.addService
服务是我们自定义
服务里面的属性需要BluetoothGattService.addCharacteristic进去
这里就涉及uuid的设置了
3.接下来就是客户端获取监听成功后的回调监听
1.蓝牙设备列表的获取
1.申请权限
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
private void requestPermission(String permission) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M){
if (ContextCompat.checkSelfPermission(this,permission) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{permission},1);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1){
if (grantResults[0] == PackageManager.PERMISSION_GRANTED){
Log.e(TAG,"success");
}
Log.e(TAG,"failure");
}
}
2.获取蓝牙列表
final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
Log.w(TAG,"BluetoothAdapter is null.");
return;
}
final Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
3.连接Gatt
bluetoothGatt = bluetoothDevice.connectGatt(this, true,new MyBlueCallback());
class MyBlueCallback extends BluetoothGattCallback{
private String TAG = "MainActivity";
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.e(TAG,"status:"+status+ " newState:"+newState);
//BluetoothGatt.STATE_CONNECTED
super.onConnectionStateChange(gatt, status, newState);
}
}
在进行BLE开发过程中可能会遇到操作失败等情况,这个时候可能需要断开与BLE的连接或者清理相关资源.在BluetoothGatt类中有两个相关的方法
1. disconnect()
2. close()
disconnect()方法: 调用了该方法之后可以调用connect()方法进行重连,这样还可以继续进行断开前的操作.
close()方法: 一但调用了该方法, 如果你想再次连接,必须调用BluetoothDevice的connectGatt()方法. 因为close()方法将释放BluetootheGatt的所有资源.
需要注意的问题:
当你需要手动断开时,调用disconnect()方法,此时断开成功后会回调onConnectionStateChange方法,在这个方法中再调用close方法释放资源。
如果在disconnect后立即调用close,会导致无法回调onConnectionStateChange方法。
名称 | 含义 | 含义 |
connect | /** * Connect back to remote device. * * <p>This method is used to re-connect to a remote device after the * connection has been dropped. If the device is not in range, the * re-connection will be triggered once the device is back in range. * * @return true, if the connection attempt was initiated successfully */ | 重连 |
disconnect | /** * Disconnects an established connection, or cancels a connection attempt * currently in progress. */ | 断连 |
close | /** * Close this Bluetooth GATT client. * * Application should call this method as early as possible after it is done with * this GATT client. */ | 关闭连接 |
4、GATT连接成功,回调onConnectionStateChange()函数
gatt连接成功或失败,会回调gattCallback下的onConnectionStateChange()函数
接下来调用mBluetoothGatt.discoverServices()函数(功能是查询已连接的gatt下的service)
5、回调onServicesDiscovered()函数,获取指定Service和Characteristic
上一步调用mBluetoothGatt.discoverServices()函数后,系统会回调gattCallback下的onServicesDiscovered()函数,这表明我们已经可以通过指定的UUID来获取指定的Service实例了
在onServicesDiscovered()函数回调后,通过UUID先获取service,然后再使用获取到的service和UUID获取Characteristic,最后mBluetoothGatt.readCharacteristic(mVIDPIDCharacteristic);读取这个Characteristic
6、onCharacteristicRead()函数回调,读取Characteristic的value值
上一步调用readCharacteristic()后,系统会回调gattCallback下的onCharacteristicRead()
此时我们使用回参characteristic直接getValue()即可读取到数值
整体代码
package com.gatt.demo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import java.util.Set;
import java.util.UUID;
/**
* 作者:libeibei
* 日期:20201222
* 类功能说明:读取指定名称遥控器的VID、PID
*/
public class MainActivity extends Activity {
public static String TAG = "BLE_READ";
public static String BLE_NAME = "川流TV";
private Context mContext;
private BluetoothManager bluetoothManager;
private BluetoothAdapter bluetoothAdapter;
protected BluetoothDevice mSelectedDevice;
private BluetoothGatt mBluetoothGatt;
private BluetoothGattCharacteristic mVIDPIDCharacteristic;
//已配对的设备
Set<BluetoothDevice> pairedDevices;
//GATT service UUID
public static final UUID DEVICE_INFO_SERVICE_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");
//Charcteristic UUID
public static final UUID VID_PID_CHARACTERISTIC_UUID = UUID.fromString("00002a50-0000-1000-8000-00805f9b34fb");
TextView tv;
String VID = "";
String PID = "";
@SuppressLint("HandlerLeak")
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 0x1:
Toast.makeText(mContext, "VID、PID读取成功", Toast.LENGTH_LONG).show();
tv.setText("VID=" + VID + " PID=" + PID);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv_vid_pid);
}
@Override
protected void onResume() {
super.onResume();
//第一步,初始化各工具
init();
//第二步,根据名称,获取指定的蓝牙设备:mSelectedDevice
getTargetBLEDevice();
//第三步,声明mGattCallback,并重写回调函数,见下面step 3
//第四步,通过上两步获取的mSelectedDevice和mGattCallback建立GATT连接
connectGatt();
//第五步,建立gatt连接后,会回调mGattCallback下的onConnectionStateChange()函数
//在onConnectionStateChange()函数中调用mBluetoothGatt.discoverServices();
//见下面step 5
//第六步,调用mBluetoothGatt.discoverServices()后
// 会回调mGattCallback下的onServicesDiscovered()函数
// 在该函数下
// 1、获取DeviceInfoService 见下面step 6-1
// 2、通过拿到的service,获取VIDPIDCharacteristic 见下面step 6-2
// 3、读取获取到的这个VIDPIDCharacteristic 见下面step 6-3
//第七步,读取VIDPIDCharacteristic后
// 会回调mGattCallback下的onCharacteristicRead()函数
// step 7-1:在这个函数下将读取出的value值
// step 7-2:转码即可
//(ascii字符转ascii值,再将十进制ascii值转为十六进制字符,即为VID和PID)
}
//step 1
private void init() {
mContext = MainActivity.this;
if (bluetoothManager == null)
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothAdapter == null)
bluetoothAdapter = bluetoothManager.getAdapter();
pairedDevices = bluetoothAdapter.getBondedDevices();
}
//step 2
private void getTargetBLEDevice() {
if (pairedDevices != null && pairedDevices.size() > 0) {
for (BluetoothDevice bluetoothDevice : pairedDevices) {
String name = bluetoothDevice.getName();
Log.i(TAG, "bluetoothDevice name " + name);
if (bluetoothDevice != null && name.equalsIgnoreCase(BLE_NAME)) {
Log.i(TAG, "已找到指定蓝牙设备,该设备MAC=" + bluetoothDevice.getAddress());
mSelectedDevice = bluetoothDevice;
break;
}
}
}
}
//step 3
BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.i(TAG, "onConnectionStateChange newstate:" + newState + " status:" + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "============>GATT Connect Success!!<=============");
//step 5
mBluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
if (mBluetoothGatt != null) {
mBluetoothGatt.close();
mBluetoothGatt = null;
}
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
Log.i(TAG, "onServicesDiscovered(), status = " + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//step 6-1:获取DeviceInfoService
BluetoothGattService mDeviceInfoService = gatt.getService(DEVICE_INFO_SERVICE_UUID);
if (mDeviceInfoService == null) {
Log.i(TAG, "Device Info Service is null ,disconnect GATT...");
gatt.disconnect();
gatt.close();
return;
}
//step 6-2:获取遥控器VIDPID Characteristic
mVIDPIDCharacteristic = mDeviceInfoService.getCharacteristic(VID_PID_CHARACTERISTIC_UUID);
if (mVIDPIDCharacteristic == null) {
Log.e(TAG, "read mModelCharacteristic not found");
return;
} else {
//step 6-3:读取遥控器VIDPID特性
mBluetoothGatt.readCharacteristic(mVIDPIDCharacteristic);
}
} else {
Log.i(TAG, "onServicesDiscovered status false");
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
String value = "";
if (characteristic.getUuid().equals(VID_PID_CHARACTERISTIC_UUID)) {
//step 7-1:读取出characteristic的value值
value = new String(characteristic.getValue()).trim().replace(" ", "");
Log.i(TAG, "=====>读取到 value =" + value);
//step 7-2:此处为ascii表字符,需转换为十进制ascii值
//再将十进制ascii值,转换为十六进制
VID = changeAsciiTo16(value.charAt(0));
PID = changeAsciiTo16(value.charAt(value.length() - 1));
//设备VID、PID读取成功,handle更新主线程界面UI
handler.sendEmptyMessage(0x1);
}
} else {
Log.i(TAG, "onCharacteristicRead status wrong");
if (mBluetoothGatt != null)
mBluetoothGatt.disconnect();
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.i(TAG, "onCharacteristicWrite:" + characteristic.getUuid().toString());
}
};
//step 4
private void connectGatt() {
if (mSelectedDevice != null)
mBluetoothGatt = mSelectedDevice.connectGatt(mContext, false, mGattCallback);
else
Toast.makeText(mContext, "没有找到指定的蓝牙设备,无法建立GATT", Toast.LENGTH_LONG).show();
}
private String changeAsciiTo16(char a) {
Log.i(TAG, "change from a =" + a);
String value = "";
int val = (int) a;
Log.i(TAG, "change to 10进制ASCII值 val =" + val);
//ascii值到
value = Integer.toHexString(val).toUpperCase();
Log.i(TAG, "change to 16进制字符串 value =" + value);
return value;
}
}
解配对
mDeviceGatt.getDevice().removeBond();
判断设备是否连接上
Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
for (BluetoothDevice device:bondedDevices) {
try {
Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null);
isConnectedMethod.setAccessible(true);
boolean isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null);
Log.e("longjiang",isConnected+"");
if (isConnected){
count++;
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}