BlueCapKit 0.7.0

BlueCapKit 0.7.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
Released最后发布2018年4月
SwiftSwift 版本4.1
SPM支持 SPM

Troy Stribling 维护。



Build Status CocoaPods Compatible Platform License Carthage Compatible

BlueCap: Swifter CoreBluetooth

功能

  • A futures 接口,用于替换协议实现。
  • Peripheral 连接、Service 扫描和 Service + Characteristic 发现以及 Characteristic 读写设定超时。
  • 一个用于指定 GATT 配置文件的 DSL。
  • 封装序列化和反序列化的特性配置文件类型。
  • 示例 应用实现了 CentralManager 和 PeripheralManager。
  • App Store 中可获得完全功能并可扩展的扫描器和周边模拟器。
  • 线程安全。
  • 综合测试覆盖率。

要求

  • iOS 10.0+
  • Xcode 9.3

安装

CocoaPods

CocoaPods 是一个 Xcode 依赖管理器。可以使用以下命令安装,

gem install cocoapods

需要 CocoaPods 1.1+,

BluCapKit 添加到您的 Podfile 中,

platform :ios, '10.0'
use_frameworks!

target 'Your Target Name' do
  pod 'BlueCapKit', '~> 0.7'
end

要启用 DBUG 输出,请将以下 post_install 脚本钩子 添加到您的 Podfile 中。

Carthage

Carthage 是 Xcode 项目的分布式依赖管理器。可以使用 Homebrew 安装,

brew update
brew install carthage

要将 BlueCapKit 添加到您的 Cartfile 中,

github "troystribling/BlueCap" ~> 0.7

运行以下命令以下载和构建 BlueCapKit.framework

carthage update

然后将其添加到您的项目中。

如果需要,使用 --no-build 选项,

carthage update --no-build

这将只下载 BlueCapKit。然后按照 手动 中的步骤将其添加到项目中。

Manual

  1. 将 BlueCap 放置在您的项目目录中的某个位置。您可以选择复制它或将它添加为 git 子模块。
  2. 打开 BlueCap 项目文件夹,将 BlueCapKit.xcodeproj 拖拽到您应用程序 Xcode 项目的项目导航器中。
  3. 在您的 Projects Info 选项卡中设置 iOS Deployment Target 为 9.0,并验证 BlueCapKit.xcodeproj 的 iOS Deployment Target 也设置为 9.0。
  4. 在您的项目目标的 General 选项卡中,将顶部 BlueCapKit.framework 添加为 Embedded Binary
  5. Build Phases 选项卡中,将 BlueCapKit.framework 添加为 Target Dependency,并在 Link Binary With Libraries 中添加 CoreLocation.framework 和 CoreBluetooth.framework。

入门

使用BlueCap可以轻松实现CentralManagerPeripheralManager应用程序,序列化和反序列化与蓝牙设备交换的消息,并定义可重用的GATT配置文件定义。BlueCap异步接口使用Futures而不是常规的阻塞接口或协议委托模式。可以将Futures逐个连接,将上一个Future的结果作为下一个Future的输入。这简化了应用程序的实现,因为消除了异步调用之间的状态持久性,并且代码不会分布在多个文件中,这在协议委托模式中是常见的,也不会深度嵌套,这在阻塞接口中是常见的。在本节中,将简要介绍应用程序的构建方法。以下章节将描述支持的使用案例。示例应用程序也可用。

CentralManager

将描述一个简单的CentralManager实现,该实现可扫描提供TiSensorTag加速度计服务的广告外围设备,在外围设备发现时连接,发现服务和特性并订阅加速度计数据更新。

所有应用程序都从调用CentralManager#whenStateChanges开始。

let manager = CentralManager(options: [CBCentralManagerOptionRestoreIdentifierKey : "us.gnos.BlueCap.central-manager-documentation" as NSString])

let stateChangeFuture = manager.whenStateChanges()

要开始扫描提供TiSensorTag加速度计服务的广告Peripherals,在whenStateChanges()后跟CentralManager#startScanning,并使用SimpleFuturesFutureStream#flatMap组合器将这两个结合起来。还定义了一个应用程序错误对象,

public enum AppError : Error {
    case invalidState
    case resetting
    case poweredOff
    case unknown
    case unlikely
}

let serviceUUID = CBUUID(string: TISensorTag.AccelerometerService.uuid)

let scanFuture = stateChangeFuture.flatMap { [weak manager] state -> FutureStream<Peripheral> in
    guard let manager = manager else {
        throw AppError.unlikely
    }
    switch state {
    case .poweredOn:
        return manager.startScanning(forServiceUUIDs: [serviceUUID])
    case .poweredOff:
        throw AppError.poweredOff
    case .unauthorized, .unsupported:
        throw AppError.invalidState
    case .resetting:
        throw AppError.resetting
    case .unknown:
        throw AppError.unknown
    }
}

scanFuture.onFailure { [weak manager] error in
    guard let appError = error as? AppError else {
        return
    }
    switch appError {
    case .invalidState:
	break
    case .resetting:
        manager?.reset()
    case .poweredOff:
        break
    case .unknown:
        break
    }
}

