简介
最近做了一个智能门锁利用蓝牙与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结合自己的项目穿插各种逻辑的时候,就需要谨慎思考了。
谢谢~
网友评论