目前在iOS中蓝牙开发框架主要有以下几种
- GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期.
- MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。
- CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙4.0。
- 前两个框架使用起来比较简单,但是缺点也比较明显:仅仅支持iOS设备,传输内容仅限于沙盒或者照片库中用户选择的文件,并且第一个框架只能在同一个应用之间进行传输(一个iOS设备安装应用A,另一个iOS设备上安装应用B是无法传输的)。CoreBluetooth就摆脱了这些束缚,它不再局限于iOS设备之间进行传输,你可以通过iOS设备向Android、Windows Phone以及其他安装有蓝牙4.0芯片的智能设备传输,因此也是目前智能家居、无线支付等热门智能设备所推崇的技术。本文主要介绍CoreBluetooth的相关开发流程.
CoreBluetooth开发模式
CoreBluetooth设计类似于客户端-服务器端的设计,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类.
CoreBluetooth架构
- 左侧叫做中心模式,以app作为中心,连接其他的外设的,而右侧称为外设模式,使用手机作为外设别其他中心设备操作的场景。
-
服务和特征,特征的属性(service and characteristic):
蓝牙设备添加若干服务,服务內添加若干特征,特征就是具体键值对,提供对数据的读取和写。蓝牙中心和外设数据的交换基于服务的特征.
外设和服务特征的关系
中心的开发
- 流程
<pre>
- 建立中心角色
- 扫描外设(discover)
- 连接外设(connect)
- 扫描外设中的服务和特征(discover)
- 4.1 获取外设的services
- 4.2 获取外设的Characteristics,获取Characteristics的值,获取 Characteristics的Descriptor和Descriptor的值
- 与外设做数据交互(explore and interact)
- 订阅Characteristic的通知
- 断开连接(disconnect)
</pre>
-
步骤
CoreBluetooth中不管是中心还是外设的开发,均是上面的流程,通过一步步的代理回调实现的,按照流程跟着走,一步一步实现代理.
<pre>
//初始化
_centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil
options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}];
//监听中心设备状态
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
if (central.state == CBCentralManagerStatePoweredOn) {
[self writeToLogWithText:@"中心设备已打开"];
[_centralManager scanForPeripheralsWithServices:nil options:nil];
} //中心设备CBCentralManagerStatePoweredOn状态下就可以开始搜索外设了
}
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
//搜索到指定的外设就可以开始连接外设了
[_centralManager connectPeripheral:peripheral options:nil];
}
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//连接成功后开始搜索服务
[peripheral discoverServices:nil];
}
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//搜索服务成功后查找指定服务中的特征
[peripheral discoverCharacteristics:nil forService:service];
}
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error{
//发现可用特征后,就可以进行相应的操作了,主要有以下三种
//情景一:读取
if (characteristic.properties & CBCharacteristicPropertyRead) {
if ([characteristic.UUID.UUIDString isEqualToString:kReadUUID]) {
[peripheral readValueForCharacteristic:characteristic];
if (characteristic.value) {
NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"读取到特征值:%@",value);
}
}
}//情景二:通知 if (characteristic.properties & CBCharacteristicPropertyNotify) { if ([characteristic.UUID.UUIDString isEqualToString:kNotifyUUID] || [characteristic.UUID.UUIDString isEqualToString:kWriteUUID]) { _curCharacter = characteristic; [peripheral setNotifyValue:YES forCharacteristic:characteristic]; [self writeToLogWithText:@"已订阅特征通知"]; } } //情景二:写数据 if (characteristic.properties & CBCharacteristicPropertyWrite) { if ([characteristic.UUID.UUIDString isEqualToString:kWriteUUID]) { [peripheral writeValue:[@"hello,外设" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; [self writeToLogWithText:@"写数据给外设"]; _curPeripherals = peripheral; _curCharacter = characteristic; } }
}
//根据不同的场景,会调用以下三个方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error;
}
</pre>
外设的开发
- 流程
<pre>- 打开peripheralManager,设置peripheralManager的委托
- 创建characteristics,characteristics的description 创建service,把characteristics添加到service中,再把service添加到peripheralManager中
- 开启广播advertising
- 对central的操作进行响应
- 4.1 读characteristics请求
- 4.2 写characteristics请求
- 4.4 订阅和取消订阅characteristics
</pre>
-
步骤
<pre>
//初始化
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
//启动外设
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
switch (peripheral.state) {
case CBPeripheralManagerStatePoweredOn:
//CBPeripheralManagerStatePoweredOn下添加服务和特征
[self setupService];
}
//创建服务,特征并添加服务到外围设备
-(void)setupService{//可通知的特征
// CBUUID *characteristicUUID = [CBUUID UUIDWithString:kNotifyUUID];
// _characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];//可读写的特征
CBUUID *UUID2 = [CBUUID UUIDWithString:kWriteUUID];
_characteristic = [[CBMutableCharacteristic alloc] initWithType:UUID2 properties:CBCharacteristicPropertyWrite|CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteEncryptionRequired];
//
//只读的特征
// CBUUID *UUID3 = [CBUUID UUIDWithString:kReadUUID];
// NSData *characteristicValue = [@"aaron才" dataUsingEncoding:NSUTF8StringEncoding];
// _characteristic = [[CBMutableCharacteristic alloc] initWithType:UUID3 properties:CBCharacteristicPropertyRead value:characteristicValue permissions:CBAttributePermissionsReadable];CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
_service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
[_service setCharacteristics:@[_characteristic]];[_peripheralManager addService:_service];
}
//添加服务后开始广播,等待中心设备的连接
-(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(nullable NSError *)error{
NSDictionary *dict = @{CBAdvertisementDataLocalNameKey:kPeripheralName};
[_peripheralManager startAdvertising:dict];
[self writeToLogWithText:@"向外围设备添加了服务"];
}
根据中心设备的响应,外设在代理中收到中心发来的信息,
//订阅特征
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;
//取消订阅
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
//中心设备读外设数据
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request;
//收到中心写来的数据
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests;
</pre>
UIBackgroundModes下的蓝牙数据传输
CoreBluetooth提供了非常友好的Background支持.按一下步骤,经过测试,app进入后台后,也能收到外设特征更新的值,据WWDC2013视频介绍,就算因为内存紧张,app在后台被杀掉,系统也会自动帮我们重新启动app进行蓝牙数据传输,不过这个就没测试到,不知道有没有人做过这方便的研究.
设置info.plist
<pre>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
</pre>
初始化方式中添加``options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}``
<pre>
_centralManager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil
options:@{CBCentralManagerOptionRestoreIdentifierKey:kRestoreIdentifierKey}];
</pre>
<pre>
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *,id> *)dict{
// NSArray *scanServices = dict[CBCentralManagerRestoredStateScanServicesKey];
// NSArray *scanOptions = dict[CBCentralManagerRestoredStateScanOptionsKey];
NSArray *peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey];
for (CBPeripheral *peripheral in peripherals) {
[self.peripherals addObject:peripheral];
peripheral.delegate = self;
}
}
</pre>
<pre>
-
(void)centralManagerDidUpdateState:(CBCentralManager *)central{
if (central.state == CBCentralManagerStatePoweredOn) {
[self writeToLogWithText:@"中心设备已打开"];
[_centralManager scanForPeripheralsWithServices:nil options:nil];//03,检查是否restore connected peripherals for (CBPeripheral *peripheral in _peripherals) { if (peripheral.state == CBPeripheralStateConnected) { NSUInteger serviceIdx = [peripheral.services indexOfObjectPassingTest:^BOOL(CBService * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { return [obj.UUID isEqual:kServiceUUID]; }]; if (serviceIdx == NSNotFound) { [peripheral discoverServices:@[kServiceUUID]]; continue; } CBService *service = peripheral.services[serviceIdx]; NSUInteger charIdx = [service.characteristics indexOfObjectPassingTest:^BOOL(CBCharacteristic * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { return [obj.UUID isEqual:kNotifyUUID]; }]; if (charIdx == NSNotFound) { [peripheral discoverCharacteristics:@[kNotifyUUID] forService:service]; continue; } CBCharacteristic *characteristic = service.characteristics[charIdx]; if (!characteristic.isNotifying) { [peripheral setNotifyValue:YES forCharacteristic:characteristic]; } } }
}else{
[_peripherals removeAllObjects];
}
}
</pre>
<pre>
-
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.NSArray *peripheralManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothPeripheralsKey];
NSArray *centraManagerIdentifiers = launchOptions[UIApplicationLaunchOptionsBluetoothCentralsKey];for (NSString *identifier in centraManagerIdentifiers) {
if ([identifier isEqualToString:kRestoreIdentifierKey]) {}
}
return YES;
}
</pre>
Demo
不动手写代码的学框架都是耍流氓,为了更好的学习,我也是自己都敲了一遍代码,前文中的三种场景以及后台模式都实现了,可以实现中心设备和外设之前的订阅特征,读写数据等功能.希望大家都交流.下面是传送门以及界面展示😊
github传送门 CoreBluetoothDemo
![中心设备]IMG_3660.PNG](http:https://img.haomeiwen.com/i1093584/e66b42d6c1c7133f.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
外设
网友评论