iOS 蓝牙开发总结,专治莫名其妙的坑!

2,442 阅读6分钟

图片来源网络,侵权立删.jpg

   初次在简书写文章,如文章有哪里不妥,望提出,海涵!

最近刚刚忙完一个有关蓝牙4.0的开发,似乎此前蓝牙在手机里还不怎么起眼!不过随着智能家居物联网的潮流兴起,低功耗蓝牙,无线 WiFi 等技术必大有作为(纯属个人见解)。以下简单推出一个需求(需求驱动开发,希望能吸引到你):

1、手机作为中心设备,连接外设,向外设发送指令;
2、智能硬件作为从设备,广播信息,接收中心的指令,做出相应动作。

一、开发模式

蓝牙开发有两种开发模式,一种是中心模式,一种是从设备模式,此时的需求正是采用中心开发模式,此处引入一个开发所需的重要框架#import <CoreBluetooth/CoreBluetooth.h>

二、中心模式的开发步骤

  • 建立中心管理者
  • 扫描外设(scan -> discover)
  • 连接外设(connect)
  • 扫描外设中的服务与特征(discover,我们需要找到“可写入的”属性的特征)
    • 获得外设的服务(services)
    • 获得外设的Characteristics,获得Characteristics的值(再往下就是获得Characteristics的Descriptor和Descriptor的值)
  • 与外设进行数据交互、订阅或通知
  • 断开与外设的连接(disconnect)

三、demo 实现

此时我们需要关注两个委托<CBCentralManagerDelegate,CBPeripheralDelegate> CBCentralManagerDelegate

#pragma mark- CBCentralManagerDelegate
// 蓝牙更新状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    if (central.state != CBCentralManagerStatePoweredOn) {
        _isConnected = NO;
    } else if (central.state == CBCentralManagerStatePoweredOn) {
        [self scanForPeripherals]; 
      /*
        一号坑在此处解决:(手机蓝牙已打开)
           打开 APP 第一次出现了搜索不到设备的 bug,几经推测发现手机的蓝牙状态为 
           CBCentralManagerStateUnknown,手机蓝牙并未达到我们所期望的开启状态,
           因此会搜索不到设备,我们需要在此委托方法中监听蓝牙状态的状态改变,
           一旦为 ON 马上扫描外设(配合自己的需求)
       */ 
    }
}

// 发现外设
- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary<NSString *,id> *)advertisementData
                  RSSI:(NSNumber *)RSSI
{
/*
二号坑在此处解决:(外设蓝牙名字修改后搜索不到的问题)
    一般在日常使用中蓝牙的名称并不需要经常改动,往往需求是造化弄人的。外设较多时我们一般会根据外设的名称进行过滤,
在此委托方法中,我么可以直接通过 peripheral.name 的属性来过滤,此时好像并没有什么问题,直到蓝牙的名称被修改后
部分蓝牙怎么就搜索不到了,此时我发现蓝牙修改前的名字却能搜索到,与硬件开发的同事几经交涉后,蓝牙的名字确确实实已经被
修改。
解决办法:
   在蓝牙的广播数据中 根据@"kCBAdvDataLocalName"这个 key 便可获得准确的蓝牙名称(在 github上一个优秀的蓝牙开源项目中发现的)
至于为什么从 peripheral.name 这个属性拿到的名称不准确仍是不解,知道的书友麻烦告知一下!
*/
    NSString *locolName = [[advertisementData objectForKey:@"kCBAdvDataLocalName"] lowercaseString];
    NSString *peripheralName = [peripheral.name lowercaseString];
    NSString *myInBleName = [myDelegate.selectedLock.inBleName lowercaseString];
    NSString *myOutBleName = [myDelegate.selectedLock.outBleName lowercaseString];
    
    YKLog(@"----------广播:%@--设备:%@--进:%@--出:%@",locolName, peripheralName,myInBleName,myOutBleName);
    
    if ([locolName isEqualToString:myInBleName] ||
        [peripheralName isEqualToString:myInBleName] ||
        [locolName isEqualToString:myOutBleName] ||
        [peripheralName isEqualToString:myOutBleName]) {
        [self.peripherals addObject:peripheral];
        // 注意:必须持有需要进行数据交互的外设
        [self connect:peripheral];
        [SVProgressHUD showInfoWithStatus:@"正在开门"];
    }
}

