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); } }