iOS蓝牙开发分为两部分,一部分是手机作为中心端;一部分是手机作为外设端。
我的项目是医疗类的,手机作为中心端,收到病人穿戴的外设发过来的脑电、心电等数据传给服务器,一般的APP都是开发中心端。
原理性的东西我就不写了,网上有很多,主要记录我在实际开发项目时用到的方法与一些注意点。
形形色色的蓝牙硬件第一步 配置文件
第二步 选择控制器
第三步 实现协议
第一步 配置文件
设置下工程plist文件,让用户允许APP使用蓝牙:
如果你想让手机在后台时也可以与外设进行交互的话,还要添加一个key:
或者这样配置:
BackgroundMode具体解释看这里: iOS后台模式BackgroundMode、iOS后台模式教程 (一)
第二步 选择控制器
首先要有一个前提,就是你的设备要在后台接收数据的话(也就是程序在后台,及手机黑屏),必须要有一个控制器引用着蓝牙中心类,最基础的是放在根控制器中。这一步很关键。
当然,我们一般根控制器是一个UITabBarController
,但是上面的几个RootViewController
都是可以的。
尤其需要注意的是,当你想要切换控制器还想操纵蓝牙外设的时候,也要把蓝牙中心和外设当做属性传给下一个控制器,然后在下一个控制器中再重新连接,设置代理,实现协议等等...
我们的控制器选好了后(我的是HomeVC),就在该控制器中引入框架,遵循协议,设置属性:
#import "JDHomeVC.h"
#import <CoreBluetooth/CoreBluetooth.h>
@interface JDHomeVC()<CBCentralManagerDelegate, CBPeripheralDelegate>
//中心
@property (nonatomic,strong) CBCentralManager *centralManager;
//外设
@property (nonatomic,strong) CBPeripheral *peripheral;
//特征
@property (nonatomic, strong) CBCharacteristic *characteristic;
@end
然后在你想要启动蓝牙的方法里加入以下代码(比如“连接外设”按钮),启动蓝牙相关的一系列代码
//初始化中心端,开始蓝牙模块
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
self.centralManager.delegate = self;
第三步 实现协议:
1、<CBCentralManagerDelegate>协议:
当centralManager创建之后,会立刻监测手机蓝牙状态,触发第一个代理方法,如果没有打开蓝牙,会有系统的弹窗“打开蓝牙来允许“XXXX”连接到配件”,并可以点击“设置”跳转到蓝牙。
第一个代理方法:
// 状态更新后触发
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBCentralManagerStatePoweredOff://蓝牙关闭
break;
case CBCentralManagerStatePoweredOn:
break;
case CBCentralManagerStateResetting:
break;
case CBCentralManagerStateUnauthorized:
break;
case CBCentralManagerStateUnknown:
break;
case CBCentralManagerStateUnsupported://当前设备不支持蓝牙
break;
default:
break;
}
// services参数为nil时, 表示扫描所有的蓝牙外设. 指定时只扫描匹配UUID的外设
[central scanForPeripheralsWithServices:nil options:nil];
}
第二个代理方法:
// 扫描到外部设备后触发//多次调用的
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData RSSI:(nonnull NSNumber *)RSSI
{
// 扫描到的外部设备
// NSString *msg = [NSString stringWithFormat:@"信号强度: %@, 外设: %@", RSSI, peripheral];
// NSLog(@"%@",msg);
if ([peripheral.name isEqualToString:@"BLE"])
{
//连接外部设备
self.peripheral = peripheral;
[central connectPeripheral:peripheral options:nil];
//停止搜索
[central stopScan];
}
}
这时候可以打开你的外设的开关,就可以通过程序搜索出来外设的名字和信号值了;
在这里可以做处理是否直接连接外设,比如ofo是以“ofo”为开头的蓝牙名称;
连接外设时要注意强引用外设,保存一下;
我们在连接上外设后,必须要停止搜索,以免影响蓝牙连接的稳定性。
第三个代理方法:
//连接失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"%@",error.localizedDescription);
[self.centralManager connectPeripheral:self.peripheral options:nil];
}
当连接失败的时候,我们要用中心端再次尝试连接我们保存的外设。
第四个代理方法:
// 当中心端连接上外设时触发
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"连接上外设");
self.peripheral.delegate = self;
[peripheral discoverServices:nil];
}
当我们的中心类已经连上了外设,就要开始实现外设协议的方法了,所以要设置代理。
第五个代理方法:
//如果连接上的两个设备突然断开了,程序里面会自动回调下面的方法
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"设备断开重连");
[self.centralManager connectPeripheral:self.peripheral options:nil];
}
当我们的外设与手机断开连接的时候,肯定要做一些处理,让他们再连接上。
在这里不得不说,蓝牙硬件芯片的好坏,信号的好坏太重要了,如果是便宜的芯片,在连接上之后会隔个几秒钟就调用这个方法。。。
所以我们不仅要做断开重连,如果你上传给服务器的数据是按赫兹来传的话,还要对上传的数据做缺省处理。
当然了,中心类还有别的代理方法,不过我就用到了这五个,下面是外设的代理方法。
2、<CBPeripheralDelegate>协议:
第一个代理方法:
// 外设端发现了服务时触发
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(@"%@",peripheral.services);
for (CBService *service in peripheral.services)
{
//只找有用的服务
if ([service.UUID.description isEqualToString:@“外设服务的UUID名称”])
{
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
一个外设有很多服务UUID,所以是一个数组,要从这些UUID中找出有用的服务,然后要discover一下,才能发现服中的特征。
第二个代理方法:
//从服务获取特征
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
NSLog(@"%@",service.characteristics);
for (CBCharacteristic *characteristic in service.characteristics)
{
NSLog(@"%@",service.characteristics);
for (CBCharacteristic *characteristic in service.characteristics)
{
// -------- 读特征的处理 --------
if ([characteristic.UUID.description isEqualToString: @"读特征的UUID名称"])
{
NSLog(@"处理读特征");
[self.peripheral readValueForCharacteristic:characteristic];
}
// -------- 写特征的处理 --------
if ([characteristic.UUID.description isEqualToString: @"写特征的UUID名称"])
{
NSLog(@"处理写特征");
//向外设发送0001命令
NSData *data = [@"0001" dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
self.characteristic = characteristic;
}
// -------- 订阅特征的处理 --------
if ([characteristic.UUID isEqual:[CBUUID @"订阅特征的UUID名称"]])
{
NSLog(@"处理了订阅特征");
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
这里就是真正与外设交互的地方了,一个UUID有三种属性:可读,可写,可监听。
可读属性,一般用来读取一次时使用。比如外设的名称,电量之类的,不常用。
可写属性,就是向外设发指令。这个很重要,不过当你写入失败的时候,记得更换你writeValue方法后面的type试试,有的是有回调的,用CBCharacteristicWriteWithResponse
,有的是没有回调的,用type:CBCharacteristicWriteWithoutResponse
一般都保存下可写特征,在外面写入发指令。
可监听属性,就是接收外设发送的数据了。一般实时变化的数据都要监听外设的,很重要。值得注意的是,当监听成功后,特征的Notify
属性的值会“=Yes”
,以此判断你是否监听成功了。
第三个代理方法:
// 写特征CBCharacteristicWriteWithResponse的数据写入的结果回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(@"数据写入失败: %@", error);
} else {
NSLog(@"数据写入成功");
[peripheral readValueForCharacteristic:characteristic];
}
}
这个就是当type为CBCharacteristicWriteWithResponse
时,向外设写入命令后的回调
第四个代理方法:
//获取外设发来的数据,不论是read和notify,获取数据都是从这个方法中读取
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSLog(@"%@",characteristic.value);
}
这里就是从外设获取数据的方法。
向外设读特征读后,该方法只调用一次;
监听外设的监听特征后,这个方法会在外设发送一个数据时调用一次,也就是说会多次调用。
可以在这个方法里做一些数据处理,然后将数据发送给服务器。
比如说服务器要接收的数据是按赫兹来接收的(就是一秒钟几个数据,还要对应时间),那就要自定义个网络工具类,不能直接用AFN了事,然后要把从外设接收的数据转成服务器要求的格式(比如多长时间一个包,定义字段等)。这时就要使用NSMutableData
在这个方法里拼接数据,然后设置请求体。
如果遇到上述情况,想知道我是如何处理的,可以看看我的另一篇文章。
第五个代理方法:
// 订阅特征的值改变时触发的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSLog(@"订阅特征的值改变了 : %@", characteristic);
NSLog(@"%@",characteristic.value);
}
当订阅特征的值改变时会触发该方法,用处不大。
蓝牙的基本功能实现就是这些,高级一点会把蓝牙的相关代理方法封装成一个中心管理类,可以看下我的下一篇文章:iOS封装蓝牙中心管理者类。
还有我在开发过程中遇到了什么问题,是如何处理的,会在另一篇文章记下来。
如果有帮助到你,给个喜欢吧:-D
网友评论