在此处,当收到.poweredOn时,开始扫描。在所有其他状态更改时,适当的错误被thrown,并在错误处理程序中处理。

要连接发现的周边设备,在扫描后跟随Peripheral#connect,并使用FutureStream#flatMap结合起来,

var peripheral: Peripheral?

let connectionFuture = scanFuture.flatMap { [weak manager] discoveredPeripheral  -> FutureStream<Void> in
    manager?.stopScanning()
    peripheral = discoveredPeripheral
    return peripheral.connect(connectionTimeout: 10.0)
}

在此处,在发现具有所需服务UUID的外围设备后也会停止扫描。

PeripheralLervicesCharacteristics需要被发现,并且需要处理连接错误。Characteristic发现是通过'Peripheral#discoverServices'和'Service#discoverCharacteristics'执行的,并在'AppError'中添加了更多错误。

public enum AppError : Error {
    case dataCharactertisticNotFound
    case enabledCharactertisticNotFound
    case updateCharactertisticNotFound
    case serviceNotFound
    case invalidState
    case resetting
    case poweredOff
    case unknown
    case unlikely
}

let discoveryFuture = connectionFuture.flatMap { [weak peripheral] () -> Future<Void> in
    guard let peripheral = peripheral else {
        throw AppError.unlikely
    }
    return peripheral.discoverServices([serviceUUID])
}.flatMap { [weak peripheral] () -> Future<Void> in
    guard let peripheral = peripheral, let service = peripheral.services(withUUID: serviceUUID)?.first else {
        throw AppError.serviceNotFound
    }
    return service.discoverCharacteristics([dataUUID, enabledUUID, updatePeriodUUID])
}

discoveryFuture.onFailure { [weak peripheral] error in
    switch error {
    case PeripheralError.disconnected:
        peripheral?.reconnect()
    case AppError.serviceNotFound:
        break
    default:
	break
    }
}

在此处,如果外围设备断开连接,则尝试重新连接,并处理AppError.serviceNotFound错误。最后,读取并订阅数据Characteristic并处理dataCharacteristicsNotFound

public enum AppError : Error {
    case dataCharactertisticNotFound
    case enabledCharactertisticNotFound
    case updateCharactertisticNotFound
    case serviceNotFound
    case invalidState
    case resetting
    case poweredOff
    case unknown
}

var accelerometerDataCharacteristic: Characteristic?

let subscriptionFuture = discoveryFuture.flatMap { [weak peripheral] () -> Future<Void> in
   guard let peripheral = peripheral, let service = peripheral.services(withUUID: serviceUUID)?.first else {
        throw AppError.serviceNotFound
    }
    guard let dataCharacteristic = service.service.characteristics(withUUID: dataUUID)?.first else {
        throw AppError.dataCharactertisticNotFound
    }
    accelerometerDataCharacteristic = dataCharacteristic
    return dataCharacteristic.read(timeout: 10.0)
}.flatMap { [weak accelerometerDataCharacteristic] () -> Future<Void> in
    guard let accelerometerDataCharacteristic = accelerometerDataCharacteristic else {
        throw AppError.dataCharactertisticNotFound
    }
    return accelerometerDataCharacteristic.startNotifying()
}.flatMap { [weak accelerometerDataCharacteristic] () -> FutureStream<Data?> in
    guard let accelerometerDataCharacteristic = accelerometerDataCharacteristic else {
        throw AppError.dataCharactertisticNotFound
    }
    return accelerometerDataCharacteristic.receiveNotificationUpdates(capacity: 10)
}

dataUpdateFuture.onFailure { [weak peripheral] error in
    switch error {
    case PeripheralError.disconnected:
        peripheral?.reconnect()
    case AppError.serviceNotFound:
        break
    case AppError.dataCharactertisticNotFound:
	break
    default:
	break
    }
}

这些示例可以编写为单个flatMap链,如CentralManager示例中所示。

PeripheralManager

将描述一个简单的PeripheralManager应用程序,该应用程序模拟一个支持所有CharacteristicsTiSensorTag加速度计服务。它将广播服务并对可写Characteristics上的特性写入请求做出响应。

首先创建CharacteristicsService,然后将Characteristics添加到Service中。

// create accelerometer service
let accelerometerService = MutableService(uuid: TISensorTag.AccelerometerService.uuid)

// create accelerometer data characteristic
let accelerometerDataCharacteristic = MutableCharacteristic(profile: RawArrayCharacteristicProfile<TISensorTag.AccelerometerService.Data>())

// create accelerometer enabled characteristic
let accelerometerEnabledCharacteristic = MutableCharacteristic(profile: RawCharacteristicProfile<TISensorTag.AccelerometerService.Enabled>())

// create accelerometer update period characteristic
let accelerometerUpdatePeriodCharacteristic = MutableCharacteristic(profile: RawCharacteristicProfile<TISensorTag.AccelerometerService.UpdatePeriod>())

// add characteristics to service
accelerometerService.characteristics = [accelerometerDataCharacteristic, accelerometerEnabledCharacteristic, accelerometerUpdatePeriodCharacteristic]

