iOS - Bluetooth 蓝牙

1、蓝牙介绍

2、iBeacon

  • 具体讲解见 Beacon

  • iBeacon 是苹果公司 2013 年 9 月发布的移动设备用 OS(iOS7)上配备的新功能。其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用 BLE 技术向周围发送自己特有的 ID,接收到该 ID 的应用软件会根据该 ID 采取一些行动。比如,在店铺里设置 iBeacon 通信模块的话,便可让 iPhone 和 iPad 上运行一资讯告知服务器,或者由服务器向顾客发送折扣券及进店积分。此外,还可以在家电发生故障或停止工作时使用 iBeacon 向应用软件发送资讯。

  • 苹果 WWDC 14 之后,对 iBeacon 加大了技术支持和对其用于室内地图的应用有个更明确的规划。苹果公司公布了 iBeacon for Developers 和 Maps for Developers 等专题页面。

3、iOS 蓝牙

3.1 常见简称

  • MFi:make for ipad ,iphone, itouch 专们为苹果设备制作的设备,开发使用 ExternalAccessory 框架。认证流程挺复杂的,而且对公司的资质要求较高,详见 iOS - MFi 认证

  • BLE:buletouch low energy,蓝牙 4.0 设备因为低耗电,所以也叫做 BLE,开发使用 CoreBluetooth 框架

  • GATT Profile(Generic Attribute Profile):GATT 配置文件是一个通用规范,用于在 BLE 链路上发送和接收被称为 “属性”(Attribute)的数据块。目前所有的 BLE 应用都基于 GATT。
    • 1) 定义两个 BLE 设备通过叫做 Service 和 Characteristic 的东西进行通信。中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。
    • 2) GATT 连接是独占的。基于 GATT 连接的方式的,只能是一个外设连接一个中心设备。
    • 3) 配置文件是设备如何在特定的应用程序中工作的规格说明,一个设备可以实现多个配置文件。
  • GAP(Generic Access Profile):用来控制设备连接和广播,GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。
    • 1) GATT 连接,必需先经过 GAP 协议。
    • 2) GAP 给设备定义了若干角色,主要两个:外围设备(Peripheral)和中心设备(Central)。
    • 3) 在 GAP 中外围设备通过两种方式向外广播数据:Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复)。
  • Profile:并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG(一个以制定蓝牙规范,以推动蓝牙技术为宗旨的跨国组织)或者外设设计者预先定义的 Service 的集合。

  • Service:服务,是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,可以自己设置。每个外设会有很多服务,每个服务中包含很多字段,这些字段的权限一般分为读 read,写 write,通知 notiy 几种,就是我们连接设备后具体需要操作的内容。

  • Characteristic:特征,GATT 事务中的最低界别,Characteristic 是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的 X/Y/Z 三轴值。与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。每个设备会提供服务和特征,类似于服务端的 API,但是机构不同。

  • Description:每个 Characteristic 可以对应一个或多个 Description 用户描述 Characteristic 的信息或属性。

  • Peripheral、Central:外设和中心,发起连接的是 Central,被连接的设备为 Peripheral。

