iOS蓝牙知识快速入门(详尽版)(转)

以前写过几篇蓝牙相关的文章,可是没有涉及扫描、收发指令这些根底功用的完成。所以计划写一篇尽可能详尽的蓝牙常识汇总,一方面给有需求的同学看,一方面是对自己学习蓝牙的一个总结。

这篇文章的意图:教你完成设备的扫描,衔接,数据收发,蓝牙数据解析。假如在完成上面任一功用遇到问题时,欢迎留下你的问题,我将进行弥补,对于说法有误的地方也请老司机予以纠正。

目录

0、思想导图

1、苹果对蓝牙设备有什么要求

2、操作蓝牙设备运用什么库

3、怎么扫描

4、怎么衔接

5、怎么发送数据和接纳数据

6、怎么解析数据

7、扩展

思想导图

iOS蓝牙知识快速入门(详尽版)(转)

第一次做图,咱们凑合着看哈。这张是我总结的蓝牙常识的结构图,下面的内容将环绕这些东西翻开进行。

iOS蓝牙知识快速入门(详尽版)(转)

这张是蓝牙衔接发送数据的流程图,下文进入coding阶段的解说顺序,咱们先有个大约印象,等阅读完本文再回来看这张图将理解的更深一些。

苹果对蓝牙设备有什么要求

BLE:bluetouch low energy,蓝牙4.0设备由于低功耗,一切也叫作BLE。苹果在iphone4s及之后的手机型号开端支持蓝牙4.0,这也是最常见的蓝牙设备。低于蓝牙4.0协议的设备需求进行MFI认证,关于MFI认证的申请作业能够看这儿:关于MFI认证你所有必要要知道的工作

在进行操作蓝牙设备前,咱们先下载一个蓝牙东西LightBlue,它能够辅助咱们的开发,在进行蓝牙开发之前建议先了解一下LightBlue这个东西。

操作蓝牙设备运用什么库

苹果自身有一个操作蓝牙的库CoreBluetooth.framework,这个是大多数人员进行蓝牙开发的首选结构,除此之外目前github还有一个比较流行的对原生结构进行封装的三方库BabyBluetooth,它的机制是将CoreBluetooth中很多的delegate写成了block办法,有兴趣的同学能够了解下。下面首要介绍的是原生蓝牙库的常识。

中心和外围设备

iOS蓝牙知识快速入门(详尽版)(转)

如图所示,电脑、Pad、手机作为中心,心跳监听器作为外设,这种中心外设模式是最常见的。简单理解就是,建议衔接的是中心设备(Central),被衔接的是外围设备(Peripheral),对应传统的客户机-服务器体系结构。Central能够扫描侦听到,正在播放广告包的外设。

服务与特征

外设能够包括一个或多个服务(CBService),服务是用于完成设备的功用或特征数据相关联的行为调集。 而每个服务又对应多个特征(CBCharacteristic),特征供给外设服务进一步的细节,外设,服务,特征对应的数据结构如下所示

iOS蓝牙知识快速入门(详尽版)(转)

怎么扫描蓝牙

在进行扫描之前咱们需求,首要新建一个类作为蓝牙类,例如FYBleManager,写成单例,作为处理蓝牙操作的管理类。引进头文件#import <CoreBluetooth/CoreBluetooth.h>CBCentralManager是蓝牙中心的管理类,操控着蓝牙的扫描,衔接,蓝牙状况的改动。

1、初始化

dispatch_queue_t centralQueue = dispatch_queue_create(“centralQueue",DISPATCH_QUEUE_SERIAL);
NSDictionary *dic = @{
    CBCentralManagerOptionShowPowerAlertKey : YES,
    CBCentralManagerOptionRestoreIdentifierKey : @"unique identifier"
};
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:dic];
  • CBCentralManagerOptionShowPowerAlertKey对应的BOOL值,当设为YES时,表明CentralManager初始化时,假如蓝牙没有翻开,将弹出Alert提示框
  • CBCentralManagerOptionRestoreIdentifierKey对应的是一个仅有标识的字符串,用于蓝牙进程被杀掉康复衔接时用的。

2、扫描

