iOS 蓝牙开发详解(基本知识、相关类图、交互流程)
本文从以下三方面讲解下蓝牙开发
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;
参考资料: