转载请注明出处ios
http://blog.csdn.net/pony_maggie/article/details/26740237
编程
做者:小马xcode
IOS学习也一段时间了,该上点干货了。前段时间研究了一下IOS蓝牙通信相关的东西,把研究的一个成果给你们分享一下。app
一 项目背景框架
简单介绍一下作的东西,设备是一个金融刷卡器,经过蓝牙与iphone手机通信。手机端的app经过发送不一样的指令(经过蓝牙)控制刷卡器执行一些动做,好比读磁条卡,读金融ic卡等。上几张图容易理解一些:iphone
看了上面几张图,你应该大概了解这是个什么东东了。函数
二 IOS 蓝牙介绍oop
蓝牙协议自己经历了从1.0到4.0的升级演变, 最新的4.0以其低功耗著称,因此通常也叫BLE(Bluetoothlow energy)。学习
iOS 有两个框架支持蓝牙与外设链接。一个是 ExternalAccessory。从ios3.0就开始支持,也是在iphone4s出来以前用的比较多的一种模式,可是它有个很差的地方,External Accessory须要拿到苹果公司的MFI认证。测试
另外一个框架则是本文要介绍的CoreBluetooth,在iphone4s开始支持,专门用于与BLE设备通信(由于它的API都是基于BLE的)。这个不须要MFI,而且如今不少蓝牙设备都支持4.0,因此也是在IOS比较推荐的一种开发方法。
三 CoreBluetooth介绍
CoreBluetooth框架的核心实际上是两个东西,peripheral和central, 能够理解成外设和中心。对应他们分别有一组相关的API和类,以下图所示:
若是你要编程的设备是central那么你大部分用到,反之亦然。在咱们这个示例中,金融刷卡器是peripheral,咱们的iphone手机 是central,因此我将大部分使用上图中左边部分的类。使用peripheral编程的例子也有不少,好比像用一个ipad和一个iphone通 讯,ipad能够认为是central,iphone端是peripheral,这种状况下在iphone端就要使用上图右边部分的类来开发了。
四 服务和特征
有个概念有必要先说明一下。什么是服务和特征呢(service and characteristic)?
每一个蓝牙4.0的设备都是经过服务和特征来展现本身的,一个设备必然包含一个或多个服务,每一个服务下面又包含若干个特征。特征是与外界交互的最小单位。好比说,一台蓝牙4.0设备,用特征A来描述本身的出厂信息,用特征B来与收发数据等。
服务和特征都是用UUID来惟一标识的,UUID的概念若是不清楚请自行google,国际蓝牙组织为一些很典型的设备(好比测量心跳和血压的设备)规定了标准的service UUID(特征的UUID比较多,这里就不列举了),以下:
固然还有不少设备并不在这个标准列表里,好比我用的这个金融刷卡器。蓝牙设备硬件厂商一般都会提供他们的设备里面各个服务(service)和特征(characteristics)的功能,好比哪些是用来交互(读写),哪些可获取模块信息(只读)等。
五 实现细节
做为一个中心要实现完整的通信,通常要通过这样几个步骤:
创建中心角色—扫描外设(discover)—链接外设(connect)—扫描外设中的服务和特征(discover)—与外设作数据交互(explore and interact)—断开链接(disconnect)。
1创建中心角色
首先在我本身类的头文件中要包含CoreBluetooth的头文件,并继承两个协议<CBCentralManagerDelegate,CBPeripheralDelegate>,代码以下:
2扫描外设(discover)
代码以下:
这个参数应该也是能够指定特定的peripheral的UUID,那么理论上这个central只会discover这个特定的设备,可是我实际测试发现,若是用特定的UUID传参根本找不到任何设备,我用的代码以下:
目前不清楚缘由,怀疑和设备自己在的广播包有关。
3链接外设(connect)
当扫描到4.0的设备后,系统会经过回调函数告诉咱们设备的信息,而后咱们就能够链接相应的设备,代码以下:
4扫描外设中的服务和特征(discover)
一样的,当链接成功后,系统会经过回调函数告诉咱们,而后咱们就在这个回调里去扫描设备下全部的服务和特征,代码以下:
一个设备里的服务和特征每每比较多,大部分状况下咱们只是关心其中几个,因此通常会在发现服务和特征的回调里去匹配咱们关心那些,好比下面的代码:
相信你应该已经注意到了,回调函数都是以"did"开头的,这些函数不用你调用,达到条件后系统后自动调用。
5与外设作数据交互(explore and interact)
发送数据很简单,咱们能够封装一个以下的函数:
_testPeripheral和_writeCharacteristic是前面咱们保存的设备对象和能够读写的特征。
而后咱们能够在外部调用它,好比固然我要触发刷卡时,先组好数据包,而后调用发送函数:
数据的读分为两种,一种是直接读(reading directly),另一种是订阅(subscribe)。从名字也能基本理解二者的不一样。实际使用中具体用一种要看具体的应用场景以及特征自己的属性。前一个好理解,特征自己的属性是指什么呢?特征有个properties字段(characteristic.properties),它是一个整型值,有以下几个定义:
好比说,你要交互的特征,它的properties的值是0x10,表示你只能用订阅的方式来接收数据。我这里是用订阅的方式,启动订阅的代码以下:
当设备有数据返回时,一样是经过一个系统回调通知我,以下所示:
6 断开链接(disconnect)
这个比较简单,只须要一个API就好了,代码以下:
六 成果展现
上几张效果图,UI没怎么修饰,主要关注功能,实现了读取磁道信息,与金融ic卡进行APDU交互等功能。
/////////**********************************************************/////////
#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define kServiceUUID @"3AF70026-AB81-4EF2-B90B-9C482B4812F1"
#define kCharacteristicUUID @"2F5A22C7-7AB5-446A-9F94-4E9B924BE508"
@interface ViewController ()<CBCentralManagerDelegate, CBPeripheralDelegate>{
CBCentralManager* _manager;
NSMutableData* _data;
CBPeripheral* _peripheral;
}
@end
@implementation ViewController
- (void)viewDidLoad {
//建立一个中央
_manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
if (central.state != CBCentralManagerStatePoweredOn) {
NSLog(@"蓝牙未打开");
return;
}
//开始寻找全部的服务
[_manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{
CBCentralManagerScanOptionAllowDuplicatesKey:@YES
}];
}
//寻找到服务
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
//中止寻找
[_manager stopScan];
if (_peripheral != peripheral) {
_peripheral = peripheral;
//开始链接周边
[_manager connectPeripheral:_peripheral options:nil];
}
}
//链接周边成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
[_data setLength:0];
_peripheral.delegate = self;
//链接周边服务
[_peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
}
//链接周边失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"链接失败");
}
//链接周边服务
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if (error) {
NSLog(@"错误的服务");
return;
}
//遍历服务
for (CBService* service in peripheral.services) {
if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
//链接特征
[_peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kCharacteristicUUID]] forService:service];
}
}
}
//发现特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error) {
NSLog(@"链接特征失败");
return;
}
//遍历特征
if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
for (CBCharacteristic* characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
//开始监听特征
[_peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
}
//监听到特征值更新
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"特征值出错");
return;
}
if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
return;
}
//若是有新值,读取新的值
if (characteristic.isNotifying) {
[peripheral readValueForCharacteristic:characteristic];
}
}
//收到新值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSString* str = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
}
@end
//////////////***********************//////////////////
#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define kServiceUUID @"3AF70026-AB81-4EF2-B90B-9C482B4812F1"
#define kCharacteristicUUID @"2F5A22C7-7AB5-446A-9F94-4E9B924BE508"
@interface ViewController ()<CBPeripheralManagerDelegate>{
CBPeripheralManager* _manager;
CBMutableService* _service;
CBMutableCharacteristic* _characteristic;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_manager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"发现外设");
}
//检测中央设备状态
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
if (peripheral.state != CBCentralManagerStatePoweredOn){
NSLog(@"蓝牙关闭");
return;
}
//开始中心服务
[self startService];
}
//开始中心服务
- (void)startService{
//经过uuid建立一个特征
CBUUID* characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
//第一个参数uuid,第二个参数决定这个特征怎么去用,第三个是是否加密
_characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
//建立一个服务uuid
CBUUID* serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
//经过uuid建立服务
_service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
//给服务设置特征
[_service setCharacteristics:@[_characteristic]];
//使用这个服务
[_manager addService:_service];
}
//添加服务后的回调
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
//若是没有错误,就能够开始广播服务了
if (error == nil) {
[_manager startAdvertising:@{
CBAdvertisementDataLocalNameKey:@"PKServer",
CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:kServiceUUID]]
}];
//[_manager updateValue:[@"pk" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:_characteristic onSubscribedCentrals:]
}
}
//有人链接成功后调用
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"链接成功");
[_manager updateValue:[@"pk" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:_characteristic onSubscribedCentrals:@[central]];
}
//断开调用
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"断开");
}
@end