转载iOS 干货整理iOS蓝牙WiFi技术

IOS蓝牙通讯详解

2016-12-04  本文已影响1052人  Larrycal

前言

最近实验室做了一个IOS设备之间使用蓝牙进行数据交互的项目。中间遇到了很多坑,现在大致讲解一下蓝牙通讯的流程。干货请直接下翻到第四节。

iOS蓝牙基础知识

背景

iOS中蓝牙的实现方案iOS中提供了4个框架用于实现蓝牙连接:

用法简单只能用于iOS设备之间的连接,多用于游戏(比如五子棋对战),从iOS7开始过期


只能用于iOS设备之间的连接,从iOS7开始引入,主要用于文件共享(仅限于沙盒的文件)


可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内较少)


可用于第三方蓝牙设备交互,必须要支持蓝牙4.0硬件至少是4s,系统至少是iOS6蓝牙4.0以低功耗著称,一般也叫BLE(BluetoothLowEnergy)目前应用比较多的案例:运动手坏、嵌入式设备、智能家居

CoreBluetooth

什么是CoreBluetooth

The Core Bluetooth framework lets your iOS and Mac apps communicate with Bluetooth low energy devices. For example, your app can discover, explore, and interact with low energy peripheral devices, such as heart rate monitors, digital thermostats, and even other iOS devices.

The framework is an abstraction of the Bluetooth 4.0 specification for use with low energy devices. That said, it hides many of the low-level details of the specification from you, the developer, making it much easier for you to develop apps that interact with Bluetooth low energy devices. Because the framework is based on the specification, some concepts and terminology from the specification have been adopted. This chapter introduces you to the key terms and concepts that you need to know to begin developing great apps using the Core Bluetooth framework.

CoreBluetooth框架就是苹果公司为我们提供的一个库,我们可以使用这个库和其他支持蓝牙4.0的设备进行数据交互。值得注意的是在IOS10之后的APP中,我们需要在 info.plist文件中添加NSBluetoothPeripheralUsageDescription字段否则APP会崩溃

蓝牙通讯中的外围设备和中心设备

通常,设备之间进行通讯的时候都少不了中心设备和外围设备。一般来说,外围设备具有一些需要传递给中心设备的数据,而中心设备获取这些数据之后,可以进行相应的数据处理。同时,中心设备处理完数据之后,也可以向外围设备发送信息。例如,下图1-1中外围设备(心率感应器)将数据发送给IOS或者MAC上的APP中,即中心设备。

1-1 中心设备和外围设备

中心设备可以发现并连接到发送了广播的外围设备

外围设备向中心设备传递数据的主要方式是广播。广播中携带的数据有限(大致为31字节),里面包含了外围设备所想要向中心设备提供的数据。例如下图1-2中,一个温度监控仪可能会向外广播现在房间的温度,中心设备就能拿到温度数据进行相应的一些操作。所以在BLE技术中,广播是外围设备让其他设备能感知到它的主要方式

1-2 外围设备广播

外围设备中的数据结构

外围设备中包含一个或者多个服务或提供关于连接信号强度的一些信息。服务是一个数据集合,它包含完成一个(或多个)设备某些功能或特性的相关行为。例如,一个心率感应器的服务可能携带从心率传感器发送而来的数据。服务本身是由特征或服务(即引用其它服务)组成。特征则为外围设备的服务提供了更详细的细节。例如,刚刚提到的心率服务中的特性就可能包含心率设备的位置和心率测量数据。图1-3展示了这个心率感应设备中的结构。

1-3 外围设备中的服务和特征

中心设备和外围设备进行数据交互

中心设备、外围设备、外围设备数据的表现形式

本地中心设备对象蓝牙数据交互方式主要都通过Core Bluetooth框架进行。

1-4 本地中心设备在Core Bluetooth中的表现形式 1-5 外围设备的树状结构

本地外围设备对象

在IOS6和OS X v10.9之后,Mac和IOS设备都能够作为 本地外围设备 给包括Mac、iPhone、和iPad的设备分享数据。当你实现本地外围设备角色功能,在蓝牙交互角色中,你就表现为外围设备。