接下来创建PeripheralManager,添加Service并开始广播。

enum AppError: Error {
    case invalidState
    case resetting
    case poweredOff
    case unsupported
    case unlikely
}

let manager = PeripheralManager(options: [CBPeripheralManagerOptionRestoreIdentifierKey : "us.gnos.BlueCap.peripheral-manager-documentation" as NSString])

let startAdvertiseFuture = manager.whenStateChanges().flatMap { [weak manager] state -> Future<Void> in
    guard let manager = manager else {
        throw AppError.unlikely
    }
    switch state {
    case .poweredOn:
        manager.removeAllServices()
        return manager.add(self.accelerometerService)
    case .poweredOff:
        throw AppError.poweredOff
    case .unauthorized, .unknown:
        throw AppError.invalidState
    case .unsupported:
        throw AppError.unsupported
    case .resetting:
        throw AppError.resetting
    }
}.flatMap { [weak manager] _ -> Future<Void> in
    guard let manager = manager else {
        throw AppError.unlikely
    }
    manager.startAdvertising(TISensorTag.AccelerometerService.name, uuids:[CBUUID(string: TISensorTag.AccelerometerService.uuid)])
}

startAdvertiseFuture.onFailure { [weak manager] error in
    switch error {
    case AppError.poweredOff:
        manager?.reset()            
    case AppError.resetting:
        manager?.reset()
    default:
	break
    }
    manager?.stopAdvertising()
}

现在响应对 accelerometerEnabledFutureaccelerometerUpdatePeriodFuture 的写事件。

// respond to Update Period write requests
let accelerometerUpdatePeriodFuture = startAdvertiseFuture.flatMap {
    accelerometerUpdatePeriodCharacteristic.startRespondingToWriteRequests()
}

accelerometerUpdatePeriodFuture.onSuccess {  [weak accelerometerUpdatePeriodCharacteristic] (request, _) in
    guard let accelerometerUpdatePeriodCharacteristic = accelerometerUpdatePeriodCharacteristic else {
        throw AppError.unlikely
    }
    guard let value = request.value, value.count > 0 && value.count <= 8 else {
        return
    }
    accelerometerUpdatePeriodCharacteristic.value = value
    accelerometerUpdatePeriodCharacteristic.respondToRequest(request, withResult:CBATTError.success)
}

// respond to Enabled write requests
let accelerometerEnabledFuture = startAdvertiseFuture.flatMap {
    accelerometerEnabledCharacteristic.startRespondingToWriteRequests(capacity: 2)
}

accelerometerEnabledFuture.onSuccess { [weak accelerometerUpdatePeriodCharacteristic] (request, _) in
    guard let accelerometerEnabledCharacteristic = accelerometerEnabledCharacteristic else {
        throw AppError.unlikely
    }
    guard let value = request.value, value.count == 1 else {
        return
    }
    accelerometerEnabledCharacteristic.value = request.value
    accelerometerEnabledCharacteristic.respondToRequest(request, withResult:CBATTError.success)
}

详情请见 外设管理器示例

测试用例

测试用例可用。要运行,请输入:

pod install

并从生成的 workspace 中的测试选项卡中运行。

示例

有可用的示例,这些示例实现了中央管理器和外设管理器。还有 BluCap 应用程序。示例项目使用 CocoaPodsCarthage 构建。CocoaPods 项目需要在构建之前安装 Pod,

pod install

Carthage 项目需要:

carthage update
BlueCap BlueCap 提供中央管理器、外设管理器和 iBeacon 范围的 GATT 配置文件实现。在中央管理器模式下,提供用于 Bluetooth LE 外设的扫描器。在外设管理器模式下,支持模拟任何包含的 GATT 配置文件或 iBeacon。在 iBeacon 范围模式下,可以配置和监控信标区域。
中央管理器 中央管理器实现 BLE 中央管理器,扫描广告 TiSensorTag 加速度计服务的服务。当发现外设时,将建立连接,发现服务,启用加速度计,并且应用程序订阅加速度计数据更新。还可以更改数据更新周期。
带有配置文件的中央管理器 使用 GATT 配置文件定义创建服务的中央管理器版本。
外设管理器 外设管理器实现 BLE 外设管理器,广告 TiSensorTag 加速度计服务。外设管理器使用板载加速度计提供数据更新。
带有配置文件的外设管理器 使用 GATT 配置文件定义创建服务的外设版本。
信标 模拟 iBeacon 的外设。
信标 iBeacon 范围

文档

BlueCap 支持许多简化蓝牙低功耗应用程序编写的功能。以下各节将描述具有示例实现的用例。

  1. CentralManager:BlueCap CentralManager实现通过CBCentralManagerDelegateCBPeripheralDelegate协议实现替换为使用SimpleFutures的Scala Future接口。

  2. PeripheralManager:BlueCap PeripheralManager实现通过CBPeripheralManagerDelegate协议实现替换为使用SimpleFutures的Scala Future接口。

  3. 序列化/反序列化:设备消息的序列化和反序列化。

  4. GATT配置文件定义:定义可重用的GATT配置文件并将配置文件添加到BlueCap应用中。