iOS Bluetooth开发基础

蓝牙知识简介

蓝牙硬件

1、蓝牙4.0:包括蓝牙4.0以上的版本,iOS 6 以上才可以使用,不一定需要MFI认证,因为4.0以上是低功耗,现在大部分的蓝牙设备都是4.0以上,本文主要讲蓝牙4.0及以上设备开发,使用CoreBluetooth 框架。

2、蓝牙2.0:又称经典蓝牙,2.0的蓝牙硬件模块必须经过苹果的MFI认证,否则无法进行蓝牙开发,使用ExternalAccessory 框架。

蓝牙基础知识

MFI ---- 是apple公司 “Made for iOS”的英文缩写,是苹果公司对其授权配件厂商生产的外置配件的一种标识使用许可。有MFI标识的设备质量普遍较好。

BLE ---- buletouch low energy,蓝牙4.0以上设备因为低耗电,所以也叫做BLE。

central,peripheral ---- 中心,外设,发起连接的一端叫central,被连接的设备为perilheral。

service,Characteristic,Description ---- 服务,特征,描述。都可以从设备读取到。一个设备可以有多个服务,一个服务可以有多个特征,特征的权限分为写write,读read,通知notify等几种,每个服务都有相应的描述。可类比为:服务wervice=类Class,特征Characteristic=属性property,Description描述=注释。

CoreBluetooth框架主要包含两种业务模式:中心模式和外设模式,前者是用手机连接其他蓝牙设备,后者是手机当做蓝牙设备被连接。本人最近只用到中心模式。

连接蓝牙流程:

1、创建中心对象

2、扫描周围蓝牙设备

3、连接蓝牙设备

4、扫描蓝牙设备的服务和特征

5、和蓝牙设备的数据交互

6、断开连接

中心模式的应用场景:例如用手机上的APP去连接蓝牙设备(手环等)。蓝牙设备的服务和特征一般有硬件工程师定义好,以及每个特征的属性(读,写,通知),类似于后台定义接口,APP端调用。读属性用于从蓝牙设备获取数据,写属性用于往蓝牙设备传数据,通知是订阅一个属性后,一旦属性的值发生改变就会通知APP,类似于OC中观察者模式。本人做的项目蓝牙设备传输数据都是通过通知实现的。

具体实现步骤

1、工程导入CoreBluetooth.framework.
2、在类里面导入头文件 CoreBluetooth/CoreBluetooth.h,添加代理,创建中心对象
#import <CoreBluetooth/CoreBluetooth.h>
 @interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
 @interface ViewController (){
        //系统蓝牙设备管理对象,可以把他理解为主设备,通过他可以去扫描和连接外设
        CBCentralManager *manager;
        //用于保存被发现设备
        NSMutableDictionary *discoverPeripherals;
    }

这里一定要添加CBCentralManagerDelegate,CBPeripheralDelegate 2个代理,蓝牙大部分的回调都是通过代理方法实现的。

    //存放持有发现的设备,如果不持有设备会导致CBPeripheralDelegate方法不能正确回调
    discoverPeripherals = [[NSMutableDictionary alloc] init];
    //初始化并设置委托,最好一个线程的参数可以为nil,默认会就main线程
    manager= [[CBCentralManager alloc] initWithDelegate:self queue:nil];

3、扫描周围蓝牙设备
//此代理方法查看手机蓝牙状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{

    switch (central.state) {
        case CBCentralManagerStateUnknown:
            break;
        case CBCentralManagerStateResetting:
            break;
        case CBCentralManagerStateUnsupported:
            break;
        case CBCentralManagerStateUnauthorized:
            break;
        case CBCentralManagerStatePoweredOff:
            break;
        case CBCentralManagerStatePoweredOn:
            //只有蓝牙开启正常,才开始扫描周围的外设
            /*
             第一个参数可以设置搜索条件,nil就是扫描周围所有的外设,扫描到外设后会进入
             - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
             */
            [manager scanForPeripheralsWithServices:nil options:nil];
            
            break;
        default:
            break;
    }
}

只有状态为CBCentralManagerStatePoweredOn才能进行后续扫描,要不然扫描后不会走代理方法

//扫描到设备会进入此方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
  
      NSLog(@"当扫描到设备名称:%@",peripheral.name);
//RSSI 蓝牙信号强度
//这里可以根据设备名称去连接你的设备
}
4、连接设备
//扫描到设备会进入此方法(还是上面这个方法😁)
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
  
      NSLog(@"当扫描到设备名称:%@",peripheral.name);
//RSSI 蓝牙信号强度
//这里可以根据设备名称去连接你的设备

    //接下连接我们的测试设备,如果你没有设备,可以下载一个app叫lightbule的app去模拟一个设备
    //这里自己去设置下连接规则,我设置的是P开头的设备
    if ([peripheral.name isEqualToString:@"XXXX"]){

        //找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
        //我开始就掉进这个坑😂😂😂,可以用个容器装着或者全局对象(你也可以用数组,看自己需求来)
        [discoverPeripherals setObject:peripheral forKey:@"peripheral"];

         /*
         连接成功,失败,断开会进入各自的委托
         - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托
         - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
         - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
         */

        //连接设备
        [manager connectPeripheral:peripheral options:nil];


     }
}

