iOS BLE基础开发

1.jpg

工作模式:

  • 当 central 和 peripheral 通信时,绝大部分操作都在 central 这边。此时,central 被描述为 CBCentralManager,这个类提供了扫描、寻找、连接 peripheral(被描述为 CBPeripheral)的方法。

    • 下图标示了 central 和 peripheral 在 Core Bluetooth 中的表示方式:
    image
    • 当你操作 peripheral 的时候,实际上是在和它的 service 和 characteristic 打交道,这两个分别由 CBService 和 CBCharacteristic 表示。

服务、特征和特征的属性

  • 一个 peripheral 包含一个或多个 service,或提供关于信号强度的信息。service 是数据和相关行为的集合。例如,一个心率监测仪的数据就可能是心率数据。

    • service 本身又是由 characteristic 或者其他 service 组成的。characteristic 又提供了更为详细的 service 信息。还是以心率监测仪为例,service 可能会包含两个 characteristic,一个描述当前心率带的位置,一个描述当前心率的数据。
    image
    • 每个 characteristic 属性分为这么几种:读,写,通知这么几种方式

步骤简述:

  • 1.导入CoreBluetooth.framework,遵循代理< CBCentralManagerDelegate , CBPeripheralDelegate >;
  • 2.建立中心角色;
  • 3.实现“蓝牙状态”的delegate方法,判断蓝牙状态,如成功则扫描指定UUID设备(如不指定UUID,则无法后台持续连接);
  • 4.实现“扫描成功”的delegate方法,连接外围设备;
  • 5.实现“连接成功”的delegate方法,断开连接,获取外围设备服务;
  • 6.实现“获取服务”的delegate方法,获取外围设备服务的特征;
  • 7.实现“获取服务特征”的delegate方法,订阅特征值的通知;
  • 8.实现“获取服务特征属性和特征属性值”的delegate方法,读取外围设备服务特征属性和特征属性值,进行数据交互;
  • 9.断开连接。

1.导入 CoreBluetooth.framework

image.png

2.设置蓝牙权限

image.png

3.导入头文件,遵循CBCentralManager和CBPeripheral代理

#import <CoreBluetooth/CoreBluetooth.h>

//让ViewController实现相应的delegate
@interface ViewController () <CBCentralManagerDelegate, CBPeripheralDelegate>

@property (nonatomic , strong) CBCentralManager *centrelManager;

@end

4.判断蓝牙状态

#pragma mark --  CBCentralManagerDelegate Delegate(监控蓝牙状态,扫描外设)
/**
 *  得到蓝牙的目前状态 判断当前设备是否只拆BLE
 *
 *  @param central central description
 
 *  当central管理器更新状态时调用。这个方法是必须实现的,为了确保当前的central设备是否支持BLE以及当前是否可以被利用,当检测到central蓝牙已经打开时,需要做一些操作,比如开始寻找Peripheral。
 *  当状态改变为CBCentralManagerStatePoweredOff时,会结束当前的寻找以及断开当前连接的peripheral。当检测到PoweredOff这个状态是所有的APP必须重新开始检索以及寻找。
 */
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    switch (central.state) {
        case CBCentralManagerStateUnknown:
            NSLog(@">>>CBCentralManagerStateUnknown");
            break;
        case CBCentralManagerStateResetting:
            NSLog(@">>>CBCentralManagerStateResetting");
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@">>>CBCentralManagerStateUnsupported");
            break;
        case CBCentralManagerStateUnauthorized:
            NSLog(@">>>CBCentralManagerStateUnauthorized");
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@">>>CBCentralManagerStatePoweredOff");
            break;
        case CBCentralManagerStatePoweredOn:
            NSLog(@">>>CBCentralManagerStatePoweredOn");
            //开始扫描周围的外设
            [centrelManager scanForPeripheralsWithServices:nil options:nil];
            /*
             1、 如果要扫描周围指定的外设,则第一个参数为
                CBUUID *uuid = [CBUUID UUIDWithString:@"0xf2a4"];
                其中f2a4是我连接到iPad mini2的LightBlue app模拟的BLE外围设备,你要换成你设备的UUID。
             2、第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入didDiscoverPeripheral
             */
            break;
        default:
            break;
     }
}

