低功耗藍牙
Android BLE API 簡介
BluetoothAdapter 擁有基本的藍牙操作闯睹,例如開啟藍牙掃描柿究,使用已知的 MAC 地址 (BluetoothAdapter#getRemoteDevice)實例化一個 BluetoothDevice 用于連接藍牙設(shè)備的操作等等僚纷。
代表一個遠程藍牙設(shè)備梢睛。這個類可以讓你連接所代表的藍牙設(shè)備或者獲取一些有關(guān)它的信息逛裤,例如它的名字誊抛,地址和綁定狀態(tài)等等狈蚤。
這個類提供了 Bluetooth GATT 的基本功能困肩。例如重新連接藍牙設(shè)備,發(fā)現(xiàn)藍牙設(shè)備的 Service 等等脆侮。
這一個類通過 BluetoothGatt#getService 獲得锌畸,如果當前服務(wù)不可見那么將返回一個 null。這一個類對應(yīng)上面說過的 Service靖避。我們可以通過這個類的 getCharacteristic(UUID uuid) 進一步獲取 Characteristic 實現(xiàn)藍牙數(shù)據(jù)的雙向傳輸潭枣。
這個類對應(yīng)上面提到的 Characteristic比默。通過這個類定義需要往外圍設(shè)備寫入的數(shù)據(jù)和讀取外圍設(shè)備發(fā)送過來的數(shù)據(jù)。
- 掃描藍牙
在 BluetoothAdapter 中盆犁,我們可以看到有兩個掃描藍牙的方法命咐。第一個方法可以指定只掃描含有特定 UUID Service 的藍牙設(shè)備,第二個方法則是掃描全部藍牙設(shè)備谐岁。
boolean startLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
boolean startLeScan(BluetoothAdapter.LeScanCallback callback)
@Override public void onLeScan(BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) { }
bluetoothDevice:藍牙設(shè)備的類醋奠,可以通過這個類建立藍牙連接獲取關(guān)于這一個設(shè)備的一系列詳細的參數(shù),例如名字伊佃,MAC 地址等等窜司。 rssi:藍牙的信號強弱指標。 scanRecord:藍牙廣播出來的數(shù)據(jù)锭魔。
//停止掃描 void stopLeScan(BluetoothAdapter.LeScanCallback callback)
5.0 系統(tǒng)后添加
public void startScan(final ScanCallback callback)
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); mBLEScanner.startScan(mScanCallback); //停止掃描 mBLEScanner.stopScan(mScanCallback);
private ScanCallback mScanCallback = new ScanCallback() {
@Override public void onScanResult(int callbackType, ScanResult result) { super.onScanResult(callbackType, result); //當發(fā)現(xiàn)一個外設(shè)時回調(diào)此方法例证。 } @Override public void onBatchScanResults(List<ScanResult> results) { super.onBatchScanResults(results); //在此返回一個包含所有掃描結(jié)果的列表集,包括以往掃描到的結(jié)果迷捧。 } @Override public void onScanFailed(int errorCode) { super.onScanFailed(errorCode); //掃描失敗后的處理。 } };
- 連接
public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) contex:You know autoConnect:表示是否需要自動連接胀葱。如果設(shè)置為 true, 表示如果設(shè)備斷開了漠秋,會不斷的嘗試自動連接。設(shè)置為 false 表示只進行一次連接嘗試抵屿。 callback:連接后進行的一系列操作的回調(diào)庆锦。
void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
status:嵌入式可以自定義的比如:19 藍牙門鎖主動斷開。 newState:2(STATE_CONNECTED)連接成功轧葛。0(STATE_DISCONNECTED)連接失敗搂抒。 只能是 2 或者0
//-------源碼部分----------
/**
* Client connection state changed
* @hide
*/
@Override
public void onClientConnectionState(int status, int clientIf,
boolean connected, String address) {
if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
+ " clientIf=" + clientIf + " device=" + address);
if (!address.equals(mDevice.getAddress())) {
return;
}
int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
BluetoothProfile.STATE_DISCONNECTED;
runOrQueueCallback(new Runnable() {
@Override
public void run() {
if (mCallback != null) {
mCallback.onConnectionStateChange(BluetoothGatt.this, status,
profileState);
}
}
});
synchronized(mStateLock) {
if (connected) {
mConnState = CONN_STATE_CONNECTED;
} else {
mConnState = CONN_STATE_IDLE;
}
}
synchronized(mDeviceBusy) {
mDeviceBusy = false;
}
}
- 發(fā)現(xiàn)服務(wù)
gatt.discoverServices() 連接成功后調(diào)用。
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
在藍牙設(shè)備中, 其包含有多個BluetoothGattService, 而每個BluetoothGattService中又包含有多個BluetoothGattCharacteristic尿扯。
當onServicesDiscovered()回調(diào)的 status == BluetoothGatt.GATT_SUCCESS求晶, 可以進行獲取service,根據(jù)嵌入式定義的 讀寫UUID 獲取到readCharacteristic。
gatt.setCharacteristicNotification(readCharacteristic, true); 不設(shè)置不會接受到數(shù)據(jù)衷笋。 此處可獲取到writeCharacteristic 用于發(fā)送數(shù)據(jù)的操作芳杏。
-------其他方式獲取方法-------- BluetoothGattService getService(UUID uuid) BluetoothGattCharacteristic getCharacteristic(UUID uuid)
- 藍牙收到數(shù)據(jù)
app 獲取到藍牙數(shù)據(jù)的出口
@Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic ch) {}
mReceivedData = ch.getValue(); 此處可以根據(jù)gatt 的uuid 來區(qū)分不同特征值的數(shù)據(jù)。
- 發(fā)送數(shù)據(jù)
boolean ret = writeCharacteristic.setValue(tempData);
gatt.writeCharacteristic(writeCharacteristic); 分包20個字節(jié)發(fā)送
- MTU
mtu20的來源:GATT是基于ATT Protocol的辟宗,而它的 core spec里面定義了ATT的默認MTU為23個bytes爵赵,除去ATT的opcode一個字節(jié)以及ATT的handle2個字節(jié)之后,剩下的20個字節(jié)便是留給GATT的了泊脐。
public boolean requestMtu (int mtu) Added in API level 21 Request an MTU size used for a given connection. When performing a write request operation (write without response), the data sent is truncated to the MTU size. This function may be used to request a larger MTU size to be able to send more data at once. A onMtuChanged(BluetoothGatt, int, int) callback will indicate whether this operation was successful. Requires BLUETOOTH permission. Returns true, if the new MTU value has been requested successfully @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status); if (status == BluetoothGatt.GATT_SUCCESS) { } }
由于十分不穩(wěn)定和兼容問題空幻,很少使用。
- AndroidQ BLE 新增COC容客, 可以用于傳輸大數(shù)據(jù)秕铛,比如用于ota升級约郁。
Bluetooth LE Connection Oriented Channels (CoC) Android 10 enables your app to use BLE CoC connections to transfer larger data streams between two BLE devices. This interface abstracts Bluetooth and connectivity mechanics to simplify implementation.
- 斷開連接
gatt.disconnect(); gatt.close(); gatt=null;
Read More
https://developer.android.com/guide/topics/connectivity/bluetooth-
le.html
https://developer.android.google.cn/reference/android/bluetooth/le/
其他摘要:
Dealing with errors
Now that we dealt with successful operations we need to look at the errors. There are a bunch of cases that are actually very normal that will present themselves as ‘errors’:
- The device disconnected itself on purpose. For example, because all data has been transferred and there is nothing else to to. You will receive status 19 (GATT_CONN_TERMINATE_PEER_USER).
- The connection timed out and the device disconnected itself. In this case you’ll get a status 8 (GATT_CONN_TIMEOUT)
- There was an low-level error in the communication which led to the loss of the connection. Typically you would receive a status 133 (GATT_ERROR) or a more specific error code if you are lucky!
- The stack never managed to connect in the first place. In this case you will also receive a status 133 (GATT_ERROR)
- The connection was lost during service discovery or bonding. In this case you will want to investigate why this happened and perhaps retry the connection.
The first two cases are totally normal and there is nothing else to do than call close() and perhaps do some internal cleanup like disposing of the BluetoothGatt object.
In the other cases, you may want to do something like informing other parts of your app or showing something in the UI. If there was a communication error you might be doing something wrong yourself. Alternatively, the device might be doing something wrong. Either way, something to deal with! It is a bit up to you to what extend you want to deal with all possible cases.
Have a look at my version in my Blessed library how I did it.
Status 133 when connecting
It is very common to see a status 133 when trying to connect to a device, especially while you are developing your code. The status 133 can have many causes and some of them you can control:
- Make sure you always call close() when there is a disconnection. If you don’t do this you’ll get a 133 for sure next time you try.
- Make sure you always use TRANSPORT_LE when calling connectGatt()
- Restart your phone if you see this while developing. You may have corrupted the stack by debugging and it is in a state where it doesn’t behave normal again. Restarting your phone may fix things.
- Make sure your device is advertising. The connectGatt with autoconnect set to false times out after 30 seconds and you will receive a 133.
- Change the batteries of your device. Devices typically start behaving erratically when they battery level is very low.
If you have tried all of the above and still see status 133 you need to simply retry the connection! This is one of the Android bugs I never managed to understand or find a workaround for. For some reason, you sometimes get a 133 when connecting to a device but if you call close() and retry it works without a problem! I suspect there is an issue with the Android cache that causes all this and the close() call puts it back in a proper state. But I am really just guessing here…If anybody figures out how to solve this one, let me know!