蓝牙知识简介
蓝牙硬件
1、蓝牙4.0:包括蓝牙4.0以上的版本,iOS 6 以上才可以使用,不一定需要MFI认证,因为4.0以上是低功耗,现在大部分的蓝牙设备都是4.0以上,本文主要讲蓝牙4.0及以上设备开发,使用CoreBluetooth 框架。
2、蓝牙2.0:又称经典蓝牙,2.0的蓝牙硬件模块必须经过苹果的MFI认证,否则无法进行蓝牙开发,使用ExternalAccessory 框架。
蓝牙基础知识
MFI ---- 是apple公司 “Made for iOS”的英文缩写,是苹果公司对其授权配件厂商生产的外置配件的一种标识使用许可。有MFI标识的设备质量普遍较好。
BLE ---- buletouch low energy,蓝牙4.0以上设备因为低耗电,所以也叫做BLE。
central,peripheral ---- 中心,外设,发起连接的一端叫central,被连接的设备为perilheral。
service,Characteristic,Description ---- 服务,特征,描述。都可以从设备读取到。一个设备可以有多个服务,一个服务可以有多个特征,特征的权限分为写write,读read,通知notify等几种,每个服务都有相应的描述。可类比为:服务wervice=类Class,特征Characteristic=属性property,Description描述=注释。
CoreBluetooth框架主要包含两种业务模式:中心模式和外设模式,前者是用手机连接其他蓝牙设备,后者是手机当做蓝牙设备被连接。本人最近只用到中心模式。
连接蓝牙流程:
1、创建中心对象
2、扫描周围蓝牙设备
3、连接蓝牙设备
4、扫描蓝牙设备的服务和特征
5、和蓝牙设备的数据交互
6、断开连接
中心模式的应用场景:例如用手机上的APP去连接蓝牙设备(手环等)。蓝牙设备的服务和特征一般有硬件工程师定义好,以及每个特征的属性(读,写,通知),类似于后台定义接口,APP端调用。读属性用于从蓝牙设备获取数据,写属性用于往蓝牙设备传数据,通知是订阅一个属性后,一旦属性的值发生改变就会通知APP,类似于OC中观察者模式。本人做的项目蓝牙设备传输数据都是通过通知实现的。
具体实现步骤
1、工程导入CoreBluetooth.framework.
2、在类里面导入头文件 CoreBluetooth/CoreBluetooth.h,添加代理,创建中心对象
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
@interface ViewController (){
//系统蓝牙设备管理对象,可以把他理解为主设备,通过他可以去扫描和连接外设
CBCentralManager *manager;
//用于保存被发现设备
NSMutableDictionary *discoverPeripherals;
}
这里一定要添加CBCentralManagerDelegate,CBPeripheralDelegate 2个代理,蓝牙大部分的回调都是通过代理方法实现的。
//存放持有发现的设备,如果不持有设备会导致CBPeripheralDelegate方法不能正确回调
discoverPeripherals = [[NSMutableDictionary alloc] init];
//初始化并设置委托,最好一个线程的参数可以为nil,默认会就main线程
manager= [[CBCentralManager alloc] initWithDelegate:self queue:nil];
3、扫描周围蓝牙设备
//此代理方法查看手机蓝牙状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStateUnknown:
break;
case CBCentralManagerStateResetting:
break;
case CBCentralManagerStateUnsupported:
break;
case CBCentralManagerStateUnauthorized:
break;
case CBCentralManagerStatePoweredOff:
break;
case CBCentralManagerStatePoweredOn:
//只有蓝牙开启正常,才开始扫描周围的外设
/*
第一个参数可以设置搜索条件,nil就是扫描周围所有的外设,扫描到外设后会进入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
*/
[manager scanForPeripheralsWithServices:nil options:nil];
break;
default:
break;
}
}
只有状态为CBCentralManagerStatePoweredOn才能进行后续扫描,要不然扫描后不会走代理方法
//扫描到设备会进入此方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"当扫描到设备名称:%@",peripheral.name);
//RSSI 蓝牙信号强度
//这里可以根据设备名称去连接你的设备
}
4、连接设备
//扫描到设备会进入此方法(还是上面这个方法😁)
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"当扫描到设备名称:%@",peripheral.name);
//RSSI 蓝牙信号强度
//这里可以根据设备名称去连接你的设备
//接下连接我们的测试设备,如果你没有设备,可以下载一个app叫lightbule的app去模拟一个设备
//这里自己去设置下连接规则,我设置的是P开头的设备
if ([peripheral.name isEqualToString:@"XXXX"]){
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
//我开始就掉进这个坑😂😂😂,可以用个容器装着或者全局对象(你也可以用数组,看自己需求来)
[discoverPeripherals setObject:peripheral forKey:@"peripheral"];
/*
连接成功,失败,断开会进入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
*/
//连接设备
[manager connectPeripheral:peripheral options:nil];
}
}
//连接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@">>>连接到名称为(%@)的设备-成功",peripheral.name);
//设置外设的代理,不设置的话连接成功不会走后续的方法了
[peripheral setDelegate:self];
//扫描外设Services,成功后会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
[peripheral discoverServices:nil];
}
//连接到Peripherals-失败
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
}
//Peripherals 断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
持有设备!持有设备!持有设备!重要的是说三遍!!!
5、扫描服务、特征
//扫描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@">>>扫描到服务:%@",peripheral.services);
if (error)
{
NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"服务UUID = %@",service.UUID);
//扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
}
//扫描到Characteristics
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error)
{
NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
//这里根据服务和特征的UUID去进行数据交互
//订阅特征
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//往设备写写数据 xxx为NSData类型数据
[peripheral writeValue:xxx forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]
}
//获取Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
{
[peripheral readValueForCharacteristic:characteristic];
}
}
//搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
for (CBCharacteristic *characteristic in service.characteristics){
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
上面2个方法扫描主要是读取服务和特征的UUID,好进行下一步操作。服务和特征都有UUID,UUID好比是类的名称,属性名称,特征的值就像属性的值
6、读取数据
//获取的charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出characteristic的UUID和值
//!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
NSLog(@"设备数据 == %@,%@,%@",characteristic.service.UUID,characteristic.UUID,characteristic.value)
}
上面就是读取特征具体的值,根据协议去解析数据,并转化为你需要的数据类型。
下面2个方法感觉用处不大,实际开发中,每个特征的的作用开发文档会写明的,或者硬件工程师会告诉你的
//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//打印出Characteristic和他的Descriptors
//NSLog(@"特征 uuid:%@",characteristic.UUID);
for (CBDescriptor *d in characteristic.descriptors) {
NSLog(@"特征描述 uuid:%@",d.UUID);
}
}
//获取到Descriptors的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//打印出DescriptorsUUID 和value
//这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析
NSLog(@"characteristic uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
//写入数据回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
NSLog(@"写入数据失败");
return;
}
NSLog(@"写入数据成功 %@",characteristic);
}
//订阅回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"订阅特征 %@失败",characteristic);
return;
}
NSLog(@"订阅特征 %@成功",characteristic);
}
//停止扫描并断开连接---自己写的不是代理😝
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止扫描
[centralManager stopScan];
//断开连接
[centralManager cancelPeripheralConnection:peripheral];
}
我自己做的蓝牙设备都是通过订阅某个特征从而获取数据的。
其中需要用到UUID的比较,留下个CBUUID转NSString的方法
- (NSString *)representativeString
{
NSData *data = [self data];
NSUInteger bytesToConvert = [data length];
const unsigned char *uuidBytes = [data bytes];
NSMutableString *outputString = [NSMutableString stringWithCapacity:16];
for (NSUInteger currentByteIndex = 0; currentByteIndex < bytesToConvert; currentByteIndex++)
{
switch (currentByteIndex)
{
case 3:
case 5:
case 7:
case 9:[outputString appendFormat:@"%02x-", uuidBytes[currentByteIndex]]; break;
default:[outputString appendFormat:@"%02x", uuidBytes[currentByteIndex]];
}
}
return outputString;
}
第一次做蓝牙设备的开发,遇到过几个坑,总结下:
1、没有持有CBCentralManager对象,导致调用连接方法后不走是否成功的代理方法。
2、忘记设置外设peripheral的代理了,导致不走后面扫描服务的代理方法
3、由于这个项目是帮别人做的,所有没有文档,只有一份安卓源码和安卓同事整理出来的一份简单文档,往哪个特征写数据,哪个特征读数据,都不清楚,把我搞得半死。其中有设备是往同一个特征读写数据;有往一个特征写数据,另外一个读数据的😂。我一个个去试,头疼啊。
项目背景
5个医疗类蓝牙设备:心电仪,血糖仪,血压仪,血氧仪,体脂称。
本人第一次写博客,第一次做蓝牙开发,如有错误,还望指正!万分谢谢!(内容有参考网络文章,侵删!)