Skip to content

Commit

Permalink
Update README and documentation (#5)
Browse files Browse the repository at this point in the history
# Update README and documentation

## ♻️ Current situation & Problem
The initial PR did not provide any links between documentation targets
or within the README as the initial documentation deployment was only
available after the PR was merged.
SPI has since then published our package and built the documentation.
This PR now adds links to the online documentation.

## ⚙️ Release Notes 
* Add links to the online documentation.
* Fix Swift 6 compatibility.

## 📚 Documentation
--


## ✅ Testing
--

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
Supereg authored Jul 15, 2024
1 parent 6b5e24f commit 641db17
Show file tree
Hide file tree
Showing 40 changed files with 303 additions and 75 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ jobs:
scheme: SpeziDevices-Package
resultBundle: SpeziDevices-iOS.xcresult
artifactname: SpeziDevices-iOS.xcresult
packageios_latest:
name: Build and Test Swift Package iOS Latest
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziDevices-Package
xcodeversion: latest
swiftVersion: 6
resultBundle: SpeziDevices-iOS-Latest.xcresult
artifactname: SpeziDevices-iOS-Latest.xcresult
ios:
name: Build and Test iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand All @@ -33,6 +43,17 @@ jobs:
scheme: TestApp
resultBundle: TestApp-iOS.xcresult
artifactname: TestApp-iOS.xcresult
ios_latest:
name: Build and Test iOS Latest
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
path: 'Tests/UITests'
scheme: TestApp
xcodeversion: latest
swiftVersion: 6
resultBundle: TestApp-iOS-Latest.xcresult
artifactname: TestApp-iOS-Latest.xcresult
codeql:
name: CodeQL
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/monthly-markdown-link-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# This source file is part of the Stanford Spezi open source project
#
# SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
#
# SPDX-License-Identifier: MIT
#

name: Monthly Markdown Link Check

on:
# Runs at midnight on the first of every month
schedule:
- cron: "0 0 1 * *"

jobs:
markdown_link_check:
name: Markdown Link Check
uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
3 changes: 3 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ jobs:
swiftlint:
name: SwiftLint
uses: StanfordBDHG/.github/.github/workflows/swiftlint.yml@v2
markdown_link_check:
name: Markdown Link Check
uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ authors:
given-names: "Andreas"
orcid: "https://orcid.org/0000-0002-1680-237X"
title: "SpeziDevices"
doi: 10.5281/zenodo.7538165
doi: 10.5281/zenodo.12627487
url: "https://github.com/StanfordSpezi/SpeziDevices"
63 changes: 43 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ SPDX-License-Identifier: MIT

[![Build and Test](https://github.com/StanfordSpezi/SpeziDevices/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/StanfordSpezi/SpeziDevices/actions/workflows/build-and-test.yml)
[![codecov](https://codecov.io/gh/StanfordSpezi/SpeziDevices/graph/badge.svg?token=pZeJyWYhAk)](https://codecov.io/gh/StanfordSpezi/SpeziDevices)
<!-- TODO: DOI BADGE-->
<!-- TODO: SPI BADES-->
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.12627487.svg)](https://doi.org/10.5281/zenodo.12627487)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FStanfordSpezi%2FSpeziDevices%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/StanfordSpezi/SpeziDevices)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FStanfordSpezi%2FSpeziDevices%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/StanfordSpezi/SpeziDevices)

Support interactions with Bluetooth Devices.

## Overview

SpeziDevices provides three different targets: `SpeziDevices`, `SpeziDevicesUI` and `SpeziOmron`.
SpeziDevices provides three different targets: [`SpeziDevices`](https://swiftpackageindex.com/StanfordSpezi/SpeziDevices/documentation/spezidevices),
[`SpeziDevicesUI`](https://swiftpackageindex.com/StanfordSpezi/SpeziDevices/documentation/spezidevicesui)
and [`SpeziOmron`](https://swiftpackageindex.com/StanfordSpezi/SpeziDevices/documentation/speziomron).

|![Screenshot showing paired devices in a grid layout. A sheet is presented in the foreground showing a nearby devices able to pair.](Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices.png#gh-light-mode-only) ![Screenshot showing paired devices in a grid layout. A sheet is presented in the foreground showing a nearby devices able to pair.](Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices~dark.png#gh-dark-mode-only)|![Displaying the device details of a paired device with information like Model number and battery percentage.](Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/DeviceDetails.png#gh-light-mode-only) ![Displaying the device details of a paired device with information like Model number and battery percentage.](Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/DeviceDetails~dark.png#gh-dark-mode-only)| ![Showing a newly recorded blood pressure measurement.](Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/MeasurementRecorded_BloodPressure.png#gh-light-mode-only) ![Showing a newly recorded blood pressure measurement.](Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/MeasurementRecorded_BloodPressure~dark.png#gh-dark-mode-only) |
|:--:|:--:|:--:|
|Display paired in a grid-layout devices using [`DevicesView`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/devicesview).|Display device details using [`DeviceDetailsView`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/devicedetailsview).|Display recorded measurements using [`MeasurementsRecordedSheet`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/measurementsrecordedsheet).|

### SpeziDevices

Expand All @@ -33,13 +40,18 @@ Pairing devices is a good way of making sure that your application only connects
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.
Use the [`PairedDevices`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/paireddevices)
module to discover and pair [`PairableDevice`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/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
To support `PairedDevices`, you need to adopt the
[`PairableDevice`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/pairabledevice) protocol for your device.
Optionally you can adopt the [`BatteryPoweredDevice`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/batterypowereddevice)
protocol, if your device supports the
[`BatteryService`](https://swiftpackageindex.com/stanfordspezi/spezibluetooth/documentation/spezibluetoothservices/batteryservice).
Once your device is loaded, register it with the `PairedDevices` module by calling the ``PairedDevices/configure(device:accessing:_:_:)`` method.
Once your device is loaded, register it with the `PairedDevices` module by calling the
[`PairedDevices/configure(device:accessing:_:_:)`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/paireddevices/configure(device:accessing:_:_:))
method.


> [!IMPORTANT]
Expand Down Expand Up @@ -80,16 +92,18 @@ class MyDevice: PairableDevice {
```

> [!TIP]
> To display and manage paired devices and support adding new paired devices, you can use the full-featured ``DevicesView`` view.
> To display and manage paired devices and support adding new paired devices, you can use the full-featured
[`DevicesView`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/devicesview).

#### Health Measurements

Use the ``HealthMeasurements`` module to collect health measurements from nearby Bluetooth devices like connected weight scales or
Use the [`HealthMeasurements`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/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.
To support `HealthMeasurements`, you need to adopt the [`HealthDevice`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/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:)`.
by calling a suitable variant of [`configureReceivingMeasurements(for:on:)`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/healthmeasurements#register-devices).

```swift
import SpeziDevices
Expand All @@ -108,7 +122,8 @@ class MyDevice: HealthDevice {
}
```

To display new measurements to the user and save them to your external data store, you can use ``MeasurementsRecordedSheet``.
To display new measurements to the user and save them to your external data store, you can use
[`MeasurementsRecordedSheet`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/measurementsrecordedsheet).
Below is a short code example.

```swift
Expand Down Expand Up @@ -140,11 +155,14 @@ SpeziDevicesUI helps you to visualize Bluetooth device state and communicate int

#### Displaying paired devices

When managing paired devices using ``PairedDevices``, SpeziDevicesUI provides reusable View components to display paired devices.
When managing paired devices using [`PairedDevices`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/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.
The [`DevicesView`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/devicesview)
provides everything you need to pair and manage paired devices.
It shows already paired devices in a grid layout using the [`DevicesGrid`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/devicesgrid).
Additionally, it places an add button in the toolbar to discover new devices using the
[`AccessorySetupSheet`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/accessorysetupsheet) view.

```swift
struct MyHomeView: View {
Expand All @@ -165,7 +183,9 @@ struct MyHomeView: View {

#### Displaying Measurements

When managing measurements using ``HealthMeasurements``, you can use the ``MeasurementsRecordedSheet`` to display pending measurements.
When managing measurements using [`HealthMeasurements`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/healthmeasurements),
you can use the [`MeasurementsRecordedSheet`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/measurementsrecordedsheet)
to display pending measurements.
Below is a short code example on how you would configure this view.

```swift
Expand Down Expand Up @@ -195,9 +215,12 @@ device support.

#### Omron Devices

The ``OmronBloodPressureCuff`` and ``OmronWeightScale`` devices provide reusable device implementations for the Omron `BP5250` blood pressure cuff
The [`OmronBloodPressureCuff`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/speziomron/omronbloodpressurecuff)
and [`OmronWeightScale`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/speziomron/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.
Both devices automatically integrate with the [`HealthMeasurements`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/healthmeasurements)
and [`PairedDevices`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevices/paireddevices) modules of SpeziDevices.
You just need to configure them for use with the [`Bluetooth`](https://swiftpackageindex.com/stanfordspezi/spezibluetooth/documentation/spezibluetooth/bluetooth#Configure-the-Bluetooth-Module)
module.

Expand Down
13 changes: 7 additions & 6 deletions Sources/SpeziDevices/HealthMeasurements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

@preconcurrency import HealthKit
import HealthKit
import OSLog
import Spezi
import SpeziBluetooth
Expand Down Expand Up @@ -43,7 +43,8 @@ import SwiftUI
/// }
/// ```
///
/// To display new measurements to the user and save them to your external data store, you can use ``MeasurementsRecordedSheet``.
/// To display new measurements to the user and save them to your external data store, you can use
/// [`MeasurementsRecordedSheet`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/measurementsrecordedsheet).
/// Below is a short code example.
///
/// ```swift
Expand Down Expand Up @@ -145,12 +146,12 @@ public final class HealthMeasurements: @unchecked Sendable {
let hkDevice = device.hkDevice

// make sure to not capture the device
service.$weightMeasurement.onChange { [weak self, weak service] measurement in
service.$weightMeasurement.onChange { @MainActor [weak self, weak service] measurement in
guard let self, let service else {
return
}
logger.debug("Received new weight measurement: \(String(describing: measurement))")
await handleNewMeasurement(.weight(measurement, service.features ?? []), from: hkDevice)
handleNewMeasurement(.weight(measurement, service.features ?? []), from: hkDevice)
}
}

Expand All @@ -165,12 +166,12 @@ public final class HealthMeasurements: @unchecked Sendable {
let hkDevice = device.hkDevice

// make sure to not capture the device
service.$bloodPressureMeasurement.onChange { [weak self, weak service] measurement in
service.$bloodPressureMeasurement.onChange { @MainActor [weak self, weak service] measurement in
guard let self, let service else {
return
}
logger.debug("Received new blood pressure measurement: \(String(describing: measurement))")
await handleNewMeasurement(.bloodPressure(measurement, service.features ?? []), from: hkDevice)
handleNewMeasurement(.bloodPressure(measurement, service.features ?? []), from: hkDevice)
}

logger.debug("Registered device \(device.label), \(device.id) with HealthMeasurements")
Expand Down
8 changes: 4 additions & 4 deletions Sources/SpeziDevices/Measurements/StoredMeasurement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ private struct SwiftDataBluetoothHealthMeasurementWorkaroundContainer {
final class StoredMeasurement {
@Attribute(.unique) var associatedMeasurement: UUID

private let measurement: SwiftDataBluetoothHealthMeasurementWorkaroundContainer
fileprivate let codableDevice: CodableHKDevice
let storageDate: Date
private var measurement: SwiftDataBluetoothHealthMeasurementWorkaroundContainer
fileprivate var codableDevice: CodableHKDevice

var storageDate: Date

var device: HKDevice {
codableDevice.hkDevice
Expand Down
8 changes: 4 additions & 4 deletions Sources/SpeziDevices/Model/PairedDeviceInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import SwiftData
@Model
public final class PairedDeviceInfo {
/// The CoreBluetooth device identifier.
@Attribute(.unique) public let id: UUID
@Attribute(.unique) public var id: UUID
/// The device type.
///
/// Stores the associated ``PairableDevice/deviceTypeIdentifier-9wsed`` device type used to locate the device implementation.
public let deviceType: String
public var deviceType: String
/// A model string of the device.
public let model: String?
public var model: String?

/// The user edit-able name of the device.
public internal(set) var name: String
Expand All @@ -30,7 +30,7 @@ public final class PairedDeviceInfo {
public internal(set) var lastBatteryPercentage: UInt8?

/// The date at which the device was paired.
public let pairedAt: Date
public var pairedAt: Date

/// Could not retrieve the device from the Bluetooth central.
@Transient public internal(set) var notLocatable: Bool = false
Expand Down
18 changes: 14 additions & 4 deletions Sources/SpeziDevices/PairedDevices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ import SwiftUI
/// }
/// ```
///
/// - Tip: To display and manage paired devices and support adding new paired devices, you can use the full-featured ``DevicesTab`` view.
/// - Tip: To display and manage paired devices and support adding new paired devices, you can use the full-featured
/// [`DevicesView`](https://swiftpackageindex.com/stanfordspezi/spezidevices/documentation/spezidevicesui/devicesview).
///
/// ## Topics
///
Expand Down Expand Up @@ -213,6 +214,13 @@ public final class PairedDevices: @unchecked Sendable {
_ advertisements: DeviceStateAccessor<AdvertisementData>,
_ nearby: DeviceStateAccessor<Bool>
) {
if bluetooth?.configuredPairableDevices[Device.deviceTypeIdentifier] == nil {
logger.warning("""
Device \(Device.self) was configured with the PairedDevices module but wasn't configured with the Bluetooth module. \
The device won't be able to be retrieved on a fresh app start. Please make sure the device is configured with Bluetooth.
""")
}

state.onChange { [weak self, weak device] oldValue, newValue in
if let device {
await self?.handleDeviceStateUpdated(device, old: oldValue, new: newValue)
Expand Down Expand Up @@ -382,10 +390,12 @@ extension PairedDevices {
await device.connect()

let id = device.id
async let _ = withTimeout(of: timeout) { @MainActor in
ongoingPairings.removeValue(forKey: id)?.signalTimeout()
let timeoutHandler = { @Sendable @MainActor in
_ = self.ongoingPairings.removeValue(forKey: id)?.signalTimeout()
}

async let _ = withTimeout(of: timeout, perform: timeoutHandler)

try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
ongoingPairings[id] = PairingContinuation(continuation)
Expand Down Expand Up @@ -670,7 +680,7 @@ extension Bluetooth {

extension PairableDevice {
fileprivate static func retrieveDevice(from bluetooth: Bluetooth, with id: UUID) async -> Self? {
await bluetooth.retrieveDevice(for: id)
await bluetooth.retrieveDevice(for: id, as: Self.self)
}
}

Expand Down
Loading

0 comments on commit 641db17

Please sign in to comment.