1. 程式人生 > >Android BLE低功耗藍芽開發(上)關於GATT伺服器的理論與搭建

Android BLE低功耗藍芽開發(上)關於GATT伺服器的理論與搭建

前言

本來寫完Android開發之BlueTooth--最簡單的Andorid傳統藍芽通訊Demo之後,我打算寫一篇Android開發之BlueTooth--最簡單的Andorid低功耗(BLE)藍芽通訊Demo的。後來看了看官方的文件,我的天,谷歌給給出的sample裡面只有客戶端的互動(就是所謂的中央裝置),而且程式碼算是比較老了。關於建立服務端(外圍裝置)隻字未提,這樣子我要寫個demo不容易啊。因為我本身窮屌絲一個,不會為了一個demo去買個藍芽裝置回來測試,打算在手機建立個服務行不~既然谷歌官方沒說到,那我找找國內大神,於是開啟scdn搜尋  Android BLE...

不得不說結果很多,但是絕大多數都是相當於翻譯谷歌文件,程式碼都不是自己整理的,也幾乎沒有關於怎麼建立伺服器的(就看到寥寥幾個提到),那隻能自己硬著頭皮去看谷歌的類導航了(真的,作為一個小白沒有sample,學習還是有點困難的)。

關於GATT服務類

GATT服務相關的類:BluetoothGattServer

繼承關係:

public final class BluetoothGattServer 
extends Object implements BluetoothProfile
java.lang.Object繼承
   ↳android.bluetooth.BluetoothGattServer

官方是這麼描述的:

這個類提供了扮演藍芽GATT伺服器角色的功能,允許應用程式建立藍芽智慧服務和characteristics。BluetoothGattServer是通過工控機控制藍芽服務的代理物件。使用openGattServer(Context, BluetoothGattServerCallback)得到這個類的一個例項。

這個類的主要方法我也copy一份下來:

Public methods

boolean

Add a service to the list of services to be hosted.

void

Disconnects an established connection, or cancels a connection attempt currently in progress.

void

Remove all services from the list of provided services.

void close()

Close this GATT server instance.

boolean

Initiate a connection to a Bluetooth GATT capable device.

Returns a list of GATT services offered by this device.

boolean

Send a notification or indication that a local characteristic has been updated.

void

Read the current transmitter PHY and receiver PHY of the connection.

boolean

Removes a service from the list of services to be provided.

boolean sendResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] value)

Send a response to a read or write request to a remote device.

void

Set the preferred connection PHY for this app.


方法很多,但是我們不需要買個都詳盡瞭解。知道個大概就行了,而且大都見名知意。我也是僅僅列出來,在寫下去就相當於把谷歌文件搬過來了~有興趣就自己去看文件更詳細瞭解每個方法的功能和引數。

GATT伺服器搭建流程

首先需要說明一點每一個周邊BluetoothGattServer,可以包含多個服務Service,每一個Service也包含多個Characteristic。

1.新建一個Characteristic

character = new BluetoothGattCharacteristic(UUID.fromString(characteristicUUID),BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ);

2.新建一個GATT服務:

service = new BluetoothGattService(UUID.fromString(serviceUUID),
BluetoothGattService.SERVICE_TYPE_PRIMARY);

3.把Characteristic新增到服務:

service.addCharacteristic(character);

4.獲取BluetoothManager:

manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

5.獲取/開啟周邊:

BluetoothGattServer server = manager.openGattServer(this,
new BluetoothGattServerCallback(){...}); 

6.把service新增到周邊:server.addService(service);

7.開始廣播通知我這裡有個service已經開啟,可以提供服務了:

BluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);

經過這7步,似乎已經可以成功建立一個服務了。但是裡面需要的引數不見得我們都會建立。

那我們來寫寫。程式碼參考自:程式碼連線傳送門。感謝程式碼編寫者開源。

程式碼示例:

