本文從以下三方面講解下藍牙開發
1、藍牙相關基本知識
2、藍牙相關類圖
3、藍牙交互流程
一、藍牙相關基本知識
涉及到藍牙開發,首先有幾個問題是需要我們理解的
1、任何設備既可以是中心設備、也可以是外圍設備
2、外設 和 中心設備 之間通過特征建立一個雙向的數據通道
3、CBCentralManager主要操作中心設備,處理鏈接上外設之前的操作,鏈接上外設后,主要靠CBPeripheral(主要操作外設)處理外設相關操作(服務、特征、數據讀寫)
4、中心設備管理 CBCentralManager
中心控制類,主要管理中心設備,以及處理跟外設(外圍設備)相關操作,主要是掃描、鏈接、斷開外設。
操作中心設備的核心類。
很重要的協議CBCentralManagerDelegate,包含中心設備狀態(是否打開藍牙)回調、發現外設回調、鏈接外設成功回調、鏈接外設失敗回調、外設鏈接斷開回調等方法。
一個中心設備可以鏈接多個外圍設備。
5、外圍設備 CBPeripheral
外設類,包含設備的基礎屬性,名字,uuid等信息。向外設寫入數據。
當中心設備連接到外設后,需要通過外設對象的代理方法進行數據交互。
操作外圍設備的核心類。
很重要的協議CBPeripheralDelegate,包含發現服務回調、發現特征回調、特征的通知設置改變回調、特征更新回調、特征已寫入數據回調等方法。
一個設備包含多個服務、一個服務包含多個特征、一個特征又包含多個描述。
6、外圍設備管理 CBPeripheralManager
設備的控制,主要可以為設備設置Service以及Characteristic,可以手動配置特定的服務和特征值,也可看作可以自定義藍牙協議,例如將手機作為外設時可以為自己的手機藍牙設置服務和特征值。CBCentralManager更適合將自己的軟件作為中心。
用的較少
7、服務 CBService
服務對象是用來管理外設提供的一些數據服務的。
一個服務可以包含多個特征
8、特征 CBCharacteristic
通過綁定服務中的特征值來進行數據的讀寫操作。
特征就是具體鍵值對,提供數據的地方。
每個特征屬性分為這么幾種:讀,寫,通知等幾種方式。
有時讀、寫、通知可以是同一個特征,也可以讀、寫、通知各用一個特征表示。
一個特征可以包含多個描述。
一般我們操作到特征這一層
9、描述 CBDescriptor
每個characteristic可以對應一個或多個Description 供用戶描述characteristic的信息或屬性。
10、CBAttribute
CBService,CBCharacteristic,CBDescriptor 類都繼承自 CBAttribute,
它們有一個共同的屬性 CBUUID,用來作為唯一的標識。
二、藍牙相關類圖
下面用類圖簡述下:
一個中心設備可以連接多個外設,一個外設包含多個服務,一個服務包含多個特征,一個特征包含多個描述
服務、特征、描述都用CBUUID唯一標識
三、藍牙交互流程
下面簡述下以手機作為中心設備、其它作為外圍設備的交互流程,大致流程如圖所示
下面是操作詳解
1、創建中心設備管理對象(初始化中心設備)
CBCentralManager 的創建是異步的,如果初始化完成之后沒有被當前創建它的類所持有,就會在下一次 RunLoop 迭代的時候釋放。
創建線程寫nil,為主線程。
初始化成功后,就會觸發 CBCentralManagerDelegate 中的中心設備狀態更新方法:centralManagerDidUpdateState:
NSDictionary *options = @{CBCentralManagerOptionShowPowerAlertKey:@NO};
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:options];
/*! * @method initWithDelegate:queue:options: * * @param delegate The delegate that will receive central role events. * @param queue The dispatch queue on which the events will be dispatched. * @param options An optional dictionary specifying options for the manager. * * @discussion The initialization call. The events of the central role will be dispatched on the provided queue. * If <i>nil</i>, the main queue will be used. * * @seealso CBCentralManagerOptionShowPowerAlertKey * @seealso CBCentralManagerOptionRestoreIdentifierKey * */ - (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate queue:(nullable dispatch_queue_t)queue options:(nullable NSDictionary<NSString *, id> *)options NS_AVAILABLE(10_9, 7_0) NS_DESIGNATED_INITIALIZER;
@protocol CBCentralManagerDelegate <NSObject> @required /*! * @method centralManagerDidUpdateState: * * @param central The central manager whose state has changed. * * @discussion Invoked whenever the central manager's state has been updated. Commands should only be issued when the state is * <code>CBCentralManagerStatePoweredOn</code>. A state below <code>CBCentralManagerStatePoweredOn</code> * implies that scanning has stopped and any connected peripherals have been disconnected. If the state moves below * <code>CBCentralManagerStatePoweredOff</code>, all <code>CBPeripheral</code> objects obtained from this central * manager become invalid and must be retrieved or discovered again. * * @see state * */ - (void)centralManagerDidUpdateState:(CBCentralManager *)central;
2、掃描外圍設備
在CBCentralManagerDelegate 中的中心設備狀態更新方法:centralManagerDidUpdateState:中,
當中心設備處於CBManagerStatePoweredOn
狀態的時候開始掃描周邊設備(可以使用指定的 UUID 發現特定的 Service,也可以傳入 nil,表示發現所有周邊的藍牙設備,不過還是建議只發現自己需要服務的設備)。
掃描外設:scanForPeripheralsWithServices:options:
該操作由CBCentralManager對象通過scanForPeripheralsWithServices:options:方法實現
case CBManagerStatePoweredOn: //藍牙正常開啟 [self startScan]; break;
- (void)startScan { // [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }]; // 掃描所有設備 當指定設備不好使時可以使用該方法 // [self.centralManager scanForPeripheralsWithServices:nil options:nil]; // 掃描指定設備 快速 [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:self.peripheralServiceUUID]] options:nil]; [self startTime]; }
/*! * @method scanForPeripheralsWithServices:options: * * @param serviceUUIDs A list of <code>CBUUID</code> objects representing the service(s) to scan for. * @param options An optional dictionary specifying options for the scan. * * @discussion Starts scanning for peripherals that are advertising any of the services listed in <i>serviceUUIDs</i>. Although strongly discouraged, * if <i>serviceUUIDs</i> is <i>nil</i> all discovered peripherals will be returned. If the central is already scanning with different * <i>serviceUUIDs</i> or <i>options</i>, the provided parameters will replace them. * Applications that have specified the <code>bluetooth-central</code> background mode are allowed to scan while backgrounded, with two * caveats: the scan must specify one or more service types in <i>serviceUUIDs</i>, and the <code>CBCentralManagerScanOptionAllowDuplicatesKey</code> * scan option will be ignored. * * @see centralManager:didDiscoverPeripheral:advertisementData:RSSI: * @seealso CBCentralManagerScanOptionAllowDuplicatesKey * @seealso CBCentralManagerScanOptionSolicitedServiceUUIDsKey * */ - (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
3、發現外圍設備
CBCentralManager對象執行掃描外設方法scanForPeripheralsWithServices:options:后
會觸發 CBCentralManagerDelegate 中的方法:(發現外設)centralManager:didDiscoverPeripheral:advertisementData:RSSI:
如果在掃描時指定了明確的服務,那么此時該方法里的外設就是包含該服務的外設,
如果傳入的是nil,那么此時該方法里的外設就是周邊所有打開的藍牙設備。
/*! * @method centralManager:didDiscoverPeripheral:advertisementData:RSSI: * * @param central The central manager providing this update. * @param peripheral A <code>CBPeripheral</code> object. * @param advertisementData A dictionary containing any advertisement and scan response data. * @param RSSI The current RSSI of <i>peripheral</i>, in dBm. A value of <code>127</code> is reserved and indicates the RSSI * was not available. * * @discussion This method is invoked while scanning, upon the discovery of <i>peripheral</i> by <i>central</i>. A discovered peripheral must * be retained in order to use it; otherwise, it is assumed to not be of interest and will be cleaned up by the central manager. For * a list of <i>advertisementData</i> keys, see {@link CBAdvertisementDataLocalNameKey} and other similar constants. * * @seealso CBAdvertisementData.h * */ - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
4、鏈接外設
在CBCentralManagerDelegate 中的發現外設方法:centralManager:didDiscoverPeripheral:advertisementData:RSSI:中,
我們會鏈接外設:connectPeripheral:options:
該操作由CBCentralManager對象通過connectPeripheral:options:方法實現
我們通過名稱或者廠商數據來確定我們需要鏈接的外設(過濾外設),找到后停止掃描,然后鏈接該外設,即鏈接指定外設
[central connectPeripheral:peripheral options:nil];
/*! * @method connectPeripheral:options: * * @param peripheral The <code>CBPeripheral</code> to be connected. * @param options An optional dictionary specifying connection behavior options. * * @discussion Initiates a connection to <i>peripheral</i>. Connection attempts never time out and, depending on the outcome, will result * in a call to either {@link centralManager:didConnectPeripheral:} or {@link centralManager:didFailToConnectPeripheral:error:}. * Pending attempts are cancelled automatically upon deallocation of <i>peripheral</i>, and explicitly via {@link cancelPeripheralConnection}. * * @see centralManager:didConnectPeripheral: * @see centralManager:didFailToConnectPeripheral:error: * @seealso CBConnectPeripheralOptionNotifyOnConnectionKey * @seealso CBConnectPeripheralOptionNotifyOnDisconnectionKey * @seealso CBConnectPeripheralOptionNotifyOnNotificationKey * @seealso CBConnectPeripheralOptionEnableTransportBridgingKey * @seealso CBConnectPeripheralOptionRequiresANCS * */ - (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
5、鏈接外設結果回調
CBCentralManager對象執行鏈接外設方法connectPeripheral:options:后
會觸發 CBCentralManagerDelegate 中的方法:
鏈接外設成功回調:centralManager:didConnectPeripheral:
/*! * @method centralManager:didConnectPeripheral: * * @param central The central manager providing this information. * @param peripheral The <code>CBPeripheral</code> that has connected. * * @discussion This method is invoked when a connection initiated by {@link connectPeripheral:options:} has succeeded. * */ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
鏈接外設失敗回調:centralManager:didFailToConnectPeripheral:error:
/*! * @method centralManager:didFailToConnectPeripheral:error: * * @param central The central manager providing this information. * @param peripheral The <code>CBPeripheral</code> that has failed to connect. * @param error The cause of the failure. * * @discussion This method is invoked when a connection initiated by {@link connectPeripheral:options:} has failed to complete. As connection attempts do not * timeout, the failure of a connection is atypical and usually indicative of a transient issue. * */ - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
6、查找服務
在CBCentralManagerDelegate 中的鏈接外設成功回調方法:centralManager:didConnectPeripheral:中,
我們會查找服務:discoverServices:
該操作由CBPeripheral對象通過discoverServices:方法實現
[peripheral discoverServices:@[[CBUUID UUIDWithString:self.peripheralServiceUUID]]];
/*! * @method discoverServices: * * @param serviceUUIDs A list of <code>CBUUID</code> objects representing the service types to be discovered. If <i>nil</i>, * all services will be discovered. * * @discussion Discovers available service(s) on the peripheral. * * @see peripheral:didDiscoverServices: */ - (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
7、發現服務
CBPeripheral對象執行查找服務方法discoverServices:后,
會觸發 CBPeripheralDelegate 中的發現服務方法:peripheral:didDiscoverServices:
/*! * @method peripheral:didDiscoverServices: * * @param peripheral The peripheral providing this information. * @param error If an error occurred, the cause of the failure. * * @discussion This method returns the result of a @link discoverServices: @/link call. If the service(s) were read successfully, they can be retrieved via * <i>peripheral</i>'s @link services @/link property. * */ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
8、查找特征
在 CBPeripheralDelegate 中的發現服務方法:peripheral:didDiscoverServices:中,
我們會查找特征:discoverCharacteristics:forService:
該操作由CBPeripheral對象通過discoverCharacteristics:forService:方法實現
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:self.peripheralCharacteristicRTXUUID]] forService:service];
/*! * @method discoverCharacteristics:forService: * * @param characteristicUUIDs A list of <code>CBUUID</code> objects representing the characteristic types to be discovered. If <i>nil</i>, * all characteristics of <i>service</i> will be discovered. * @param service A GATT service. * * @discussion Discovers the specified characteristic(s) of <i>service</i>. * * @see peripheral:didDiscoverCharacteristicsForService:error: */ - (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;
9、發現特征
CBPeripheral對象執行查找特征方法discoverCharacteristics:forService:后,
會觸發CBPeripheralDelegate 中的發現特征方法:peripheral:didDiscoverCharacteristicsForService:error:
/*! * @method peripheral:didDiscoverCharacteristicsForService:error: * * @param peripheral The peripheral providing this information. * @param service The <code>CBService</code> object containing the characteristic(s). * @param error If an error occurred, the cause of the failure. * * @discussion This method returns the result of a @link discoverCharacteristics:forService: @/link call. If the characteristic(s) were read successfully, * they can be retrieved via <i>service</i>'s <code>characteristics</code> property. */ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
10、寫特征、通知特征(讀取)、讀特征
在 CBPeripheralDelegate 中的發現特征方法:peripheral:didDiscoverCharacteristicsForService:error:中,
我們會處理特征(讀、寫、通知),一般會保存寫入特征,方便后期寫入數據,打開使能通知,方便讀取數據
寫:
保存寫特征,方便后期寫入數據。
讀:
通知特征和讀特征都是為了讀取,一般我們使用的都是通知,
使用通知的時候,要打開使能通知(訂閱),
該操作由CBPeripheral對象通過setNotifyValue:forCharacteristic:方法打開指定通知特征
通知特征發送的數據在didUpdateValueForCharacteristic方法里接受(讀取)
該操作會回調CBPeripheralDelegate 中的方法peripheral:didUpdateNotificationStateForCharacteristic:error:
通過characteristic.isNotifying知曉通知狀態
/*! * @method peripheral:didUpdateNotificationStateForCharacteristic:error: * * @param peripheral The peripheral providing this information. * @param characteristic A <code>CBCharacteristic</code> object. * @param error If an error occurred, the cause of the failure. * * @discussion This method returns the result of a @link setNotifyValue:forCharacteristic: @/link call. */ - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
for (CBCharacteristic *characteristic in service.characteristics) { // if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:self.peripheralCharacteristicTXUUID]]) { // // 打開使能通知 (訂閱)該特征發送的數據在didUpdateValueForCharacteristic方法里接受(讀取) // [peripheral setNotifyValue:YES forCharacteristic:characteristic]; // } else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:self.peripheralCharacteristicRXUUID]]) { // // 保存外設接受特征(寫入特征) // self.characteristicRX = characteristic;
11、寫入數據
寫入方法:writeValue:forCharacteristic:type:
寫入操作由CBPeripheral對象通過writeValue:forCharacteristic:type:方法寫入指定寫入特征
[self.peripheral writeValue:peripheralRXData forCharacteristic:self.characteristicRX type:
CBCharacteristicWriteWithResponse];
/*! * @method writeValue:forCharacteristic:type: * * @param data The value to write. * @param characteristic The characteristic whose characteristic value will be written. * @param type The type of write to be executed. * * @discussion Writes <i>value</i> to <i>characteristic</i>'s characteristic value. * If the <code>CBCharacteristicWriteWithResponse</code> type is specified, {@link peripheral:didWriteValueForCharacteristic:error:} * is called with the result of the write request. * If the <code>CBCharacteristicWriteWithoutResponse</code> type is specified, and canSendWriteWithoutResponse is false, the delivery * of the data is best-effort and may not be guaranteed. * * @see peripheral:didWriteValueForCharacteristic:error: * @see peripheralIsReadyToSendWriteWithoutResponse: * @see canSendWriteWithoutResponse * @see CBCharacteristicWriteType */ - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;
寫入數據有兩種方式:
/* 32 typedef NS_ENUM(NSInteger, CBCharacteristicWriteType) { 33 CBCharacteristicWriteWithResponse = 0,//寫數據並且接收成功與否回執 34 CBCharacteristicWriteWithoutResponse,//寫數據不接收回執 35 }; 36 */
如果寫入類型為CBCharacteristicWriteWithResponse 回調CBPeripheralDelegate 中的方法:
peripheral:didWriteValueForCharacteristic:error:,
如果寫入類型為CBCharacteristicWriteWithoutResponse不回調此方法,
該方法只是告知寫入數據是否成功
/*! * @method peripheral:didWriteValueForCharacteristic:error: * * @param peripheral The peripheral providing this information. * @param characteristic A <code>CBCharacteristic</code> object. * @param error If an error occurred, the cause of the failure. * * @discussion This method returns the result of a {@link writeValue:forCharacteristic:type:} call, when the <code>CBCharacteristicWriteWithResponse</code> type is used. */ - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
寫入數據后外設響應數據在特征值更新方法didUpdateValueForCharacteristic:error:中讀取
12、讀取數據
讀取外設發送給中心設備的數據,
無論是read的回調,還是notify(訂閱)的回調都是CBPeripheralDelegate 中的方法:
特征值更新:didUpdateValueForCharacteristic:error:
/*! * @method peripheral:didUpdateValueForCharacteristic:error: * * @param peripheral The peripheral providing this information. * @param characteristic A <code>CBCharacteristic</code> object. * @param error If an error occurred, the cause of the failure. * * @discussion This method is invoked after a @link readValueForCharacteristic: @/link call, or upon receipt of a notification/indication. */ - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
13、斷開鏈接
斷開鏈接:cancelPeripheralConnection:
斷開鏈接操作由CBCentralManager對象通過cancelPeripheralConnection:方法實現
該方法不會觸發CBCentralManagerDelegate 中的方法:
斷開外設(僅在異常斷開時會觸發):centralManager:didDisconnectPeripheral:error:
/*! * @method cancelPeripheralConnection: * * @param peripheral A <code>CBPeripheral</code>. * * @discussion Cancels an active or pending connection to <i>peripheral</i>. Note that this is non-blocking, and any <code>CBPeripheral</code> * commands that are still pending to <i>peripheral</i> may or may not complete. * * @see centralManager:didDisconnectPeripheral:error: * */ - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
參考資料: