最近一段時間一直在做公司的BLE藍芽SDK,sdk主要負責外設和手機的連線以及資料通訊。過程中遇到了一些比較有價值的問題,現在總結記錄下。
藍芽開發使用系統框架
#import <CoreBluetooth/CoreBluetooth.h>
使用[[CBCentralManager alloc] initWithDelegate:self queue:nil]
初始化CBCentralManager
物件。(設定CBCentralManagerDelegate
為self,nil表示在主執行緒) 初始化成功後系統會自動檢測藍芽狀態。實現centralManagerDidUpdateState:
代理方法可實時獲取藍芽狀態。當central.state
為CBManagerStatePoweredOn
時表示可用。
初始化完成可使用
[self.centralManager scanForPeripheralsWithServices:nil options:nil]
方法掃描周圍裝置(Services表示只掃描具有某些ServiceUUID的裝置,nil為掃描全部)。 當掃描到裝置時會執行代理方法centralManager:didDiscoverPeripheral:advertisementData:RSSI:
返回每一個掃描到的裝置及裝置的相關資訊。
為了使用方便,可以把掃描的裝置、連線的裝置、裝置的服務、裝置的特徵分別儲存到陣列中
@property(nonatomic, strong) NSMutableArray<CBPeripheral *> *peripheralArr; //掃描到的裝置陣列
@property(nonatomic, strong) NSMutableArray<CBPeripheral *> *currentPeripheralArr; //當前連線的所有裝置
@property(nonatomic, strong) NSMutableArray<CBService *> *serviceArr; //當前連線裝置的服務
@property(nonatomic, strong) NSMutableArray<CBCharacteristic *> *characteristicArr; //當前連線的裝置的所有特徵
複製程式碼
儲存到陣列中的裝置可通過
UUID
來進行區分。從iOS7
之後蘋果不提供外設的mac
地址,外設的唯一標識換成了由mac
封裝加密後的UUID
,需要注意的是不同的手機獲取同一個外設的UUID
是不同的,所以在不同手機之間UUID
不是唯一的,但在本機上可以作為唯一標識。
//通過裝置物件連線裝置
[self.centralManager connectPeripheral:peripheral options:nil];
複製程式碼
連線失敗時執行
centralManager:didFailToConnectPeripheral:error:
//連線裝置成功時呼叫
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
//設定代理
peripheral.delegate = self;
//獲取裝置的服務,傳nil代表獲取所有的服務
[peripheral discoverServices:nil];
}
複製程式碼
ble藍芽主要有 裝置-->服務-->特徵 3層。分別都是一對多的關係。它們都有個唯一標識UUID,裝置UUID,服務UUID,特徵UUID。
發現服務
//發現服務時
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if(!error)
{
//遍歷peripheral.services陣列
for (CBService * service in peripheral.services)
{
if (![self.serviceArr containsObject:service])
{
NSLog(@"裝置:%@發現新服務:%@",peripheral.identifier.UUIDString, service.UUID.UUIDString);
[self.serviceArr addObject:service];
//發現特徵
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
else{
NSLog(@"發現服務失敗的錯誤資訊%@", error);
}
}
複製程式碼
發現特徵
//發現特徵時
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if(!error)
{
//把特徵儲存到陣列
for (CBCharacteristic *charact in service.characteristics)
{
if (![self.characteristicArr containsObject:charact])
{
NSLog(@"裝置:%@的服務:%@發現新特徵:%@",peripheral.identifier.UUIDString, service.UUID.UUIDString, charact.UUID.UUIDString);
//儲存到陣列
[self.characteristicArr addObject:charact];
}
}
/*
把裝置儲存到已連線的裝置陣列
此時的需求是當特徵發現完成時才算連線成功
*/
[self.currentPeripheralArr addObject:peripheral];
}
else{
NSLog(@"發現特徵失敗的錯誤資訊%@", error);
}
}
複製程式碼
至此以上4個陣列都已填滿。 手機和藍芽硬體之間的通訊主要是使用藍芽特徵值的讀寫,接下來就是連線裝置之後對裝置的特徵值進行讀、寫、訂閱。
寫入特徵值
/*
peripheral是寫入的裝置物件,charact是特徵物件,valueData是要寫入的資料
type的取值有CBCharacteristicWriteWithResponse(有回覆)和CBCharacteristicWriteWithoutResponse(無回覆),和硬體的設定有關
*/
[peripheral writeValue:valueData forCharacteristic:charact type:CBCharacteristicWriteWithResponse];
複製程式碼
當type是
CBCharacteristicWriteWithResponse
時 實現peripheral:didWriteValueForCharacteristic:error:
代理方法能夠獲取寫入結果的回撥。
讀取特徵值
//呼叫此方法去讀取引數特徵的value
[peripheral readValueForCharacteristic:charact];
複製程式碼
實現
peripheral:didUpdateValueForCharacteristic:error:
代理方法 獲取characteristic.value
即為讀取的特徵值。
訂閱特徵
//設定某特徵的Notify為YES為訂閱狀態
[peripheral setNotifyValue:YES forCharacteristic:charact];
複製程式碼
實現
peripheral:didUpdateNotificationStateForCharacteristic:error:
代理方法當訂閱狀態發生改變時會執行。 當訂閱裝置的某個特徵時,裝置端給這個特徵傳送notify訊息時會呼叫peripheral:didUpdateValueForCharacteristic:error:
代理方法把notify要傳的值傳送過來。
有關藍芽基礎的最後一點就是斷開藍芽連線了,也是非常重要的一點,所以寫在最後
斷開連線很簡單,只需要呼叫
[self.centralManager cancelPeripheralConnection:peripheral]
傳入需要斷開連線的裝置物件就行了。斷開連線時會自動呼叫centralManager:didDisconnectPeripheral:error:
代理方法。 按照之前的慣例,當error為nil時表示斷開成功,error不為nil時斷開失敗。這種理解時錯誤的。
這個代理方法官方的解釋
/*!
* @method centralManager:didDisconnectPeripheral:error:
*
* @param central The central manager providing this information.
* @param peripheral The <code>CBPeripheral</code> that has disconnected.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method is invoked upon the disconnection of a peripheral that was connected by {@link connectPeripheral:options:}. If the disconnection
* was not initiated by {@link cancelPeripheralConnection}, the cause will be detailed in the <i>error</i> parameter. Once this method has been
* called, no more methods will be invoked on <i>peripheral</i>'s <code>CBPeripheralDelegate</code>.
*
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
複製程式碼
大致意思理解為當你呼叫cancelPeripheralConnection:
方法(主動斷開
)斷開連線時error為nil ; 沒有呼叫這個方法(異常斷開
)而斷開時error返回的是異常斷開的原因。也可以理解為主動呼叫斷開連線方法一定會斷開。
接下來就是斷開重連的問題了,對藍芽功能進行封裝時肯定少不了斷開重連。首先斷開時可通過上面的代理方法的error是否為nil
判斷是否是異常斷開
,一般情況下異常斷開時是需要重連的。
重新連線後發現讀寫資料時沒效果了???
原因就是當裝置斷開連線後
peripheral.services
為nil
了,當然service.characteristics
也是nil
,所以需要在斷開連線時把儲存這個裝置對應的服務和特徵全部清除,然後在連線成功時重新過一遍發現服務和發現特徵的流程就好了。
回顧個人的ble藍芽開發過程總結出來基礎篇遇到的問題大致就這麼多了。本篇文章旨在個人總結和幫助正在做這方面的人理解藍芽開發中這些東西的概念。
理解了事物,解決這個事物相關的問題就不難了。