Skip to content

StanfordSpezi/SpeziDevices

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SpeziDevices

Build and Test codecov DOI

Support interactions with Bluetooth Devices.

Overview

SpeziDevices provides three different targets: SpeziDevices, SpeziDevicesUI and SpeziOmron.

Screenshot showing paired devices in a grid layout. A sheet is presented in the foreground showing a nearby devices able to pair. Screenshot showing paired devices in a grid layout. A sheet is presented in the foreground showing a nearby devices able to pair. Displaying the device details of a paired device with information like Model number and battery percentage. Displaying the device details of a paired device with information like Model number and battery percentage. Showing a newly recorded blood pressure measurement. Showing a newly recorded blood pressure measurement.
Display paired in a grid-layout devices using DevicesView. Display device details using DeviceDetailsView. Display recorded measurements using MeasurementsRecordedSheet.

SpeziDevices

SpeziDevices abstracts common interactions with Bluetooth devices that are implemented using SpeziBluetooth. It supports pairing with devices and process health measurements.

Pairing Devices

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 PairableDevices 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.

Health Measurements

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

SpeziDevicesUI helps you to visualize Bluetooth device state and communicate interactions to the user.

Displaying paired devices

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")
                }
        }
    }
}

Displaying Measurements

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

SpeziOmron extends SpeziDevices with support for Omron devices. This includes Omron-specific models, characteristics, services and fully reusable device support.

Omron Devices

The OmronBloodPressureCuff and OmronWeightScale devices provide reusable device implementations for Omron blood pressure cuffs and the Omron weight scales respectively. 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: .advertisedService(BloodPressureService.self))
                Discover(OmronWeightScale.self, by: .advertisedService(WeightScaleService.self))
            }

            // If required, configure the PairedDevices and HealthMeasurements modules
            PairedDevices()
            HealthMeasurements()
        }
    }
}

Setup

You need to add the SpeziDevices Swift package to your app in Xcode or Swift package.

License

This project is licensed under the MIT License. See Licenses for more information.

Contributors

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.

Stanford Byers Center for Biodesign Logo Stanford Byers Center for Biodesign Logo