简介
最近做了一个智能门锁利用蓝牙与app交互的项目。整理一下相关蓝牙知识。下面要讲的是利用原生<CoreBluetooth.framework>框架封装demo,且支持蓝牙4.0。蓝牙官方文档
背景
蓝牙分为两种形式: 1)中心者模式 2)管理者模式,一般绝大部分我们都是使用第一种模式,中心者模式是我们手机作为主机,连接蓝牙外设,而管理者模式是我们手机自己作为外设,自己创建服务和特征,然后有其他的设备连接我们的手机。
接下来我们就是围绕第一种模式:中心者模式来讲解。
步骤
蓝牙连接可以大致分为以下几个步骤:
1.建立一个Central Manager实例进行蓝牙管理
2.搜索外围设备
3.连接外围设备
4.获得外围设备的服务
5.获得服务的特征
6.从外围设备读数据、给外围设备发送数据
简言之:就是我们的app创建一个蓝牙中心管理者对象,调用SDK的方法去搜索周围可发现的设备,搜索成功并发现有可用的设备后,进行连接,连接成功后再获取设备的服务与特征,最后进行数据的交互。
疑问:什么是服务?什么是特征?
下面用一张图进行讲解~
各层级关系
简言之:就是一个外设有几个服务,每一个服务又有几个特征,我们可以通过服务判断这个外设是不是我们要连接的外设,通过特征获取想要的数据。在特征有一个叫做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
共同学习,写下你的评论
评论加载中...
作者其他优质文章