YmsCoreBluetooth 1.10.0

YmsCoreBluetooth 1.10.0

测试已测试
语言语言 Obj-CObjective C
许可证 Apache 2
发布最后发布2015年10月

Charles Choi 维护。



  • 作者
  • Charles Choi

YmsCoreBluetooth v1.10.0

一个基于块的框架,用于使用 CoreBluetooth API 构建 Bluetooth 4.0 低功耗(即智能或 LE)iOS 7 或 OS X 10.9 应用程序。包括 DeannaDeannaMac,分别用于 iOS 和 OS X 中的 TI SensorTag 通信的应用。

YmsCoreBluetooth 设计意图:或者,为什么你想使用这个框架

tl;dr

  • 针对蓝牙低功耗(BLE)通信的ObjectiveC基于块的API。
  • 操作(例如扫描、检索、连接、读取、写入)映射到 CoreBluetooth 的数据对象层次结构。
  • 您可以使用 CoreBluetooth 更快地构建应用程序。

更详细说明

块很酷。

蓝牙低功耗(BLE)事务在其本质上是两步的(请求-响应):CoreBluetooth 抽象化了此协议,使得请求行为与响应行为分离。两个阶段通过委托模式来协调:启动请求阶段的对象有一个代表对象,其有一个代表方法来处理相应的响应阶段。虽然功能强大,但委托模式可能难以使用,因为两步事务的行为被分成代码中的两个不同位置。

更方便的编程模式是使用与请求定义的回调块。当收到响应时,回调块可以被执行以处理它。“YmsCoreBluetooth 的设计意图是使用 Objective-C 块来定义 BLE 请求的响应行为”因此,这些请求包括

  • 扫描和/或检索外围设备
  • 连接到外围设备
  • 发现外围设备的服务
  • 发现服务的特征
  • 特征写和读
  • 设置特征的推送通知状态

层次结构操作很酷。

CoreBluetooth 的数据对象层次结构可以这样描述

  • A CBCentralManager 实例可以连接到多个 CBPeripheral 实例。
    • A CBPeripheral 实例可以有多个 CBService 实例。
      • A CBService 实例可以有多个 CBCharacteristic 实例。
        • 一个 CBCharacteristic 实例可以拥有多个 CBDescriptor 实例。

但是,现有的 CoreBluetooth API 并没有将 BLE 请求映射到数据对象层级。例如,连接到一个 CBPeripheral 实例是通过 CBCentralManager 实例实现的,而不是通过 CBPeripheral。写操作、读操作和设置 CBCharacteristic 的通知状态都是从 CBPeripheral 实例发起的,而不是从 CBCharacteristic。YmsCoreBluetooth 提供了一个 API,该 API 可以更自然地将操作映射到数据对象层级。

YMSCoreBluetooth 定义了容器类,这些类对应于 CoreBluetooth 对象层级

  • YMSCBCentralManager - 包含一个 CBCentralManager 实例。
    • YMSCBPeripheral - 包含一个 CBPeripheral 实例。
      • YMSCBService - 包含一个 CBService 实例。
        • YMSCBCharacteristic - 包含一个 CBCharacteristic 实例。
          • YMSCBDescriptor - 包含一个 CBDescriptor 实例。

但是,它们与 CoreBluetooth 的区别在于操作是基于对象类型的

  • YMSCBCentralManager
    • 扫描设备
    • 检索设备
  • YMSCBPeripheral
    • 连接和断开与中心设备
    • 发现与此设备关联的服务
  • YMSCBService
    • 发现与此服务关联的特性
    • 处理已设置为通知的特性通知更新
  • YMSCBCharacteristic
    • 设置通知状态(开 / 关)
    • 向特性写值
    • 读取特性的值
    • 发现与此特性关联的描述符
  • YMSCBDescriptor
    • 向描述符写值
    • 读取描述符的值

Deanna 和 DeannaMac

应用程序 DeannaDeannaMac 是演示应用程序,用于说明 YmsCoreBluetooth 的使用。这两个应用程序都使用 YmsCoreBluetooth 来表征 TI SensorTag,并提供对设备上六个传感器的可控制和可观察性。它还具有扫描其他 BLE 设备的能力。

应用程序 Deanna 最近进行了更改(版本 0.943),以支持 Wayne Dahlberg 提供的图形设计贡献。

