iOS中的蓝牙
概述
iOS中提供了4个框架用于实现蓝牙连接
-
1、GameKit.framework(用法简单)
只能用于iOS设备之间的同个应用的内连接,多用于游戏,从iOS7开始过期
-
2、MultipeerConnectivity.framework(代替1)
只能用于iOS设备之间的连接,从iOS7开始引入,主要用于非联网状态下,通过WIFI或蓝牙进行文件共享(仅限于沙盒的文件),多用于附近无网聊天。
-
3、ExternalAccessory.framework(MFI)
可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果 MFI 认证(国内很少)。
-
4、CoreBluetooth.framework(主流)
可用于第三方蓝牙设备交流,必须要支持蓝牙4.0
硬件至少是4s,系统至少是iOS6
蓝牙4.0以低功耗著称,一般也叫BLE(Bluetooth Low Energy)
目前应用比较多的案例:运动手环,嵌入式设备,智能家居
涉及到的系统/框架
-
HealthKit / 物联网 HomeKit / wattchOS1,2,3 / Beacon
一、了解CoreBluetooth.framework
-
中心模式流程
1.建立一个CentralManager实例进行蓝牙管理
2.搜索外围设备
3.连接外围设备
4.获得外围设备的服务
5.获得服务的特征
6.从外围设备读数据
7.给外围设备发送数据
8.其他:提醒
-
外设模式流程
1.启动一个Peripheral管理对象
2.本地peripheral设置服务,特征,描述,权限等
3.peripheral发送广告
4.设置处理订阅,取消订阅,读characteristic,写characteristic的代理方法
-
中心设备,外设,服务,特征,描述之间的关系
借鉴一张图大致了解一下:
-
peripheral,central:外设和中心设备,发起链接的是central(一般是指手机),被链接的设备是peripheral(运动手环)
每个设备都会有一个或多个服务
每个服务里都会有一个或多个特征
特征就是具体键值对,提供数据的地方
每个特征属性分为:读、写、通知等。
-
外设服务特征的关系
二、中心设备代码实现步骤
1、建立一个CentralManager实例进行蓝牙管理
// 引入头文件
#import <CoreBluetooth/CoreBluetooth.h>
/** 中心管理者 */
@property (nonatomic, strong) CBCentralManager *centralManager;
/** 懒加载初始化中心管理者 */
- (CBCentralManager *)centralManager {
if (!_centralManager) {
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:nil];
}
return _centralManager;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"BELCenter";
// 先将中心管理者初始化
// 打开后会回调 CBCentralManagerDelegate 协议中的方法centralManagerDidUpdateState
[self centralManager];
}
2、搜索外围设备
// 引入协议@interface KYCenterBLEVC () <CBCentralManagerDelegate>
#pragma mark - CBCentralManagerDelegate, 中心管理者初始化就会触发此代理方法
/* 中心管理器初始化后,会回调该协议, 当中心管理器状态打开时,扫描外围连接设备 */
#pragma mark - CBCentralManagerDelegate, 中心管理者初始化就会触发此代理方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
/*
CBManagerStateUnknown = 0,
CBManagerStateResetting,
CBManagerStateUnsupported,
CBManagerStateUnauthorized,
CBManagerStatePoweredOff,
CBManagerStatePoweredOn,
*/
switch (central.state) {
case 0:
NSLog(@"CBManagerStateUnknown");
break;
case 1:
NSLog(@"CBManagerStateResetting");
break;
case 2:
NSLog(@"CBManagerStateUnsupported");
break;
case 3:
NSLog(@"CBManagerStateUnauthorized");
break;
case 4:
NSLog(@"CBManagerStatePoweredOff");
break;
case 5: {
NSLog(@"CBManagerStatePoweredOn");
// 扫描外围设备,
// 发现外设后,会自动回调CBCentralManagerDelegate协议方法centralManager:didDiscoverPeripheral:
[self.centralManager scanForPeripheralsWithServices:nil // 通过某些服务筛选外设
options:nil]; // dict,条件
}
break;
default:
break;
}
}
3、连接外围设备
/** 发现外设后调用的方法 */
- (void)centralManager:(CBCentralManager *)central // 中心管理者
didDiscoverPeripheral:(CBPeripheral *)peripheral // 外设
advertisementData:(NSDictionary<NSString *, id> *)advertisementData // 外设携带的数据
RSSI:(NSNumber *)RSSI { // 外设发出的外设蓝牙强度
NSLog(@"%s, line = %d, central=%@, peripheral=%@, advertisementData=%@, RSSI=%d", __FUNCTION__, __LINE__, central, peripheral, advertisementData, RSSI.intValue);
/*
1、过滤外设,可以根据设备名称,或者advertisementData 中携带的参数数据过滤设备
2、过滤信号强度,RSSI为负数,
3、一般会把搜索到的设备可视化为列表,然后由用户手动连接设备,此处demo过滤后直接连接了外设
*/
if ([peripheral.name containsString:@"康鹏鹏"] && abs(RSSI.intValue) < 90 && abs(RSSI.intValue) > 0) {
// 通常过滤后的外设存储到可变数组中,此处使用属性仅对一组外设进行存储
self.peripheral = peripheral; // 连接外设,连接成功后,回调centralManager:didConnectPeripheral:
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
/** 管理者连接外设成功 */
- (void)centralManager:(CBCentralManager *)central // 中心管理者
didConnectPeripheral:(CBPeripheral *)peripheral { // 外设
NSLog(@"%s, line = %d, 外设<-%@->连接成功", __FUNCTION__, __LINE__, peripheral.name);
// 连接成功之后可以进行一些数据的交互
// 1、设置外设代理
self.peripheral.delegate = self;
// 2、发现服务,nil代表不过滤
// 这里会触发外设CBPeripheralDelegate代理方法 peripheral: didDiscoverServices:
//[self.peripheral discoverServices:nil];
[self.peripheral discoverServices:@[[CBUUID UUIDWithString:Service1StrUUID]]];
}
/** 连接失败 */
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
NSLog(@"%s, line = %d, 外设<-%@->连接失败", __FUNCTION__, __LINE__, peripheral.name);
}
/** 丢失连接 */
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
NSLog(@"%s, line = %d, 外设<-%@->连接断开", __FUNCTION__, __LINE__, peripheral.name);
}
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict {
NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
}
4、获得外围设备的服务
// 添加CBPeripheralDelegate 协议
@interface KYCenterBLEVC () <CBCentralManagerDelegate, CBPeripheralDelegate>
#pragma mark - CBPeripheralDelegate 外设代理
/** 发现外设服务后自动回调 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
NSLog(@"%s, line = %d, peripheral = %@", __FUNCTION__, __LINE__, peripheral);
if (error) {
NSLog(@"%s, line = %d, error = %@", __FUNCTION__, __LINE__, error);
return;
}
for (CBService *service in peripheral.services) {
// 发现服务,再让设备发现服务内部特征,根据UUID过滤外设服务(外设服务可以有多个) // 发现特征后,会触发peripheral: didDiscoverCharacteristicsForService: error:
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:notiyCharacteristicStrUUID]] forService:service];
}
}
5、获得服务的特征
/** 发现外设服务里的特征的时候调用的代理方法 */
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
for (CBCharacteristic *chart in service.characteristics) {
NSLog(@"%s, line = %d, chart = %@", __FUNCTION__, __LINE__, chart);
// 获取特征对应的描述,会触发peripheral: didDiscoverDescriptorsForCharacteristic: error:
[peripheral discoverDescriptorsForCharacteristic:chart];
// 获取特征值
// 发现外设特征后,回调peripheral: didUpdateValueForCharacteristic: error:
[peripheral readValueForCharacteristic:chart];
}
}
// 发现外设特征描述
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
// 此处读取描述即可
for (CBDescriptor *descriptor in characteristic.descriptors) {
// 它会触发peripheral: didUpdateValueForDescriptor: error:
[peripheral readValueForDescriptor:descriptor];
NSLog(@"%s, line = %d, descriptor = %@", __FUNCTION__, __LINE__, descriptor);
}
}
6、从外围设备读数据
// 更新特征的value时调用,或读取特征值时自动调用
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
for (CBDescriptor *descriptor in characteristic.descriptors) {
NSLog(@"%s, line = %d, characteristicValue = %@", __FUNCTION__, __LINE__, [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]);
}
}
// 更新特征的描述值的时候调用,或读取特征描述时自动调用
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
NSLog(@"%s, line = %d, descriptorValue = %@", __FUNCTION__, __LINE__, descriptor.value);
// 这里当描述的值更新时,回调此方法
}
7、给外围设备发送数据
- (void)ky_peripheral:(CBPeripheral *)peripheral didWriteData:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)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
};
打印出特征的权限(characteristic.properties),可以看到有很多种,这是一个NS_OPTIONS的枚举,可以是多个值
常见的又read,write,noitfy,indicate.知道这几个基本够用了,前俩是读写权限,后俩都是通知,俩不同的通知方式
*/
NSLog(@"%s, line = %d, char.pro = %lu", __FUNCTION__, __LINE__, (unsigned long)characteristic.properties);
// 此时由于枚举属性是NS_OPTIONS,所以一个枚举可能对应多个类型,所以判断不能用 = ,而应该用包含&
// 判断是否支持写属性
if (characteristic.properties & CBCharacteristicPropertyWrite) {
// 核心代码在这里
[peripheral writeValue:data // 写入的数据
forCharacteristic:characteristic // 写给哪个特征
type:CBCharacteristicWriteWithResponse];// 通过此响应记录是否成功写入
}
}
8、其他:提醒
/**
通知的订阅
实际核心代码是一个方法
一般这两个方法要根据产品需求来确定写在何处
*/
- (void)ky_peripheral:(CBPeripheral *)peripheral regNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic {
// 外设为特征订阅通知 数据会进入 peripheral: didUpdateValueForCharacteristic:error:方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
// 通知的取消订阅
- (void)ky_peripheral:(CBPeripheral *)peripheral CancleRegNotifyWithCharacteristic:(nonnull CBCharacteristic *)characteristic {
// 外设取消订阅通知 数据会进入 peripheral:didUpdateValueForCharacteristic:error:方法
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
9、断开连接
// 断开连接
- (void)ky_dismissConentedWithPeripheral:(CBPeripheral *)peripheral {
// 停止扫描
[self.centralManager stopScan];
if (peripheral) {
// 断开连接
[self.centralManager cancelPeripheralConnection:peripheral];
}
}