Support interactions with Bluetooth Devices.
SpeziDevices provides three different targets: SpeziDevices
, SpeziDevicesUI
and SpeziOmron
.
SpeziDevices abstracts common interactions with Bluetooth devices that are implemented using SpeziBluetooth. It supports pairing with devices and process health measurements.
Pairing devices is a good way of making sure that your application only connects to fixed set of devices and doesn't accept data from non-authorized devices. Further, it might be necessary to ensure certain operations stay secure.
Use the PairedDevices
module to discover and pair PairableDevice
s and automatically manage connection establishment
of connected devices.
To support PairedDevices
, you need to adopt the PairableDevice
protocol for your device.
Optionally you can adopt the BatteryPoweredDevice
protocol, if your device supports the
BatteryService
.
Once your device is loaded, register it with the PairedDevices
module by calling the PairedDevices/configure(device:accessing:_:_:)
method.
Important
Don't forget to configure the PairedDevices
module in
your SpeziAppDelegate
.
import SpeziDevices
class MyDevice: PairableDevice {
@DeviceState(\.id) var id
@DeviceState(\.name) var name
@DeviceState(\.state) var state
@DeviceState(\.advertisementData) var advertisementData
@DeviceState(\.nearby) var nearby
@Service var deviceInformation = DeviceInformationService()
@DeviceAction(\.connect) var connect
@DeviceAction(\.disconnect) var disconnect
var isInPairingMode: Bool {
// determine if a nearby device is in pairing mode
}
@Dependency private var pairedDevices: PairedDevices?
required init() {}
func configure() {
pairedDevices?.configure(device: self, accessing: $state, $advertisementData, $nearby)
}
func handleSuccessfulPairing() { // called on events where a device can be considered paired (e.g., incoming notifications)
pairedDevices?.signalDevicePaired(self)
}
}
Tip
To display and manage paired devices and support adding new paired devices, you can use the full-featured DevicesView
view.
Use the HealthMeasurements
module to collect health measurements from nearby Bluetooth devices like connected weight scales or
blood pressure cuffs.
To support HealthMeasurements
, you need to adopt the HealthDevice
protocol for your device.
One your device is loaded, register its measurement service with the HealthMeasurements
module
by calling a suitable variant of configureReceivingMeasurements(for:on:)
.
import SpeziDevices
class MyDevice: HealthDevice {
@Service var deviceInformation = DeviceInformationService()
@Service var weightScale = WeightScaleService()
@Dependency private var measurements: HealthMeasurements?
required init() {}
func configure() {
measurements?.configureReceivingMeasurements(for: self, on: weightScale)
}
}
To display new measurements to the user and save them to your external data store, you can use MeasurementsRecordedSheet
.
Below is a short code example.
import SpeziDevices
import SpeziDevicesUI
struct MyHomeView: View {
@Environment(HealthMeasurements.self) private var measurements
var body: some View {
@Bindable var measurements = measurements
ContentView()
.sheet(isPresented: $measurements.shouldPresentMeasurements) {
MeasurementsRecordedSheet { measurement in
// handle saving the measurement
}
}
}
}
Important
Don't forget to configure the HealthMeasurements
module in
your SpeziAppDelegate
.
SpeziDevicesUI helps you to visualize Bluetooth device state and communicate interactions to the user.
When managing paired devices using PairedDevices
, SpeziDevicesUI provides reusable View components to display paired devices.
The DevicesView
provides everything you need to pair and manage paired devices.
It shows already paired devices in a grid layout using the DevicesGrid
. Additionally, it places an add button in the toolbar
to discover new devices using the AccessorySetupSheet
view.
struct MyHomeView: View {
var body: some View {
TabView {
NavigationStack {
DevicesView(appName: "Example") {
Text("Provide helpful pairing instructions to the user.")
}
}
.tabItem {
Label("Devices", systemImage: "sensor.fill")
}
}
}
}
When managing measurements using HealthMeasurements
, you can use the MeasurementsRecordedSheet
to display pending measurements.
Below is a short code example on how you would configure this view.
struct MyHomeView: View {
@Environment(HealthMeasurements.self) private var measurements
var body: some View {
@Bindable var measurements = measurements
ContentView()
.sheet(isPresented: $measurements.shouldPresentMeasurements) {
MeasurementsRecordedSheet { samples in
// save the array of HKSamples
}
}
}
}
Important
Don't forget to configure the HealthMeasurements
module in
your SpeziAppDelegate
.
SpeziOmron extends SpeziDevices with support for Omron devices. This includes Omron-specific models, characteristics, services and fully reusable device support.
The OmronBloodPressureCuff
and OmronWeightScale
devices provide reusable device implementations for the Omron BP5250
blood pressure cuff
and the Omron SC-150
weight scale.
Both devices automatically integrate with the HealthMeasurements
and PairedDevices
modules of SpeziDevices.
You just need to configure them for use with the Bluetooth
module.
import SpeziBluetooth
import SpeziBluetoothServices
import SpeziDevices
import SpeziOmron
class ExampleAppDelegate: SpeziAppDelegate {
override var configuration: Configuration {
Configuration {
Bluetooth {
Discover(OmronBloodPressureCuff.self, by: .accessory(manufacturer: .omronHealthcareCoLtd, advertising: BloodPressureService.self))
Discover(OmronWeightScale.self, by: .accessory(manufacturer: .omronHealthcareCoLtd, advertising: WeightScaleService.self))
}
// If required, configure the PairedDevices and HealthMeasurements modules
PairedDevices()
HealthMeasurements()
}
}
}
You need to add the SpeziDevices Swift package to your app in Xcode or Swift package.
This project is licensed under the MIT License. See Licenses for more information.
This project is developed as part of the Stanford Byers Center for Biodesign at Stanford University. See CONTRIBUTORS.md for a full list of all TemplatePackage contributors.