一 GATT簡介
藍牙分為經(jīng)典藍牙和低功耗藍牙(BLE),我們常用的藍牙遙控器就是低功耗藍牙
低功耗藍牙(BLE)連接都是建立在 GATT (Generic Attribute Profile) 協(xié)議之上匠抗。
GATT全稱Generic Attribute Profile(直譯即:通用屬性協(xié)議)耻警,是一個在藍牙連接之上的發(fā)送和接收較短的數(shù)據(jù)段的通用規(guī)范倔既,這些很短的數(shù)據(jù)段被稱為屬性(Attribute)醋安。
二 GATT的結構
GATT的結構簡單的講召夹,如下圖:
- gatt是多個service的集合宝当,gatt包含多個不同的service
- service下包含多個不同的Charcteristic(特征)
- Charcteristic又包含value和Descriptor
每個Service和Charcteristic有一個唯一標識UUID
一般我們讀取BLE設備的信息,就是讀取Charcteristic下的Value值
所以我們需要知道service和Charcteristic的UUID荚坞,就能拿到這個Charcteristic下的值
三 如何知道指定的service和Charcteristic的UUID
一般使用BLE測試工具即可
我這里使用的BLE5.1ScanDemo apk挑宠,上傳到網(wǎng)盤供大家使用
https://pan.baidu.com/s/1TdsznZsdhPPDcKbkJknyug
提取碼:6666
- 把BLE5.1ScanDemo apk安裝到手機
- 遙控器或其他BLE設備與手機藍牙配對連接
-
打開apk界面會顯示已配對的藍牙設備
-
點擊擊打開設備后,可以看到這個設備GATT下所有的service和對應的UUID
-
打開service后颓影,可以看到service下所有的Charcteristic的和對應的UUID
點擊Charcteristic后各淀,在上面可以看到其Value值(如下圖,讀取到的是設備型號SCCN001)
四 如何建立GATT連接及如何讀取Charcteristic下的數(shù)據(jù)
android 4.0以后添加了BLE的支持诡挂,在系統(tǒng)BluetoothDevice.java源碼中已經(jīng)提供了Gatt連接的接口函數(shù)
那么我們只需要找到指定的藍牙設備獲取它的BluetoothDevice實例碎浇,然后調用connectGatt函數(shù)即可
1、獲取指定的藍牙設備BluetoothDevice實例
2璃俗、建立GATT連接
使用上一步獲取到的設備實例奴璃,調用connectGatt函數(shù)建立連接
3、重寫GattCallback回調
在第2步建立GATT連接連接時城豁,需要傳入gattcallback實例
所以我們要先實例BluetoothGattCallback類并重寫其回調函數(shù)苟穆,如圖
這幾個回調函數(shù)后續(xù)會用到
4、GATT連接成功唱星,onConnectionStateChange()函數(shù)回調
gatt連接成功或失敗雳旅,會回調gattCallback下的onConnectionStateChange()函數(shù)
接下來調用mBluetoothGatt.discoverServices()函數(shù)(功能是查詢已連接的gatt下的service)
5、onServicesDiscovered()函數(shù)回調间聊,獲取指定Service和Characteristic
上一步調用mBluetoothGatt.discoverServices()函數(shù)后攒盈,系統(tǒng)會回調gattCallback下的onServicesDiscovered()函數(shù),這表明我們已經(jīng)可以通過指定的UUID來獲取指定的Service實例了
如下圖,在onServicesDiscovered()函數(shù)回調后哎榴,通過UUID先獲取service
然后再使用獲取到的service和UUID獲取Characteristic
最后mBluetoothGatt.readCharacteristic(mVIDPIDCharacteristic);讀取這個Characteristic
6型豁、onCharacteristicRead()函數(shù)回調,讀取Characteristic的value值
上一步調用readCharacteristic()后尚蝌,系統(tǒng)會回調gattCallback下的onCharacteristicRead()
此時我們使用回參characteristic直接getValue()即可讀取到數(shù)值
7迎变、value值轉換
有的藍牙設備value值使用的ASCII碼,需要轉為VID和PID16進制值
下圖為例
ASCII碼:] 轉為ASCII值=93 轉為16進制值VID=5D
ASCII碼:q 轉為ASCII值=113 轉為16進制值VID=71
轉換過程見底部整體代碼示例
五 整體代碼示例
以讀取泰凌芯片藍牙遙控器的VID和PID值為例
整體代碼示例如下飘言,代碼中有詳細步驟注釋
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();
//第二步泵喘,根據(jù)名稱,獲取指定的藍牙設備:mSelectedDevice
getTargetBLEDevice();
//第三步般妙,聲明mGattCallback纪铺,并重寫回調函數(shù),見下面step 3
//第四步,通過上兩步獲取的mSelectedDevice和mGattCallback建立GATT連接
connectGatt();
//第五步碟渺,建立gatt連接后鲜锚,會回調mGattCallback下的onConnectionStateChange()函數(shù)
//在onConnectionStateChange()函數(shù)中調用mBluetoothGatt.discoverServices();
//見下面step 5
//第六步,調用mBluetoothGatt.discoverServices()后
// 會回調mGattCallback下的onServicesDiscovered()函數(shù)
// 在該函數(shù)下
// 1苫拍、獲取DeviceInfoService 見下面step 6-1
// 2芜繁、通過拿到的service,獲取VIDPIDCharacteristic 見下面step 6-2
// 3绒极、讀取獲取到的這個VIDPIDCharacteristic 見下面step 6-3
//第七步骏令,讀取VIDPIDCharacteristic后
// 會回調mGattCallback下的onCharacteristicRead()函數(shù)
// step 7-1:在這個函數(shù)下將讀取出的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;
}
}
最終通過GATT讀取到的遙控器VID和PID顯示到界面上如下