//不重复扫描已发现设备
NSDictionary *option = @{
    CBCentralManagerScanOptionAllowDuplicatesKey : [NSNumber numberWithBool:NO],
    CBCentralManagerOptionShowPowerAlertKey:YES
};
[self.centralManager scanForPeripheralsWithServices:nil options:option];
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
  • 扫面办法
    • serviceUUIDs用于第一步的筛选,扫描此UUID的设备
    • options有两个常用参数:
      • CBCentralManagerScanOptionAllowDuplicatesKey
        • 设置为NO表明不重复扫瞄已发现设备,为YES就是允许
      • CBCentralManagerOptionShowPowerAlertKey
      • 设置为YES就是在蓝牙未翻开的时分显现弹框

3、CBCentralManagerDelegate署理办法

在初始化的时分咱们调用了署理,在CoreBluetooth中有两个署理,

  • CBCentralManagerDelegate
  • CBPeripheralDelegate

iOS的命名很友好,咱们经过名字就能看出,上面那个是关于中心设备的署理办法,下面是关于外设的署理办法。
咱们这儿先研究CBCentralManagerDelegate中的署理办法

- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

这个办法标了@required是有必要添加的,咱们在self.centralManager初始换之后会调用这个办法,回调蓝牙的状况。状况有以下几种:

typedef NS_ENUM(NSInteger, CBCentralManagerState{
    CBCentralManagerStateUnknown = CBManagerStateUnknown,//不知道状况
    CBCentralManagerStateResetting = CBManagerStateResetting,//重启状况
    CBCentralManagerStateUnsupported = CBManagerStateUnsupported,//不支持
    CBCentralManagerStateUnauthorized = CBManagerStateUnauthorized,//未授权
    CBCentralManagerStatePoweredOff = CBManagerStatePoweredOff,//蓝牙未开启
    CBCentralManagerStatePoweredOn = CBManagerStatePoweredOn,//蓝牙开启}NS_DEPRECATED(NA, NA, 5_0, 10_0, "Use CBManagerState instead”
);

该枚举在iOS10之后现已废除了,体系引荐运用CBManagerState,类型都是对应的

typedef NS_ENUM(NSInteger, CBManagerState{
    CBManagerStateUnknown = 0,
    CBManagerStateResetting,
    CBManagerStateUnsupported,
    CBManagerStateUnauthorized,
    CBManagerStatePoweredOff,
    CBManagerStatePoweredOn,
} NS_ENUM_AVAILABLE(NA, 10_0);
- (void)centralManager:(CBCentralManager *)central 
    didDiscoverPeripheral:(CBPeripheral *)peripheral
    advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
  • peripheral是外设类
  • advertisementData是广播的值,一般携带设备名,serviceUUIDs等信息
  • RSSI绝对值越大,表明信号越差,设备离的越远。假如想转换成百分比强度,(RSSI+100)/100,(这是一个约数,蓝牙信号值并不一定是-100 – 0的值,但近似能够如此表明)
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;

在蓝牙于后台被杀掉时,重连之后会首要调用此办法,能够获取蓝牙康复时的各种状况

怎么衔接

在扫面的署理办法中,咱们衔接外设名是MI的蓝牙设备

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"advertisementData:%@,RSSI:%@",advertisementData,RSSI);
    if([peripheral.name isEqualToString:@"MI"]){ 
        [self.centralManager connectPeripheral:peripheral options:nil];//建议衔接的指令
        self.peripheral = peripheral; 
    }
}

衔接的状况对应另外的CBCentralManagerDelegate署理办法 衔接成功的回调

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;

衔接失败的回调

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

衔接断开的回调

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

衔接成功之后并没有结束,还记得CBPeripheral中的CBServiceCBService中的CBCharacteristic吗,对数据的读写是由CBCharacteristic操控的。
咱们先用lightblue衔接小米手环为例,来看一下,手环内部的数据是不是咱们说的那样。

iOS蓝牙知识快速入门(详尽版)(转)

其间ADVERTISEMENT DATA显现的就是广播信息。

iOS蓝牙无法直接获取设备蓝牙MAC地址,能够将MAC地址放到这儿广播出来

FEEOServiceUUIDs,里边的FF01FF02CBCharacteristic的UUID

Properties是特征的特点,能够看出FF01具有读的权限,FF02具有读写的权限。
特征具有的权限类别有如下几种:

typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties{
    CBCharacteristicPropertyBroadcast = 0x01,
    CBCharacteristicPropertyRead = 0x02,
    CBCharacteristicPropertyWriteWithoutResponse = 0x04,
    CBCharacteristicPropertyWrite = 0x08,
    CBCharacteristicPropertyNotify = 0x10,
    CBCharacteristicPropertyIndicate = 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
    CBCharacteristicPropertyExtendedProperties = 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
};

怎么发送并接纳数据

经过上面的步骤咱们发现CBCentralManagerDelegate供给了蓝牙状况监测、扫描、衔接的署理办法,可是CBPeripheralDelegate的署理办法却还没运用。别急,立刻就要用到了,经过称号判断这个署理的效果,肯定是跟Peripheral有关,咱们进入体系API,看它的署理办法都有什么,由于这儿的署理办法较多,我就选择几个常用的拿出来说明一下。

1、署理办法

//发现服务的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
//发现特征的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
//读数据的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
//是否写入成功的回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

2、步骤

经过这几个办法咱们构建一个流程:衔接成功->获取指定的服务->获取指定的特征->订阅指定特征值->经过具有写权限的特征值写数据->在didUpdateValueForCharacteristic回调中读取蓝牙反应值

解释一下订阅特征值:特征值具有Notify权限才能够进行订阅,订阅之后该特征值的value发生变化才会回调didUpdateValueForCharacteristic

3、完成上面流程的实例代码

//衔接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    //衔接成功之后寻觅服务,传nil会寻觅一切服务
    [peripheral discoverServices:nil];
}
//发现服务的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
   if (!error) { 
     for (CBService *service in peripheral.services) {     
         NSLog(@"serviceUUID:%@", service.UUID.UUIDString); 
            if ([service.UUID.UUIDString isEqualToString:ST_SERVICE_UUID]) {
            //发现特定服务的特征值
               [service.peripheral discoverCharacteristics:nil forService:service]; 
            } 
        } 
    }
}
//发现characteristics,由发现服务调用(上一步),获取读和写的characteristics
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    for (CBCharacteristic *characteristic in service.characteristics) { 
        //有时读写的操作是由一个characteristic完成 
        if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]) {
            self.read = characteristic;
            [self.peripheral setNotifyValue:YES forCharacteristic:self.read]; 
        } else if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_WRITE]) {
            self.write = characteristic; 
        } 
     }
}
//是否写入成功的署理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) { 
        NSLog(@"===写入错误:%@",error); 
    }else{
        NSLog(@"===写入成功"); 
    }
}
//数据接纳
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { 
    if([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]){          //获取订阅特征回复的数据
        NSData *value = characteristic.value;
        NSLog(@"蓝牙回复:%@",value);
    }
}

比如咱们要获取蓝牙电量,由硬件文档查询得知该指令是 0x1B9901,那么获取电量的办法就能够写成

- (void)getBattery{
    Byte value[3]={0};
    value[0]=x1B;
    value[1]=x99;
    value[2]=x01;
    NSData * data = [NSData dataWithBytes:&value length:sizeof(value)];
    //发送数据
    [self.peripheral writeValue:data forCharacteristic:self.write type:CBCharacteristicWriteWithoutResponse];
}

假如写入成功,咱们将会在didUpdateValueForCharacteristic办法中获取蓝牙回复的信息。

怎么解析蓝牙数据

假如你顺利完成了上一步的操作,而且看到了蓝牙回来的数据,那么恭喜你,蓝牙的常用操作你现已了解多半了。由于蓝牙的任务大部分就是环绕发送指令,获取指令,将蓝牙数据呈现给用户。上一步咱们现已获取了蓝牙指令,可是获取的却是0x567b0629这样的数据,这是什么意思呢。这时咱们参阅硬件协议文档,看到这样一段:

iOS蓝牙知识快速入门(详尽版)(转)

那么咱们就能够得出设备电量是 60%。

对数据解析的流程就是:

  • 判断校验和是否正确
    • 是不是一条正确的数据->该条数据是不是咱们需求的电量数据
    • 即首字节为0x567b->根据界说规矩解析电量,传给view显现。
  • 其间第一步校验数据,视情况而定,也有不需求的情况。

扩展

iOS蓝牙中的进制转换

蓝牙固件晋级

nRF芯片设备DFU晋级

参阅Demo