Blues 1.0.3

Blues 1.0.3

Vincent Esche 维护。



Blues 1.0.3

Blues

Blues 是一个针对 iOS 的 Core Bluetooth 的高层次、面向对象的类型安全包装。

示例用法

Core Bluetooth 本身不提供任何类型安全,通过 "线" 发送裸未类型化的 Data 数据包。

相比之下,Blues 允许用户轻松指定外围设备、服务、特征、描述符以及通过 BLE 协议发送的实际值的具体类型。

让我们看一下如何实现一个简单的类型安全特性,例如 Battery 服务的 Battery Level 特性,该特性在蓝牙低功耗 GATT 规范中指定

电池级别值

电池级别特性被指定为返回 0 到 100(包括)的整数,对应于百分比电量

使用 GATT 读取特性值子过程读取电池级别特性,并返回当前电池电量的百分比,范围从 0%100%0% 表示电量完全耗尽,100% 表示电量完全充满。

一个表示这种值的 struct 实现可能看起来像这样

public struct BatteryLevel {
    public let percentage: UInt8

    public init(percentage: UInt8) {
        assert(percentage <= 100)
        self.percentage = percentage
    }
}

extension BatteryLevel: Equatable {
    public static func == (lhs: Battery.Level.Value, rhs: Battery.Level.Value) -> Bool {
        return lhs.percentage == rhs.percentage
    }
}

extension BatteryLevel: Comparable {
    public static func < (lhs: Battery.Level.Value, rhs: Battery.Level.Value) -> Bool {
        return lhs.percentage < rhs.percentage
    }
}

extension BatteryLevel: CustomStringConvertible {
    public var description: String {
        return "\(self.percentage)%"
    }
}

电池电平值转换器

Blues封装了Core Bluetooth,它本身只是发送和接收Data实例。

我们定义了一个CharacteristicValueTransformer,以允许Blues在从电池电平特性读取时发挥最大潜力。

import Blues

public struct BatteryLevelTransformer: CharacteristicValueTransformer {
    public typealias Value = Battery.Level.Value

    private static let codingError = "Expected value within 0 and 100 (inclusive)."

    public func transform(data: Data) -> Result<Value, TypedCharacteristicError> {
        let expectedLength = 1
        guard data.count == expectedLength else {
            return .err(.decodingFailed(message: "Expected data of \(expectedLength) bytes, found \(data.count)."))
        }
        return data.withUnsafeBytes { (buffer: UnsafePointer<UInt8>) in
            let percentage = buffer[0]
            if percentage <= 100 {
                return .ok(Value(percentage: percentage))
            } else {
                return .err(.decodingFailed(message: Transformer.codingError))
            }
        }
    }

    public func transform(value: Value) -> Result<Data, TypedCharacteristicError> {
        return .err(.transformNotImplemented)
    }
}

鉴于GATT规范将电池电平特性定义为只读,我们未完全实现transform(value:),而是直接返回错误。

电池电平特性

现在我们已经有了类型安全的BatteryLevel类型和匹配的值转换器,是时候编写类型安全的特性来利用它了。

import Blues

public class BatteryLevelCharacteristic: Blues.Characteristic,
    DelegatedCharacteristicProtocol,
    StringConvertibleCharacteristicProtocol,
    TypedCharacteristicProtocol,
    TypeIdentifiable
{
    public static let typeIdentifier = Identifier(string: "2A19")
    
    public typealias Transformer = BatteryLevelTransformer

    public let transformer: Transformer = .init()

    open override var name: String? {
        return "Battery-Level"
    }

    public weak var delegate: CharacteristicDelegate? = nil
}

通过提供weak public var delegate: CharacteristicDelegate?并遵守DelegatedCharacteristicProtocolBatteryLevelCharacteristic将自动将所有相关方法调用转发到其delegate

电池服务

Battery Level特性(即BatteryLevelCharacteristic)由GATT指定为电池服务(即BatteryService)的一部分。

import Blues

public class BatteryService: Blues.Service,
    DelegatedServiceProtocol,
    TypeIdentifiable
{
    public static let typeIdentifier = Identifier(string: "180F")

    weak public var delegate: ServiceDelegate?
    
    open var automaticallyDiscoveredCharacteristics: [Identifier]? {
        return [
            BatteryLevelCharacteristic.typeIdentifier
        ]
    }
}

extension BatteryService: ServiceDataSource {
    public func characteristic(with identifier: Identifier, for service: Service) -> Characteristic {
        switch identifier {
        case BatteryLevelCharacteristic.typeIdentifier:
            return BatteryLevelCharacteristic(identifier: identifier, service: service)
        default:
            return DefaultCharacteristic(identifier: identifier, service: service)
        }
    }
}

就像我们对BatteryLevelCharacteristic所做的一样,我们通过遵守相应的DelegatedServiceProtocol来启用BatteryService的自动调用代理。

我们覆盖了var automaticallyDiscoveredCharacteristics: [Identifier]?以启用对BatteryLevelCharacteristic特性的自动发现。

实现characteristic(with:for:)允许我们建立一个类型感知的服务和特性层次结构。

电池感知的外设

接下来,我们需要使我们的外围设备类感知到BatteryService,这通过实现ServiceDataSource来完成。

open class BatteryAwarePeripheral: Blues.Peripheral,
    DelegatedPeripheralProtocol,
    DataSourcedPeripheralProtocol
{
    public weak var delegate: PeripheralDelegate?

    open var automaticallyDiscoveredServices: [Identifier]? {
        return [
            BatteryService.typeIdentifier
        ]
    }
}

extension BatteryAwarePeripheral: ServiceDataSource {
    public func service(with identifier: Identifier, for peripheral: Peripheral) -> Service {
        switch identifier {
        case BatteryService.typeIdentifier:
            return BatteryService(identifier: identifier, peripheral: peripheral)
        default:
            return DefaultService(identifier: identifier, peripheral: peripheral)
        }
    }
}

BatteryService所做的一样,我们覆盖了var automaticallyDiscoveredServices: [Identifier]?以启用对BatteryService服务的自动发现。

中央管理器

最后但同样重要的是,我们需要为我们的 CentralManager 提供数据源

public class InsoleCentralManagerDataSource: CentralManagerDataSource {
    public func peripheral(
        with identifier: Identifier,
        advertisement: Advertisement?,
        for manager: CentralManager
    ) -> Peripheral {
        return BatteryAwarePeripheral(identifier: identifier, centralManager: manager)
    }
}

安装

向您的项目添加 Blues 的推荐方法是使用 Carthage

github 'regexident/Blues'

或者使用 CocoaPodsBlues 添加到您的项目

pod 'Blues'

许可证

Blues 使用的是 MPL-2许可证。详细信息请参见 LICENSE 文件。