1-6 外围设备在Core Bluetooth中的表现形式 1-7 本地外围设备服务和特征的树状结构

蓝牙通讯应用实例

对蓝牙基础知识有了一个大致了解之后,我们现在利用CoreBluetooth框架进行实战。我们需要外围设备和中心设备模式,所以创建两个分别名为BluetoothCentralTestBluetoothPeripheralTest的项目。 在后期我们需要测试蓝牙的时候,如果只有一个IOS设备,我们可以下载一个名为 LightBlue 的软件用来模拟另一个蓝牙设备

创建外围设备

1、开启外围设备管理

2、设置服务、特征、描述等信息

3、开始广播


//

//LARPeripheralManager.m

//BlueToothPeripheralTest

//

//Created by柳钰柯on 2016/12/4.

//Copyright © 2016年柳钰柯. All rights reserved.

//

#import"LARPeripheralManager.h"

staticNSString *constServiceUUID1 =@"FFF0";

staticNSString *constnotiyCharacteristicUUID =@"FFF1";

staticNSString *constreadwriteCharacteristicUUID =@"FFF2";

staticNSString *constServiceUUID2 =@"FFE0";

staticNSString *constreadCharacteristicUUID =@"FFE1";

staticNSString *constLocalNameKey =@"iPhone";

@implementationLARPeripheralManager{

//外围设备管理器

CBPeripheralManager *peripheralManager;

//定时器

NSTimer *timer;

}

+ (instancetype)shareInstance

{

staticLARPeripheralManager *peripheral;

staticdispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

peripheral = [[selfalloc] init];

});

returnperipheral;

}

- (instancetype)init

{

if(self= [superinit]) {

NSLog(@"外围设备单例创建");

peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:selfqueue:nil];

}

returnself;

}

//初始化一些UUID和特征信息,此处设置的一些信息,在中心设备中可以根据需要进行过滤

- (void)setUp

{

// characteristic字段描述

CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];

/*

可以通知的Characteristic

properties:CBCharacteristicPropertyNotify

permissions: CBAttributePermissionsReadable

*/

CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nilpermissions:CBAttributePermissionsReadable];

/*

可读写的characteristics

properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead

permissions CBAttributePermissionsReadable | CBAttributePermissionsWriteable

*/

CBMutableCharacteristic *readwriteCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readwriteCharacteristicUUID] properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead value:nilpermissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];

//设置descriptor

CBMutableDescriptor *readwriteCharacteristicDescription1 = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"name"];

[readwriteCharacteristic setDescriptors:@[readwriteCharacteristicDescription1]];

/*

只读的Characteristic

properties:CBCharacteristicPropertyRead

permissions CBAttributePermissionsReadable

*/

CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readCharacteristicUUID] properties:CBCharacteristicPropertyRead value:nilpermissions:CBAttributePermissionsReadable];

//service1初始化并加入两个characteristics

CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];

[service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];

//service2初始化并加入一个characteristics

CBMutableService *service2 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];

[service2 setCharacteristics:@[readCharacteristic]];

//添加后就会调用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error

[peripheralManager addService:service1];

[peripheralManager addService:service2];

}

#pragma mark - <CBPeripheralManagerDelegate>

//检测蓝牙状态变化,当蓝牙状态改变时,自动回调

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral

{

switch(peripheral.state) {

//在这里判断蓝牙设别的状态当开启了则可调用setUp方法(自定义)

caseCBManagerStatePoweredOn:

NSLog(@"powered on");

//运行初始化方法

[selfsetUp];

break;

caseCBManagerStatePoweredOff:

NSLog(@"powered off");

break;

default:

break;

}

}

//添加了服务,添加服务后需要广播,一旦广播,外围设备就可以被中心设备发现,同样外围设备所携带的数据也能被中心设备捕获

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error

{

if(error) {

NSLog(@"%@",[error localizedDescription]);

return;

}

//添加服务后,发送广播

//发送广播后会自动调用

// - (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error

[peripheralManager startAdvertising:@{

CBAdvertisementDataServiceUUIDsKey :@[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],

CBAdvertisementDataLocalNameKey : LocalNameKey

}];

}

