1、蓝牙介绍
2、iBeacon
具体讲解见 Beacon
iBeacon 是苹果公司 2013 年 9 月发布的移动设备用 OS(iOS7)上配备的新功能。其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用 BLE 技术向周围发送自己特有的 ID,接收到该 ID 的应用软件会根据该 ID 采取一些行动。比如,在店铺里设置 iBeacon 通信模块的话,便可让 iPhone 和 iPad 上运行一资讯告知服务器,或者由服务器向顾客发送折扣券及进店积分。此外,还可以在家电发生故障或停止工作时使用 iBeacon 向应用软件发送资讯。
苹果 WWDC 14 之后,对 iBeacon 加大了技术支持和对其用于室内地图的应用有个更明确的规划。苹果公司公布了 iBeacon for Developers 和 Maps for Developers 等专题页面。
3、iOS 蓝牙
3.1 常见简称
MFi:make for ipad ,iphone, itouch 专们为苹果设备制作的设备,开发使用 ExternalAccessory 框架。认证流程挺复杂的,而且对公司的资质要求较高,详见 iOS - MFi 认证。
BLE:buletouch low energy,蓝牙 4.0 设备因为低耗电,所以也叫做 BLE,开发使用 CoreBluetooth 框架。
- GATT Profile(Generic Attribute Profile):GATT 配置文件是一个通用规范,用于在 BLE 链路上发送和接收被称为 “属性”(Attribute)的数据块。目前所有的 BLE 应用都基于 GATT。
- 1) 定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。
- 2) GATT 连接是独占的。基于 GATT 连接的方式的,只能是一个外设连接一个中心设备。
- 3) 配置文件是设备如何在特定的应用程序中工作的规格说明,一个设备可以实现多个配置文件。
- GAP(Generic Access Profile):用来控制设备连接和广播,GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。
- 1) GATT 连接,必需先经过 GAP 协议。
- 2) GAP 给设备定义了若干角色,主要两个:外围设备(Peripheral)和中心设备(Central)。
- 3) 在 GAP 中外围设备通过两种方式向外广播数据:Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复)。
Profile:并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG(一个以制定蓝牙规范,以推动蓝牙技术为宗旨的跨国组织)或者外设设计者预先定义的 Service 的集合。
Service:服务,是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,可以自己设置。每个外设会有很多服务,每个服务中包含很多字段,这些字段的权限一般分为读 read,写 write,通知 notiy 几种,就是我们连接设备后具体需要操作的内容。
Characteristic:特征,GATT 事务中的最低界别,Characteristic 是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的 X/Y/Z 三轴值。与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。每个设备会提供服务和特征,类似于服务端的 API,但是机构不同。
Description:每个 Characteristic 可以对应一个或多个 Description 用户描述 Characteristic 的信息或属性。
Peripheral、Central:外设和中心,发起连接的是 Central,被连接的设备为 Peripheral。
3.2 工作模式
蓝牙通信中,首先需要提到的就是 central 和 peripheral 两个概念。这是设备在通信过程中扮演的两种角色。直译过来就是 [中心] 和 [周边(可以理解为外设)]。iOS 设备既可以作为 central,也可以作为 peripheral,这主要取决于通信需求。
例如在和心率监测仪通信的过程中,监测仪作为 peripheral,iOS 设备作为 central。区分的方式即是这两个角色的重要特点:提供数据的是谁,谁就是 peripheral;需要数据的是谁,谁就是 central。就像是 client 和 server 之间的关系一样。
那怎么发现 peripheral 呢
在 BLE 中,最常见的就是广播。实际上,peripheral 在不停的发送广播,希望被 central 找到。广播的信息中包含它的名字等信息。如果是一个温度调节器,那么广播的信息应该还会包含当前温度什么的。那么 central 的作用则是去 scan,找到需要连接的 peripheral,连接后便可进行通信了。
当 central 成功连上 peripheral 后,它便可以获取 peripheral 提供的所有 service 和 characteristic。通过对 characteristic 的数据进行读写,便可以实现 central 和 peripheral 的通信。
CoreBluetooth 框架的核心其实是两个东西,central 和 peripheral, 对应他们分别有一组相关的 API 和类。
这两组 API 分别对应不同的业务场景,如下图,左侧叫做中心模式,就是以你的手机(App)作为中心,连接其他的外设的场景。而右侧称为外设模式,使用手机作为外设连接其他中心设备操作的场景。
iOS 设备(App)作为 central 时:
当 central 和 peripheral 通信时,绝大部分操作都在 central 这边。此时,central 被描述为 CBCentralManager,这个类提供了扫描、寻找、连接 peripheral(被描述为 CBPeripheral)的方法。
下图标示了 central 和 peripheral 在 Core Bluetooth 中的表示方式:
当你操作 peripheral 的时候,实际上是在和它的 service 和 characteristic 打交道,这两个分别由 CBService 和 CBCharacteristic 表示。
iOS 设备(App)作为 Peripheral 时:
在 OS X 10.9 和 iOS 6 以后,设备除了能作为 central 外,还可以作为 peripheral。也就是说,可以发起数据,而不像以前只能管理数据了。
那么在此时,它被描述为 CBPeripheralManager,既然是作为 peripheral,那么这个类提供的主要方法则是对 service 的管理,同时还兼备着向 central 广播数据的功能。peripheral 同样会对 central 的读写要求做出相应。
下图则是设备作为 central 和 Peripheral 的示意图:
在充当 peripheral 时,CBPeripheralManager 处理的是可变的 service 和 characteristic,分别由 CBMutableService 和 CBMutableCharacteristic 表示。
中心模式(CBCentralManager)流程:
- 1、建立中心角色
- 2、扫描外设(discover)
- 3、连接外设(connect)
- 4、扫描外设中的服务和特征(discover)
- 4.1 获取外设的 services
- 4.2 获取外设的 Characteristics,获取 Characteristics 的值,获取 Characteristics 的 Descriptor 和 Descriptor 的值
- 5、与外设做数据交互(explore and interact)
- 6、订阅 Characteristic 的通知
- 7、断开连接(disconnect)
外设模式(CBPeripheralManager)流程:
- 1、启动一个 Peripheral 管理对象
- 2、设置本地 Peripheral 服务、特性、描述、权限等等
- 3、设置 Peripheral 发送广播
- 4、设置处理订阅、取消订阅、读 characteristic、写 characteristic 的委托方法
3.3 服务、特征和特征的属性
一个 peripheral 包含一个或多个 service,或提供关于信号强度的信息。service 是数据和相关行为的集合。例如,一个心率监测仪的数据就可能是心率数据。
service 本身又是由 characteristic 或者其他 service 组成的。characteristic 又提供了更为详细的 service 信息。还是以心率监测仪为例,service 可能会包含两个 characteristic,一个描述当前心率带的位置,一个描述当前心率的数据。
每个 characteristic 属性分为这么几种:读,写,通知这么几种方式。
// 特征的定义枚举 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 };
外设、服务、特征间的关系
- 一个 CBPeripheral(蓝牙设备) 有一个或者多个 CBService(服务),而每一个 CBService 有一个或者多个 CBCharacteristic(特征),通过可写的 CBCharacteristic 发送数据,而每一个 CBCharacteristic 有一个或者多个 Description 用于描述 characteristic 的信息或属性。
3.4 设备状态
蓝牙设备状态:
- 1、待机状态(standby):设备没有传输和发送数据,并且没有连接到任何设备。
- 2、广播状态(Advertiser):周期性广播状态。
- 3、扫描状态(Scanner):主动寻找正在广播的设备。
- 4、发起链接状态(Initiator):主动向扫描设备发起连接。
- 5、主设备(Master):作为主设备连接到其他设备。
- 6、从设备(Slave):作为从设备连接到其他设备。
五种工作状态:
- 准备(standby)
- 广播(advertising)
- 监听扫描(Scanning)
- 发起连接(Initiating)
- 已连接(Connected)
3.5 蓝牙和版本的使用限制
蓝牙 2.0:越狱设备
蓝牙 4.0:iOS 6 以上
MFi 认证设备:无限制
3.6 设置系统使用蓝牙权限
设置系统使用蓝牙权限
4、中心模式的使用
中心模式的应用场景:主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般来说,外设(蓝牙设备,比如智能手环之类的东西)会由硬件工程师开发好,并定义好设备提供的服务,每个服务对于的特征,每个特征的属性(只读,只写,通知等等)。
蓝牙程序需要使用真机调试。
4.1 App 连接外设的实现
1、建立中心角色
导入 CoreBluetooth 头文件,建立中心设备管理类,设置主设备委托。
// 包含头文件 #import <CoreBluetooth/CoreBluetooth.h> // 遵守协议 @interface ViewController () <CBCentralManagerDelegate> // 中心设备管理器 @property (nonatomic, strong) CBCentralManager *centralManager; - (IBAction)start:(UIButton *)sender { // 初始化 centralManager,nil 默认为主线程 self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; } #pragma mark - CBCentralManagerDelegate // 检查 App 设备蓝牙是否可用,协议方法 - (void)centralManagerDidUpdateState:(CBCentralManager *)central { // 在初始化 CBCentralManager 的时候会打开设备,只有当设备正确打开后才能使用 switch (central.state){ case CBManagerStatePoweredOn: // 蓝牙已打开,开始扫描外设 NSLog(@"蓝牙已打开,开始扫描外设"); // 开始扫描周围的设备,自定义方法 [self sacnNearPerpherals]; break; case CBManagerStateUnsupported: NSLog(@"您的设备不支持蓝牙或蓝牙 4.0"); break; case CBManagerStateUnauthorized: NSLog(@"未授权打开蓝牙"); break; case CBManagerStatePoweredOff: // 蓝牙未打开,系统会自动提示打开,所以不用自行提示 default: break; } } // 发现外围设备,协议方法 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI { /* * central 中心设备 * peripheral 外围设备 * advertisementData 特征数据 * RSSI 信号强度 */ NSMutableString *string = [NSMutableString stringWithString:@"\n\n"]; [string appendFormat:@"NAME: %@\n" , peripheral.name]; [string appendFormat:@"UUID(identifier): %@\n", peripheral.identifier]; [string appendFormat:@"RSSI: %@\n" , RSSI]; [string appendFormat:@"adverisement:%@\n" , advertisementData]; NSLog(@"发现外设 Peripheral Info:\n %@", string); // 连接指定的设备,自定义方法 [self connectPeripheral:peripheral]; } // 连接外设成功,协议方法 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@"%@ 连接成功", peripheral.name); // 停止扫描 [central stopScan]; // 扫描外设中的服务和特征,自定义方法 [self discoverPeripheralServices:peripheral]; } // 连接外设失败,协议方法 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"%@ 连接失败", peripheral.name); } // 连接外设断开,协议方法 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"%@ 连接已断开", peripheral.name); }
2、扫描外设(discover)
扫描外设的方法需要放在 centralManager 成功打开的代理方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
中,因为只有设备成功打开,才能开始扫描,否则会报错。扫描到外设后会进入代理方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
中。// 开始扫描周围的设备,自定义方法 - (void)sacnNearPerpherals { NSLog(@"开始扫描周围的设备"); /* * 第一个参数为 Services 的 UUID(外设端的 UUID),nil 为扫描周围所有的外设。 * 第二参数的 CBCentralManagerScanOptionAllowDuplicatesKey 为已发现的设备是否重复扫描,YES 同一设备会多次回调。nil 时默认为 NO。 */ [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}]; }
3、连接外设(connect)
对要连接的设备需要进行强引用,否则会报错。
一个主设备最多能连 7 个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的代理方法中。
// 设备 @property (nonatomic, strong) CBPeripheral *peripheral; // 连接指定的设备,自定义方法 - (void)connectPeripheral:(CBPeripheral *)peripheral { NSLog(@"连接指定的设备"); // 设置连接规则,这里设置的是 以 J 开头的设备 if ([peripheral.name hasPrefix:@"J"]) { // 对要连接的设备进行强引用,否则会报错 self.peripheral = peripheral; // 连接设备 [self.centralManager connectPeripheral:peripheral options:nil]; } }
4、扫描外设中的服务和特征(discover)
设备连接成功后,就可以扫描设备的服务了,同样是通过委托形式,扫描到结果后会进入委托方法。但是这个委托已经不再是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),这个委托包含了主设备与外设交互的许多回调方法,包括获取 services,获取 characteristics,获取 characteristics 的值,获取 characteristics 的 Descriptor,和 Descriptor的值,写数据,读 RSSI,用通知的方式订阅数据等等。
// 遵守协议 @interface ViewController () <CBPeripheralDelegate> // 扫描外设中的服务和特征,自定义方法 - (void)discoverPeripheralServices:(CBPeripheral *)peripheral { // 设置外设代理 self.peripheral.delegate = self; // 开始扫描外设 [self.peripheral discoverServices:nil]; } #pragma mark - CBPeripheralDelegate // 扫描到外设服务,协议方法 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { if (error) { NSLog(@"Discovered services for %@ with error: %@", peripheral.name, error.localizedDescription); return; } for (CBService *service in peripheral.services) { NSLog(@"扫描到外设服务:%@", service); // 扫描服务的特征 [peripheral discoverCharacteristics:nil forService:service]; } } // 扫描到服务的特征,协议方法 - (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) { NSLog(@"扫描到服务:%@ 的特征:%@", service.UUID, characteristic.UUID); // 获取特征的值 [peripheral readValueForCharacteristic:characteristic]; // 搜索特征的 Descriptors [peripheral discoverDescriptorsForCharacteristic:characteristic]; // // 连接成功,开始配对,发送第一次校验的数据,自定义方法 // [self writeCharacteristic:peripheral characteristic:characteristic value:self.pairAuthDatas[0]]; } } // 获取到特征的值,协议方法 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { // value 的类型是 NSData,具体开发时,会根据外设协议制定的方式去解析数据 NSLog(@"获取到特征:%@ 的值:%@", characteristic.UUID, [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]); // if (...) { // 第一次配对成功 // // [self writeCharacteristic:peripheral char