个人认为iOS蓝牙开发算是相对偏门的领域,做iOS蓝牙相关的开发也有一段时间了,在此写下蓝牙相关的东西,一则方便日后查阅,二则给后来之人一点参考。
关于蓝牙是什么就不多做解释了,百度一搜一大把。iOS蓝牙开发。蓝牙开发有两种:
1.手机做中心 - CBCentralManager、外部硬件做外设 - CBPeripheral
2.外部硬件做中心 - CBCentralManager、手机做外设 - CBPeripheral
一般情况下开发,都是以手机(App)作为中心,硬件设备(智能硬件、手环之类的)作为外设。这里只记录这种情况。
最开始主要有两个概念:服务(CBService)、特征(CBCharacteristic)。
服务:每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知这么几种方式。
特征:读、写数据等。
画了个简图,没有美术天赋。(“、、、”表示重复的省略)
刚接触蓝牙的人,估计会不太理解服务和特性是什么东西,我刚刚开始也没搞明白。我最开始接触蓝牙是从大学玩单片机的蓝牙2.0开始的(苹果是4.0),学电子的应该知道。那时候只需要设置AT指令,调用收发数据的API即可。
我的理解是:
【1】服务是用来提供传输数据通道的。比如,一个人体数据监测仪,服务A用于传输心率、服务B用于传输血压、服务C用于传输脑电波。。。嗯,是的,这么打个比方吧。
【2】服务A中(传输心率那个)有:特性1->用于App读取数据、特性2->用于App发送指令或者数据到硬件,特性3->用于读取硬件的mac地址。这样就将这些功能模块(心率、血压、脑电波等)及其接口(读取、发送等)都很好的分开了,我理解成面向对象的解耦。
下面是已手机App作为中心的大致步骤:
-[1]创建管理中心,并设置其代理。
-[2]判断手机蓝牙状态。(是否打开、是否支持、是否授权),这里可以做提示用户打开手机蓝牙之类的。。。
-[3]扫描外设。每次扫描到一个蓝牙外设,都会通过管理中心的代理方法将外设相关信息回调过来(外设名字、信号值、广播等),此时可以自定义一个对象用于保存这个外设。然后将这个模型添加进数组中,利用自定义tableview显示出来,就和网络请求展示数据类似。
-[4]连接外设。
-[5]连接外设成功,寻找需要的服务(比如我只需要监测心率,不需要发脑电波,那就只需要找到监测心率那个服务就好了)。
-[6]寻找到需要的服务后,接下来 -> 寻找需要的特性(读数据、写指令、读mac等需要的特性)。找到的这些特性,都将其作为属性保存起来,后续发送数据等会需要用到。
-[7]订阅读数据的特性。这个特性是这样的:每次硬件传输数据过来时,都会走这里,所以需要订阅它,以便能收到硬件的数据。
-[8]发送自己的指令,注意:每次最多只能发送20个字节。
-[9]有以上步骤,收发数据基本OK了,还有的就是写完善功能:自动连接,断线重连,过滤不需要的设备,按指定mac地址连接等等。
以下是相关代码:
///创建管理中心
_manager = [[CBCentralManager alloc] initWithDelegate:self
queue:nil];///默认在主线程中
///初始化数组,用于存放搜索到的外设
_allPeripheral = [NSMutableArray array];
///开始扫描外设
[_manager scanForPeripheralsWithServices:nil options:nil];
以下是管理中心代理方法:
#pragma mark - - - - - -管理中心代理 - - - - - - - - -
#pragma mark 【1】监测蓝牙状态
///这里可以做相应处理,当蓝牙状态: 未打开->提示用于打开蓝牙
/// 其他->提示其他
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state){
case CBManagerStateUnknown:
NSLog(@"蓝牙-未知");
break;
case CBManagerStateUnsupported:
NSLog(@"蓝牙-不支持");
break;
case CBManagerStateUnauthorized:
NSLog(@"蓝牙-未授权");
break;
case CBManagerStatePoweredOff:{///蓝牙关闭
NSLog(@"蓝牙-已关闭");
}
break;
case CBManagerStateResetting:
NSLog(@"蓝牙-复位");
break;
case CBManagerStatePoweredOn:{///蓝牙打开
NSLog(@"蓝牙-已打开");
}
break;
}
}
#pragma mark 【2】发现外部设备
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI{
BLEModel *model = [[BLEModel alloc] initWithPeripheral:peripheral rssi:RSSI advertisementData:advertisementData];
for (BLEModel *saveModel in _allPeripheral) {
if ([saveModel.peripheral isEqual:peripheral]) {
return;
}
}
[_allPeripheral addObject:model];
[_tableView reloadData];
}
#pragma mark 【3】连接外部蓝牙设备
- (void)connectToPeripheral:(CBPeripheral *)peripheral{
if (!peripheral) {
return;
}
[_manager connectPeripheral:peripheral options:nil];
}
#pragma mark 【4】连接外部蓝牙设备成功
- (void)centralManager:(CBCentralManager *)central
didConnectPeripheral:(CBPeripheral *)peripheral{
///连接成功
NSLog(@"连接成功,开始寻找服务和特征");
[peripheral discoverServices:nil];
}
#pragma mark 【5】连接外部蓝牙设备失败
- (void)centralManager:(CBCentralManager *)central
didFailToConnectPeripheral:(CBPeripheral *)peripheral
error:(NSError *)error{
///这里可以尝试重连
[_manager connectPeripheral:peripheral options:nil];
}
#pragma mark 【6】蓝牙外设连接断开,自动重连
- (void)centralManager:(CBCentralManager *)central
didDisconnectPeripheral:(CBPeripheral *)peripheral
error:(NSError *)error{
///当连接断开时,会走这个回调,可以做重连等
}
以下是外设代理方法:
#pragma mark - - - - - -外设代理 - - - - - - - - -
#pragma mark 【1】寻找蓝牙服务
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if(error){
NSLog(@"外围设备寻找服务过程中发生错误,错误信息:%@",error.localizedDescription);
}
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
for (CBService *service in peripheral.services) {
if([service.UUID isEqual:serviceUUID]){
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:kNotifyUUID],[CBUUID UUIDWithString:kWriteUUID],[CBUUID UUIDWithString:kReadMacUUID]] forService:service];
}
}
}
#pragma mark 【2】寻找蓝牙服务中的特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
if (error) {//报错直接返回退出
NSLog(@"didDiscoverCharacteristicsForService error : %@", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics)
{
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kNotifyUUID]]){///订阅读数据,执行此行代码,每次收到数据时才会走下面 “【8】直接读取特征值被更新后”
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
self.readCharacteristic = characteristic;
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kWriteUUID]]) {
///保存写数据的特征,用于给硬件设备发送数据
self.writeCharacteristic = characteristic;
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kReadMacUUID]]) {
///这个特征,在我目前的项目中是读取蓝牙mac地址的
self.readMACCharacteristic = characteristic;
}
}
if (_readCharacteristic && _writeCharacteristic) {
NSLog(@"连接成功");///此时才算真正连接成功,因为此时才有读、写特征,可以正常进行数据交互
}
}
#pragma mark 【3】直接读取特征值被更新后、即收到订阅的那个特征的数据
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
return;
}
NSLog(@"收到数据 -- %@",characteristic.value);
///这里就是处理数据了
}
然后是tableView代理
#pragma mark - tableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return _allPeripheral.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
BLETableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BLETableViewCell" forIndexPath:indexPath];
cell.model = _allPeripheral[indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
BLEModel *bleModel = _allPeripheral[indexPath.row];
bleModel.peripheral.delegate = self;
[_manager connectPeripheral:bleModel.peripheral options:nil];
}
代码中的几个宏定义:
#pragma mark - 蓝牙服务及属性
#define kServiceUUID @"0001"
#define kWriteUUID @"0002"
#define kNotifyUUID @"0003"
#define kReadMacUUID @"0004"
为什么是0001、0002等,是有原因的,这里介绍个App,叫LightBlue,可以查看到蓝牙设备的特性。当然这些也可以找硬件工程师拿到,或者自己查DataSheet也可以查到。
再介绍个串口调试助手,CoolTerm。能实时调试蓝牙模块。
代码地址:https://github.com/chan106/BLEDemo.git