//通知发送了广播

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error

{

NSLog(@"已经开始广播");

}

//中心设备订阅特征后会调用这个方法

- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic

{

NSLog(@"订阅了%@的数据",characteristic.UUID);

//分配定时任务

timer = [NSTimer scheduledTimerWithTimeInterval:1

target:self

selector:@selector(sendData:)

userInfo:characteristic

repeats:YES];

}

//中心设备取消订阅特征后调用这个方法

-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{

NSLog(@"取消订阅%@的数据",characteristic.UUID);

//取消定时器

[timer invalidate];

}

//发送数据

- (void)sendData:(NSTimer *)t{

CBMutableCharacteristic *characteristic = t.userInfo;

if([peripheralManager updateValue:[[NSString stringWithFormat:@"Sending Data"] dataUsingEncoding:NSUTF8StringEncoding]forCharacteristic:characteristic onSubscribedCentrals:nil]) {

NSLog(@"发送数据成功");

}else{

NSLog(@"发送数据错误");

}

}

//中心设备读characteristics请求

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{

NSLog(@"didReceiveReadRequest");

//判断是否有读数据的权限

if(request.characteristic.properties & CBCharacteristicPropertyRead) {

//NSData *data = request.characteristic.value;

NSData *data = [[NSString stringWithFormat:@"通过characteristics读请求"] dataUsingEncoding:NSUTF8StringEncoding];

[request setValue:data];

//对请求作出成功响应

[peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];

}else{

[peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];

}

}

//外围设备写characteristics请求

- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{

NSLog(@"didReceiveWriteRequests");

CBATTRequest *request = requests[0];

//判断是否有写数据的权限

if(request.characteristic.properties & CBCharacteristicPropertyWrite) {

//需要转换成CBMutableCharacteristic对象才能进行写值

CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;

c.value = request.value;

NSLog(@"收到中心设备发送信息:%@",[[NSString alloc] initWithData:c.value encoding:NSUTF8StringEncoding]);

[peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];

}else{

[peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];

}

}

//外围设备更新描述后调用

- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral{

NSLog(@"peripheralManagerIsReadyToUpdateSubscribers");

}

@end

外围设备运行结果

创建中心设备

1、创建中心设备
2、发现外围设备
3、链接外围设备
4、发现服务和特征

4.1 获取服务
4.2 获取特征值
4.3 获取特征描述

5、 数据交互
6、 订阅通知
7、 断开链接


//

//LARCentralBlueTooth.m

//Unity-iPhone

//

//Created by柳钰柯on 2016/11/18.

//

//

#import"LARCentralBlueTooth.h"

staticNSString *constServiceUUID1 =@"FFF0";

staticNSString *constnotiyCharacteristicUUID =@"FFF1";

staticNSString *constreadwriteCharacteristicUUID =@"FFF2";

@interfaceLARCentralBlueTooth ()

/**系统蓝牙管理对象*/

@property(strong,nonatomic,readwrite) CBCentralManager *manager;

/**扫描到的设备*/

@property(strong,nonatomic,readwrite) NSMutableArray *discoverPeripheral;

/**当前连接设备*/

@property(strong,nonatomic) CBPeripheral *currentPeripheral;

@end

@implementationLARCentralBlueTooth

+ (instancetype)shareInstance{

staticLARCentralBlueTooth *blueTooth;

staticdispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

blueTooth = [[selfalloc] init];

});

returnblueTooth;

}

- (instancetype)init

{

if(self= [superinit]){

NSLog(@"单例实例化");

_manager = [[CBCentralManager alloc] initWithDelegate:selfqueue:dispatch_get_global_queue(0,0)];

_discoverPeripheral = [[NSMutableArray alloc] init];

}

returnself;

}

#pragma mark -

//检查设备蓝牙开关的状态

- (void)centralManagerDidUpdateState:(CBCentralManager *)central

