android專題-藍牙掃描、連接、讀寫
概念
外圍設備
可以被其他藍牙設備連接的外部藍牙設備,不斷廣播自身的藍牙名及其資料,如小米手環、共享單車、藍牙體重秤
中央設備
可以搜索并連接周邊的外圍設備,并與之進行資料讀寫通訊,如手機
日常生活中常見的場景是手機app通過藍牙開啟共享單車,手機app通過藍牙獲取藍牙體重秤的體重結果,這時候共享單車、藍牙體重秤就稱為外圍設備,而手機就稱為中央設備
經典藍牙BT
泛指支持藍牙協議在4.0以下的模塊,一般用于資料量比較大的傳輸,如:語音、音樂等較高資料量的傳輸,經典藍牙模塊又可細分為:傳統藍牙和高速藍牙模塊,傳統藍牙模塊在2004年推出,主要代表是支持藍牙2.1協議的模塊,在智能手機爆發的時期得到了廣泛的使用,高速藍牙模塊在2009年推出,速率提高到約24Mbps,傳輸速率是經典藍牙的八倍,可以輕松的應用于錄像機到電視、PC到PMP、UMPC到列印機之間的資料傳輸,
低功耗藍牙BLE
是指支持藍牙協議4.0或者以上的模塊,也被稱為BLE模塊,最大的特點就是成本和功耗的降低,可以應用于實時性要求較高的產品當中,比如:智能家居類(藍牙鎖、藍牙燈)、傳感設備的資料發送(血壓計、溫度傳感器)、消費類電子(電子煙、遙控玩具)等,
目前市面上大部分的藍牙都是4.0以上的低功耗藍牙,經典藍牙已經很少見到了,
通訊程序
一個外圍設備可以發布多個服務service,每個服務可以包含多個特征值characteristic,每個特征值都有他的屬性,例如長度(size),權限(permission),值(value),描述(descriptor),讀寫通訊都是通過Characteristic進行的,
每個service、characteristic都含有一個對應的UUID,通過和外圍設備藍牙約定UUID來進行讀寫通訊,
整個通訊流程為:
常用業務API
1.判斷當前藍牙是否已經開啟,如果沒有開啟提示用戶開啟
2.實時掃描周邊藍牙,獲取藍牙名給用戶選擇
3.決議藍牙廣播資料處理業務
4.監聽外圍設備發送給app的資料,處理對應業務
5.app發送資料給外圍設備以處理業務
Android常用的第三方藍牙框架:okble、Android-BluetoothKit
以okble為例:
package com.wrs.project.module.app.common.bluetooth;
import android.content.Context;
import android.util.Log;
import com.a1anwang.okble.client.core.OKBLEDevice;
import com.a1anwang.okble.client.core.OKBLEDeviceImp;
import com.a1anwang.okble.client.core.OKBLEDeviceListener;
import com.a1anwang.okble.client.core.OKBLEOperation;
import com.a1anwang.okble.client.scan.BLEScanResult;
import com.a1anwang.okble.client.scan.DeviceScanCallBack;
import com.a1anwang.okble.client.scan.OKBLEScanManager;
import com.a1anwang.okble.common.OKBLECharacteristicModel;
import com.a1anwang.okble.common.OKBLEServiceModel;
import com.wrs.project.module.app.common.AppMgr;
import java.util.List;
public class Bluetooth implements DeviceScanCallBack, OKBLEDeviceListener{
private OKBLEScanManager scanManager;
private OKBLEDevice okbleDevice;// 當前連接的藍牙設備
private OKBLECharacteristicModel writeCharacteristicModel; // 藍牙可寫的Characteristic
private Context context = AppMgr.context;
private String tag = "Bluetooth";
public Bluetooth() {
scanManager = new OKBLEScanManager(context);
scanManager.setScanCallBack(this);
}
/**
* 掃描到藍牙設備
* @param device
* @param rssi
*/
@Override
public void onBLEDeviceScan(BLEScanResult device, int rssi) {
Log.e(tag, "掃描到藍牙設備:" + device.toString());
String localName = device.getCompleteLocalName();
if (null != localName && localName.startsWith("ABC-")) {
stopScanBluetooth();
connectBluetoothDevice(device);
}
}
/**
* 掃描失敗
* @param code
*/
@Override
public void onFailed(int code) {
}
@Override
public void onStartSuccess() {
}
/**
* 藍牙是否已經開啟
* @return
*/
public boolean bluetoothIsEnable() {
if (null != scanManager) {
scanManager.bluetoothIsEnable();
}
return false;
}
/**
* 關閉手機藍牙
*/
public void disableBluetooth() {
if (null != scanManager) {
scanManager.disableBluetooth();
}
}
/**
* 打開手機藍牙
*/
public void enableBluetooth() {
if (null != scanManager) {
scanManager.enableBluetooth();
}
}
/**
* 開始掃描藍牙
*/
public void startScanBluetooth() {
if (null != scanManager) {
if (!scanManager.isScanning()) {
scanManager.startScan();
}
}
}
/**
* 停止掃描藍牙
*/
public void stopScanBluetooth() {
if (null != scanManager) {
if (scanManager.isScanning()) {
scanManager.stopScan();
}
}
}
/**
* 斷開藍牙連接
*/
public void disConnect() {
if (null != okbleDevice) { // 如果當前已經連接其他設備,先斷開連接
okbleDevice.removeDeviceListener(this);
okbleDevice.disConnect(false);
okbleDevice = null;
}
}
/**
* 發送資料給藍牙設備
* @param data
*/
public void writeData(byte[] data) {
if (null != okbleDevice && null != writeCharacteristicModel && null != data && data.length > 0) {
okbleDevice.addWriteOperation(writeCharacteristicModel.getUuid(), data, new OKBLEOperation.WriteOperationListener() {
@Override
public void onWriteValue(byte[] value) {
Log.e(tag, "藍牙寫資料成功");
}
@Override
public void onFail(int code, String errMsg) {
Log.e(tag, "藍牙寫資料失敗:" + code + " " + errMsg);
}
@Override
public void onExecuteSuccess(OKBLEOperation.OperationType type) {
}
});
} else {
Log.e(tag, "藍牙寫資料失敗: 藍牙沒有連接或沒發現可寫Characteristic");
}
}
public void connectBluetoothDevice(BLEScanResult device) {
// 先斷開當前連接
disableBluetooth();
okbleDevice = new OKBLEDeviceImp(context, device);
okbleDevice.addDeviceListener(this);
okbleDevice.connect(true);//true表示連接斷開后OKBLE的會自動重連
}
/**
* 藍牙設備連接成功
* @param deviceTAG
*/
@Override
public void onConnected(String deviceTAG) {
Log.e(tag, "設備連接成功 " + deviceTAG);
// 連上藍牙后,獲取藍牙的WriteCharacteristic用后面給藍牙設備發送資料,獲取藍牙的ReadCharacteristic用來監聽藍牙設備發送過來的資料
List<OKBLEServiceModel> serviceModels = okbleDevice.getServiceModels();
if (null != serviceModels && serviceModels.size() > 0) {
for (int i = 0; i < serviceModels.size(); i++) {
OKBLEServiceModel serviceModel = serviceModels.get(i);
String serviceUUID = serviceModel.getUuid();
if (serviceUUID.startsWith("aaaaaaa-")) { // 匹配找到讀寫的服務
List<OKBLECharacteristicModel> characteristicModels = serviceModel.getCharacteristicModels();
if (null != characteristicModels && characteristicModels.size() > 0) {
for (int j = 0; j < characteristicModels.size(); j++) {
OKBLECharacteristicModel characteristicModel = characteristicModels.get(j);
String characteristicUUID = characteristicModel.getUuid();
if (characteristicUUID.startsWith("bbbbbbbbb") && characteristicModel.isCanWrite() && characteristicModel.isCanWriteNoResponse()) { // 匹配找到寫的Characteristic
findWriteCharacteristic(characteristicModel);
} else if (characteristicUUID.startsWith("8653000b-") && characteristicModel.isCanNotify()) { // 匹配找到讀的Characteristic
findReadCharacteristic(characteristicModel);
}
}
}
break;
}
}
}
}
@Override
public void onDisconnected(String deviceTAG) {
}
@Override
public void onReadBattery(String deviceTAG, int battery) {
}
/**
* 接收到藍牙發送的資料
* @param deviceTAG
* @param uuid
* @param value
*/
@Override
public void onReceivedValue(String deviceTAG, String uuid, byte[] value) {
}
@Override
public void onWriteValue(String deviceTAG, String uuid, byte[] value, boolean success) {
}
@Override
public void onReadValue(String deviceTAG, String uuid, byte[] value, boolean success) {
}
@Override
public void onNotifyOrIndicateComplete(String deviceTAG, String uuid, boolean enable, boolean success) {
}
private void findWriteCharacteristic(OKBLECharacteristicModel characteristic) {
if (null != okbleDevice && null != characteristic) {
writeCharacteristicModel = characteristic;
}
}
private void findReadCharacteristic(OKBLECharacteristicModel characteristic) {
if (null != okbleDevice && null != characteristic) {
String uuid = characteristic.getUuid();
boolean enableNotifyEnable = okbleDevice.isNotifyEnabled(uuid);
if (enableNotifyEnable) {
Log.e(tag, "打開讀屬性成功");
} else {
okbleDevice.addNotifyOrIndicateOperation(uuid, true, new OKBLEOperation.NotifyOrIndicateOperationListener() {
@Override
public void onFail(int code, String errMsg) {
Log.e(tag, "打開讀屬性失敗");
}
@Override
public void onExecuteSuccess(OKBLEOperation.OperationType type) {
Log.e(tag, "打開讀屬性成功");
}
@Override
public void onNotifyOrIndicateComplete() {
Log.e(tag, "打開讀屬性成功");
}
});
}
}
}
}
藍牙廣播資料包決議
廣播包有兩種: 廣播包 (Advertising Data)和 回應包 (Scan Response),其中廣播包是每個設備必須廣播的,而回應包是可選的,
每個包都是 31 位元組,分為有效資料和無效資料兩部分,
有效資料部分 :包含若干個廣播資料單元,稱為 AD Structure ,
AD Structure 的組成是:
第一個位元組是長度值 Len ,表示接下來的 Len 個位元組是資料部分,
資料部分的第一個位元組表示資料的型別 AD Type ,剩下的 Len - 1 個位元組是真正的資料 AD data ,其中 AD type 非常關鍵,決定了 AD Data 的資料代表的是什么和怎么決議,
無效資料部分 :因為廣播包的長度必須是 31 個 byte,如果有效資料部 分不到 31 自己,剩下的就用 0 補全,這部分的資料是無效的,解釋的時候,忽略即可,在 Android 中,系統會把這兩個資料拼接在一起,回傳一個 62 位元組的陣列,

