android藍牙BLE(一) —— 掃描android
在藍牙開發中,有些狀況是不須要鏈接的,只要外設廣播本身的數據便可,例如蘋果的ibeacon。自Android 5.0更新藍牙API後,手機能夠做爲外設廣播數據。post
廣播包有兩種:測試
其中廣播包是每一個外設都必須廣播的,而響應包是可選的。每一個廣播包的長度必須是31個字節,若是不到31個字節 ,則剩下的全用0填充。 補全。這部分的數據是無效的 ui
廣播包中包含若干個廣播數據單元,廣播數據單元也稱爲 AD Structure。this
廣播數據單元 = 長度值Length + AD type + AD Data。spa
長度值Length只佔一個字節,而且位於廣播數據單元的第一個字節。code
概念的東西有些抽象,先看看下面的廣播報文:
0x表明這串字符串是十六進制的字符串。兩位十六進制數表明一個字節。由於兩個字符組成的十六進制字符串最大爲FF,即255,而Java中byte類型的取值範圍是-128到127,恰好能夠表示一個255的大小。因此兩個十六進制的字符串表示一個字節。
繼續查看報文內容,開始讀取第一個廣播數據單元。讀取第一個字節:0x07,轉換爲十進制就是7,即表示後面的7個字節是這個廣播數據單元的數據內容。超過這7個字節的數據內容後,表示是一個新的廣播數據單元。
而第二個廣播數據單元,第一個字節的值是0x16,轉換爲十進制就是22,表示後面22個字節爲第二個廣播數據單元。
在廣播數據單元的數據部分中,第一個字節表明數據類型(AD type),決定數據部分表示的是什麼數據。(即廣播數據單元第二個字節爲AD type)
AD Type的類型以下:
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: 預留
這bit 1~7分別表明着發送該廣播的藍牙芯片的物理鏈接狀態。當bit的值爲1時,表示支持該功能。 例:
非完整的16bit UUID: TYPE = 0x02;
完整的16bit UUID 列表: TYPE = 0x03;
非完整的32bit UUID 列表: TYPE = 0x04;
完整的32bit UUID 列表: TYPE = 0x05;
非完整的128bit UUID 列表: TYPE = 0x06;
完整的128bit UUID: TYPE = 0x07;
縮寫的設備名稱: TYPE = 0x08
完整的設備名稱: TYPE = 0x09
16 bit UUID Service: TYPE = 0x16, 前 2 字節是 UUID,後面是 Service 的數據;
32 bit UUID Service: TYPE = 0x20, 前 4 字節是 UUID,後面是 Service 的數據;
128 bit UUID Service: TYPE = 0x21, 前 16 字節是 UUID,後面是 Service 的數據;
藍牙廣播的數據格式大體講了一下,有助於下面的廣播操做的理解。
先自定義UUID:
//UUID
public static UUID UUID_SERVICE = UUID.fromString("0000fff7-0000-1000-8000-00805f9b34fb");
複製代碼
開啓廣播通常須要3~4對象:廣播設置(AdvertiseSettings)、廣播包(AdvertiseData)、掃描包(可選)、廣播回調(AdvertiseCallback)。
先看看廣播設置(AdvertiseSettings)如何定義:
//初始化廣播設置
mAdvertiseSettings = new AdvertiseSettings.Builder()
//設置廣播模式,以控制廣播的功率和延遲。
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)
//發射功率級別
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
//不得超過180000毫秒。值爲0將禁用時間限制。
.setTimeout(3000)
//設置是否能夠鏈接
.setConnectable(false)
.build();
複製代碼
(1)、經過AdvertiseSettings.Builder#setAdvertiseMode() 設置廣播模式。其中有3種模式:
一、在均衡電源模式下執行藍牙LE廣播:AdvertiseSettings#ADVERTISE_MODE_BALANCED
二、在低延遲,高功率模式下執行藍牙LE廣播: AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY
三、在低功耗模式下執行藍牙LE廣播:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER
(2)、經過AdvertiseSettings.Builder#setAdvertiseMode() 設置廣播發射功率。共有4種功率模式:
一、使用高TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_HIGH
二、使用低TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_LOW
三、使用中等TX功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM
四、使用最低傳輸(TX)功率級別進行廣播:AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW
(3)、經過AdvertiseSettings.Builder#setTimeout()設置持續廣播的時間,單位爲毫秒。最多180000毫秒。當值爲0則無時間限制,持續廣播,除非調用BluetoothLeAdvertiser#stopAdvertising()。
(4)、經過AdvertiseSettings.Builder#setConnectable()設置該廣播是否能夠鏈接的。
以前說過,外設必須廣播廣播包,掃描包是可選。但添加掃描包也意味着廣播更多得數據,便可廣播62個字節。
//初始化廣播包
mAdvertiseData = new AdvertiseData.Builder()
//設置廣播設備名稱
.setIncludeDeviceName(true)
//設置發射功率級別
.setIncludeDeviceName(true)
.build();
//初始化掃描響應包
mScanResponseData = new AdvertiseData.Builder()
//隱藏廣播設備名稱
.setIncludeDeviceName(false)
//隱藏發射功率級別
.setIncludeDeviceName(false)
//設置廣播的服務UUID
.addServiceUuid(new ParcelUuid(UUID_SERVICE))
//設置廠商數據
.addManufacturerData(0x11,hexStrToByte(mData))
.build();
複製代碼
可見不管是廣播包仍是掃描包,其廣播的內容都是用AdvertiseData類封裝的。
(1)、AdvertiseData.Builder#setIncludeDeviceName()方法,能夠設置廣播包中是否包含藍牙的名稱。
(2)、AdvertiseData.Builder#setIncludeTxPowerLevel()方法,能夠設置廣播包中是否包含藍牙的發射功率。
(3)、AdvertiseData.Builder#addServiceUuid(ParcelUuid)方法,能夠設置特定的UUID在廣播包中。
(4)、AdvertiseData.Builder#addServiceData(ParcelUuid,byte[])方法,能夠設置特定的UUID和其數據在廣播包中。
(5)、AdvertiseData.Builder#addManufacturerData(int,byte[])方法,能夠設置特定廠商Id和其數據在廣播包中。
從AdvertiseData.Builder的設置中能夠看出,若是一個外設須要在不鏈接的狀況下對外廣播數據,其數據能夠存儲在UUID對應的數據中,也能夠存儲在廠商數據中。但因爲廠商ID是須要由Bluetooth SIG進行分配的,廠商間通常都將數據設置在廠商數據。
另外能夠經過BluetoothAdapter#setName()設置廣播的名稱
//獲取藍牙設配器
BluetoothManager bluetoothManager = (BluetoothManager)
getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//設置設備藍牙名稱
mBluetoothAdapter.setName("daqi");
複製代碼
先看一個例子,咱們分別在廣播包和掃描包中設置AdvertiseData.Builder的每一種廣播報文參數,獲得一下報文內容:
(1)、Type = 0x01 表示設備LE物理鏈接。
(2)、Type = 0x09 表示設備的全名
(3)、Type = 0x03 表示完整的16bit UUId。其值爲0xFFF7。
(4)、Type = 0xFF 表示廠商數據。前兩個字節表示廠商ID,即廠商ID爲0x11。後面的爲廠商數據,具體由用戶自行定義。
(5)、Type = 0x16 表示16 bit UUID的數據,因此前兩個字節爲UUID,即UUID爲0xF117,後續爲UUID對應的數據,具體由用戶自行定義。
最後繼承AdvertiseCallback自定義廣播回調。
private class daqiAdvertiseCallback extends AdvertiseCallback {
//開啓廣播成功回調
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect){
super.onStartSuccess(settingsInEffect);
Log.d("daqi","開啓服務成功");
}
//沒法啓動廣播回調。
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
Log.d("daqi","開啓服務失敗,失敗碼 = " + errorCode);
}
}
複製代碼
初始化完畢上面的對象後,就能夠進行廣播:
//獲取BLE廣播的操做對象。
//若是藍牙關閉或此設備不支持藍牙LE廣播,則返回null。
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
//mBluetoothLeAdvertiser不爲空,且藍牙已開打
if(mBluetoothAdapter.isEnabled()){
if (mBluetoothLeAdvertiser != null){
//開啓廣播
mBluetoothLeAdvertiser.startAdvertising(mAdvertiseSettings,
mAdvertiseData, mScanResponseData, mAdvertiseCallback);
}else {
Log.d("daqi","該手機不支持ble廣播");
}
}else{
Log.d("daqi","手機藍牙未開啓");
}
複製代碼
廣播主要是經過BluetoothLeAdvertiser#startAdvertising()方法實現,但在以前須要先獲取BluetoothLeAdvertiser對象。
BluetoothLeAdvertiser對象存在兩個狀況獲取爲Null:
一、手機藍牙模塊不支持BLE廣播
二、藍牙未開啓
因此在調用BluetoothAdapter#getBluetoothLeAdvertiser()前,須要先調用判斷藍牙已開啓,並判斷在BluetoothAdapter中獲取的BluetoothLeAdvertiser是否爲空(測試過某些華爲手機mBluetoothAdapter.isMultipleAdvertisementSupported()爲false,可是能發送ble廣播)。
與廣播成對出現就是BluetoothLeAdvertiser.stopAdvertising()中止廣播了,傳入開啓廣播時傳遞的廣播回調對象,便可關閉廣播:
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback)
複製代碼
雖然經過廣播告知外邊自身擁有這些Service,但手機自身並無初始化Gattd的Service。致使外部的中心設備鏈接手機後,並不能找到對應的GATT Service 和 獲取對應的數據。
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,
BluetoothGattService.SERVICE_TYPE_PRIMARY);
複製代碼
建立BluetoothGattService時,傳入兩個參數:UUID和Service類型。
Service類型有兩個級別:
咱們都知道Gatt中,Service的下一級是Characteristic,Characteristic是最小的通訊單元,經過對Characteristic進行讀寫操做來進行通訊。
//初始化特徵值
mGattCharacteristic = new BluetoothGattCharacteristic(UUID_CHARACTERISTIC,
BluetoothGattCharacteristic.PROPERTY_WRITE|
BluetoothGattCharacteristic.PROPERTY_NOTIFY|
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE|
BluetoothGattCharacteristic.PERMISSION_READ);
複製代碼
建立BluetoothGattCharacteristic時,傳入三兩個參數:UUID、特徵屬性 和 權限屬性。
特徵屬性表示該BluetoothGattCharacteristic擁有什麼功能,即能對BluetoothGattCharacteristic進行什麼操做。其中主要有3種:
權限屬性用於配置該特徵值所具備的功能。主要兩種:
當特徵值只有讀權限時,調用BluetoothGatt#writeCharacteristic()對特徵值進行修改時,將返回false,沒法寫入。並不會觸發BluetoothGattCallback#onCharacteristicWrite()回調。
當特徵值只有寫權限時,調用BluetoothGatt#readCharacteristic()對特徵值進行讀取時,將返回false,沒法寫入。並不會觸發BluetoothGattCallback#onCharacteristicRead()回調。
Characteristic下還有Descriptor,初始化BluetoothGattDescriptor時傳入:Descriptor UUID 和 權限屬性
//初始化描述
mGattDescriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattDescriptor.PERMISSION_WRITE);
複製代碼
爲Service添addCharacteristic,爲Characteristic添加Service:
//Service添加特徵值
mGattService.addCharacteristic(mGattCharacteristic);
mGattService.addCharacteristic(mGattReadCharacteristic);
//特徵值添加描述
mGattCharacteristic.addDescriptor(mGattDescriptor);
複製代碼
經過藍牙管理器mBluetoothManager獲取Gatt Server,用來添加Gatt Service。添加完Gatt Service後,外部中心設備鏈接手機時,將能獲取到對應的GATT Service 和 獲取對應的數據
//初始化GattServer回調
mBluetoothGattServerCallback = new daqiBluetoothGattServerCallback();
if (mBluetoothManager != null)
mBluetoothGattServer = mBluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
boolean result = mBluetoothGattServer.addService(mGattService);
if (result){
Toast.makeText(daqiActivity.this,"添加服務成功",Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(daqiActivity.this,"添加服務失敗",Toast.LENGTH_SHORT).show();
}
複製代碼
定義Gatt Server回調。當中心設備鏈接該手機外設、修改特徵值、讀取特徵值等狀況時,會獲得相應狀況的回調。
private class daqiBluetoothGattServerCallback extends BluetoothGattServerCallback{
//設備鏈接/斷開鏈接回調
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
}
//添加本地服務回調
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
}
//特徵值讀取回調
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
//特徵值寫入回調
@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);
}
//描述讀取回調
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
}
//描述寫入回調
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
}
}
複製代碼
最後開啓廣播後,用nRF鏈接後看到的特徵值信息以下圖所示:(加多了一個只能都的特徵值)