SpeziBluetooth

Build and Test codecov DOI

使用现代编程范式连接 Bluetooth 设备并进行通信。

概述

Spezi Bluetooth 模块提供了一种便捷的方式来处理 Bluetooth 设备的 State 管理,从不同的服务和特征检索数据,以及向服务和特征的组合写入数据。

此软件包在底层使用了 Apple 的 CoreBluetooth 框架。

注意

您需要对 Bluetooth 术语和底层软件模型有一定的了解,才能理解 Spezi Bluetooth 模块的结构和 API。 您可以在Wikipedia Bluetooth Low Energy (LE) 软件模型部分蓝牙技术开发者指南中找到一个不错的概述。

设置

添加 Spezi Bluetooth 作为依赖项

您需要将 Spezi Bluetooth Swift 软件包添加到 Xcode 中的应用Swift 软件包中。

重要

如果您的应用程序尚未配置为使用 Spezi,请按照Spezi 设置文章设置核心 Spezi 基础设施。

注册模块

需要在基于 Spezi 的应用程序中使用 configurationSpeziAppDelegate 中注册 Bluetooth 模块。

class ExampleAppDelegate: SpeziAppDelegate {
    override var configuration: Configuration {
        Configuration {
            Bluetooth {
                // discover devices ...
            }
        }
    }
}

注意

您可以在 Spezi 文档中了解有关 Module 的更多信息

示例

创建您的 Bluetooth 设备

Bluetooth 模块允许使用 BluetoothDevice 实现和属性包装器(如 ServiceCharacteristic)来声明性地定义 Bluetooth 设备。

以下代码示例演示了如何实现自己的 Bluetooth 设备。

首先,我们通过实现 BluetoothService 来定义我们的 Bluetooth 服务。 我们使用 Characteristic 属性包装器来声明其特性。 请注意,值类型必须是可选的,并分别符合 ByteEncodableByteDecodableByteCodable

struct DeviceInformationService: BluetoothService {
    static let id: BTUUID = "180A"

    @Characteristic(id: "2A29")
    var manufacturer: String?
    @Characteristic(id: "2A26")
    var firmwareRevision: String?
}

我们现在可以在 MyDevice 实现中使用此 Bluetooth 服务,如下所示。

提示

我们使用 DeviceStateDeviceAction 属性包装器来访问设备状态及其操作。 这两个属性包装器也可以在 BluetoothService 类型中使用。

class MyDevice: BluetoothDevice {
    @DeviceState(\.id)
    var id: UUID
    @DeviceState(\.name)
    var name: String?
    @DeviceState(\.state)
    var state: PeripheralState

    @Service var deviceInformation = DeviceInformationService()

    @DeviceAction(\.connect)
    var connect
    @DeviceAction(\.disconnect)
    var disconnect

    required init() {}
}

配置 Bluetooth 模块

我们使用上述 BluetoothDevice 实现来配置 Bluetooth 模块在 SpeziAppDelegate 中。

import Spezi

class ExampleDelegate: SpeziAppDelegate {
    override var configuration: Configuration {
        Configuration {
            Bluetooth {
                // Define which devices type to discover by what criteria .
                // In this case we search for some custom FFF0 service that is advertised.
                Discover(MyDevice.self, by: .advertisedService("FFF0"))
            }
        }
    }
}

使用 Bluetooth 模块

在您的 Spezi 应用中配置了 Bluetooth 模块后,您可以在 Environment 中访问该模块。

您可以使用 scanNearbyDevices(enabled:with:minimumRSSI:advertisementStaleInterval:autoConnect:)autoConnect(enabled:with:minimumRSSI:advertisementStaleInterval:) 修饰符来扫描附近的设备和/或自动连接到第一个可用设备。 否则,您也可以使用 scanNearbyDevices(minimumRSSI:advertisementStaleInterval:autoConnect:)stopScanning() 手动启动和停止扫描附近的设备。

要检索附近设备的列表,您可以使用 nearbyDevices(for:)

提示

要轻松访问第一个连接的设备,您可以从 SwiftUI Environment 查询您的 BluetoothDevice 类型。 请务必使用相应的 Environment(_:) 初始化程序将属性声明为可选。

以下代码示例演示了从环境中检索 Bluetooth 模块、列出所有附近设备、自动连接到第一个设备以及显示当前连接设备的一些基本信息的所有这些步骤。

import SpeziBluetooth
import SwiftUI

struct MyView: View {
    @Environment(Bluetooth.self)
    var bluetooth
    @Environment(MyDevice.self)
    var myDevice: MyDevice?

    var body: some View {
        List {
            if let myDevice {
                Section {
                    Text("Device")
                    Spacer()
                    Text("\(myDevice.state.description)")
                }
            }

            Section {
                ForEach(bluetooth.nearbyDevices(for: MyDevice.self), id: \.id) { device in
                    Text("\(device.name ?? "unknown")")
                }
            } header: {
                HStack {
                    Text("Devices")
                        .padding(.trailing, 10)
                    if bluetooth.isScanning {
                        ProgressView()
                    }
                }
            }
        }
            .scanNearbyDevices(with: bluetooth, autoConnect: true)
    }
}

提示

使用 ConnectedDevices 从 SwiftUI 环境中检索已连接设备的完整列表。

检索设备

上一节介绍了如何发现附近的设备以及从环境中检索当前连接的设备。 这对于与当前附近的设备建立临时的连接非常有用。 但是,如果您想连接到特定的、以前配对的设备,这可能不是最有效的方法。 在这种情况下,您可以使用 retrieveDevice(for:as:) 方法来检索已知的设备。

以下是一个简短的代码示例,说明了此方法。

let id: UUID = ... // a Bluetooth peripheral identifier (e.g., previously retrieved when pairing the device)

let device = bluetooth.retrieveDevice(for: id, as: MyDevice.self)

await device.connect() // assume declaration of @DeviceAction(\.connect)

// Connect doesn't time out. Connection with the device will be established as soon as the device is in reach.

与 Spezi 模块集成

Spezi Module 是一种将您的应用程序构建到不同子系统中的好方法,并提供了广泛的功能来建模模块之间的关系和依赖性。 每个 BluetoothDevice 都是一个 Module。 因此,您可以使用标准的 模块依赖项基础设施,从任何 Spezi Module 中轻松访问您的 SpeziBluetooth 设备。 同时,每个 BluetoothDevice 都可以受益于与其他 Spezi Module 相同的功能。

以下是一个简短的代码示例,演示了 BluetoothDevice 如何使用 @Dependency 属性来与 Spezi 应用程序中配置的 Spezi Module 交互。

class Measurements: Module, EnvironmentAccessible, DefaultInitializable {
    required init() {}

    func recordNewMeasurement(_ measurement: WeightMeasurement) {
        // ... process measurement
    }
}

class MyDevice: BluetoothDevice {
    @Service var weightScale = WeightScaleService()
    
    // declare dependency to a configured Spezi Module
    @Dependency var measurements: Measurements
    
    required init() {}
    
    func configure() {
        weightScale.$weightMeasurement.onChange { [weak self] value in
            self?.handleNewMeasurement(value)
        }
    }
    
    private func handleNewMeasurement(_ measurement: WeightMeasurement) {
        measurements.recordNewMeasurement(measurement)
    }
}

有关更多信息,请参阅 API 文档

贡献

欢迎对此项目进行贡献。请务必先阅读贡献指南贡献者盟约行为准则

许可证

此项目已获得 MIT 许可证的许可。 有关更多信息,请参阅 许可证

Spezi Footer Spezi Footer