package cn.small_qi.bluetoothtest.gattserver;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelUuid;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import cn.small_qi.bluetoothtest.R;

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class GattServerActivity extends AppCompatActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothManager mBluetoothManager;
    private BluetoothGattServer mBluetoothGattServer;
    private Set<BluetoothDevice> mRegisteredDevices = new HashSet<>();

    //先定幾個服務型別的UUID
     /* Current Time Service UUID */
    public static UUID TIME_SERVICE = UUID.fromString("00001805-0000-1000-8000-00805f9b34fb");
    /* Mandatory Current Time Information Characteristic */
    public static UUID CURRENT_TIME    = UUID.fromString("00002a2b-0000-1000-8000-00805f9b34fb");
    /* Optional Local Time Information Characteristic */
    public static UUID LOCAL_TIME_INFO = UUID.fromString("00002a0f-0000-1000-8000-00805f9b34fb");
    /* Mandatory Client Characteristic Config Descriptor */
    public static UUID CLIENT_CONFIG = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gatt_server);
        openAndInitBt();//初始化需要一定時間
        createGattServer();//所以以下這兩個方法在這裡直接執行是錯誤的,一定要在藍芽正確開啟,並且支援BLE在執行
        startAdvertising();//我解除安裝這裡只是為了展示呼叫順序。切記切記!!
    }

    //1.初始化並開啟藍芽
    private void openAndInitBt(){
        mBluetoothManager=(BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (mBluetoothAdapter==null){return;}//不支援藍芽
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return ;//不支援ble藍芽
        }
        //.判斷藍芽是否開啟
        if (!mBluetoothAdapter.enable()) {
            //沒開啟請求開啟
            Intent btEnable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(btEnable, 100);
        }
    }
    //2.建立GATT服務
    private void createGattServer() {
        //2.1.新建一個服務
        BluetoothGattService service = new BluetoothGattService(TIME_SERVICE,BluetoothGattService.SERVICE_TYPE_PRIMARY);
        //2.2 新建一個Characteristic
        BluetoothGattCharacteristic currentTime = new BluetoothGattCharacteristic(CURRENT_TIME,
                //Read-only characteristic, supports notifications
                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ);
        //2.3 新建特性描述並配置--這一步非必需
        BluetoothGattDescriptor configDescriptor = new BluetoothGattDescriptor(CLIENT_CONFIG,
                //Read/write descriptor
                BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
        currentTime.addDescriptor(configDescriptor);
        //2.4 將特性配置到服務
        service.addCharacteristic(currentTime);
        //2.5 開啟外圍裝置  注意這個services和server的區別,別記錯了
        mBluetoothGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
        if (mBluetoothGattServer == null) {
            return;
        }
        mBluetoothGattServer.addService(service);
    }

    //3.通知服務開啟
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void startAdvertising() {
        BluetoothLeAdvertiser mBluetoothLeAdvertiser= mBluetoothAdapter.getBluetoothLeAdvertiser();
        if (mBluetoothLeAdvertiser == null) {
            //建立失敗
            return;
        }
        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
                .setConnectable(true)
                .setTimeout(0)
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
                .build();

        AdvertiseData data = new AdvertiseData.Builder()
                .setIncludeDeviceName(true)
                .setIncludeTxPowerLevel(false)
                .addServiceUuid(new ParcelUuid(TIME_SERVICE))//繫結服務uuid
                .build();
        mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
    }

    private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            Log.i("", "LE Advertise Started.");
        }
        @Override
        public void onStartFailure(int errorCode) {
            Log.w("", "LE Advertise Failed: "+errorCode);
        }
    };


    /**
     * Callback to handle incoming requests to the GATT server.
     * 所有characteristics 和 descriptors 的讀寫請求都在這裡處理
     * 這裡我忽略了處理邏輯,這個根據實際需求寫
     */
    private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {

        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            //連線狀態改變
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.i("", "BluetoothDevice CONNECTED: " + device);
                mRegisteredDevices.add(device);
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.i("", "BluetoothDevice DISCONNECTED: " + device);
                mRegisteredDevices.remove(device);
            }
        }

        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
                                                BluetoothGattCharacteristic characteristic) {
            //請求讀特徵 如果包含有多個服務,就要區分請求讀的是什麼,這裡我只有一個服務
            if(CURRENT_TIME.equals(characteristic.getUuid())){
                //迴應
                mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,0,"隨便迴應".getBytes());
            }

        }

        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset,
                                            BluetoothGattDescriptor descriptor) {
            if( CLIENT_CONFIG.equals(descriptor.getUuid())){
                Log.d("", "Config descriptor read");
                byte[] returnValue;
                if (mRegisteredDevices.contains(device)) {
                    returnValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
                } else {
                    returnValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
                }
                mBluetoothGattServer.sendResponse(device,
                        requestId,
                        BluetoothGatt.GATT_SUCCESS,
                        0,
                        returnValue);
            } else {
                Log.w("", "Unknown descriptor read request");
                mBluetoothGattServer.sendResponse(device,
                        requestId,
                        BluetoothGatt.GATT_FAILURE,
                        0,
                        null);
            }

        }

        @Override
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,BluetoothGattDescriptor descriptor,boolean preparedWrite, boolean responseNeeded,
                                             int offset, byte[] value) {
            if (CLIENT_CONFIG.equals(descriptor.getUuid())) {
                if (Arrays.equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, value)) {
                    Log.d("", "Subscribe device to notifications: " + device);
                    mRegisteredDevices.add(device);
                } else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
                    Log.d("", "Unsubscribe device from notifications: " + device);
                    mRegisteredDevices.remove(device);
                }

                if (responseNeeded) {
                    mBluetoothGattServer.sendResponse(device,
                            requestId,
                            BluetoothGatt.GATT_SUCCESS,
                            0,
                            null);
                }
            } else {
                Log.w("", "Unknown descriptor write request");
                if (responseNeeded) {
                    mBluetoothGattServer.sendResponse(device,
                            requestId,
                            BluetoothGatt.GATT_FAILURE,
                            0,
                            null);
                }
            }
        }

        //這個實際可以用於反向寫資料
        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
        }
    };
}