3.2 工作模式

  • 蓝牙通信中,首先需要提到的就是 central 和 peripheral 两个概念。这是设备在通信过程中扮演的两种角色。直译过来就是 [中心] 和 [周边(可以理解为外设)]。iOS 设备既可以作为 central,也可以作为 peripheral,这主要取决于通信需求。

    • 例如在和心率监测仪通信的过程中,监测仪作为 peripheral,iOS 设备作为 central。区分的方式即是这两个角色的重要特点:提供数据的是谁,谁就是 peripheral;需要数据的是谁,谁就是 central。就像是 client 和 server 之间的关系一样。

      Bluetooth14

  • 那怎么发现 peripheral 呢

    • 在 BLE 中,最常见的就是广播。实际上,peripheral 在不停的发送广播,希望被 central 找到。广播的信息中包含它的名字等信息。如果是一个温度调节器,那么广播的信息应该还会包含当前温度什么的。那么 central 的作用则是去 scan,找到需要连接的 peripheral,连接后便可进行通信了。

    • 当 central 成功连上 peripheral 后,它便可以获取 peripheral 提供的所有 service 和 characteristic。通过对 characteristic 的数据进行读写,便可以实现 central 和 peripheral 的通信。

  • CoreBluetooth 框架的核心其实是两个东西,central 和 peripheral, 对应他们分别有一组相关的 API 和类。

    • 这两组 API 分别对应不同的业务场景,如下图,左侧叫做中心模式,就是以你的手机(App)作为中心,连接其他的外设的场景。而右侧称为外设模式,使用手机作为外设连接其他中心设备操作的场景。

      Bluetooth13

  • iOS 设备(App)作为 central 时:

    • 当 central 和 peripheral 通信时,绝大部分操作都在 central 这边。此时,central 被描述为 CBCentralManager,这个类提供了扫描、寻找、连接 peripheral(被描述为 CBPeripheral)的方法。

    • 下图标示了 central 和 peripheral 在 Core Bluetooth 中的表示方式:

      Bluetooth16

    • 当你操作 peripheral 的时候,实际上是在和它的 service 和 characteristic 打交道,这两个分别由 CBService 和 CBCharacteristic 表示。

  • iOS 设备(App)作为 Peripheral 时:

    • 在 OS X 10.9 和 iOS 6 以后,设备除了能作为 central 外,还可以作为 peripheral。也就是说,可以发起数据,而不像以前只能管理数据了。

    • 那么在此时,它被描述为 CBPeripheralManager,既然是作为 peripheral,那么这个类提供的主要方法则是对 service 的管理,同时还兼备着向 central 广播数据的功能。peripheral 同样会对 central 的读写要求做出相应。

    • 下图则是设备作为 central 和 Peripheral 的示意图:

      993906-20170205031717323-1168208350.png

    • 在充当 peripheral 时,CBPeripheralManager 处理的是可变的 service 和 characteristic,分别由 CBMutableService 和 CBMutableCharacteristic 表示。

  • 中心模式(CBCentralManager)流程:

    • 1、建立中心角色
    • 2、扫描外设(discover)
    • 3、连接外设(connect)
    • 4、扫描外设中的服务和特征(discover)
      • 4.1 获取外设的 services
      • 4.2 获取外设的 Characteristics,获取 Characteristics 的值,获取 Characteristics 的 Descriptor 和 Descriptor 的值
    • 5、与外设做数据交互(explore and interact)
    • 6、订阅 Characteristic 的通知
    • 7、断开连接(disconnect)
  • 外设模式(CBPeripheralManager)流程:

    • 1、启动一个 Peripheral 管理对象
    • 2、设置本地 Peripheral 服务、特性、描述、权限等等
    • 3、设置 Peripheral 发送广播
    • 4、设置处理订阅、取消订阅、读 characteristic、写 characteristic 的委托方法

3.3 服务、特征和特征的属性

  • 一个 peripheral 包含一个或多个 service,或提供关于信号强度的信息。service 是数据和相关行为的集合。例如,一个心率监测仪的数据就可能是心率数据。

  • service 本身又是由 characteristic 或者其他 service 组成的。characteristic 又提供了更为详细的 service 信息。还是以心率监测仪为例,service 可能会包含两个 characteristic,一个描述当前心率带的位置,一个描述当前心率的数据。

    Bluetooth15

  • 每个 characteristic 属性分为这么几种:读,写,通知这么几种方式。

        // 特征的定义枚举
        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
        };
  • 外设、服务、特征间的关系

    Bluetooth1

    • 一个 CBPeripheral(蓝牙设备) 有一个或者多个 CBService(服务),而每一个 CBService 有一个或者多个 CBCharacteristic(特征),通过可写的 CBCharacteristic 发送数据,而每一个 CBCharacteristic 有一个或者多个 Description 用于描述 characteristic 的信息或属性。

3.4 设备状态

  • 蓝牙设备状态:

    • 1、待机状态(standby):设备没有传输和发送数据,并且没有连接到任何设备。
    • 2、广播状态(Advertiser):周期性广播状态。
    • 3、扫描状态(Scanner):主动寻找正在广播的设备。
    • 4、发起链接状态(Initiator):主动向扫描设备发起连接。
    • 5、主设备(Master):作为主设备连接到其他设备。
    • 6、从设备(Slave):作为从设备连接到其他设备。
  • 五种工作状态:

    • 准备(standby)
    • 广播(advertising)
    • 监听扫描(Scanning)
    • 发起连接(Initiating)
    • 已连接(Connected)

