低功耗藍牙BLE外圍模式(peripheral)-使用BLE作為服務端
Android對外模模式(peripheral)的支持
從Android5.0開始才支持
關鍵術語和概念
以下是關鍵BLE術語和概念的摘要:
- 通用屬性簡檔(GATT) - GATT簡檔是用於通過BLE鏈路發送和接收稱為“屬性”的短數據塊的一般規范。 所有當前的低能量應用配置文件都基於GATT。
藍牙SIG為低能量設備定義了許多配置文件 。 配置文件是設備在特定應用程序中的工作方式的規范。 請注意,設備可以實現多個配置文件。 例如,設備可以包含心率監視器和電池水平檢測器。 - 屬性協議(ATT) -GATT建立在屬性協議(ATT)之上。 這也稱為GATT / ATT。 ATT經過優化,可在BLE設備上運行。 為此,它使用盡可能少的字節。 每個屬性由通用唯一標識符(UUID)唯一標識,UUID是用於唯一標識信息的字符串ID的標准化128位格式。 由ATT傳送的屬性被格式化為特征和服務 。
- 特性 -A特性包含描述特性值的單個值和0-n個描述符。 一個特性可以被認為是一個類型,類似於類。
- 描述符 - 描述符是描述特征值的定義屬性。 例如,描述符可以指定人類可讀的描述,特征值的可接受范圍或特征值的特定的測量單位。
- 服務 - 服務是一個集合的特點。 例如,您可以有一個名為“心率監視器”的服務,其中包括諸如“心率測量”的特征。 您可以在bluetooth.org上找到現有基於GATT的個人資料和服務的列表 。
角色和職責
以下是Android設備與BLE設備互動時適用的角色和職責:
中央與外圍。 這適用於BLE連接本身。 處於中心角色的設備掃描,尋找廣告,並且外圍角色中的設備進行廣告。
GATT服務器與GATT客戶端。 這決定了兩個設備在建立連接后如何相互通信。
BLE權限
首先,需要在manifest中聲明使用藍牙和操作藍牙的權限
在應用程序清單文件中聲明藍牙權限。 例如:
如果您要聲明自己的應用只適用於支持BLE的設備,請在應用清單中包含以下內容:
<uses-feature android:name =“android.hardware.bluetooth_le”android:required =“true”/>
不過,如果您想讓應用程式適用於不支援BLE的裝置,您仍應在應用的清單中加入這個元素,但required="false"設為required="false" 。
然后在運行時,您可以通過使用PackageManager.hasSystemFeature()確定BLE可用性:
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
在android 6.0 以后,要想獲得藍牙掃描結果,還需要下面的權限
<manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
...
</manifest>
設置藍牙
1.Get the BluetoothAdapter
獲得藍牙適配器
private BluetoothAdapter mBluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
2.Enable Bluetooth
打開藍牙
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
3.初始化BLE藍牙廣播(廣告)
(1)廣播的設置
(2)設置廣播的數據
(3)設置響應的數據
(4)設置連接回調
private void initGATTServer() {
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setConnectable(true)
.build();
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.setIncludeTxPowerLevel(true)
.build();
AdvertiseData scanResponseData = new AdvertiseData.Builder()
.addServiceUuid(new ParcelUuid(UUID_SERVER))
.setIncludeTxPowerLevel(true)
.build();
AdvertiseCallback callback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.d(TAG, "BLE advertisement added successfully");
showText("1. initGATTServer success");
println("1. initGATTServer success");
initServices(getContext());
}
@Override
public void onStartFailure(int errorCode) {
Log.e(TAG, "Failed to add BLE advertisement, reason: " + errorCode);
showText("1. initGATTServer failure");
}
};
BluetoothLeAdvertiser bluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponseData, callback);
}
在被BLE設備連接后,將觸發 AdvertiseCallback 的 onStartSuccess,我們在這之后,初始化GATT的服務
4.初始化GATT的服務
(1) 通過 mBluetoothManager.openGattServer() 獲得 bluetoothGattServer
(2) 添加 服務,特征,描述。這些內容要讓客戶端知道。
private void initServices(Context context) {
bluetoothGattServer = mBluetoothManager.openGattServer(context, bluetoothGattServerCallback);
BluetoothGattService service = new BluetoothGattService(UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY);
//add a read characteristic.
characteristicRead = new BluetoothGattCharacteristic(UUID_CHARREAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
//add a descriptor
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR, BluetoothGattCharacteristic.PERMISSION_WRITE);
characteristicRead.addDescriptor(descriptor);
service.addCharacteristic(characteristicRead);
//add a write characteristic.
BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHARWRITE,
BluetoothGattCharacteristic.PROPERTY_WRITE |
BluetoothGattCharacteristic.PROPERTY_READ |
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(characteristicWrite);
bluetoothGattServer.addService(service);
Log.e(TAG, "2. initServices ok");
showText("2. initServices ok");
}
在 openGattServer 方法中,我們需要傳入個回調
bluetoothGattServer = mBluetoothManager.openGattServer(context, bluetoothGattServerCallback);
5.配置數據交互回調
回調時間有:連接狀態變化,收發消息,通知消息
/**
* 服務事件的回調
*/
private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() {
/**
* 1.連接狀態發生變化時
* @param device
* @param status
* @param newState
*/
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
Log.e(TAG, String.format("1.onConnectionStateChange:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("1.onConnectionStateChange:status = %s, newState =%s ", status, newState));
super.onConnectionStateChange(device, status, newState);
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
Log.e(TAG, String.format("onServiceAdded:status = %s", status));
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, String.format("onCharacteristicReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("onCharacteristicReadRequest:requestId = %s, offset = %s", requestId, offset));
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
// super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
/**
* 3. onCharacteristicWriteRequest,接收具體的字節
* @param device
* @param requestId
* @param characteristic
* @param preparedWrite
* @param responseNeeded
* @param offset
* @param requestBytes
*/
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
Log.e(TAG, String.format("3.onCharacteristicWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("3.onCharacteristicWriteRequest:requestId = %s, preparedWrite=%s, responseNeeded=%s, offset=%s, value=%s", requestId, preparedWrite, responseNeeded, offset, OutputStringUtil.toHexString(requestBytes)));
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);
//4.處理響應內容
onResponseToClient(requestBytes, device, requestId, characteristic);
}
/**
* 2.描述被寫入時,在這里執行 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS... 收,觸發 onCharacteristicWriteRequest
* @param device
* @param requestId
* @param descriptor
* @param preparedWrite
* @param responseNeeded
* @param offset
* @param value
*/
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.e(TAG, String.format("2.onDescriptorWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("2.onDescriptorWriteRequest:requestId = %s, preparedWrite = %s, responseNeeded = %s, offset = %s, value = %s,", requestId, preparedWrite, responseNeeded, offset, OutputStringUtil.toHexString(value)));
// now tell the connected device that this was all successfull
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
/**
* 5.特征被讀取。當回復響應成功后,客戶端會讀取然后觸發本方法
* @param device
* @param requestId
* @param offset
* @param descriptor
*/
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
Log.e(TAG, String.format("onDescriptorReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("onDescriptorReadRequest:requestId = %s", requestId));
// super.onDescriptorReadRequest(device, requestId, offset, descriptor);
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
Log.e(TAG, String.format("5.onNotificationSent:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("5.onNotificationSent:status = %s", status));
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
Log.e(TAG, String.format("onMtuChanged:mtu = %s", mtu));
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
Log.e(TAG, String.format("onExecuteWrite:requestId = %s", requestId));
}
};
6.處理來自客戶端發來的數據和發送回復數據:
調用 bluetoothGattServer.notifyCharacteristicChanged 方法,通知數據改變。
/**
* 4.處理響應內容
*
* @param reqeustBytes
* @param device
* @param requestId
* @param characteristic
*/
private void onResponseToClient(byte[] reqeustBytes, BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, String.format("4.onResponseToClient:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("4.onResponseToClient:requestId = %s", requestId));
String msg = OutputStringUtil.transferForPrint(reqeustBytes);
println("4.收到:" + msg);
showText("4.收到:" + msg);
String str = new String(reqeustBytes) + " hello>";
characteristicRead.setValue(str.getBytes());
bluetoothGattServer.notifyCharacteristicChanged(device, characteristicRead, false);
println("4.響應:" + str);
showText("4.響應:" + str);
}
交互流程:
(1) 當客戶端開始寫入數據時: 觸發回調方法 onDescriptorWriteRequest
(2) 在 onDescriptorWriteRequest 方法中,執行下面的方法表示 寫入成功 BluetoothGatt.GATT_SUCCESS
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
執行 sendResponse后,會觸發回調方法 onCharacteristicWriteRequest
(3) 在 onCharacteristicWriteRequest方法中
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
這個里可以獲得 來自客戶端發來的數據 requestBytes
(4) 處理響應內容,我寫了這個方法:
onResponseToClient(requestBytes, device, requestId, characteristic);
在這個方法中,通過 bluetoothGattServer.notifyCharacteristicChanged()方法 回復數據
通過日志,我們看看事件觸發的順序
1.onConnectionStateChange:device name = null, address = 74:32:DE:49:3C:28
1.onConnectionStateChange:status = 0, newState =2
2.onDescriptorWriteRequest:device name = null, address = 74:32:DE:49:3C:28
2.onDescriptorWriteRequest:requestId = 1, preparedWrite = false, responseNeeded = true, offset = 0, value = [01,00,],
3.onCharacteristicWriteRequest:device name = null, address = 74:32:DE:49:3C:28
3.onCharacteristicWriteRequest:requestId = 2, preparedWrite=false, responseNeeded=false, offset=0, value=[41,54,45,30,0D,]
4.onResponseToClient:device name = null, address = 74:32:DE:49:3C:28
4.onResponseToClient:requestId = 2
4.收到:ATE0
4.響應:ATE0 hello>
5.onNotificationSent:device name = null, address = 74:32:DE:49:3C:28
5.onNotificationSent:status = 0
代碼托管到github:
https://github.com/vir56k/bluetoothDemo 找到 bleperipheraldemo 文件夾