显示代码

扫描外围设备

在下面的代码示例中,self 是 YMSCBCentralManager 的子类的实例。

__weak DEACentralManager *this = self;
[self scanForPeripheralsWithServices:nil
                             options:options
                           withBlock:^(CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI, NSError *error) {
                               if (error) {
                                   NSLog(@"Something bad happened with scanForPeripheralWithServices:options:withBlock:");
                                   return;
                               }

                               NSLog(@"DISCOVERED: %@, %@, %@ db", peripheral, peripheral.name, RSSI);
                               [this handleFoundPeripheral:peripheral];
                           }];

检索外围设备

在下面的代码示例中,self 是 YMSCBCentralManager 的子类的实例,而 identifiers 是一个包含 NSUUID 实例的数组,这些实例映射到已由 iOS 设备发现的设备。

[self retrievePeripheralsWithIdentifiers:identifiers];

注意 - 由于弃用了 [YMSCBCentralManager retrievePeripherals:withBlock:],此 API 在 iOS7 中已更改。

// I AM OLD CODE. 
__weak DEACentralManager *this = self;
[self retrievePeripherals:peripheralUUIDs
                withBlock:^(CBPeripheral *peripheral) {
                    [this handleFoundPeripheral:peripheral];
                }];

连接到外围设备

在下面的代码示例中,self 是 YMSCBPeripheral 的子类的实例。请注意,在回调中,发现服务和特性是以嵌套方式处理的。

- (void)connect {
    // Watchdog aware method
    [self resetWatchdog];

    [self connectWithOptions:nil withBlock:^(YMSCBPeripheral *yp, NSError *error) {
        if (error) {
            return;
        }

        // Example where only a subset of services is to be discovered.
        //[yp discoverServices:[yp servicesSubset:@[@"temperature", @"simplekeys", @"devinfo"]] withBlock:^(NSArray *yservices, NSError *error) {

        [yp discoverServices:[yp services] withBlock:^(NSArray *yservices, NSError *error) {
            if (error) {
                return;
            }

            for (YMSCBService *service in yservices) {
                if ([service.name isEqualToString:@"simplekeys"]) {
                    __weak DEASimpleKeysService *thisService = (DEASimpleKeysService *)service;
                    [service discoverCharacteristics:[service characteristics] withBlock:^(NSDictionary *chDict, NSError *error) {
                        [thisService turnOn];
                    }];

                } else if ([service.name isEqualToString:@"devinfo"]) {
                    __weak DEADeviceInfoService *thisService = (DEADeviceInfoService *)service;
                    [service discoverCharacteristics:[service characteristics] withBlock:^(NSDictionary *chDict, NSError *error) {
                        [thisService readDeviceInfo];
                    }];

                } else {
                    __weak DEABaseService *thisService = (DEABaseService *)service;
                    [service discoverCharacteristics:[service characteristics] withBlock:^(NSDictionary *chDict, NSError *error) {
                        for (NSString *key in chDict) {
                            YMSCBCharacteristic *ct = chDict[key];
                            //NSLog(@"%@ %@ %@", ct, ct.cbCharacteristic, ct.uuid);

                            [ct discoverDescriptorsWithBlock:^(NSArray *ydescriptors, NSError *error) {
                                if (error) {
                                    return;
                                }
                                for (YMSCBDescriptor *yd in ydescriptors) {
                                    NSLog(@"Descriptor: %@ %@ %@", thisService.name, yd.UUID, yd.cbDescriptor);
                                }
                            }];
                        }
                    }];
                }
            }
        }];
    }];
}

读取特性

在下面的代码示例中,self 是 YMSCBService 的子类的实例。所有发现的特性都被存储在 [YMSCBService characteristicDict] 中。

