蓝牙通信

在iOS开发中,实现蓝牙通信有两种方式,一种是使用传统的GameKit.framework,另一种就是使用在iOS 5中加入的CoreBluetooth.framework。

利用CoreBluetooth框架,我们可以轻松实现两个iOS设备、iOS设备与非iOS蓝牙设备的交互。要注意的一点是目前这个框架只能支持蓝牙4.0BLE标准,所以对硬件上是有一定要求的,iPhone 4S及以后的设备,第三代iPad及以后的设备是支持这一标准的。

术语解释

我们首先看一下CoreBluetooth框架的结构。这是CoreBluetooth/CoreBluetooth.h文件中的声明。

#ifndef _CORE_BLUETOOTH_H_

#define _CORE_BLUETOOTH_H_

#endif

#import #import #import #import #import #import #import #import #import #import #import

其中,CBCentral开头的都是中心设备类,CBPeripheral开头的都是外设类。这就要讲到蓝牙设备的两个角色了。

中心设备:中心设备可以理解为是处理数据的iOS设备,比如你的 iPhone、iPad 等。

外设:外设顾名思义,就是产生数据的外部设备。这个外部设备可以是单片机、嵌入式设备甚至是另一个iOS设备等等。外设可以通过其传感器等产生有用数据,数据后通过蓝牙传给中心设备使用。

在建立连接的之前,外设向外发出广播数据(advertisementData,官方描述“A dictionary containing any advertisement and scan response data.”),广播数据是一个字典类数据,中心设备可以获取一定范围内的外设发出的广播数据。

现在开始

初始化

为了使用CoreBluetooth框架中的回调方法,我们要使用CBCentralManagerDelegate、CBPeripheralDelegate这两个协议。

我们先要初始化一个CBCentralManager类的对象。代码如下:

@interface SKBluetoothManager : NSObject{

CBCentralManager *manager;

id delegate;

}

manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

manager.delegate = self;

扫描外设

初始化完成,我们就可以让管理器开始扫描外设了:

[manager scanForPeripheralsWithServices:nil options:nil];

在设备发现周围外设的advertisementData后,会回调这个方法:

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

其中参数central指回调这个方法的中心设备,peripheral指发现的外设对象CBPeripheral,advertisementData就是前面说的字典类型广播数据,RSSI是当前外设的信号强度,单位是dbm。

刚开始对陌生的蓝牙设备调试时,建议我们先用一个数组NSArray将所扫描到的外设进行保存:

[_peripherals addObject:peripheral];

连接外设

保存后,可根据设备的UUID来确定该设备是否为我们需要进行操作的蓝牙设备,在确认外设身份后,即可发起对外设的连接操作:

if ([peripheral.identifier.UUIDString isEqualToString:kPeripheralUUID]) {

[manager stopScan];

[manager connectPeripheral:peripheral options:nil];

NSLog(@"连接外设:%@",peripheral.description);

self.peripheral = peripheral;

}

在此步操作后,我们完成了对蓝牙设备的扫描工作,接下来的回调方法分为两种情况:

连接到外设后

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

NSLog(@"已经连接到:%@", peripheral.description);

peripheral.delegate = self;

[central stopScan];

[peripheral discoverServices:nil];

}

一旦连接好外设,我们就可以马上停止扫描。然后发起对服务的搜索:

- (void)discoverServices:(NSArray *)serviceUUIDs;

此处参数位需要扫描的服务的UUID的数组。文档中特别提到,若该参数为nil,将会扫描所有的服务。

连接失败后

在连接外设失败的回调方法中,提供了error参数,可根据实际需要来做异常处理,在此不做过多说明

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

NSLog(@"连接%@失败",peripheral);

}

在搜索到蓝牙设备的服务后,将会回调

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

若有错误发生,通过NSError异常处理。

扫描服务

由于服务在peripheral里是以NSArray的形式存在的,所以我们要对peripheral中的所有服务进行遍历:

for (CBService *service in peripheral.services) {

//发现服务

if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {

NSLog(@"发现服务:%@", service.UUID);

[peripheral discoverCharacteristics:nil forService:service];

break;

}

}

扫描特征值

在遍历中,趁热打铁,直接对其特征值进行扫描,

[peripheral discoverCharacteristics:nil forService:service];

这里与扫描service是相同的,若扫描所有的特征值,直接传入nil作为参数即可。

这里的kServiceUUID是我们根据蓝牙设备的具体情况,提前设置好的UUID常量。本文所有kXxxxxUUID均为预设的UUID常量。

同样的,characteristics也是一个数组,我们利用像遍历services一样的方式来遍历所有的特征值。

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

if (error) {

NSLog(@"搜索特征%@时发生错误:%@", service.UUID, [error localizedDescription]);

return;

}

NSLog(@"服务:%@",service.UUID);

for (CBCharacteristic *characteristic in service.characteristics) {

//        NSLog(@"特征:%@",characteristic);

//发现特征

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

_writeCharacteristic = characteristic;

}

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

NSLog(@"监听特征:%@",characteristic);//监听特征

[self.peripheral setNotifyValue:YES forCharacteristic:characteristic];

_isConnected = YES;

}

}

}

特别要提到的是,我们不同的蓝牙设备有不同的服务和特征值。我的蓝牙模块的说明文档中已经说清楚了,write特征、read特征、notify特征,所以在此根据自身需要,来对不同的特征值进行操作。

设置监听

我在此要解释一下,当我们试图去读取蓝牙外设发过来的数据时,一定要找准特征值,用这个方法进行订阅。每次特征值变化的时候,就会有回调方法执行,从而达到读取数据的目的。容易出错误,一定分清楚到底哪个特征值该被订阅。

在订阅了特征值后,我们尝试用蓝牙外设发送一些数据出来,即可回调下一阶段的方法:

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

if (error) {

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

return;

}

// 收到数据

[delegate didGetDataForString:[self hexadecimalString:characteristic.value]];

//    NSLog(@"%@",[self hexadecimalString:characteristic.value]);

}

数据的转换

我们接收到的数据,正是characteristic.value,这是一个NSData类数据,我们可以通过UTF8StringEncoding来转化为NSString,为了代码结构清晰,我专门把NSData和NSString互转写成了两个方法:

//将传入的NSData类型转换成NSString并返回

- (NSString*)hexadecimalString:(NSData *)data{

NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

return result;

}

//将传入的NSString类型转换成NSData并返回

- (NSData*)dataWithHexstring:(NSString *)hexstring{

NSData *aData;

return aData = [hexstring dataUsingEncoding: NSASCIIStringEncoding];

}

在拿到字符串后,通过各种回调方法,处理UI变动。

结语

整个蓝牙开发实现方便,但回调方法非常多,新手容易晕头转向。按部就班把每个回调方法实现,即可保证蓝牙开发的顺利进行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容