ble的概念以及原理的簡(jiǎn)單理解
藍(lán)牙是一種短距離無(wú)線通信技術(shù),而藍(lán)牙低功耗(BLE)是在藍(lán)牙4.0協(xié)議上修改以適用低功耗應(yīng)用場(chǎng)景的一種藍(lán)牙協(xié)議。
那么4.0以后的藍(lán)牙為什么低功耗了呢?傳統(tǒng)藍(lán)牙是通過(guò)廣播收發(fā)狀態(tài),連接建立后通過(guò)socket建立連接,交互數(shù)據(jù)
ble相對(duì)于傳統(tǒng)藍(lán)牙
- 廣播頻段和廣播時(shí)射頻開啟時(shí)間的減少:傳統(tǒng)藍(lán)牙使用16~32個(gè)頻段進(jìn)行廣播,而BLE僅使用3個(gè)廣播頻段; 每次廣播時(shí)的射頻
開啟時(shí)間由傳統(tǒng)藍(lán)牙的22ms減少為0.6~1.2ms; - BLE設(shè)計(jì)了深度睡眠狀態(tài)(Duty-Cycle)來(lái)替代傳統(tǒng)藍(lán)牙的空閑時(shí)間,并且在Duty-Cycle時(shí),發(fā)送數(shù)據(jù)間隔也被增大.
- BLE 的連接采用先進(jìn)的Sniffer-Subrating模式.
- 傳統(tǒng)藍(lán)牙規(guī)范規(guī)定,若某一設(shè)備正在進(jìn)行廣播,則它不會(huì)響應(yīng)當(dāng)前正在進(jìn)行的設(shè)備掃描,而BLE允許正在進(jìn)行廣播的設(shè)備連接正
在掃描的設(shè)備,這有效避免了重復(fù)連接。通過(guò)對(duì)連接機(jī)制的改進(jìn),BLE連接建立過(guò)程可控制在3ms內(nèi)完成。 - BLE在每個(gè)從設(shè)備和每個(gè)數(shù)據(jù)包上使用32位的存取地址,優(yōu)化了傳統(tǒng)藍(lán)牙一對(duì)一的連接,實(shí)現(xiàn)一對(duì)多(目前測(cè)試的是1帶6)。
- BLE增加了GFSK調(diào)制,降低峰值功耗。
以上參考了https://blog.csdn.net/ZQ07506149/article/details/82380509
什么是GATT?
通用屬性配置文件層(Generic Attribute profile,簡(jiǎn)寫 GATT),簡(jiǎn)單點(diǎn)就是ble應(yīng)用運(yùn)行的環(huán)境或者場(chǎng)景,官方的專業(yè)解釋,個(gè)人實(shí)在懶的貼出來(lái),而且覺得也不好理解
GATT的層次結(jié)構(gòu)
GATT通常有一個(gè)或者多個(gè)“Services”組成,一個(gè) Characteristic 可以包含若干 Descriptor和value。而 Characteristic 定義了數(shù)值和操作。Characteristic 的操作這幾種權(quán)限:讀、寫、通知等權(quán)限。
uuid
Service、Characteristic 還有 Descriptor 都是使用 UUID 唯一標(biāo)示的。一會(huì)你可以看到在實(shí)際的操作中,都是通過(guò)uuid來(lái)查找你對(duì)應(yīng)的設(shè)備
ble的開發(fā)
ble也是C/S的開發(fā)模式,特別注意一點(diǎn)ble開發(fā)要在主線程(底層源碼注釋),所以當(dāng)兩個(gè)設(shè)備建立連接之后,它們就處于下面兩種角色之一:
- GATT服務(wù)器:為GATT客戶端提供數(shù)據(jù)服務(wù)的設(shè)備。外圍設(shè)備 可以創(chuàng)建uuid的服務(wù)
- GATT客戶端:從GATT服務(wù)器讀寫應(yīng)用數(shù)據(jù)的設(shè)備。中央設(shè)備
android的支持版本
官方說(shuō)從android4.3版本開始,就支持了ble,但是在實(shí)際測(cè)試可不是這樣,國(guó)內(nèi)好多機(jī)型開始支持的版本號(hào)都不一樣啊,這也是一個(gè)android碎片化的體現(xiàn)
GATT的連接問(wèn)題
GATT連接是獨(dú)占的。外設(shè)只能建立一個(gè)連接,但是客戶端可以連接多臺(tái)設(shè)備
GATT通信
中央設(shè)備在給外設(shè)發(fā)消息的時(shí)候,有時(shí)候只能發(fā)送一次, 因?yàn)閷懱卣髦登翱梢栽O(shè)置寫的類型setWriteType(),寫類型有三種,如下:
- WRITE_TYPE_DEFAULT 默認(rèn)類型,需要外圍設(shè)備的確認(rèn),也就是需要外圍設(shè)備的回應(yīng),這樣才能繼續(xù)發(fā)送寫。
- WRITE_TYPE_NO_RESPONSE 設(shè)置該類型不需要外圍設(shè)備的回應(yīng),可以繼續(xù)寫數(shù)據(jù)。加快傳輸速率。
- WRITE_TYPE_SIGNED 寫特征攜帶認(rèn)證簽名,具體作用不太清楚。
適配ios
在開發(fā)ble服務(wù)端的時(shí)候,遇到過(guò)一個(gè)很坑的問(wèn)題,調(diào)試了很久,需要在Characteristic特征中加入以下這個(gè)BluetoothGattCharacteristic.PROPERTY_NOTIFY,否則ios接收不到,android沒(méi)事,不知道ios的ble模塊是怎樣實(shí)現(xiàn)的,有知道可以留言一下
Beacon
非連接型的ble的數(shù)據(jù)收發(fā)
外圍設(shè)備代碼開發(fā)
初始化
mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(), createAdvData(), mAdvertiseCallback);
廣告模式的回調(diào)
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.d(TAG, "onStartSuccess");
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
Log.d(TAG, "onStartFailure " + errorCode);
}
};
設(shè)置廣播數(shù)據(jù)
private AdvertiseSettings createAdvSettings() {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
//手機(jī)的發(fā)射功率,簡(jiǎn)單說(shuō)就是藍(lán)牙走多遠(yuǎn)
builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
builder.setConnectable(true);
builder.setTimeout(0);
//ADVERTISE_MODE_LOW_POWER 在低功耗模式下執(zhí)行藍(lán)牙LE廣告。這是默認(rèn)和首選的廣告模式,因?yàn)樗淖钌俚碾娏Α? //ADVERTISE_MODE_BALANCED 在平衡電源模式下執(zhí)行藍(lán)牙LE廣告。這是廣告頻率和功耗之間的平衡。
//ADVERTISE_MODE_LOW_LATENCY 在低延遲,高功率模式下執(zhí)行藍(lán)牙LE廣告。這是最高的功耗,不應(yīng)該用于連續(xù)的背景廣告。
// TODO: 18-1-7 測(cè)試模式的值
builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
return builder.build();
}
設(shè)置響應(yīng)數(shù)據(jù)
private AdvertiseData createAdvData() {
AdvertiseData.Builder builder = new AdvertiseData.Builder();
builder.addServiceUuid(ParcelUuid.fromString(UUID_SAMPLE_NAME_SERVICE));
//這樣即使定義service uuid跟別人的有沖突,也可以在中心過(guò)濾該magic number來(lái)找到符合自己需求的外圍設(shè)備,,但目前x是以設(shè)備名+系列號(hào)
byte mLeManufacturerData[] = {(byte) 0x4C, (byte) 0x00, (byte) 0x02, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15};
builder.addManufacturerData(0x3103 + 1, mLeManufacturerData);
builder.setIncludeTxPowerLevel(false);
builder.setIncludeDeviceName(true);
return builder.build();
}
初始化GATT服務(wù)
//初始化GATT服務(wù)
BluetoothGattService nameService = new BluetoothGattService(UUID.fromString(UUID_SAMPLE_NAME_SERVICE),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
//增加讀寫特征
mBleGattCharacteristicWrite = new BluetoothGattCharacteristic(
UUID.fromString(UUID_SAMPLE_NAME_CHARACTERISTIC),
BluetoothGattCharacteristic.PROPERTY_READ
| BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ
| BluetoothGattCharacteristic.PERMISSION_WRITE);
nameService.addCharacteristic(mBleGattCharacteristicWrite);
mGattServer.addService(nameService);
GATT服務(wù)的回調(diào)
mGattServer = mBluetoothManager.openGattServer(mContext, new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
{
switch (newState) {
case BluetoothProfile.STATE_DISCONNECTED:
Log.d(TAG, device.getName() + " 斷開");
sendHandlerMsg(device.getName() + " 斷開");
break;
case BluetoothProfile.STATE_CONNECTED:
Log.d(TAG, device.getName() + " 連接");
sendHandlerMsg(device.getName() + " 連接");
break;
default:
break;
}
}
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
Log.d(TAG, "onServiceAdded");
}
/**A remote client has requested to read a local characteristic.*/
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
Log.d(TAG, "read");
}
/**
* 接收消息的方法 - 接收具體的字節(jié)
*/
@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);
characteristic.setValue(mReceiveSuccess.getBytes());
//觸發(fā)發(fā)消息給客戶端
mGattServer.notifyCharacteristicChanged(device,characteristic,false);
String msg = new String(value);
Log.d(TAG, "write " + msg);
sendHandlerMsg(msg);
//.處理響應(yīng)內(nèi)容
//onResponseToClient(requestBytes, device, requestId, characteristic);
}
/**
* 特征被讀取。當(dāng)回復(fù)響應(yīng)成功后,客戶端會(huì)讀取然后觸發(fā)本方法
*
*/
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
Log.d(TAG, "DescriptorRead");
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
/**
* 2.描述被寫入時(shí)
*/
@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);
Log.d(TAG, "DescriptorWrite");
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
Log.d(TAG, "onExecuteWrite");
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
Log.d(TAG, "onNotificationSent");
}
});
客戶端代碼 - 中央設(shè)備
初始化-掃描-連接-連接GATT服務(wù)-搜索服務(wù)-通信
檢查藍(lán)牙打開,ble可用,得到BluetoothAdapter對(duì)象
掃描
該方法專門針對(duì)ble的掃描設(shè)備,不是傳統(tǒng)掃描,無(wú)法掃描非ble外設(shè),api要求21以上
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mBluetoothLeScanner.startScan(mScanCallback);
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (null != result) {
BluetoothDevice device = result.getDevice();
}
}
};
連接
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
return false;
}
//根據(jù)地址連接遠(yuǎn)程設(shè)備
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
return false;
}
//連接已開啟的GATT服務(wù)
mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
mBluetoothDeviceAddress = address;
GATT服務(wù)的回調(diào),成功連接后開始搜索服務(wù) mBluetoothGatt.discoverServices();
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
/**
* 連接狀態(tài)的改變
*/
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
//成功連接后開始搜索服務(wù)
if (newState == BluetoothProfile.STATE_CONNECTED) {
discoverServices();
setConnectionStatus(STATE_CONNECTED);
if (null != mIBleGattCallback) {
mIBleGattCallback.onConnectionStateChange(gatt, mConnectionState);
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
setConnectionStatus(STATE_DISCONNECTED);
if (null != mIBleGattCallback) {
mIBleGattCallback.onConnectionStateChange(gatt, mConnectionState);
}
}
}
/**
* 發(fā)現(xiàn)服務(wù)
*/
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.d(TAG,"onServicesDiscovered");
if (status == BluetoothGatt.GATT_SUCCESS) {
if (null != mIBleGattCallback) {
mIBleGattCallback.onServicesDiscovered(gatt);
}
} else {
excuteError(status);
}
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (null != mIBleGattCallback) {
mIBleGattCallback.onCharacteristicRead(gatt, characteristic);
}
} else {
excuteError(status);
}
}
/**
* 接收外設(shè)的消息
* @param gatt BluetoothProfile的對(duì)象
* @param characteristic 特征
*/
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (null != mIBleGattCallback) {
mIBleGattCallback.onCharacteristicChanged(gatt, characteristic);
onReceiveMsg(characteristic);
}
}
};
發(fā)數(shù)據(jù)
獲取可用的特征
可以通過(guò)協(xié)定好的uuid獲取,也可以遍歷獲取可寫的服務(wù)
//判斷特征可寫
public boolean ifCharacteristicWritable(BluetoothGattCharacteristic characteristic){
return ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 ||
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0);
}
//直接獲取相應(yīng)uuid的服務(wù)
UUID uuid = UUID.fromString(BleStatusConstant.UUID_SAMPLE_NAME_SERVICE);
BluetoothGattService service = gatt.getService(uuid);
UUID uuid2 = UUID.fromString(BleStatusConstant.UUID_SAMPLE_NAME_CHARACTERISTIC);
mBleCharacteristic = service.getCharacteristic(uuid2);
寫入屬性
/**
* 寫入屬性
*
* @param characteristic 特征
* @return true寫入成功 false寫入失敗
*/
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
return false;
}
return mBluetoothGatt.writeCharacteristic(characteristic);
}
結(jié)束別忘了釋放,如果有掃描也需要停止掃描
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;