iOS 蓝牙开发(框架集成和数据交互)

最近几个月都在做蓝牙的项目,趁现在有空,就把在蓝牙开发过程中的心得和踩过的坑给记录下来,分享给大家,避免大家在蓝牙开发过程中能避免踩相同的坑。

网上关于蓝牙开发的原理知识都有了,这里就不在重复累赘,只简单说一下实现过程逻辑:

  1. 首先需要在info.plist添加请求蓝牙功能的权限“NSBluetoothPeripheralUsageDescription”
  2. 创建全局蓝牙管理类
  3. 用蓝牙管理类进行扫描
  4. 从扫描的结果中找到你的蓝牙外设,进行手动或自动连接
  5. 连接成功收扫描外设的服务和特征,每个服务都包含一个特征的数组,这些不同的特征就是用来跟蓝牙外设进行数据通信的对象,特征一般分两种,一种是可读characteristic_notify,一种是可读写characteristic_readWrite。这两个特征蓝牙硬件工程师会告诉你,你只需要找到这两个特征用私有变量保存起来即可,然后把可读特征characteristic_notify设置为notify,你就能收到蓝牙外设发送过来的实时数据。characteristic_readWrite特征就是用来发送数据了。

接下来看一下代码实现:
一. 定义全局蓝牙管理类

  1. 这个类负责蓝牙的扫描,停止扫描,连接,断开连接
    它有一个代理CBCentralManagerDelegate,必须实现它的centralManagerDidUpdateState方法,用来返回蓝牙功能打开和关闭的状态
self.centralManage = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];

这句代码实现后会自动判断你的设备有没有打开蓝牙功能,如果没有回自动弹框让你去设置里打开开关。

  1. 然后我们在centralManagerDidUpdateState代理中实现蓝牙扫描:
#pragma mark - CBCentralManagerDelegate

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    if (central.state == CBCentralManagerStatePoweredOn) {
        [self.centralManage scanForPeripheralsWithServices:nil options:nil];
    }else{
        [MBProgressHUD wj_showPlainText:@"蓝牙已关闭" view:nil];
    }
    
}
  1. 扫描结果会在didDiscoverPeripheral返回
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
    NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
    DLog(@"广播数据---------------%@",data);
   if ([peripheral.name isEqualToString:xxx]) {
        // 这里找到你匹配的外设名字
       [self.centralManage connectPeripheral:peripheral options:nil];
    }
    
    NSString *dataStr = [FGDataChangeTool hexadecimalString:data];
    if ([dataStr isEqualToString:xxx]) {
        // 这里找到外设给你发的广播数据匹配
         [self.centralManage connectPeripheral:peripheral options:nil];
    }
}

注意:上面实例代码中的两个if判断条件为非必须,这个需要根据你的业务需求,比如可以根据你的外设名称进行匹配(不靠谱,因为名字可变)、或者让你的蓝牙硬件工程师发一个标识码用来区分。
随便说一句 *由于iOS蓝牙开发不能直接获取蓝牙的Mac地址,如果需要用到Mac地址作为外设的唯一标识码,只能让硬件工程师把Mac地址用广播发送过来,字段还是用“ kCBAdvDataManufacturerData” *
4.1. 连接后,会走didConnectPeripheral代理方法,在这个方法里面需要把peripheral蓝牙外设对象设置一下代理CBPeripheralDelegate,并且实现对服务的扫描:

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{

    // 4.对外设进行扫描
    peripheral.delegate = self;
    [peripheral discoverServices:nil];

}

4.2. 断开连接,用户主动断开或者代码执行断开连接都会执行这个方法:

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    [MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
    [MBProgressHUD wj_showError:@"蓝牙已断开连接" toView:nil];
}

4.3 连接失败:

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    [MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
    [MBProgressHUD wj_showError:@"连接失败,请重试" toView:nil];
}

二. 前面4.1我们进行对服务扫描后,如果成功没什么问题就会走CBPeripheralDelegate代理方法didDiscoverServices发现服务,返回的服务是多个,每个服务又包含多个特征,这里只需要把所有的服务遍历然后扫描所有的特征即可。

#pragma mark - CBPeripheralDelegate
// 外设发现服务回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error{
        // 保存私有变量peripheral
        self.peripheral = peripheral;
        for (CBService *service in peripheral.services) {
        //4. 1对外设扫描到的服务进行特征扫描
        [self.peripheral discoverCharacteristics:nil forService:service];
        
    }
    
    [self beginRefresh];
   //停止扫描
    [self.centralManager stopScan];
}

扫描特征后会在代理方法返回所有的特征:

// 外设发现特征回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    
    
    for (CBCharacteristic *characteristic in service.characteristics) {
        DLog(@"特征---------%@",characteristic);
        if ([characteristic.UUID.UUIDString isEqualToString:@"你的读写特征值"]) {
            self.characteristic_write = characteristic;
            
        }
        else if ([characteristic.UUID.UUIDString isEqualToString:@"你的只读特征值"]){
            self.characteristic_notify = characteristic;
            [self.peripheral setNotifyValue:YES forCharacteristic:self.characteristic_notify];
        }

        //电池特征值,注意打印的时候是显示Battery Level而不是2A19,但是代码中需要用2A19进行匹配。
        else if ([characteristic.UUID.UUIDString isEqualToString:@"2A19"]){
            self.characteristic_Battery = characteristic;
            // 设置readValueForCharacteristic才能获得电量值
            [peripheral readValueForCharacteristic:characteristic];
           // 设置setNotifyValue才能收到电量改变的通知
            [self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
       }
    }

}

三. 蓝牙发送的所有数据都会在这个代理方法didUpdateValueForCharacteristic返回一个NSData对象:

// 当特征的值发生变化时回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

    for (CBService *sever in peripheral.services) {
        for (CBCharacteristic *character in sever.characteristics) {
            if ([character.UUID.UUIDString isEqualToString:@"2A19"]) {
                DLog(@"电量信息----------%@",character);
            }
        }
    }

        // 其他数据处理
        [self dealWithData];
    
}

四. 上面的代码基本已经完成大部分的需求了,包括蓝牙的连接,断开,扫描设备,保存特征值,如果需要进行下一步的蓝牙数据通讯,只需要用writeValue方法进行发送即可。发送后的同样在上面的didUpdateValueForCharacteristic代理方法接收数据。

Byte *bytes = (Byte *)malloc(6);
    bytes[0] = 0xaa;
    bytes[1] = data1;
    bytes[2] = data2;
    bytes[3] = (Byte)(((bytes[1] & 0xff) + (bytes[2] & 0xff)) / 3);
    bytes[4] = (Byte)(((bytes[1] & 0xff) + (bytes[2] & 0xff)) % 3);
    bytes[5] = 0xdc;
NSData *sendData = [NSData dataWithBytes:sendByte length:6];
[self.peripheral writeValue:sendData forCharacteristic:self.characteristic type:0];

五. 在蓝牙手法数据过程中需要对数据进行解析,数据都是16进制的NSData对象,这些数据可不是像HTTP通讯那样简单传个Json对象给你解析就完事了,需要按照硬件工程师给你的协议进行一一解析,解码成看得懂的数据。这里发一份我在工作中常用到的一些解析工具类,包括一些进制间的转化。喜欢的点个star,这是给我最好的鼓励😁
iOSBlueTool

最后:我在第二篇介绍蓝牙固件升级的相关操作

iOS 蓝牙开发(固件升级&空中升级)

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

推荐阅读更多精彩内容