- (void)readDeviceInfo {

    YMSCBCharacteristic *system_idCt = self.characteristicDict[@"system_id"];
    __weak DEADeviceInfoService *this = self;
    [system_idCt readValueWithBlock:^(NSData *data, NSError *error) {
        NSMutableString *tmpString = [NSMutableString stringWithFormat:@""];
        unsigned char bytes[data.length];
        [data getBytes:bytes];
        for (int ii = (int)data.length; ii >= 0;ii--) {
            [tmpString appendFormat:@"%02hhx",bytes[ii]];
            if (ii) {
                [tmpString appendFormat:@":"];
            }
        }

        NSLog(@"system id: %@", tmpString);
        _YMS_PERFORM_ON_MAIN_THREAD(^{
            this.system_id = tmpString;
        });

    }];

    YMSCBCharacteristic *model_numberCt = self.characteristicDict[@"model_number"];
    [model_numberCt readValueWithBlock:^(NSData *data, NSError *error) {
        if (error) {
            NSLog(@"ERROR: %@", error);
            return;
        }

        NSString *payload = [[NSString alloc] initWithData:data encoding:NSStringEncodingConversionAllowLossy];
        NSLog(@"model number: %@", payload);
        _YMS_PERFORM_ON_MAIN_THREAD(^{
            this.model_number = payload;
        });
    }];
}

向特性写入

在下面的代码示例中,self 是 YMSCBService 的子类的实例。在此示例中,先向 'config' 特性写入,然后读取 'calibration' 特性。

- (void)requestCalibration {
    if (self.isCalibrating == NO) {

        __weak DEABarometerService *this = self;

        YMSCBCharacteristic *configCt = self.characteristicDict[@"config"];
        [configCt writeByte:0x2 withBlock:^(NSError *error) {
            if (error) {
                NSLog(@"ERROR: write request to barometer config to start calibration failed.");
                return;
            }

            YMSCBCharacteristic *calibrationCt = this.characteristicDict[@"calibration"];
            [calibrationCt readValueWithBlock:^(NSData *data, NSError *error) {
                if (error) {
                    NSLog(@"ERROR: read request to barometer calibration failed.");
                    return;
                }

                this.isCalibrating = NO;
                char val[data.length];
                [data getBytes:&val length:data.length];

                int i = 0;
                while (i < data.length) {
                    uint16_t lo = val[i];
                    uint16_t hi = val[i+1];
                    uint16_t cx = ((lo & 0xff)| ((hi << 8) & 0xff00));
                    int index = i/2 + 1;

                    if (index == 1) self.c1 = cx;
                    else if (index == 2) this.c2 = cx;
                    else if (index == 3) this.c3 = cx;
                    else if (index == 4) this.c4 = cx;
                    else if (index == 5) this.c5 = cx;
                    else if (index == 6) this.c6 = cx;
                    else if (index == 7) this.c7 = cx;
                    else if (index == 8) this.c8 = cx;

                    i = i + 2;
                }

                this.isCalibrating = YES;
                this.isCalibrated = YES;

            }];
        }];

    }
}

处理特性通知更新

YmsCoreBluetooth在没有使用块(blocks)处理BLE响应的地方之一是特色通知更新。这是因为此类更新是非同步和不可预测的。因此,必须为YMSCBService的任何子类实现处理器方法[YMSCBService notifyCharacteristicHandler:error:]来处理此类更新。

以下代码示例中,self是YMSCBService子类的一个实例。

- (void)turnOn {
    __weak DEABaseService *this = self;

    YMSCBCharacteristic *configCt = self.characteristicDict[@"config"];
    [configCt writeByte:0x1 withBlock:^(NSError *error) {
        if (error) {
            NSLog(@"ERROR: %@", error);
            return;
        }

        NSLog(@"TURNED ON: %@", this.name);
    }];

    YMSCBCharacteristic *dataCt = self.characteristicDict[@"data"];
    [dataCt setNotifyValue:YES withBlock:^(NSError *error) {
        NSLog(@"Data notification for %@ on", this.name);
    }];


    _YMS_PERFORM_ON_MAIN_THREAD(^{
        this.isOn = YES;
    });
}

- (void)notifyCharacteristicHandler:(YMSCBCharacteristic *)yc error:(NSError *)error {
    if (error) {
        return;
    }

    if ([yc.name isEqualToString:@"data"]) {
        NSData *data = yc.cbCharacteristic.value;

        char val[data.length];
        [data getBytes:&val length:data.length];

        int16_t v0 = val[0];
        int16_t v1 = val[1];
        int16_t v2 = val[2];
        int16_t v3 = val[3];

        int16_t amb = ((v2 & 0xff)| ((v3 << 8) & 0xff00));
        int16_t objT = ((v0 & 0xff)| ((v1 << 8) & 0xff00));

        double tempAmb = calcTmpLocal(amb);

        __weak DEATemperatureService *this = self;
        _YMS_PERFORM_ON_MAIN_THREAD(^{
            this.ambientTemp = @(tempAmb);
            this.objectTemp = @(calcTmpTarget(objT, tempAmb));
        });
    }
}

