iOS 蓝牙开发

自己之前做的蓝牙项目,刚开始的时候真是一脸蒙圈,摸索了很久踩了很多坑。不过现在想来其实也没什么难的,简单总结一下,但愿能够给初次开发的小伙伴一些帮助,当然如果有不足之处,也欢迎大家指正。

  • CoreBluetooth框架简单介绍

    • CBCentralManager 中心管理者——核心角色,用于扫描外设等
    • CBCentralManagerState 蓝牙状态——CBCentralManagerStatePoweredOn表示可用
    • CBPeripheral 外设
    • CBService 服务信息——可以拿到服务的UUID
    • CBCharacteristic 特征值——包括读、写特征,用于读取或写入数据
    • CBDescriptor 描述值——描述特征值的信息或属性
  • 外设的基本信息

    • advertisementData 广播包——是一个字典,存储设备相关信息
    • kCBAdvDataIsConnectable 连接外设的状态——1表示已连接
    • kCBAdvDataManufacturerData 厂家提供的数据——会含有外设的Mac地址,开发中重点看这里的数据
    • kCBAdvDataServiceUUIDs 服务的所有UUID——数组
    • RSSI——信号强度
  • 连接外设的流程及Mac地址

    • 流程描述
      1、建立中心角色:CBCentralManager
      2、扫描外设:discoverPeripheral
      3、发现并连接外设:connectPeripheral
      4、扫描外设中的服务:discoverServices
      5、根据服务去获取特征及相关描述:discoverCharacteristicsForService
      6、订阅Characteristic的通知:setNotifyValue
      7、根据特征值进行相关数据的读写操作:writeValue | characteristic.value
      8、断开连接:disConnect
    • 关于Mac地址
      蓝牙外设的Mac地址是一个物理地址,每个设备都是唯一的。我们在蓝牙开发的时候需要用到这个地址来连接外设,但是CoreBluetooth框架对这个进行了封装,没有暴露出来,只是给我们提供了一个UUID,可是这个UUID在实际情况中并不实用。询问Android同事了解到他们是可以获取到Mac地址的,并且能够通过Mac地址直接连接设备,所以我们iOS端只能自己想办法获取外设的Mac地址。通常来说,硬件工程师都会将设备的Mac地址(16进制)写入到广播包中,我们可以轻松拿到(如果没有的话,那你可能需要联系设备厂商了)。在连接外设的时候我的做法是根据后台提供的Mac地址与从外设广播包中获取Mac地址进行匹配。
      请看代码:
    //获取广播包中的数据
    NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
    //格式转换
    NSString *str = [self transformStringWithData:data];

    //mac地址匹配,连接设备,self.peripheralMacAddress即从后台获取的Mac地址
    if ([[self macAddressWith:str] isEqualToString:self.peripheralMacAddress]) {
       //匹配成功后进行外设连接操作
    }
  //对广播包中的数据进行格式转换
  - (NSString *)transformStringWithData:(NSData *)data{
     NSString *result;
    const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
    if (!dataBuffer) {
        return nil;
    }
    NSUInteger dataLength = [data length];
    NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
    for (int i = 0; i < dataLength; i++) {
        //02x 表示两个位置 显示的16进制
        [hexString appendString:[NSString stringWithFormat:@"%02lx",(unsigned long)dataBuffer[i]]];
    }
    result = [NSString stringWithString:hexString];
    
    return result;
}
//获取外设Mac地址 
- (NSString *)macAddressWith:(NSString *)aString{
    NSMutableString *macString = [[NSMutableString alloc] init];
    if (aString.length >= 16) {
        [macString appendString:[[aString substringWithRange:NSMakeRange(4, 2)] uppercaseString]];
        [macString appendString:@":"];
        [macString appendString:[[aString substringWithRange:NSMakeRange(6, 2)] uppercaseString]];
        [macString appendString:@":"];
        [macString appendString:[[aString substringWithRange:NSMakeRange(8, 2)] uppercaseString]];
        [macString appendString:@":"];
        [macString appendString:[[aString substringWithRange:NSMakeRange(10, 2)] uppercaseString]];
        [macString appendString:@":"];
        [macString appendString:[[aString substringWithRange:NSMakeRange(12, 2)] uppercaseString]];
        [macString appendString:@":"];
        [macString appendString:[[aString substringWithRange:NSMakeRange(14, 2)] uppercaseString]];
    }
    EBLog(@"macString:%@",macString);
    return macString;
}
  • 部分相关代码(代理方法)

    • CBCentralManagerDelegate
// 当状态更新时调用(如果不实现会崩溃)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    switch (central.state) {
        case CBCentralManagerStateUnknown:
            EBLog(@"蓝牙状态-> 未知");
            break;
        case CBCentralManagerStateResetting:
            EBLog(@"蓝牙状态-> 重置");
            break;
        case CBCentralManagerStateUnsupported:
            EBLog(@"蓝牙状态-> 不支持");
            break;
        case CBCentralManagerStateUnauthorized:
            EBLog(@"蓝牙状态-> 未授权");
            break;
        case CBCentralManagerStatePoweredOff:
            EBLog(@"蓝牙状态-> 关闭");
            break;
        case CBCentralManagerStatePoweredOn:
            EBLog(@"蓝牙状态-> 可用");
            break;
        default:
            break;
    }
}

