1. 思维导图

蓝牙知识的结构图
蓝牙数据通讯流程图

2. 苹果对蓝牙设备的要求

BLE:bluetouch low energy,蓝牙4.0设备因为低功耗,所有也叫作 BLE。苹果在 iPhone 4s 及之后的手机型号开始支持蓝牙 4.0,这也是最常见的蓝牙设备。低于蓝牙 4.0 协议的设备需要进行 MFI 认证。

在进行操作蓝牙设备前,我们先下载一个蓝牙工具 LightBlue,它可以辅助我们的开发,在进行蓝牙开发之前建议先熟悉一下 LightBlue 这个工具。

3. 操作蓝牙设备使用的库

苹果自身有一个操作蓝牙的库 CoreBluetooth.framework,这个是大多数人员进行蓝牙开发的首选框架,除此之外目前 github 还有一个比较流行的对原生框架进行封装的三方库 BabyBluetooth,它的机制是将 CoreBluetooth 中众多的 delegate 写成了 block 方法。下面主要介绍的是原生蓝牙库的知识。

3.1 中心和外围设备


如图所示,电脑、Pad、手机作为中心,心跳监听器作为外设,这种中心外设模式是最常见的。简单理解就是,发起连接的是中心设备(Central),被连接的是外围设备(Peripheral),对应传统的客户机-服务器体系结构。Central 能够扫描侦听到正在播放广告包的外设。

3.2服务与特征

外设可以包含一个或多个服务(CBService),服务是用于实现装置的功能或特征数据相关联的行为集合。
而每个服务又对应多个特征(CBCharacteristic),特征提供外设服务进一步的细节,外设,服务,特征对应的数据结构如下所示:

4. 如何扫描蓝牙

首先新建一个类作为蓝牙类,例如 BLEManager,写成单例,作为处理蓝牙操作的管理类。引入头文件#import <CoreBluetooth/CoreBluetooth.h>
CBCentralManager 是蓝牙中心的管理类,控制着蓝牙的扫描,连接,蓝牙状态的改变。

4.1 初始化
dispatch_queue_t centralQueue = dispatch_queue_create(“centralQueue",DISPATCH_QUEUE_SERIAL);
NSDictionary *dic = @{CBCentralManagerOptionShowPowerAlertKey : YES,CBCentralManagerOptionRestoreIdentifierKey : @"unique identifier"};
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:dic];

CBCentralManagerOptionShowPowerAlertKey 对应的 BOOL 值,当设为 YES 时,表示 CentralManager 初始化时,如果蓝牙没有打开,将弹出 Alert 提示框。
CBCentralManagerOptionRestoreIdentifierKey 对应的是一个唯一标识的字符串,用于蓝牙进程被杀掉恢复连接时用的。

4.2 扫描
//   不重复扫描已发现设备
NSDictionary *option = @{
CBCentralManagerScanOptionAllowDuplicatesKey : [NSNumber numberWithBool:NO],
CBCentralManagerOptionShowPowerAlertKey:YES
};
[self.centralManager scanForPeripheralsWithServices:nil options:option];
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;

扫面方法,serviceUUIDs 用于第一步的筛选,扫描此 UUID 的设备
options 有两个常用参数:CBCentralManagerScanOptionAllowDuplicatesKey 设置为 NO 表示不重复扫瞄已发现设备,为 YES 就是允许。CBCentralManagerOptionShowPowerAlertKey 设置为 YES 就是在蓝牙未打开的时候显示弹框。

4.3 CBCentralManagerDelegate 代理方法

在初始化的时候我们调用了代理,在 CoreBluetooth 中有两个代理,

  1. CBCentralManagerDelegate
  2. CBPeripheralDelegate

iOS 的命名很友好,我们通过名字就能看出,上面那个是关于中心设备的代理方法,下面是关于外设的代理方法。我们这里先研究 CBCentralManagerDelegate 中的代理方法

- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

这个方法标了 @required 是必须添加的,我们在 self.centralManager 初始换之后会调用这个方法,回调蓝牙的状态。状态有以下几种:

typedef NS_ENUM(NSInteger, CBCentralManagerState{CBCentralManagerStateUnknown = CBManagerStateUnknown,          //  未知状态CBCentralManagerStateResetting = CBManagerStateResetting,      //  重启状态CBCentralManagerStateUnsupported = CBManagerStateUnsupported,  //  不支持CBCentralManagerStateUnauthorized = CBManagerStateUnauthorized, //  未授权CBCentralManagerStatePoweredOff = CBManagerStatePoweredOff,     //  蓝牙未开启CBCentralManagerStatePoweredOn = CBManagerStatePoweredOn,     //  蓝牙开启
} NS_DEPRECATED(NA, NA, 5_0, 10_0, "Use CBManagerState instead”);

该枚举在 iOS10 之后已经废除了,系统推荐使用 CBManagerState,类型都是对应的

typedef NS_ENUM(NSInteger, CBManagerState{CBManagerStateUnknown = 0,CBManagerStateResetting,CBManagerStateUnsupported,CBManagerStateUnauthorized,CBManagerStatePoweredOff,CBManagerStatePoweredOn,
} NS_ENUM_AVAILABLE(NA, 10_0);- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;

peripheral 是外设类。
advertisementData 是广播的值,一般携带设备名,serviceUUIDs 等信息
RSSI 绝对值越大,表示信号越差,设备离的越远。如果想装换成百分比强度,(RSSI+100)/100,(这是一个约数,蓝牙信号值并不一定是-100 - 0的值,但近似可以如此表示)

- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *, id> *)dict;

在蓝牙于后台被杀掉时,重连之后会首先调用此方法,可以获取蓝牙恢复时的各种状态

5. 如何连接

在扫面的代理方法中,我们连接外设名是MI的蓝牙设备

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{    NSLog(@"advertisementData:%@,RSSI:%@",advertisementData,RSSI);      if([peripheral.name isEqualToString:@"MI"]){        [self.centralManager connectPeripheral:peripheral options:nil]; //  发起连接的命令!!!     self.peripheral = peripheral;     }
}
5.1 连接的状态

对应另外的 CBCentralManagerDelegate 代理方法
连接成功的回调

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

连接失败的回调

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

连接断开的回调

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

连接成功之后并没有结束,对数据的读写是由 CBCharacteristic 控制的。我们先用 lightblue 连接小米手环为例,来看一下,手环内部的数据是不是我们说的那样。

其中 ADVERTISEMENT DATA 显示的就是广播信息。

iOS 蓝牙无法直接获取设备蓝牙 MAC 地址,可以将 MAC 地址放到这里广播出来

FEEO 是 ServiceUUIDs,里面的 FF01、FF02 是 CBCharacteristic 的 UUID

Properties 是特征的属性,可以看出 FF01 具有读的权限,FF02 具有读写的权限。特征拥有的权限类别有如下几种:

typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties{CBCharacteristicPropertyBroadcast = 0x01,CBCharacteristicPropertyRead = 0x02,CBCharacteristicPropertyWriteWithoutResponse = 0x04,CBCharacteristicPropertyWrite = 0x08,CBCharacteristicPropertyNotify = 0x10,CBCharacteristicPropertyIndicate = 0x20,CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,CBCharacteristicPropertyExtendedProperties = 0x80,CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200};
5.2 如何发送并接收数据

通过上面的步骤我们发现CBCentralManagerDelegate提供了蓝牙状态监测、扫描、连接的代理方法,但是CBPeripheralDelegate的代理方法却还没使用。别急,马上就要用到了,通过名称判断这个代理的作用,肯定是跟Peripheral有关,我们进入系统API,看它的代理方法都有什么,因为这里的代理方法较多,我就挑选几个常用的拿出来说明一下。

1.代理方法

//   发现服务的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
//  发现特征的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
//  读数据的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
//  是否写入成功的回调- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

2.步骤
通过这几个方法我们构建一个流程:连接成功 -> 获取指定的服务 -> 获取指定的特征 -> 订阅指定特征值 -> 通过具有写权限的特征值写数据 -> 在 didUpdateValueForCharacteristic 回调中读取蓝牙反馈值

解释一下订阅特征值:特征值具有 Notify 权限才可以进行订阅,订阅之后该特征值的 value 发生变化才会回调didUpdateValueForCharacteristic

3. 实现上面流程的实例代码

//   连接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{//连接成功之后寻找服务,传nil会寻找所有服务[peripheral discoverServices:nil];
}// 发现服务的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{   if (!error) {        for (CBService *service in peripheral.services) {                NSLog(@"serviceUUID:%@", service.UUID.UUIDString);            if ([service.UUID.UUIDString isEqualToString:ST_SERVICE_UUID]) {//发现特定服务的特征值               [service.peripheral discoverCharacteristics:nil forService:service];            }        }    }
}// 发现 characteristics,由发现服务调用(上一步),获取读和写的 characteristics
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {    for (CBCharacteristic *characteristic in service.characteristics) {        //有时读写的操作是由一个characteristic完成        if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]) {   self.read = characteristic;           [self.peripheral setNotifyValue:YES forCharacteristic:self.read];        } else if ([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_WRITE]) {  self.write = characteristic;        }    }
}// 是否写入成功的代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{   if (error) {        NSLog(@"===写入错误:%@",error);    }else{        NSLog(@"===写入成功");    }
}// 数据接收
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {    if([characteristic.UUID.UUIDString isEqualToString:ST_CHARACTERISTIC_UUID_READ]){//获取订阅特征回复的数据NSData *value = characteristic.value;        NSLog(@"蓝牙回复:%@",value);}
}

比如我们要获取蓝牙电量,由硬件文档查询得知该指令是 0x1B9901 ,那么获取电量的方法就可以写成:

- (void)getBattery{Byte value[3]={0};value[0]=x1B;value[1]=x99;value[2]=x01;NSData * data = [NSData dataWithBytes:&value length:sizeof(value)];//   发送数据[self.peripheral writeValue:data forCharacteristic:self.write type:CBCharacteristicWriteWithoutResponse];
}

如果写入成功,我们将会在 didUpdateValueForCharacteristic 方法中获取蓝牙回复的信息。

6. 如何解析蓝牙数据

如果你顺利完成了上一步的操作,并且看到了蓝牙返回的数据,那么恭喜你,蓝牙的常用操作你已经了解大半了。因为蓝牙的任务大部分就是围绕发送指令,获取指令,将蓝牙数据呈现给用户。上一步我们已经获取了蓝牙指令,但是获取的却是 0x567b0629 这样的数据,这是什么意思呢。这时我们参考硬件文档,看到这样一段:

那么我们就可以得出设备电量是 60%。

对数据解析的流程就是:判断校验和是否正确,是不是一条正确的数据 -> 该条数据是不是我们需要的电量数据,即首字节为0x567b -> 根据定义规则解析电量,传给 view 显示。其中第一步校验数据,视情况而定,也有不需要的情况。

7. 扩展

iOS蓝牙中的进制转换
蓝牙固件升级
nRF芯片设备DFU升级

iOS:蓝牙通讯开发快速上手相关推荐

  1. Netron开发快速上手(一):GraphControl,Shape,Connector和Connection

    版权所有,引用请注明出处:<<http://www.cnblogs.com/dragon/p/5203663.html >> 本文所用示例下载FlowChart.zip 一个用 ...

  2. mongodb 输出数组字段_三分钟 mongodb 开发快速上手

    三分钟 mongodb 开发快速上手 小熊昨天晚上做了一个非常真实的噩梦,有读者朋友催我"怎么又没有发文!让我们等的好辛苦",今天一睁开眼眼看后台留言,特么,居然是真的! 我bil ...

  3. 《Iphone SDK3开发快速上手》

    <Iphone SDK3开发快速上手> --iPhone SDK 3 Visual QuickStart Guide 译者:(美)Duncan Campbell   著 刘红伟 等译 IS ...

  4. Java开发快速上手

    Java开发快速上手 前言 1.我的大学 2.对初学者的建议 3.大牛的三大特点 4.与他人的差距 第一章 了解Java开发语言 前言 基础常识 1.1 什么是Java 1.1.1 跨平台性 1.2 ...

  5. 《Unity2018AR与VR开发快速上手》随书内容资源相关说明

    我的第二本书,<Unity2018AR与VR开发快速上手>终于上市了,现在天猫和京东都有卖. 随书资源的百度盘链接被关闭了,只给了个"此链接分享内容可能因为涉及侵权.色情.反动. ...

  6. 《Unity 2018 AR与VR开发快速上手》简介

    #好书推荐##好书奇遇季#<Unity 2018 AR与VR开发快速上手>,京东当当天猫都有发售. Unity3D是由Unity Technologies公司开发的一款跨平台的游戏行业软件 ...

  7. 微信小程序开发快速上手

    微信小程序开发快速上手 由于学生工作需要,我最近在一些工具书的参考帮助下尝试进行了微信小程序的开发,本文主要内容为我本人在近期微信小程序开发的过程中,关于指定类型的微信小程序开发的一点心得体会. 1. ...

  8. 小程序|云开发快速上手

    文章目录 前言 云开发快速上手 注册微信小程序 注册页面:小程序注册页面 后台管理页:小程序后台管理登录页 必备工具与云开发文档 体验云开发模板小程序 开通云开发服务 找到云开发的环境 ID 指定小程 ...

  9. 《Unity3D平台AR开发快速上手--基于EasyAR4.0》随书资源和相关说明

    新手<Unity3D平台AR开发快速上手–基于EasyAR4.0>上市了,现在京东和淘宝都有卖.书分为2个部分,第一部分是EasyAR4.0基础内容和使用,第二部分是利用EasyAR的稀疏 ...

最新文章

  1. DOM+XPath提取规则注记!
  2. 数据结构:前缀,中缀,后缀表达式(逆波兰表达式)
  3. CVPR 2021 | 五官复原效果惊艳,腾讯ARC利用GAN人脸先验来解决
  4. x265-bitstream.h
  5. server.xml拒绝访问 无法修改
  6. 《101 Windows Phone 7 Apps》读书笔记-BABY MILESTONES
  7. java表单重复提交_JavaWeb防止表单重复提交(转载)
  8. 工欲善其事必先利其器,用Emmet提高HTML编写速度
  9. Java Script小技巧【对象,属性】(转载)
  10. 将Nginx加入service服务中
  11. 《Lua游戏AI开发指南》一第2章 创建并移动智能体
  12. 19-离线词典生成原理、图像描述子用BoW转化为BoW向量和FeatureVe
  13. 5G SA信令流程介绍
  14. 结算清单html模板,结算清单样本.xls
  15. 免费赠书,元旦快乐!
  16. 复盘二: 了解自我和管理自我,诚惶诚恐,保持敬畏-- 宁向东的清华管理学课总结
  17. AIGC能否助力三六零实现“戴维斯双击”?
  18. Matlab GUI编程技巧(十五):scroll滚动到组件内的位置及ScrollBar动画演示
  19. python相关pyc文件的编译、运行和反编译
  20. 计算机 发声原理,单片机的发声原理

热门文章

  1. python-opencv图像的高通滤波和低通滤波
  2. Linux学习之路——常见指令详细总结(中)
  3. Cisco-ENSA-网络安全考试测试题
  4. ESP8266(ESP-12F)案例实操 -- 8x32点阵显示(MAX7219)
  5. Iphone 和mac 登陆同一账户
  6. apereo CAS Server 6.2.x 和 6.4.x 的部署
  7. 基于web的家电维修系统/上门维修管理系统
  8. 原生javascript取代jquery的一些方法(jQuery-free)
  9. UnityShader_缺省属性贴图默认透明得方法和如何解决Assetbundle打包的坑
  10. 湖南省2022年成人高考招生全国统一考试考生须知