Android bluetooth創(chuàng)建GATT連接并讀取設備信息

一 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
image.png

每個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界面會顯示已配對的藍牙設備


    image.png
  • 點擊擊打開設備后,可以看到這個設備GATT下所有的service和對應的UUID


    image.png
  • 打開service后颓影,可以看到service下所有的Charcteristic的和對應的UUID
    點擊Charcteristic后各淀,在上面可以看到其Value值(如下圖,讀取到的是設備型號SCCN001)


    image.png

四 如何建立GATT連接及如何讀取Charcteristic下的數(shù)據(jù)

android 4.0以后添加了BLE的支持诡挂,在系統(tǒng)BluetoothDevice.java源碼中已經(jīng)提供了Gatt連接的接口函數(shù)
那么我們只需要找到指定的藍牙設備獲取它的BluetoothDevice實例碎浇,然后調用connectGatt函數(shù)即可


image.png
1、獲取指定的藍牙設備BluetoothDevice實例
image.png
2璃俗、建立GATT連接

使用上一步獲取到的設備實例奴璃,調用connectGatt函數(shù)建立連接


image.png
3、重寫GattCallback回調

在第2步建立GATT連接連接時城豁,需要傳入gattcallback實例
所以我們要先實例BluetoothGattCallback類并重寫其回調函數(shù)苟穆,如圖
這幾個回調函數(shù)后續(xù)會用到


image.png
4、GATT連接成功唱星,onConnectionStateChange()函數(shù)回調

gatt連接成功或失敗雳旅,會回調gattCallback下的onConnectionStateChange()函數(shù)
接下來調用mBluetoothGatt.discoverServices()函數(shù)(功能是查詢已連接的gatt下的service)


image.png
5、onServicesDiscovered()函數(shù)回調间聊,獲取指定Service和Characteristic

上一步調用mBluetoothGatt.discoverServices()函數(shù)后攒盈,系統(tǒng)會回調gattCallback下的onServicesDiscovered()函數(shù),這表明我們已經(jīng)可以通過指定的UUID來獲取指定的Service實例了


image.png

如下圖,在onServicesDiscovered()函數(shù)回調后哎榴,通過UUID先獲取service
然后再使用獲取到的service和UUID獲取Characteristic
最后mBluetoothGatt.readCharacteristic(mVIDPIDCharacteristic);讀取這個Characteristic


image.png
6型豁、onCharacteristicRead()函數(shù)回調,讀取Characteristic的value值

上一步調用readCharacteristic()后尚蝌,系統(tǒng)會回調gattCallback下的onCharacteristicRead()
此時我們使用回參characteristic直接getValue()即可讀取到數(shù)值


image.png
7迎变、value值轉換

有的藍牙設備value值使用的ASCII碼,需要轉為VID和PID16進制值
下圖為例
ASCII碼:] 轉為ASCII值=93 轉為16進制值VID=5D
ASCII碼:q 轉為ASCII值=113 轉為16進制值VID=71
轉換過程見底部整體代碼示例


image.png

五 整體代碼示例

以讀取泰凌芯片藍牙遙控器的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顯示到界面上如下


image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末滩报,一起剝皮案震驚了整個濱河市锅知,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌露泊,老刑警劉巖喉镰,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惭笑,居然都是意外死亡侣姆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門沉噩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捺宗,“玉大人,你說我怎么就攤上這事川蒙⊙晾鳎” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵畜眨,是天一觀的道長昼牛。 經(jīng)常有香客問我术瓮,道長,這世上最難降的妖魔是什么贰健? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任胞四,我火速辦了婚禮,結果婚禮上伶椿,老公的妹妹穿的比我還像新娘辜伟。我一直安慰自己,他們只是感情好脊另,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布导狡。 她就那樣靜靜地躺著,像睡著了一般偎痛。 火紅的嫁衣襯著肌膚如雪旱捧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天看彼,我揣著相機與錄音廊佩,去河邊找鬼。 笑死靖榕,一個胖子當著我的面吹牛标锄,可吹牛的內容都是我干的。 我是一名探鬼主播茁计,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼料皇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了星压?” 一聲冷哼從身側響起践剂,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娜膘,沒想到半個月后逊脯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡竣贪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年军洼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片演怎。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡匕争,死狀恐怖,靈堂內的尸體忽然破棺而出爷耀,到底是詐尸還是另有隱情甘桑,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站跑杭,受9級特大地震影響铆帽,放射性物質發(fā)生泄漏。R本人自食惡果不足惜艘蹋,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一锄贼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧女阀,春花似錦、人聲如沸屑迂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惹盼。三九已至庸汗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間手报,已是汗流浹背蚯舱。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掩蛤,地道東北人枉昏。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像揍鸟,于是被迫代替她去往敵國和親兄裂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容