學習筆記之低功耗藍芽開發
概述
ClassicBluetooth:學習筆記之經典藍芽開發經典藍芽與低功耗(BLE)藍芽的區別
①經典藍芽:發現/搜尋裝置-->配對/繫結裝置-->建立連線-->資料通訊
②BLE藍芽:中央 VS 外圍裝置,中央裝置掃描,尋找廣播;外圍裝置發出廣播。二者通過GATT協議進行資料通訊互動
③只要有藍芽功能基本都支援經典藍芽,那麼BLE藍芽Google在Android 4.3才開始支援BLE API
④BLE有裝置角色的概念以及基於GATT協議來達到互動,至於經典藍芽的資料通訊,檢視android.bluetooth包下發現BluetoothServerSocket與BluetoothSocket你就大致知道了
⑤BLE與傳統的藍芽相比最大的優勢是功耗降低90%,同時傳輸距離增大(超過100米)、安全和穩定性提高(支援AES加密和CRC驗證)
iBeacon與BLE的區別
雖然iBeacon技術基於BLE,但是iBeacon的UUID和BLE的Service、Characteristic、Descriptor的UUID是沒關係,iBeacon的UUID是廣播的時候發出,是由Apple自己定義的標準,而Service、Characteristic、Descriptor必須是連上BLE終端後才得到,是BLE標準。
執行效果
許可權
在經典藍芽需要的許可權基礎上再加一個<uses-feature Android:name="android.hardware.bluetooth_le" android:required="true"/>
這裡的true標示手機如果不支援低功耗藍芽就直接不讓安裝,但是如果想讓你的app提供給那些不支援BLE的裝置,需要在manifest中包括上面程式碼並設定required="false",然後在執行時可以通過使用getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)確定BLE的可用性。
開啟藍芽
開啟方法與經典藍芽開啟方式相同,區別在於BLE通過getSystemService(Context.BLUETOOTH_SERVICE)得到BluetoothManager(API level 18),BluetoothManager.getAdapter()得到BluetoothAdapter。
發現/搜尋BLE裝置
通過呼叫BluetoothAdapter的startLeScan(BluetoothAdapter.LeScanCallback)搜尋BLE裝置。呼叫此方法時需要傳入 BluetoothAdapter.LeScanCallback引數。因此你需要實現 BluetoothAdapter.LeScanCallback介面,BLE裝置的搜尋結果將通過這個callback返回。如果你只需要搜尋指定UUID的外設,你可以呼叫 startLeScan(UUID[], BluetoothAdapter.LeScanCallback)方法。其中UUID陣列指定你的應用程式所支援的GATT Services的UUID。stopLeScan(BluetoothAdapter.LeScanCallback)停止搜尋BLE裝置,由於搜尋需要儘量減少功耗,因此在實際使用時需要注意。
onLeScan 方法在Android 5.0以下及Android 5.0及以上所執行的執行緒不同。
@Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
if (Looper.myLooper() == Looper.getMainLooper()) {// Android 5.0 及以上
addData(device, rssi, scanRecord);
} else {
runOnUiThread(new Runnable() {// Android 5.0 以下
@Override
public void run() {
addData(device, rssi, scanRecord);
}
});
}
}
處理髮現的BLE裝置,並展示在UI上
public void addData(BluetoothDevice device, int rssi, byte[] scanRecord) {
if (device != null) {
/**
* 這裡呼叫ibeacon工具類封裝bieacon並轉換距離
* 非ibeacon裝置可替換自己的業務
*/
BleBuletoothDeviceBean mdb = IbeaconUtils.fromScanData(new BleBuletoothDeviceBean(), device, rssi, scanRecord);
if (mainList.size() < 1) {
mainList.add(mdb);
lvAdapter.notifyDataSetChanged();
return;
}
for (int i = 0; i < mainList.size(); i++) {
BleBuletoothDeviceBean bbdb = mainList.get(i);
if (bbdb.getType() == 1) {
if (bbdb.getDevice().getAddress().equals(device.getAddress())) {
bbdb.setDistance(mdb.getDistance());
bbdb.setRssi(mdb.getRssi());
bbdb.setTxPower(mdb.getTxPower());
}
} else {
if (!bbdb.getDevice().getAddress().equals(mdb.getDevice().getAddress())) {
mainList.add(mdb);
}
}
}
lvAdapter.notifyDataSetChanged();
}
}
連線BLE裝置
低功耗藍芽(BLE)連線都是建立在 GATT (Generic Attribute Profile) 協議之上。GATT 是一個在藍芽連線之上的傳送和接收很短的資料段的通用規範,這些很短的資料段被稱為屬性(Attribute)。具體的內容檢視GATT Profile簡介,用一張圖來概括GATT的結構連線GATT
連線GATT,連線結果非同步通過onConnectionStateChange回撥public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.e(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
if (!mBluetoothDeviceAddress.equals("") && address.equals(mBluetoothDeviceAddress)
&& !Gatt()) {
Log.e(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.e(TAG, "Device not found. Unable to connect.");
return false;
}
mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
Log.e(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
return true;
}
GATT回撥監聽:連線更改和服務發現,mgl是自己實現的監聽回撥,用於UI介面的業務private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {//成功連線後發現服務的嘗試
if (mgl != null)
mgl.onConnect(gatt);
Log.e(TAG, "Connected to GATT server.");
Log.e(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//斷開
if (mgl != null) mgl.onDisconnect(gatt);
Log.e(TAG, "Disconnected from GATT server.");
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {//services被發現
if (status == BluetoothGatt.GATT_SUCCESS) {
if (mgl != null) {
mgl.onServiceDiscover(gatt);
}
} else {
Log.e(TAG, "onServicesDiscovered received: " + status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
int status) {
if (mgl != null)//characteristic被讀到
mgl.onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (mgl != null)//characteristic被寫入
mgl.onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, "onCharacteristicChanged:come in ");
}
};
Gatt的服務列表
當服務被發現時,呼叫getservices()獲取Gatt的服務列表/**
* 檢索的連線裝置支援的GATT的服務列表。只有在成功完成後才能呼叫該函式。
*
* @return
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (Gatt()) return null;
return mBluetoothGatt.getServices();
}
處理gatt返回的資料並填充展示(一個gatt包含多個service;一個sercice包含多個Characteristics;一個Characteristics包含多個Descriptors)
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) return;
for (BluetoothGattService gattService : gattServices) {
//-----Service的欄位資訊-----//
Log.e(TAG, "========Service的欄位資訊========");
Log.e(TAG, "-->service type:" + gattService.getType());
Log.e(TAG, "-->includedServices size:" + gattService.getIncludedServices().size());
Log.e(TAG, "-->service uuid:" + gattService.getUuid().toString());
HashMap<String, String> hmServiceData = new HashMap<String, String>();
hmServiceData.put(LIST_NAME, GattUtils.lookup(gattService.getUuid().toString(), "serviceName==null"));
hmServiceData.put(LIST_UUID, gattService.getUuid().toString());
gattServiceData.add(hmServiceData);
ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>();
List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>();
//-----Characteristics的欄位資訊-----//
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
Log.e(TAG, "========Characteristics的欄位資訊========");
Log.e(TAG, "---->char uuid:" + gattCharacteristic.getUuid());
Log.e(TAG, "---->char permission:" + gattCharacteristic.getPermissions());
Log.e(TAG, "---->char property:" + gattCharacteristic.getProperties());
if (gattCharacteristic.getValue() != null && gattCharacteristic.getValue().length > 0) {
Log.e(TAG, "---->char value:" + new String(gattCharacteristic.getValue()));
}
charas.add(gattCharacteristic);
HashMap<String, String> hmCharaData = new HashMap<String, String>();
hmCharaData.put(LIST_NAME, GattUtils.lookup(gattCharacteristic.getUuid().toString(),
"characteristicsName==null"));
hmCharaData.put(LIST_UUID, gattCharacteristic.getUuid().toString());
gattCharacteristicGroupData.add(hmCharaData);
//-----Descriptors的欄位資訊-----//
List<BluetoothGattDescriptor> descriptors = gattCharacteristic.getDescriptors();
for (BluetoothGattDescriptor gattDescriptor : descriptors) {
Log.e(TAG, "========Descriptors的欄位資訊========");
Log.e(TAG, "-------->desc uuid:" + gattDescriptor.getUuid());
Log.e(TAG, "-------->desc permission:" + gattDescriptor.getPermissions());
if (gattDescriptor.getValue() != null && gattDescriptor.getValue().length > 0) {
Log.e(TAG, "-------->desc value:" + new String(gattDescriptor.getValue()));
}
}
}
bleGattCharacteristicList.add(charas);
gattCharacteristicData.add(gattCharacteristicGroupData);
}
updateUi(2);
}
讀取Characteristic資料
觸發點選事件,gatt呼叫readCharacteristic()@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
if (bleGattCharacteristicList.size() > 0) {
BluetoothGattCharacteristic bgc = bleGattCharacteristicList.get(groupPosition).get(childPosition);
int properties = bgc.getProperties();
if ((properties | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
//如果在特性上有一個活動通知,請首先清除它,這樣它就不會更新使用者介面上的資料欄位.
if (mNotifyCharacteristic != null) {
gc.setCharacteristicNotification(mNotifyCharacteristic, false);
mNotifyCharacteristic = null;
}
gc.readCharacteristic(bgc);
}
if ((properties | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
mNotifyCharacteristic = bgc;
gc.setCharacteristicNotification(bgc, true);
}
}
return false;
}
在onCharacteristicRead()監聽讀取內容,並展示在UI上。 /**
* BLE終端資料被讀的事件
*/
@Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
Log.e(TAG, "調onCharacteristicRead");
if (status == BluetoothGatt.GATT_SUCCESS) {
final String text = GattUtils.bytesToHexString(characteristic.getValue());
final String wid = characteristic.getUuid().toString();
Log.e(TAG, "BLE終端資料被讀的事件onCharRead " + gatt.getDevice().getName()
+ " read "
+ characteristic.getUuid().toString()
+ " -> "
+ (text.equals("") ? "data=null" : text));
runOnUiThread(new Runnable() {
@Override
public void run() {
data_value.setText(text.equals("") ? "read==null" : text);
writerId_value.setText(wid.equals("") ? "writer==null" : wid);
writeId = wid;
}
});
} else {
Log.e(TAG, "沒有讀到");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(BleGattActivity.this, "沒有讀到data", Toast.LENGTH_SHORT).show();
}
});
}
}
資料寫入Characteristic
設定寫入的內容,gatt呼叫writeCharacteristic()public void writeSomeData() {
//可以跟藍芽模組串列埠通訊的Characteristic
Log.e(TAG, "寫入的Characteristic:" + mNotifyCharacteristic.getUuid().toString());
//接受Characteristic被寫的通知,收到藍芽模組的資料後會觸發mOnDataAvailable.onCharacteristicWrite()
gc.setCharacteristicNotification(mNotifyCharacteristic, true);
String str = "9";
//設定資料內容
boolean data = mNotifyCharacteristic.setValue(str);
//往藍芽模組寫入資料
boolean flag = gc.writeCharacteristic(mNotifyCharacteristic);
Log.e(TAG, "設定資料是否成功:"+data);
Log.e(TAG, "寫入資料是否成功:"+flag);
}
在onCharacteristicWrite()監聽寫入的結果,並展示在UI上。
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
final BluetoothGattCharacteristic characteristic, final int status) {
Log.e(TAG, "調onCharacteristicWrite");
Log.e(TAG, "收到BLE終端寫入資料回撥onCharWrite " + gatt.getDevice().getName()
+ " write "
+ characteristic.getUuid().toString()
+ " -> "
+ GattUtils.bytesToHexString(characteristic.getValue()));
if (status == BluetoothGatt.GATT_SUCCESS) {
runOnUiThread(new Runnable() {
@Override
public void run() {
writer_in_value.setText(GattUtils.bytesToHexString(characteristic.getValue()));
}
});
} else if (status == BluetoothGatt.GATT_FAILURE) {
runOnUiThread(new Runnable() {
@Override
public void run() {
writer_in_value.setText("寫入失敗==" + status);
}
});
} else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
runOnUiThread(new Runnable() {
@Override
public void run() {
writer_in_value.setText("沒有寫入的許可權==" + status);
}
});
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
writer_in_value.setText("寫入異常狀態==" + status);
}
});
}
}
用一張圖來描述流程
GATT連線過程Log日誌 02-17 13:52:04.809 26265-26265/com.lhj.classic.bluetooth E/GattConnectControl: Trying to create a new connection.
02-17 13:52:04.809 26265-26265/com.lhj.classic.bluetooth E/flag: 開始連線gatt=true
02-17 13:52:08.794 26265-26282/com.lhj.classic.bluetooth E/GattConnectControl: Connected to GATT server.
02-17 13:52:08.798 26265-26282/com.lhj.classic.bluetooth E/GattConnectControl: Attempting to start service discovery:true
02-17 13:52:10.657 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: 搜尋到BLE終端服務的事件
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ========Service的欄位資訊========
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -->service type:0
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -->includedServices size:0
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -->service uuid:00001801-0000-1000-8000-00805f9b34fb
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ========Characteristics的欄位資訊========
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ---->char uuid:00002a05-0000-1000-8000-00805f9b34fb
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ---->char permission:0
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ---->char property:34
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: ========Descriptors的欄位資訊========
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -------->desc uuid:00002902-0000-1000-8000-00805f9b34fb
02-17 13:52:10.659 26265-26308/com.lhj.classic.bluetooth E/BleGattActivity: -------->desc permission:0
02-17 13:52:14.428 26265-26283/com.lhj.classic.bluetooth E/BleGattActivity: 調onCharacteristicRead
02-17 13:52:14.430 26265-26283/com.lhj.classic.bluetooth E/BleGattActivity: BLE終端資料被讀的事件onCharRead null read 00002a02-0000-1000-8000-00805f9b34fb -> 00
02-17 13:52:15.917 26265-26265/com.lhj.classic.bluetooth E/BleGattActivity: 寫入的Characteristic:00002a02-0000-1000-8000-00805f9b34fb
02-17 13:52:15.942 26265-26265/com.lhj.classic.bluetooth E/BleGattActivity: true=======true
02-17 13:52:15.980 26265-26282/com.lhj.classic.bluetooth E/BleGattActivity: 調onCharacteristicWrite
02-17 13:52:15.985 26265-26282/com.lhj.classic.bluetooth E/BleGattActivity: 收到BLE終端寫入資料回撥onCharWrite null write 00002a02-0000-1000-8000-00805f9b34fb -> 39
02-17 13:52:18.821 26265-26283/com.lhj.classic.bluetooth E/GattConnectControl: Disconnected from GATT server
當然還有其它資訊的讀取與寫入,如writeDescriptor,readDescriptor,readRemoteRssi等與之對應的就有onDescriptorRead,onDescriptorWrite,onReadRemoteRssi。 注意靈活使用(取決於BLE裝置的通訊方式)setCharacteristicNotification(characteristic, enabled:設定當指定characteristic值變化時,發出通知。
小結
實戰才是真理,專案已經上傳至GitHub,專案包含經典藍芽與BLE以及ibeacon,請自行閱讀需要的模組。
以上便關於低功耗藍芽的主要內容,如果有任何不妥之處,還請各位指出。