例如:

第一個 位元組代表廣播資料單元的長度 ,02 轉為10進制就是 2代表其資料長度為2 , 而資料單元的第一個位元組代表型別 ,
01 代表 代表物理連接功能為普通發現模式 06代表其資料類容
緊接著下一個資料單元:
0B代表資料長度為11 ,資料型別為 02 即Serviceuuid代表是非完整的16bit uuid, 所以緊接著的后10位就是其uuid,
接下來就是下一個資料單元
首位是13轉為二進制就是19,其長度就是19,型別就是09 ,代表設備名稱,30-》字符0,65代表字符e,61代表字符a,73代表字符s,79代表字符y,4E代表N,65代表e,57代表W,44代表D,43代表C,53代表S ,00 代表字符null,01代表字符soh(SOH是序始字符(Start Of Header),它表示標題的開始),56代表V ,31代表字符1,2E代表字符.,30代表字符0,44代表D所有其設備名稱就是0easyNewDCS V1.0D,
接下來的一個資料單元長度是5,廣播型別12 連接間隔范圍,有四個位元組,接下來資料長度是02,型別是0A代表信號強度 剩余都是00000都是補位的無效資料,
廣播資料型別:
(1)Flags: TYPE = 0x01,這個資料用來標識設備 LE 物理連接的功能,DATA 是 0 到多個位元組的 Flag 值,每個 bit 上用 0 或者 1 來表示是否為 True,如果有任何一個 bit 不為 0,并且廣播包是可連接的,就必須包含此資料,各 bit 的定義如下: bit 0: LE 有限發現模式 bit 1: LE 普通發現模式 bit 2: 不支持 BR/EDR bit 3: 對 Same Device Capable(Controller) 同時支持 BLE 和 BR/EDR bit 4: 對 Same Device Capable(Host) 同時支持 BLE 和 BR/EDR bit 5…7: 預留
(2)Service UUID: 廣播資料中一般都會把設備支持的 GATT Service 廣播出來,用來告訴外面本設備所支持的 Service,有三種型別的 UUID:16 bit, 32bit, 128 bit,廣播中,每種型別型別有有兩個類別:完整和非完整的,這樣就共有 6 種 AD Type,
非完整的 16 bit UUID 串列: TYPE = 0x02;
完整的 16 bit UUID 串列: TYPE = 0x03;
非完整的 32 bit UUID 串列: TYPE = 0x04;
完整的 32 bit UUID 串列: TYPE = 0x05;
非完整的 128 bit UUID 串列: TYPE = 0x06;
完整的 128 bit UUID 串列: TYPE = 0x07;
(3) Local Name: 設備名字,DATA 是名字的字串, Local Name 可以是設備的全名,也可以是設備名字的縮寫,其中縮寫必須是全名的前面的若干字符, 設備全名: TYPE = 0x08 設備簡稱: TYPE = 0x09
(4)TX Power Level: TYPE = 0x0A,表示設備發送廣播包的信號強度,DATA 部分是一個位元組,表示 -127 到 + 127 dBm,
(5) 帶外安全管理(Security Manager Out of Band):TYPE = 0x11,DATA 也是 Flag,每個 bit 表示一個功能: bit 0: OOB Flag,0 表示沒有 OOB 資料,1 表示有 bit 1: 支持 LE bit 2: 對 Same Device Capable(Host) 同時支持 BLE 和 BR/EDR bit 3: 地址型別,0 表示公開地址,1 表示隨機地址 ,
(6)外設(Slave)連接間隔范圍:TYPE = 0x12,資料中定義了 Slave 最大和最小連接間隔,資料包含 4 個位元組:
前 2 位元組:定義最小連接間隔,取值范圍:0x0006 ~ 0x0C80,而 0xFFFF 表示未定義; 后 2 位元組:定義最大連接間隔,同上,不過需要保證最大連接間隔大于或者等于最小連接間隔,
(7) 服務搜尋:外圍設備可以要請中心設備提供相應的 Service,其資料定義和前面的 Service UUID 類似:
16 bit UUID 串列: TYPE = 0x14
32 bit UUID 串列: TYPE = 0x??
128 bit UUID 串列: TYPE = 0x15
(8) Service Data: Service 對應的資料,
16 bit UUID Service: TYPE = 0x16, 前 2 位元組是 UUID,后面是 Service 的資料;
32 bit UUID Service: TYPE = 0x??, 前 4 位元組是 UUID,后面是 Service 的資料;
128 bit UUID Service: TYPE = 0x??, 前 16 位元組是 UUID,后面是 Service 的資料;
(9) 公開目標地址:TYPE = 0x17,表示希望這個廣播包被指定的目標設備處理,此設備系結了公開地址,DATA 是目標地址串列,每個地址 6 位元組,
(10) 隨機目標地址:TYPE = 0x18,定義和前一個類似,表示希望這個廣播包被指定的目標設備處理,此設備系結了隨機地址,DATA 是目標地址串列,每個地址 6 位元組,
(11) Appearance:TYPE = 0x19,DATA 是表示了設備的外觀,
(12) 廠商自定義資料: TYPE = 0xFF,廠商自定義的資料中,前兩個位元組表示廠商 ID,剩下的是廠商自己按照需求添加,里面的資料內容自己定義,
專案原始碼:https://codechina.csdn.net/android1/projectbasic
上篇:android專題-資料庫Room 目錄 下篇: Android專題-常用第三方框架
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/256866.html
標籤:其他
下一篇:手擼六足機器人(一)----------PCA9685[16路舵機控制]各暫存器詳解及控制原始碼示例[STM32實作]