3.5 蓝牙和版本的使用限制

  • 蓝牙 2.0:越狱设备

  • 蓝牙 4.0:iOS 6 以上

  • MFi 认证设备:无限制

3.6 设置系统使用蓝牙权限

  • 设置系统使用蓝牙权限

    Bluetooth2

4、中心模式的使用

  • 中心模式的应用场景:主设备(手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用。一般来说,外设(蓝牙设备,比如智能手环之类的东西)会由硬件工程师开发好,并定义好设备提供的服务,每个服务对于的特征,每个特征的属性(只读,只写,通知等等)。

  • 蓝牙程序需要使用真机调试。

4.1 App 连接外设的实现

  • 1、建立中心角色

    • 导入 CoreBluetooth 头文件,建立中心设备管理类,设置主设备委托。

          // 包含头文件
          #import <CoreBluetooth/CoreBluetooth.h>
      
          // 遵守协议
          @interface ViewController () <CBCentralManagerDelegate>
      
          // 中心设备管理器
          @property (nonatomic, strong) CBCentralManager *centralManager;
      
          - (IBAction)start:(UIButton *)sender {
      
              // 初始化 centralManager,nil 默认为主线程
              self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
          }
      
          #pragma mark - CBCentralManagerDelegate
      
          // 检查 App 设备蓝牙是否可用,协议方法
          - (void)centralManagerDidUpdateState:(CBCentralManager *)central {
      
              // 在初始化 CBCentralManager 的时候会打开设备,只有当设备正确打开后才能使用
      
              switch (central.state){
      
                  case CBManagerStatePoweredOn:        // 蓝牙已打开,开始扫描外设
      
                      NSLog(@"蓝牙已打开,开始扫描外设");
      
                      // 开始扫描周围的设备,自定义方法
                      [self sacnNearPerpherals];
      
                      break;
      
                  case CBManagerStateUnsupported:
      
                      NSLog(@"您的设备不支持蓝牙或蓝牙 4.0");
      
                      break;
      
                  case CBManagerStateUnauthorized:
      
                      NSLog(@"未授权打开蓝牙");
      
                      break;
      
                  case CBManagerStatePoweredOff:       // 蓝牙未打开,系统会自动提示打开,所以不用自行提示
      
                  default:
                      break;
              }
          }
      
          // 发现外围设备,协议方法
          - (void)centralManager:(CBCentralManager *)central
           didDiscoverPeripheral:(CBPeripheral *)peripheral
               advertisementData:(NSDictionary<NSString *,id> *)advertisementData
                            RSSI:(NSNumber *)RSSI {
              /*
               * central              中心设备
               * peripheral           外围设备
               * advertisementData    特征数据
               * RSSI                 信号强度
               */
      
              NSMutableString *string = [NSMutableString stringWithString:@"\n\n"];
              [string appendFormat:@"NAME: %@\n"            , peripheral.name];
              [string appendFormat:@"UUID(identifier): %@\n", peripheral.identifier];
              [string appendFormat:@"RSSI: %@\n"            , RSSI];
              [string appendFormat:@"adverisement:%@\n"     , advertisementData];
      
              NSLog(@"发现外设 Peripheral Info:\n %@", string);
      
              // 连接指定的设备,自定义方法
              [self connectPeripheral:peripheral];
          }
      
          // 连接外设成功,协议方法
          - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
      
              NSLog(@"%@ 连接成功", peripheral.name);
      
              // 停止扫描
              [central stopScan];
      
              // 扫描外设中的服务和特征,自定义方法
              [self discoverPeripheralServices:peripheral];
          }
      
          // 连接外设失败,协议方法
          - (void)centralManager:(CBCentralManager *)central
                  didFailToConnectPeripheral:(CBPeripheral *)peripheral
                           error:(NSError *)error {
      
              NSLog(@"%@ 连接失败", peripheral.name);
          }
      
          // 连接外设断开,协议方法
          - (void)centralManager:(CBCentralManager *)central
                  didDisconnectPeripheral:(CBPeripheral *)peripheral
                           error:(NSError *)error {
      
              NSLog(@"%@ 连接已断开", peripheral.name);
          }
  • 2、扫描外设(discover)

    • 扫描外设的方法需要放在 centralManager 成功打开的代理方法 - (void)centralManagerDidUpdateState:(CBCentralManager *)central 中,因为只有设备成功打开,才能开始扫描,否则会报错。

    • 扫描到外设后会进入代理方法 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI; 中。

          // 开始扫描周围的设备,自定义方法
          - (void)sacnNearPerpherals {
      
              NSLog(@"开始扫描周围的设备");
      
              /*
               * 第一个参数为 Services 的 UUID(外设端的 UUID),nil 为扫描周围所有的外设。
               * 第二参数的 CBCentralManagerScanOptionAllowDuplicatesKey 为已发现的设备是否重复扫描,YES 同一设备会多次回调。nil 时默认为 NO。
               */
              [self.centralManager scanForPeripheralsWithServices:nil
                                                          options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@NO}];
          }
  • 3、连接外设(connect)

    • 对要连接的设备需要进行强引用,否则会报错。

    • 一个主设备最多能连 7 个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的代理方法中。

          // 设备
          @property (nonatomic, strong) CBPeripheral *peripheral;
      
          // 连接指定的设备,自定义方法
          - (void)connectPeripheral:(CBPeripheral *)peripheral {
      
              NSLog(@"连接指定的设备");
      
              // 设置连接规则,这里设置的是 以 J 开头的设备
              if ([peripheral.name hasPrefix:@"J"]) {
      
                  // 对要连接的设备进行强引用,否则会报错
                  self.peripheral = peripheral;
      
                  // 连接设备
                  [self.centralManager connectPeripheral:peripheral options:nil];
              }
          }
  • 4、扫描外设中的服务和特征(discover)

    • 设备连接成功后,就可以扫描设备的服务了,同样是通过委托形式,扫描到结果后会进入委托方法。但是这个委托已经不再是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),这个委托包含了主设备与外设交互的许多回调方法,包括获取 services,获取 characteristics,获取 characteristics 的值,获取 characteristics 的 Descriptor,和 Descriptor的值,写数据,读 RSSI,用通知的方式订阅数据等等。

          // 遵守协议
          @interface ViewController () <CBPeripheralDelegate>
      
          // 扫描外设中的服务和特征,自定义方法
          - (void)discoverPeripheralServices:(CBPeripheral *)peripheral {
      
              // 设置外设代理
              self.peripheral.delegate = self;
      
              // 开始扫描外设
              [self.peripheral discoverServices:nil];
          }
      
          #pragma mark - CBPeripheralDelegate
      
          // 扫描到外设服务,协议方法
          - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
      
              if (error) {
                  NSLog(@"Discovered services for %@ with error: %@", peripheral.name, error.localizedDescription);
                  return;
              }
      
              for (CBService *service in peripheral.services) {
      
                  NSLog(@"扫描到外设服务:%@", service);
      
                  // 扫描服务的特征
                  [peripheral discoverCharacteristics:nil forService:service];
              }
          }
      
          // 扫描到服务的特征,协议方法
          - (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) {
      
                  NSLog(@"扫描到服务:%@ 的特征:%@", service.UUID, characteristic.UUID);
      
                  // 获取特征的值
                  [peripheral readValueForCharacteristic:characteristic];
      
                  // 搜索特征的 Descriptors
                  [peripheral discoverDescriptorsForCharacteristic:characteristic];
      
                  // // 连接成功,开始配对,发送第一次校验的数据,自定义方法
                  // [self writeCharacteristic:peripheral characteristic:characteristic value:self.pairAuthDatas[0]];
              }
          }
      
          // 获取到特征的值,协议方法
          - (void)peripheral:(CBPeripheral *)peripheral
                  didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
                       error:(NSError *)error {
      
              // value 的类型是 NSData,具体开发时,会根据外设协议制定的方式去解析数据
      
              NSLog(@"获取到特征:%@ 的值:%@", characteristic.UUID, [[NSString alloc] initWithData:characteristic.value
                                                                                     encoding:NSUTF8StringEncoding]);
      
              // if (...) {      // 第一次配对成功
              //     
              //     [self writeCharacteristic:peripheral char
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值