5.扫描外围设备

#pragma mark -- 扫描外围设备
/**
 *  如果发现周围有可联系的peripheral,该方法会不断地执行
 *
 *  @param central           中心设备
 *  @param peripheral        外围设备
 *  @param advertisementData 外围设备广播内容
 *  @param RSSI              信号强度
 *
 *  当central管理器发现一个peripheral时调用,广播的数据被以AdvertisementDataRetrievalKeys键值的形式接受。
 *  你必须建立一个本地的副本,当需要对他进行操作时。
 *  在一般的情况下,你的APP要保证能够在一定的范围内自动连接还是上peripheral,你能够使用RSSI的值来判断与发现的peripheral的距离。
 */
- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary *)advertisementData
                  RSSI:(NSNumber *)RSSI{
    
    /*
     advertisementData:(NSDictionary *)advertisementData:
     CBAdverisementDataLocalNameKey:             一个包含了peripheral设备名称的字符串。
     CBAdvertisementDataManudfactureDataKey:     一个包含了制造商信息的NSdata对象。
     CBAdvertisementDataServiceDataKey:          这个keys是一个CBUUID的对象代表了CBServicesUUID,这个NSData代表了服务指定的Data。
     CBAdvertisementDataServiceUUIDsKey:         这是一包含了服务的UUID的数组。
     CBAdvertisementDataOverflowServiceUUIDsKey: 这个数组包含了一个或者更多的CBUUID对象,这些对象代表了被广播在溢出区域的数据,关于这个keys的详细介绍请看我的上一篇译文《CBPeripheralManager 类介绍》。
     CBAdvertisementDataTxPowerLeverlKey:        这个NSnumber对象包含了peripheral传输功率的强弱。如果peripheral广播的数据中包含了传输的功率是这个key值将可以被使用,使用RSSI和传输功率可以计算之间的距离。
     CBAdvertisementDataIsconnectable:           这个BOOL值指明当前广播的事件是否可连接。
     CBAdvertisementDataSolicitedServiceUUIDsKEy:一个包含了CBServices UUIDs的数组。
     */
    
    //取得 local name,唯一标识设备
    NSString *localName = peripheral.name;
        
        if([localName hasPrefix:@“LL”]) {

            self.peripheral = [peripheral copy];
            
            //连接外设,有时候会连接不上,可以尝试用timer多连接几次
            [self.centrelManager connectPeripheral:peripheral options:nil];
          }
      }
}

一个主设备最多能连7个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的代理方法。

6.连接外围设备成功:

#pragma mark -- 连接成功
/**
 *  连接成功
 *
 *  @param central    中心设备
 *  @param peripheral 外围设备
 */
- (void)centralManager:(CBCentralManager *)central
  didConnectPeripheral:(CBPeripheral *)peripheral{
    
    //中央设备停止扫描外围设备
    [self.centrelManager stopScan];

    // 设置外围设备代理
    peripheral.delegate = self;
    
    //寻找服务 查找设备中所包含的服务了  执行完后会执行CBPeripheral delegate didDiscoverServices
    [peripheral discoverServices:nil];
    /*
      可根据所设定的参数搜寻指定的服务
      NSArray *serviceUUIDs = @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]];
     [peripheral discoverServices:serviceUUIDs];
    */
}

7.外围设备连接失败:

/*
 *  连接失败
 *
 *  @param central    中心设备
 *  @param peripheral 外围设备
 *  @param error      错误参数
 *
 *  这个方法在方法connectPeripheral:options建立的连接断开时调用,应为建立连接的动作是不能超时的,通常在失败连接时你需要再次试图连接peripheral。
 */
- (void)centralManager:(CBCentralManager *)central
didFailToConnectPeripheral:(CBPeripheral *)peripheral
                 error:(NSError *)error{
        if (error) {
            NSLog(@"Failed to connect to %@. (%@)", peripheral, [error localizedDescription]);
      }
  }  

8.断开外围设备的连接和连接超时:

/*
 *  连接超时 断开连接
 *
 *  @param central    中心设备
 *  @param peripheral 外围设备
 *  @param error      错误参数
 *    
 *  当已经建立的连接被断开时调用。
 *  这个方法在connectPeripheral:options方法建立的连接断开时调用。
 *  如果断开连接不是有cancelPeripheralConnection方法发起的,那么断开连接的详细信息就在error参数中。
 *  当这个方法被调用只有peripheral代理中的方法不在被调用。
 *  注意:当peripheral断开连接时,peripheral所有的service、characteristic、descriptors都无效。
 */
- (void)centralManager:(CBCentralManager *)central
didDisconnectPeripheral:(CBPeripheral *)peripheral
                 error:(NSError *)error{

    if (error) {
          NSLog(@"Disconnect to %@. (%@)", peripheral, [error localizedDescription]);
    }  

    // 重连机制,可设置重新连接次数,结合连接成功的代理累计
    if (self.peripheral) {
       
        [self.centrelManager connectPeripheral:self.peripheral options:nil];
    }
}

9.搜寻外围设备中服务

/**
 *  查找外围设备中服务
 *
 *  @param peripheral 外围设备
 *  @param error      错误参数
 */
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverServices:(NSError *)error{
    
    if (error) {
        
        NSLog(@"Service discover was unsuccessfull! error:%@ ",[error localizedDescription]);
        
    }else{
        
        for (int i = 0; i < peripheral.services.count; i++) {
            
            CBService *service = [peripheral.services objectAtIndex:i];
            
            NSLog(@"service UUID:%@", service.UUID);
            
            self.serviceUUID = service.UUID;

            //知道每个service下有多少个Characteristics特性,有服务的话扫描特征
            [peripheral discoverCharacteristics:nil forService:service];
            
            /*
              可根据所设定的参数搜寻指定服务的特征
              for (CBService *service in peripheral.services) {
                    //serviceId筛选
                    if ([service.UUID.UUIDString isEqualToString:TRANSFER_SERVICE_UUID1]) {

                          [peripheral discoverCharacteristics:nil forService:service];
                          
                         // 可根据指定的特征UUID搜寻
                         // NSArray *characteristicUUIDs = @[[CBUUID UUIDWithString:TRANSFER_CHRARCTERISTIC_UUID]];
                         // [peripheral discoverCharacteristics:charateristicUUIDs forService:service];
                        
                    }
              }
            */  
       }
    }
}

在做该类项目时,外设需求往往有一个UUID来确定需要连接的服务,对应这边service的UUID,而不是peripheral的UUID
(在使用lightblue模拟测试时,可以添加service并设置其UUID来模拟测试)

10.设置读取和订阅外围设备特征值的通知

/**
*  设置读取和订阅外围设备特征值的通知
 *  知道什么样的Services服务 什么样的Characteristics特性之后再进行处理, 并且注册通知 当BLE周边发信息过来时就会收到通知并得到资料
 *
 *  @param peripheral 中心设备
 *  @param service    服务
 *  @param error      错误参数
 */
