iOS蓝牙开发学习(2)--连接、读写、DFU升级篇

大致了解了coreBluetooth后,接下来开始着手建立连接的事情。连接大致分为以下几个步骤。

 

重要提示!!!Xcode10 之后,扫描外设出来的值,会变成   类似这样的!!!  这会导致,原来的mac 扫描识别,直接取值出现问题。请自行转换!

 

  1. 根据自己的需求,确定是否创建一个蓝牙管理的单例类,个人建议是最好使用蓝牙管理的单例类,这样其他地方调用也方便一点。
  2. 初始化CBCentralManager,初始化之后,记得设置代理。
    - (instancetype)initWithDelegate:(nullable id)delegate
    						   queue:(nullable dispatch_queue_t)queue
    						 options:(nullable NSDictionary *)options NS_AVAILABLE(10_9, 7_0) NS_DESIGNATED_INITIALIZER;

    其中需要注意:options的参数,参数有很多,举例说两个,其他的参考API
     

    /*!
     *  @const  CBCentralManagerOptionShowPowerAlertKey
     *
     *  @discussion An NSNumber (Boolean) indicating that the system should, if Bluetooth is powered off when CBCentralManager is instantiated, display
     *				a warning dialog to the user.
     *
     *  @see		initWithDelegate:queue:options:
     *
     */
    CB_EXTERN NSString * const CBCentralManagerOptionShowPowerAlertKey NS_AVAILABLE(10_9, 7_0);

    大致意思是说,如果是给的YES,那么在初始化CBCentralManager的时候,如果蓝牙没有打开,那么会以alert的形式提示用户打开蓝牙,但是似乎,iOS11以后就不太好用了。

    /*!
     *  @const  CBCentralManagerOptionRestoreIdentifierKey
     *
     *  @discussion An NSString containing a unique identifier (UID) for the CBCentralManager that is being instantiated. This UID is used
     *				by the system to identify a specific CBCentralManager instance for restoration and, therefore, must remain the same for
     *				subsequent application executions in order for the manager to be restored.
     *
     *  @see		initWithDelegate:queue:options:
     *  @seealso	centralManager:willRestoreState:
     *
     */
    CB_EXTERN NSString * const CBCentralManagerOptionRestoreIdentifierKey NS_AVAILABLE(10_13, 7_0);

    个人理解,就是将你初始化的这个CBCentralManager存储到系统蓝牙队列里面,如果你的程序gg以后,当系统蓝牙唤醒你的app以后会通过 centralManager:willRestoreState: 直接将之前的CBCentralManager还给你,而不会再通过init初始化了,所以要注意哦~ 关于这点,后面具体说。willRestore记得设置代理。
    其他的请自行查阅文档。

  3. 初始化成功以后,不是连接,而是检查系统蓝牙的状态。

    - (void)centralManagerDidUpdateState:(CBCentralManager *)central;

    通过该回调 central.state,获取CBCentralManager的状态也就是系统蓝牙的状态,

    typedef NS_ENUM(NSInteger, CBManagerState) {
    	CBManagerStateUnknown = 0,
    	CBManagerStateResetting,
    	CBManagerStateUnsupported,
    	CBManagerStateUnauthorized,
    	CBManagerStatePoweredOff,
    	CBManagerStatePoweredOn,
    } NS_ENUM_AVAILABLE(10_13, 10_0);

    一共有这几种状态,最后两个是 已关闭、已打开,其他的自行根据api查看。并根据不同的状态触发不同的业务逻辑。

  4. 当蓝牙是打开的时候,进行扫描。

    - (void)scanForPeripheralsWithServices:(nullable NSArray *)serviceUUIDs options:(nullable NSDictionary *)options;

    需要提到的是,如果需要扫描包含某个或几个的指定服务的per时,以CBUUID类型为元素的数组,options 中CBCentralManagerScanOptionSolicitedServiceUUIDsKey, 这样就可以只扫描出包含指定服务的per了。
    如果什么都不需要直接填nil 即可。

  5. 当有符合条件的per被扫描到后,触发回调

    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;

    advertisementData:扫描的per所附带的广播数据,可根据与厂商的协议,获取per的mac值,进行匹配连接
    RSSI:蓝牙信号强度

  6. 当扫描到匹配的per后,调用连接函数。注意,连接之前,需要把per作为单例的属性保存起来,不然会有不可描述的事情等着你。当然,通过查阅apple的api发现,这个函数,是个很强大的函数,它会将你的连接请求,加入到系统的队列中,如果你的连接丢失,在丢失的回调里,直接再次写入连接就可以了,如果你app在期间gg了,还记得willrestore那个回调吗?

  7. - (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary *)options;

    个人感觉,options里的几个参数没什么意义,直接写nil即可,如果有兴趣,自己也可以测试一下。
    建议在此时写一个延时操作,用来校验是否连接成功,如果没有,需要再次连接,或者进行其他逻辑操作。

    同时需要把扫描关闭,以减低功耗

    - (void)stopScan;

     

  8. 连接成功/失败的回调。成功之后,记得设置代理噢~ per的代理。

    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

    没啥好说的,自己看着办吧

  9. 获取服务与特征。  一般针对per的读写操作,是针对到具体的特征值,所以我们要将自己之后需要操作的特征值,作为单例管理类的对象存储起来。再连接成功的回调里,最好是将per的所以服务特征遍历读一遍。

    - (void)discoverServices:(nullable NSArray *)serviceUUIDs;

    注意啦,这个是per的对象方法,别用central去调用。参数如果给nil就是遍历,去读取所有的服务。读取到的服务,会触发回调

    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;

    是不是发现回调里没有service之类的参数?哈哈,这个时候Peripheral这个对象的属性services已经是有值得了,直接get就可以得到。
    接下来就是获取特征值了,根据第一篇里面提到的,特征是属于服务的,所以我们获取特征的时候,需要提供其所属的服务,所以遍历services

    - (void)discoverCharacteristics:(nullable NSArray *)characteristicUUIDs forService:(CBService *)service;

    类似于获取特征,给nil就是获取该服务的所有特征,该方法触发回调

    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;

    呐,其实和获取服务的回调差不多,自己根据厂商的协议,将制定的特征set给单例管理类对应的特征对象的属性即可。
    这个回调里,可以根据业务需求,直接先读一下或者订阅下,读或者订阅怎么操作。下面接着讲。。

  10. 读。 需要注意读不等同于订阅,两者是有区别的,读,是代码控制去读一下,只是读一下。而 订阅,在开启后,会根据固件方的设定,主动发消息过来,所以,这里也从侧面证明,蓝牙连接,是个长连接。读一次,触发一次回调

    //读取指定特征
    - (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
    //特征读取回调
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
    

    读取到的特征通过回调中的 characteristic.value获得,所获得的值为NSData类型,下面是解析示例

    Byte *byte = (Byte *)[characteristic.value bytes];
    NSLog(@"%s", byte);
    int sate = byte[0];
    NSLog(@"%d", sate);

    根据业务需求自行解析

  11. 订阅(使能),订阅时需要注意不要重复开启订阅或者关闭订阅。使用场景:动态心跳监测,订阅指定特征后,开启心跳监测,该特征值根据厂商协议,一般会每秒更新一次,具体间隔,由固件决定。app只是被动接收。

    - (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic;

    同样的,也是通过didUpdateNotificationStateForCharacteristic回调更新特征的值。这里与读的区别,是读一次触发一次,而订阅,是会多次触发,每次收到per发送新的值,就触发一次。

  12. 写入。写入,同样是写入NSData对象数据,需要注意的是CBCharacteristicWriteType,要严格按照协议的约定选择CBCharacteristicWriteWithResponse或者CBCharacteristicWriteWithoutResponse。

    - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;

    如果type是CBCharacteristicWriteWithResponse,则会触发回调

    -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

    举例一个写入数据

    + (NSData *)ZJjiaozhunDataXinlv:(NSInteger)xinlv Gaoya:(NSInteger)gaoya diya:(NSInteger)diya {
        NSData *dda = [[NSData alloc]init];
        unsigned char command[4] = {0x82, gaoya, diya, xinlv};
        dda = [[NSData alloc] initWithBytes:&command length:4];
        return dda;
    }

     

  13. 取消连接

    - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;

    连接取消后,会触发回调

    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error

    连接已经断开,注意清空单例类的一些属性值以及对应的代理

  14. 意外断开连接需要重连。实际情况中,可能会因为用户关闭蓝牙或者距离过远以及新号不稳定等情况,导致连接中段,这个时候 我们就需要重连了。根据官方文档,在断开连接的回调里直接调用连接函数即可。

    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
        [central connectPeripheral:peripheral options:nil];
    }

     

  15. 关于固件升级,是用iOS-DFULibrary第三方库进行升级,列举下主要函数

    //发送固件包到指定蓝牙设备。 这里的url我使用的是本地url,先通过AFNet将固件包下载到本地
    - (void)sendFileToWatchWithURL:(NSURL *)url peripheral:(CBPeripheral *)peripheral;
    //传输过程中的回调
    - (void)dfuProgressDidChangeFor:(NSInteger)part outOf:(NSInteger)totalParts to:(NSInteger)progress currentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond {
        //    NSLog(@"part: %ld \n outof:%ld \n progress:%ld \n currentSpeedBytesPerSecond:%.2f \n avgSpeedBytesPerSecond:%.2f",part, totalParts, progress,currentSpeedBytesPerSecond,avgSpeedBytesPerSecond);
        [SVProgressHUD showProgress:progress/100.0 status:@"固件包传输中"];
        
    }
    //传输失败回调
    - (void)dfuError:(enum DFUError)error didOccurWithMessage:(NSString *)message;
    //整个固件升级的per状态变化
    - (void)dfuStateDidChangeTo:(enum DFUState)state {
        switch (state) {
            case DFUStateConnecting:
                NSLog(@"连接:服务正在连接到DFU目标");
                break;
            case DFUStateStarting:
                NSLog(@"启动:DFU服务正在初始化DFU操作");
                break;
            case DFUStateEnablingDfuMode:
                NSLog(@"服务正在将设备切换到DFU模式");
                break;
            case DFUStateUploading:
                NSLog(@"上传:服务正在上传固件");
                break;
            case DFUStateValidating:
                NSLog(@"验证:DFU目标正在验证固件");
                break;
            case DFUStateDisconnecting:
                NSLog(@"disconnecting:iDevice正在断开连接或等待断开连接");
                break;
            case DFUStateCompleted:
                NSLog(@"完成:DFU操作完成并成功");
                self.isDFU = NO;
                [self.manager cancelPeripheralConnection:self.connectPeripheral];
                [self updataSucess];
                break;
            case DFUStateAborted:
                NSLog(@"aborted:DFU操作已中止");
                [self updataFail];
                break;
            default:
                break;
        }
    }

    也可以加我qq 1003072244 备注信息:蓝牙博客。    


    最后,说一点最重要的,手打不易,生活不易,给个面包钱可好?

    iOS蓝牙开发学习(2)--连接、读写、DFU升级篇_第1张图片

你可能感兴趣的:(蓝牙学习)