//Peripherals断开连接
- (void) centralManager: (CBCentralManager *)central
didDisconnectPeripheral: (CBPeripheral *)peripheral
                  error: (NSError *)error{
    if (error == NULL) {
        [SVProgressHUD showSuccessWithStatus:@"开门成功.."];
        _myDisType = kDisconnectRight;
    }
    YKLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
    _isConnected = NO;
    self.connectedPeripheral = nil;
    self.peripherals = nil;
}

//连接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    _isConnected = YES;
    self.connectedPeripheral = peripheral;
    [peripheral setDelegate:self];
    CBUUID *uuid = [CBUUID UUIDWithString:@"0003CDD0-0000-1000-8000-00805F9B0131"];
    [peripheral discoverServices:@[uuid]];
}

CBPeripheralDelegate

#pragma mark- CBPeripheralDelegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    //YKLog(@"--------发现服务,扫描服务-----------");
    if (error) {
       YKLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
        return;
    }
    
    [peripheral.services enumerateObjectsUsingBlock:^(CBService * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [peripheral discoverCharacteristics:nil forService:obj];
       YKLog(@"%@",obj);
    }];
}

/**
 *  获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    YKLog(@"---------发现特征----------");
    if (error)
    {
        YKLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
        return;
    }
    
    [service.characteristics enumerateObjectsUsingBlock:^(CBCharacteristic * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        [self notifyCharacteristic:self.connectedPeripheral characteristic:obj];
        if (obj.properties == CBCharacteristicPropertyWriteWithoutResponse || 
            obj.properties == CBCharacteristicWriteWithResponse) {
            self.WCharacteristi = obj;
            [peripheral setNotifyValue:NO forCharacteristic:obj];
            [self writeCharacteristic:peripheral characteristic:self.WCharacteristi value:self.valueW];
            YKLog(@"%@",self.valueW);
        }
    }];
}

//获取的charateristic的值
-(void)peripheral:              (CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
            error:(NSError *)error
{
    //打印出characteristic的UUID和值
    //!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
    YKLog(@"特征的值更新---characteristic uuid:%@  value:%lu",characteristic.UUID,(unsigned long)characteristic.properties);
    [peripheral setNotifyValue:NO forCharacteristic:characteristic];
    [self disconnect];
}

数据交互

#pragma mark- 读写数据
//写数据
-(void)writeCharacteristic:(CBPeripheral *)peripheral
            characteristic:(CBCharacteristic *)characteristic
                     value:(NSData *)value
{
    YKLog(@"%@--%lu",value, (unsigned long)characteristic.properties);
    //只有 characteristic.properties 有write的权限才可以写
    if((characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) != 0){
        [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
    }else{
        [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }
}

//设置通知
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
             characteristic:(CBCharacteristic *)characteristic{
    //设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
    
}

四、结尾

三号坑在此处提出:调用断开蓝牙的接口,手机蓝牙并没有马上与外设断开连接,而是等待5秒左右的时间后才真正断开。

断开连接的接口:
[self.manager cancelPeripheralConnection:self.connectedPeripheral];

查阅了苹果的官方文档,指出调用此接口后有可能会断开与外设的连接,是“有可能”也就是说也有可能是不会断开。由于是物理层的特性,我们无法在手机端做到马上断开连接。

  • 解决方法:与硬件开发的同事沟通,从设备收到数据后主动断开连接即可。
  • Swift demo 文章就写到此处,感谢您的阅读,欢迎沟通学习!