- (void)peripheral:(CBPeripheral *)peripheral
didDiscoverCharacteristicsForService:(CBService *)service
             error:(NSError *)error{

        if (error) {
        
              NSLog(@"Characteristic discover was unsuccessfull! error:%@ ",[error localizedDescription]);
        
         }else{

            // 获取service的characteristics
            NSArray *characteristics = [service characteristics];
    
            for (CBCharacteristic *characteristic in characteristics) {
        
                  if ([characteristic.UUID.UUIDString isEqualToString:TRANSFER_CHRARCTERISTIC_UUID]) {
        
                  self.characteristic = characteristic;

                  [peripheral readValueForCharacteristic:self.characteristic];
            
                  //注册监听
                  [peripheral setNotifyValue:YES forCharacteristic:self.characteristic];
                  //我们可以使用readValueForCharacteristic:来读取数据。
                  //如果数据是不断更新的,则可以使用setNotifyValue:forCharacteristic:来实现只要有新数据,就获取。
                  //外围设备支持监听模式,特征值发生变化时广播。
        }
    }
}

11.数据处理:读取特征值和所监听的特征的特征值发生变化时所调用代理

/**
 * 数据处理
 * 连上完成后 取得数据非常重要   当外设发送新值时候,此方法自动被动用
 *
 *  @param peripheral     外围设备
 *  @param characteristic 特征
 *  @param error          错误参数
 */
- (void)peripheral:(CBPeripheral *)peripheral
didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error{
    
        if (error) {
        
              NSLog(@"updateValueForCharacteristic failed! error:%@ ",[error localizedDescription]);
              return;

         }else{

            //读取数据,当周边发送新的值,此方法的第二个参数包含特征。可以用value属性读取他的值,这是一个包含特征的值的NSData。
             unsigned char characteristicValueData[1024] = {0};
             [characteristic.value getBytes:characteristicValueData];
            
             NSLog(@"characteristicValue:%@ - Length:%lu",characteristicValueData,(unsigned long)characteristicValueData.length);

            // 与外围设备进行交互

            /*
             *可以通过特征值来校验是否为正确的数据,做相对应的操作
             *   if (characteristicValueData[0] == 0x02) {
             *      //此时为第一位为2才是正确数据,做相对应的操作
             *
             *   }else if (characteristicValueData[0] == 0x04){
             *
             *     //此时为第一位为4不是正确数据或者是其他操作的数据,做相对应的操作
             *    (如设置监听后特征值发生变化为正确的数据)
             *   }
             */
            
        }
}

中心设备单次发送的数据最大不能超过20个字节,蓝牙模块内部接收缓冲区只有20个字节,如果超过20个字节,需要分包发送。

/**
 * 数据处理
 * 
 * 传输数据大于20个字节时需要分包处理
 *
 *  @param data  传输的数据
 * 
 *  TranslateDataLenght为最大传输数据值20
 */
- (void)transmissionWithData:(NSData *)data{
    
    if(data.length > TranslateDataLenght){
        
        int i = 0;

        while ((i + 1) * TranslateDataLenght <= data.length) {
            
            NSData *dataSend = [data subdataWithRange:NSMakeRange(i * TranslateDataLenght, TranslateDataLenght)];
            
            [self write: self.peripheral  data:dataSend];
            
            i++;

           //根据接收模块的处理能力做相应延时
           usleep(20 * 1000);
        }
        
        i = data.length % TranslateDataLenght;
        
        if(i >0){
            
            NSData *dataSend = [data subdataWithRange:NSMakeRange(data.length - i, i)];
            
            [self write:self.peripheral data:dataSend];
        }

        //根据接收模块的处理能力做相应延时
        usleep(20 * 1000);
        
    }else{
        
        [self write:self.peripheral data:data];
    }
}

/*
 * 向外围设备传输数据
 * @param peripheral  外围设备
 * @param data  传输的数据
 */
- (void) write:(CBPeripheral *)peripheral
          data:(NSData *)data{
      /*
       *  如果设置为CBCharacteristicWriteWithResponse,
       *  则可以写成功一次,只可读,写入数据有反馈
       * 
       *  如果为CBCharacteristicPropertyWriteWithoutResponse,
       *  则一次也不能写成功,可以读,也可以通知。
       */
      if(characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse){
        
        [peripheral writeValue:data
             forCharacteristic:characteristic
                          type:CBCharacteristicWriteWithoutResponse];
    }else{
        
        [peripheral writeValue:data
             forCharacteristic:characteristic
                          type:CBCharacteristicWriteWithResponse];
    }
}

12.数据写入成功后反馈

/*
 * 数据写入成功后反馈
 *
 * 外围设备写入数据后的类型为CBCharacteristicWriteWithResponse,会调用该方法
 * @param peripheral  外围设备
 * @param data  传输的数据
 */
- (void)peripheral:(CBPeripheral *)peripheral
didWriteValueForCharacteristic:(CBCharacteristic *)characteristic
             error:(NSError *)error{
    
    if (error) {
        
        NSLog(@"Write value for characteristic failed : %@",error.userInfo);
    }else{
        
        NSLog(@"Write value for characteristic successfull!! ");
        // 如果写入数据成功,外围设备被中心设备所监听的设备的服务特征发生改变,会调用- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error,做相对应的处理
    }
}

各位大神,有不足之处,请多多指教,勿喷。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342