iOS蓝牙开发-1-通识

个人认为iOS蓝牙开发算是相对偏门的领域,做iOS蓝牙相关的开发也有一段时间了,在此写下蓝牙相关的东西,一则方便日后查阅,二则给后来之人一点参考。
关于蓝牙是什么就不多做解释了,百度一搜一大把。iOS蓝牙开发。蓝牙开发有两种:

    1.手机做中心 - CBCentralManager、外部硬件做外设 - CBPeripheral
    2.外部硬件做中心 - CBCentralManager、手机做外设 - CBPeripheral

一般情况下开发,都是以手机(App)作为中心,硬件设备(智能硬件、手环之类的)作为外设。这里只记录这种情况。
最开始主要有两个概念:服务(CBService)、特征(CBCharacteristic)。
服务:每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知这么几种方式。
特征:读、写数据等。
画了个简图,没有美术天赋。(“、、、”表示重复的省略)


外设-服务-特性结构图.png

刚接触蓝牙的人,估计会不太理解服务和特性是什么东西,我刚刚开始也没搞明白。我最开始接触蓝牙是从大学玩单片机的蓝牙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也可以查到。

IMG_1632.PNG

再介绍个串口调试助手,CoolTerm。能实时调试蓝牙模块。
代码地址:https://github.com/chan106/BLEDemo.git

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

推荐阅读更多精彩内容