为了账号安全,请及时绑定邮箱和手机立即绑定

利用CoreBluetooth原生框架做蓝牙4.0开发

标签:
Android iOS

简介

最近做了一个智能门锁利用蓝牙与app交互的项目。整理一下相关蓝牙知识。下面要讲的是利用原生<CoreBluetooth.framework>框架封装demo,且支持蓝牙4.0。蓝牙官方文档

背景

蓝牙分为两种形式: 1)中心者模式 2)管理者模式,一般绝大部分我们都是使用第一种模式,中心者模式是我们手机作为主机,连接蓝牙外设,而管理者模式是我们手机自己作为外设,自己创建服务和特征,然后有其他的设备连接我们的手机。

接下来我们就是围绕第一种模式:中心者模式来讲解。

步骤

蓝牙连接可以大致分为以下几个步骤:
1.建立一个Central Manager实例进行蓝牙管理
2.搜索外围设备
3.连接外围设备
4.获得外围设备的服务
5.获得服务的特征
6.从外围设备读数据、给外围设备发送数据

简言之:就是我们的app创建一个蓝牙中心管理者对象,调用SDK的方法去搜索周围可发现的设备,搜索成功并发现有可用的设备后,进行连接,连接成功后再获取设备的服务与特征,最后进行数据的交互。
疑问:什么是服务?什么是特征?
下面用一张图进行讲解~

https://img1.sycdn.imooc.com//5d2fdb2e00011ae207100456.jpg

各层级关系


简言之:就是一个外设有几个服务,每一个服务又有几个特征,我们可以通过服务判断这个外设是不是我们要连接的外设,通过特征获取想要的数据。在特征有一个叫做UUID的属性,这个属性可以作为判断该特征是否是我们想要的特征的依据,这个跟硬件工程师要对接好UUID的值。


代码

在.h文件中定义一些全局属性:

//中心管理者@property (nonatomic, strong) CBCentralManager *centralManager;//外围设备@property (nonatomic, strong) CBPeripheral *peripheral;//读取设备信息的特征@property (nonatomic, strong) CBCharacteristic *readCharteristic;//收取消息的特征@property (nonatomic, strong) CBCharacteristic *notifyCharteristic;//发送消息的特征@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;

首先,我们要创建一个中心管理者单例

  //queue中传入nil默认就是在主线程
  self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];  //设置代理
  self.centralManager.delegate = self;

实例化一个中心管理者之后,会自动调用下面代理方法:

-(void)centralManagerDidUpdateState:(CBCentralManager *)central{    
    switch (central.state) {            
        case CBManagerStatePoweredOff:{   
            NSLog(@"蓝牙关闭");            break;            
        case CBManagerStatePoweredOn:{            NSLog(@"蓝牙已打开");
            [central scanForPeripheralsWithServices:nil options:nil];
        }            break;            
        case CBManagerStateResetting:            break;        case CBManagerStateUnauthorized:            NSLog(@"系统蓝未被授权");            break;        case CBManagerStateUnknown:            NSLog(@"系统蓝牙当前状态不明确");            break;        case CBManagerStateUnsupported:             NSLog(@"系统蓝牙设备不支持");            break;            
        default:            break;     
    }  
}

这个代理方法是判断系统的蓝牙状态,如果蓝牙已经打开,则调用下面的代码:

 //在给services和options都传入nil则是代表扫描所有条件的外围设备
 [central scanForPeripheralsWithServices:nil options:nil];

当扫描到外围设备后,会调用下面这个代理方法:(扫描到多少个外设就执行多少次)

- (void)centralManager:(CBCentralManager *)central // 中心管理didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral  // 外设advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData // 外设携带的数据 RSSI:(nonnull NSNumber *)RSSI// 外设发出的蓝牙信号强度{         //注意:这里可以获取到扫描到外设的Mac地址,
         NSString *mac = advertisementData[@"kCBAdvDataManufacturerData"]        //这里也可以过滤掉不需要的服务
        //接下来可以把我们想要连接的Mac地址与扫描到的外设的Mac地址进行匹配,如果一致就获取
            if ([self.peripheralMac isEqualToString:mac]) {                   //这里就是我们要连接的外设
                   //获取外部设备
                   self.peripheral = peripheral;                   //用中心管理者去调用连接获取到的外设的方法
                   [self.centralManager connectPeripheral:peripheral options:nil];    
                   //停止搜索
                   [self.centralManager stopScan];
            }
}