//连接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@">>>连接到名称为(%@)的设备-成功",peripheral.name);
    //设置外设的代理,不设置的话连接成功不会走后续的方法了
    [peripheral setDelegate:self];

    //扫描外设Services,成功后会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    [peripheral discoverServices:nil];
}

//连接到Peripherals-失败
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@">>>连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
}

//Peripherals 断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@">>>外设连接断开连接 %@: %@\n", [peripheral name], [error localizedDescription]);
    

持有设备!持有设备!持有设备!重要的是说三遍!!!

5、扫描服务、特征
//扫描到Services
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
      NSLog(@">>>扫描到服务:%@",peripheral.services);
    if (error)
    {
        NSLog(@">>>Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
        return;
    }
    
    for (CBService *service in peripheral.services) {
        NSLog(@"服务UUID = %@",service.UUID);
        //扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
        [peripheral discoverCharacteristics:nil forService:service];
    }
    
}
//扫描到Characteristics
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    if (error)
    {
        NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
        return;
    }
    
    for (CBCharacteristic *characteristic in service.characteristics)
    {
        //这里根据服务和特征的UUID去进行数据交互
            //订阅特征
            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
            //往设备写写数据  xxx为NSData类型数据
            [peripheral writeValue:xxx forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]

    }

    //获取Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    for (CBCharacteristic *characteristic in service.characteristics){
        {
            [peripheral readValueForCharacteristic:characteristic];
        }
    }
    
    //搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    for (CBCharacteristic *characteristic in service.characteristics){
        [peripheral discoverDescriptorsForCharacteristic:characteristic];
    }

}

上面2个方法扫描主要是读取服务和特征的UUID,好进行下一步操作。服务和特征都有UUID,UUID好比是类的名称,属性名称,特征的值就像属性的值

6、读取数据
//获取的charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    //打印出characteristic的UUID和值
    //!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据

 NSLog(@"设备数据 == %@,%@,%@",characteristic.service.UUID,characteristic.UUID,characteristic.value)

}

上面就是读取特征具体的值,根据协议去解析数据,并转化为你需要的数据类型。

下面2个方法感觉用处不大,实际开发中,每个特征的的作用开发文档会写明的,或者硬件工程师会告诉你的

//搜索到Characteristic的Descriptors
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    

    //打印出Characteristic和他的Descriptors
    //NSLog(@"特征 uuid:%@",characteristic.UUID);
    for (CBDescriptor *d in characteristic.descriptors) {
        NSLog(@"特征描述 uuid:%@",d.UUID);
    }
}

//获取到Descriptors的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
    //打印出DescriptorsUUID 和value
    //这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析
    NSLog(@"characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
//写入数据回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
{
    if (error) {
        
        NSLog(@"写入数据失败");
        return;
    }
        NSLog(@"写入数据成功 %@",characteristic); 
}
//订阅回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    
    if (error) {
        NSLog(@"订阅特征 %@失败",characteristic);
        return;
    }
    NSLog(@"订阅特征 %@成功",characteristic);
}
//停止扫描并断开连接---自己写的不是代理😝
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
                 peripheral:(CBPeripheral *)peripheral{
    //停止扫描
    [centralManager stopScan];
    //断开连接
    [centralManager cancelPeripheralConnection:peripheral];
}

我自己做的蓝牙设备都是通过订阅某个特征从而获取数据的。

其中需要用到UUID的比较,留下个CBUUID转NSString的方法

- (NSString *)representativeString
{
    NSData *data = [self data];
    NSUInteger bytesToConvert = [data length];
    const unsigned char *uuidBytes = [data bytes];
    NSMutableString *outputString = [NSMutableString stringWithCapacity:16];
    for (NSUInteger currentByteIndex = 0; currentByteIndex < bytesToConvert; currentByteIndex++)
    {
        switch (currentByteIndex)
        {
            case 3:
            case 5:
            case 7:
            case 9:[outputString appendFormat:@"%02x-", uuidBytes[currentByteIndex]]; break;
            default:[outputString appendFormat:@"%02x", uuidBytes[currentByteIndex]];
        }
    }
    return outputString;
}

第一次做蓝牙设备的开发,遇到过几个坑,总结下:
1、没有持有CBCentralManager对象,导致调用连接方法后不走是否成功的代理方法。
2、忘记设置外设peripheral的代理了,导致不走后面扫描服务的代理方法
3、由于这个项目是帮别人做的,所有没有文档,只有一份安卓源码和安卓同事整理出来的一份简单文档,往哪个特征写数据,哪个特征读数据,都不清楚,把我搞得半死。其中有设备是往同一个特征读写数据;有往一个特征写数据,另外一个读数据的😂。我一个个去试,头疼啊。

项目背景

5个医疗类蓝牙设备:心电仪,血糖仪,血压仪,血氧仪,体脂称。

本人第一次写博客,第一次做蓝牙开发,如有错误,还望指正!万分谢谢!(内容有参考网络文章,侵删!)

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

推荐阅读更多精彩内容