/**
 发现设备
 @param central 中心管理者
 @param peripheral 扫描到的设备
 @param advertisementData 广告信息
 @param RSSI 信号强度
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
    EBLog(@"发现设备->\n name=%@\n advertisementData=%@\n UUIDString=%@\n services=%@\n 信号->%@",peripheral.name,advertisementData,peripheral.identifier.UUIDString,peripheral.services,RSSI);
    
    NSData *data = advertisementData[kAdDataKey];
    NSString *str = [HandleDataManager transformStringWithData:data];

    if ([[self macAddressWith:str] isEqualToString:self.peripheralMacAddress]) {
        //停止扫描
        [self.centralManager stopScan];
        
        //连接外设
        self.peripheral = peripheral;
        [self.centralManager connectPeripheral:self.peripheral options:nil];

        //将外设对象添加到数组中
        if (![self.peripheralArray containsObject:peripheral]){
            [self.peripheralArray addObject:self.peripheral];
        }
    }
}

/**
 连接成功
 @param central 中心管理者
 @param peripheral 连接成功的设备
 */
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    EBLog(@"连接设备成功-> %@",peripheral.name);
    //停止扫描
    [self.centralManager stopScan];

    //设置peripheral代理
    self.peripheral.delegate = self;
    //扫描服务操作
    ......
}

/**
 连接失败
 @param central 中心管理者
 @param peripheral 连接失败的设备
 @param error 错误信息
 */
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    if (error) {
        EBLog(@"连接失败-> %@",error.description);
    }
}

/**
 连接断开
 @param central 中心管理者
 @param peripheral 连接断开的设备
 @param error 错误信息
 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    
    if (error) {
        EBLog(@"连接断开-> %@",error.description);
   }
}
  • CBPeripheralDelegate
/**
 扫描到服务
 @param peripheral 服务对应的设备
 @param error 扫描错误信息
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    for (CBService *service in peripheral.services) {
        EBLog(@"服务UUID: %@",service.UUID);
        // 获取对应的服务
        if ([service.UUID.UUIDString isEqualToString:@"厂商提供的服务ID"]){
            // 根据服务去扫描特征
            [self.peripheral discoverCharacteristics:nil forService:service];
        }
    }
}

/**
 扫描到对应的特征
 @param peripheral 设备
 @param service 特征对应的服务
 @param error 错误信息
 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    EBLog(@"发现特征值=%@", service.characteristics);
    if (!error) {
            // 遍历所有的特征
        [service.characteristics enumerateObjectsUsingBlock:^(CBCharacteristic * _Nonnull characteristic, NSUInteger idx, BOOL * _Nonnull stop) {
            EBLog(@"特征的读写等属性=%lu", (unsigned long)characteristic.properties);

            if (characteristic.properties == CBCharacteristicPropertyNotify) {
                // 订阅, 实时接收
                [self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }
            else if (characteristic.properties == CBCharacteristicPropertyWrite){
                self.writeCharacteristic = characteristic;
            }
        }];
    }
}

/**
 根据特征读到数据
 @param peripheral 读取到数据对应的设备
 @param characteristic 特征
 @param error 错误信息
 */
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
    EBLog(@"didUpdateValue 特征值=%@",characteristic);
    if (error) {
         EBLog(@"特征值=%@ == error %@",characteristic.UUID, error);
     }
     else{
         EBLog(@"收到特征值=%@ updated 发来的数据: %@", characteristic.UUID, characteristic.value);
         if (characteristic.value) {
         //读取到相关数据的操作
         }
     }
}

//更新描述值的时候会调用
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
    EBLog(@"描述(%@)",descriptor.description);
}

//发现外设的特征的描述数组
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
    // 在此处读取描述即可
    for (CBDescriptor *descriptor in characteristic.descriptors) {
        EBLog(@"发现外设的特征descriptor(%@)",descriptor);
        [self.peripheral readValueForDescriptor:descriptor];
    }
}

//写入指令是否成功回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error{
    if (error) {
        EBLog(@"特征值=%@ 写入失败 error %@",characteristic.UUID,error.description);
    }
    else{
        EBLog(@"特征值=%@ 写入成功",characteristic.UUID);
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    EBLog(@"通知发生改变->特征值=%@,isNotifying=%@",characteristic.UUID,characteristic.isNotifying?@"YES":@"NO");
    //订阅成功
    if (characteristic.isNotifying == YES) {
     
    }
}

  • BabyBluetooth蓝牙库

BabyBluetooth这个库采用的链式编程思想,其中的block方法,可以重新按照功能和顺序组织代码,并提供许多方法减少蓝牙开发过程中的代码量。
详细介绍参考链接
BabyBluetooth蓝牙库介绍
BabyBluetooth源码GitHub地址

  • 自己封装的蓝牙库

ZYBluetoothManager是一个轻量级的蓝牙工具类,简单实用,可以满足基本的需求。细节及其他功能还在不断完善与优化中,也欢迎各位大神给出宝贵意见。
源码GitHub地址:ZYBluetoothManager

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