zoukankan      html  css  js  c++  java
  • 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

     CBServiceCBCharacteristicCBDescriptor 类都继承自 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;

     参考资料:

    http://www.myexceptions.net/operating-system/2052286.html

    https://blog.csdn.net/TAO_HUADAO/article/details/54629745?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

    https://www.jianshu.com/p/ec659ffcacfe

  • 相关阅读:
    我所理解的readonly和const
    代码中的良好习惯从点滴做起
    常用正则表达式收集
    优化网站加载速度的14个技巧
    关于easyUI的模态对话框
    关于jq ajax封装以及ajax上传Webapi
    Linux完全卸载Oracle的操作步骤
    Linux下安装oracle的一般步骤
    CentOS Linux上安装Oracle11g笔记
    Oracle中建库时报Exception in thread main
  • 原文地址:https://www.cnblogs.com/lijianyi/p/14765794.html
Copyright © 2011-2022 走看看