這樣就能簡單的建立一個GATT服務了,手上只有一臺4.3以上的手機,測試結果目前還不知道~而且搭建伺服器要求API 21以上哦

當然,還不要忘了結束應用或者關閉藍芽的時候關閉服務哦

protected void onDestroy() {
        if (mBluetoothLeAdvertiser!=null) {
            mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
        }
        if(mBluetoothGattServer!=null) {
            mBluetoothGattServer.close();
        }
        super.onDestroy();
    }


關於裡面的一些術語在這裡也列出來一下,方便大家閱讀:

通用屬性描述(Generic Attribute Profile (GATT))— GATT是一個通用規範,用於在 BLE 連結上傳送和接收被稱為“屬性”的短資料塊。目前所有的低功耗應用程式規範都是基於GATT。藍芽技術聯盟(Bluetooth SIG)為低功耗裝置定義了很多規範。一個規範就是對一個裝置在特定應用程式上如何執行的具體說明。注意,一個裝置可以支援多種規範。例如,一個裝置可以包含一個心率檢測器和一個電池電壓檢測器。


屬性協議(Attribute Protocol (ATT))—GATT 是建立在 ATT 之上的。它也被稱為 GATT/ATT 。ATT在BLE裝置上執行表現優異。為此,它儘可能使用少的位元組。每個屬性(Attribute)都用一個UUID(Universally Unique Idetifier)進行唯一標識。這是一個標準的128位格式字串ID,用於唯一標識資訊。通過 ATT 傳送的這些屬性(Attribute)被格式化為特性(Characteristics)和服務(Services)。


特性(Characteristic) — 一個特性包含一個單獨的值和 0-n 個描述符,這些描述符描述了特性的值。一個特性可以被認為是一個型別,類似於一個類。


描述符(Descriptor)— 描述符被定義為描述一個特性值的屬性。例如,一個描述符可能是指定一個人類可讀的描述、一個特性值所能接受的範圍或一個專門用於特性值的測量單位。


服務(Service)— 一個服務是一些特性的一個集合。例如,你可能會有一個被稱為“心率檢測器”的服務,它包含如“心率測量”這個特性。