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)— 一個服務是一些特性的一個集合。例如,你可能會有一個被稱為“心率檢測器”的服務,它包含如“心率測量”這個特性。