重要:为了通过KVO让主线程中的UI组件知道属性已更改,您必须在主线程上更新该属性。使用GCD调用dispatch_async()的实现宏_YMS_PERFORM_ON_MAIN_THREAD可以完成此操作。

#define _YMS_PERFORM_ON_MAIN_THREAD(block) dispatch_async(dispatch_get_main_queue(), block);

块回调设计

YmsCoreBluetooth使用的回调模式使用单个回调来处理成功和失败的响应。这通过包含一个error参数来实现。如果error对象不是nil,则可以实施处理失败的逻辑。否则,将实施处理成功的逻辑。

^(NSError *error) {
   if (error) {
      // Code to handle failure
      return;
   }
   // Code to handle success
}

文件命名约定

YmsCoreBluetooth框架是位于YmsCoreBluetooth目录下以YMSCB为前缀的文件集。

Deanna iOS应用程序的文件以DEA为前缀,位于Deanna目录中。

DeannaMac OS X应用程序的文件以DEM为前缀,位于DeannaMac目录中。

推荐的代码讲解

为了更好地了解YmsCoreBluetooth的工作原理,建议首先阅读以下BLE服务实现的源代码:

  • DEAAccelerometerService,DEABarometerService,DEAGyroscopeService,DEAHumidityService,DEAMagnetometerService,DEASimpleKeysService,DEATemperatureService,DEADeviceInfoService

然后是TI SensorTag的BLE外围实现

  • DEASensorTag

然后是管理所有已知外围设备的应用程序服务

  • DEACentralManager

类层次结构有助于说明上述类与YmsCoreBluetooth框架之间的关系。

使用YmsCoreBluetooth编写自己的蓝牙低功耗服务

通过阅读教程中如何为TI SensorTag完成实例的方法,了解如何编写自己的蓝牙低功耗服务。

问题

虽然YmsCoreBluetooth相当实用,但仍有改进的空间。请将任何问题或问题提交给YmsCoreBluetooth的GitHub项目

你正在使用YmsCoreBluetooth吗?让我们知道!

我正在整理使用YmsCoreBluetooth的项目列表。如果你不害羞,请让我知道,感谢您使用YmsCoreBluetooth!即使你害羞,也请让我知道,我保证保密。

注意

以下设备上测试了代码

  • iPhone 4S,iPod touch,iPhone 5,均运行iOS7
  • TI SensorTag固件1.2,1.3
  • iMac 27 Mid-2010,OS X 10.8.5

已知问题

  • 仅在支持CoreBluetooth的iOS或Mac硬件上使用此代码。iOS模拟器不受支持。

最新更改

周日 9月 27 2015

移除了对现已过时的 iOS 9 CoreBluetooth 代理方法的支持

  • [CBPeripheralDelegate peripheralDidInvalidateServices:]
  • [CBCentralManagerDelegate centralManager:didRetrievePeripherals:]
  • [CBCentralManagerDelegate centralManager:didRetrieveConnectedPeripherals:]

管理性更改

  • 更新版权至 2015
  • 使用语义版本号;版本号现在设置为 1.1.0 (4)

周一 1月 13 2014

添加了支持 BLE 地址的方法

  • initWithName:parent:baseHi:baseLo:serviceBLEOffset
  • addCharacteristic:withBLEOffset

周三 1月 1 2014

  • #97 - 添加 [YMSCBPeripheral readRSSI] 方法
  • #98 - 将 objectForKeyedSubscript: 方法公开给 YMSCBPeripheral 和 YMSCBService
  • #96 - 为 cbPeripheral.name 添加 [YMSCBPeripheral name] 方便访问者

周一 12月 2 2013

  • #92 - 重新实现 [YMSCBCentralManager count] 以使用 KVO 收集方法。
  • #94 - 实现 [YMSCBCentralManager removeAllPeripherals] 方法。

测试了 Mac 环境

  • OS X 10.8.3
  • iMac 27 Mid-2010

查看 以前更改列表

许可

Copyright 2013-2014 Yummy Melon Software LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://apache.ac.cn/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Author: Charles Y. Choi <[email protected]>