注意:代理方法是不会直接给我们返回外设的Mac地址的,我们可以通过advertisementData[@"kCBAdvDataManufacturerData"]去得到Mac地址,这个要跟硬件工程师沟通好怎么返回,以什么形式去返回。

连接成功后会自动执行下面这个代理方法:

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{    //获取到已经连接上的外设之后,设置代理
    self.peripheral.delegate = self;    //用外设去获取服务
    [peripheral discoverServices:nil];
}

如果连接失败,会调用下面这个方法:

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{    NSLog(@"连接失败%@",error.localizedDescription);
}

如果两个已经上的设置突然断开了连接,会自动调用下面的方法:

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error        //这种情况一般是,你蓝牙不稳定或者蓝牙断开等一些外部环境问题
        NSLog(@"已经断开蓝牙连接");
{

连接外设成功并调用获取外设服务的方法后,获取外设服务成功后会调用下面的方法:

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{   for (CBService *service in peripheral.services){        NSLog(@"外设服务号:%@",service.UUID.UUIDString);        if ([service.UUID.UUIDString isEqualToString:kServiceUUID]) {            //外围设备查找指定服务中的特征,characteristics为nil,表示寻找所有特征
            [peripheral discoverCharacteristics:nil forService:service];            break;
        }
        
    }
}

当获取外设服务的特征成功后,自动执行下面的代理方法:在这个方法中,就根据

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{//kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是与硬件对接好的特征的UUID,
 for (CBCharacteristic *characteristic in service.characteristics)
        
    {        //写入特征
        if ([characteristic.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID]  ) {            
            self.writeCharacteristic = characteristic;
        }        //通知特征
        if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]) {            
            self.notifyCharteristic = characteristic;            /* 设置是否向特征订阅数据实时通知,YES表示会实时多次会调用代理方法读取数据 */
             [peripheral setNotifyValue:YES forCharacteristic:characteristic];
        }        //读取特征
        if ([characteristic.UUID.UUIDString isEqualToString:kReadCharacteristicUUID]) {            
            self.readCharteristic = characteristic;            /* 读取特征的数据,调用此方法后会调用一次代理方法读取数据 */
            [peripheral readValueForCharacteristic:characteristic];
        }
        
    }
}

注意:kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是与硬件对接好的特征的UUID,用来判断是不是我们想要的特征,这里分为写入特征、读取特征、通知特征,分别是代表app想设备发送信息、app从设备获取信息、app获取设备的信息。

来到这里就已经获取到我们想要的特征了,接下来我们开始最后一步,写入信息和读取信息了。
下面的这个代理方法是,设备所有的数据都是从中这个代理方法返回的:

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{   
      //从我们想要的特征中获取返回的数据
      if ([self.notifyCharteristic isEqual:characteristic]) {           NSLog(@"设备爱返回的数据:%@",characteristic.value);
       }

}

当我们想向设备写入数据的时候,调用下面这个方法:

 if(self.characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse)
    {        //手机向外设发送数据,写数据
        [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}

注意:这里写入的data是一个二进制形式

写到这里就结束了,上面就是讲解了开头的6个步骤的实现方法,利用原生的api去封装一个蓝牙通讯的SDK不难,关键是这个蓝牙通讯的SDK结合自己的项目穿插各种逻辑的时候,就需要谨慎思考了。

谢谢~

             

作者:真的真心瓜子
链接:https://www.jianshu.com/p/de7106a5fc8d


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消