iOS之BLE蓝牙SDK开发个人总结(基础篇)

4,358 阅读5分钟

最近一段时间一直在做公司的BLE蓝牙SDK,sdk主要负责外设和手机的连接以及数据通信。过程中遇到了一些比较有价值的问题,现在总结记录下。

蓝牙开发使用系统框架#import <CoreBluetooth/CoreBluetooth.h> 使用[[CBCentralManager alloc] initWithDelegate:self queue:nil]初始化CBCentralManager对象。(设置CBCentralManagerDelegate为self,nil表示在主线程) 初始化成功后系统会自动检测蓝牙状态。实现centralManagerDidUpdateState:代理方法可实时获取蓝牙状态。当central.stateCBManagerStatePoweredOn时表示可用。

初始化完成可使用[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.servicesnil了,当然service.characteristics也是nil,所以需要在断开连接时把保存这个设备对应的服务和特征全部清除,然后在连接成功时重新过一遍发现服务和发现特征的流程就好了。

回顾个人的ble蓝牙开发过程总结出来基础篇遇到的问题大致就这么多了。本篇文章旨在个人总结和帮助正在做这方面的人理解蓝牙开发中这些东西的概念。

理解了事物,解决这个事物相关的问题就不难了。