{

if(central.state == CBManagerStatePoweredOn) {

NSLog(@"蓝牙已打开");

//开始扫描设备

//扫描到设备之后会进入

//-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI方法

[_manager scanForPeripheralsWithServices:niloptions:nil];

}else{

NSLog(@"蓝牙已关闭");

}

}

//发现设备后,根据过滤设置进行连接

-(void)centralManager:(CBCentralManager *)central

didDiscoverPeripheral:(CBPeripheral *)peripheral

advertisementData:(NSDictionary *)advertisementData

RSSI:(NSNumber *)RSSI

{

NSLog(@"搜索到了设备%@",peripheral.name);

//我的另外一个设备的名字以闫开头,可以在这里自行设置过滤

if([peripheral.name hasPrefix:@"闫"]) {

//持有设备

if(![self.discoverPeripheral containsObject:peripheral]) {

//数组或者变量保留外围设备二者选其一

//使用数组保留外围设备引用

// [self.discoverPeripheral addObject:peripheral];

//使用一个strong变量强引用持有外围设备

self.currentPeripheral = peripheral;

//连接设备

[_manager connectPeripheral:peripheral options:nil];

NSLog(@"准备连接设备%@",peripheral.name);

}

}

}

#pragma mark -连接状态

//连接设备失败自动调用

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

{

NSLog(@">>>连接%@失败>>>错误:%@",peripheral.name,[error localizedDescription]);

}

//断开连接自动调用

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral

{

NSLog(@">>>断开%@连接",peripheral.name);

}

//连接成功自动调用

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

{

NSLog(@">>>连接%@成功",peripheral.name);

//设置外围设备代理

[peripheral setDelegate:self];

//开始扫描外围设备的服务

//扫描到服务会进入

// - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error

[peripheral discoverServices:nil];

}

#pragma mark -

//扫描到设备的服务后会进入这个方法

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error

{

if(error){

NSLog(@"扫描服务错误:%@",[error localizedDescription]);

}else{

for(CBService *serverinperipheral.services) {

//扫描服务的特征

//扫描到后会进入

// - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error

[peripheral discoverCharacteristics:nilforService:server];

NSLog(@"搜索到了一个服务:%@",server.UUID.UUIDString);

}

}

}

//扫描到服务的特征值

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error

{

if(error) {

NSLog(@"扫描服务:%@的特征值错误:%@",service.UUID,[error localizedDescription]);

return;

}

//获取到特征值

for(CBCharacteristic *characteristicinservice.characteristics){

if([characteristic.UUID isEqual:[CBUUID UUIDWithString:notiyCharacteristicUUID]]) {

//订阅特征值的数据

[selfnotifyCharacteristic:peripheral characteristic:characteristic];

//读取发送的数据

[peripheral readValueForCharacteristic:characteristic];

}

}

}

//当获取到外围设备更新的描述信息后(即数据)会调用此方法

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{

if(error)

{

NSLog(@"更新特征值数据: %@错误: %@", characteristic.UUID,[error localizedDescription]);

}

//这里对收到的数据进行处理,需要和发送端一致

if(characteristic.value !=nil) {

NSString *data = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];

NSLog(@"收到外围设备发送的信息:%@",data);

[peripheral writeValue:[[NSString stringWithFormat:@"get"] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];

}

}

//设置通知

- (void)notifyCharacteristic:(CBPeripheral *)peripheral

characteristic:(CBCharacteristic *)characteristic{

NSLog(@"订阅通知成功");

//设置通知

[peripheral setNotifyValue:YESforCharacteristic:characteristic];

}

//取消通知

- (void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral

characteristic:(CBCharacteristic *)characteristic{

[peripheral setNotifyValue:NOforCharacteristic:characteristic];

}

//停止扫描并断开连接

-(void)disconnectPeripheral:(CBCentralManager *)centralManager

peripheral:(CBPeripheral *)peripheral{

//停止扫描

[centralManager stopScan];

//断开连接

[centralManager cancelPeripheralConnection:peripheral];

}

@end

中心设备获取数据

结语

参考

Core Bluetooth Overview

上一篇 下一篇

猜你喜欢

热点阅读