Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for the AccessorySetupKit #15

Draft
wants to merge 43 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
45a8120
Allow to customize the time stamp of the measurement for Omron mock d…
Supereg Sep 16, 2024
660b9bb
Allow to provide the measurements for HealthMeasurements mock methods
Supereg Sep 17, 2024
b8378b5
Add .git again. Seems to resolve some caching issues?
Supereg Sep 17, 2024
73a3f84
Enable Swift 6 language mode
Supereg Sep 17, 2024
f910b0d
Enable Swift 6 Language mode for UITests
Supereg Sep 17, 2024
5b7d0ac
Update conformance
Supereg Sep 17, 2024
5ae7820
Disable CodeQl
Supereg Sep 17, 2024
709a168
Support for the AccessorySetupKit
Supereg Sep 23, 2024
7d5bb7b
Initial draft implementation
Supereg Sep 23, 2024
bafeb87
Only if compatible
Supereg Sep 23, 2024
18db0a6
Ensure we do not capture task local values from configure
Supereg Sep 23, 2024
4f9b7dc
Some hints
Supereg Sep 23, 2024
895f883
Minor todo annotations
Supereg Sep 24, 2024
9fde7e5
Update for device appearance
Supereg Sep 26, 2024
10c0d31
Integrate with new device defined appearance
Supereg Sep 26, 2024
3304796
LabeledContent, localization and rename button implementation
Supereg Sep 26, 2024
1186741
Make it compile
Supereg Sep 26, 2024
ed8aadd
Improve some state handling
Supereg Sep 26, 2024
762251a
Fix discovery for bp5250 and sc150
Supereg Sep 27, 2024
2cd4710
Minor changes
Supereg Sep 27, 2024
f5ddb87
More adjustments and fixes
Supereg Sep 27, 2024
6b00000
Make sure we connect and disconnect from SpeziBluetooth actor
Supereg Sep 27, 2024
8521d72
Dismiss later
Supereg Sep 27, 2024
660760d
Minor fixes and adjustments
Supereg Sep 27, 2024
918534d
Make sure to cancel connection attempt upon forgetting about the device
Supereg Sep 27, 2024
d5af0a5
Prevent connection attempt race condition
Supereg Sep 27, 2024
b10194f
Modularize the PairedDevices module a bit.
Supereg Sep 27, 2024
e57e9d6
Fix forget device not removing from local store
Supereg Sep 27, 2024
3a254df
Minor adjustments
Supereg Sep 27, 2024
b639968
Minor checks
Supereg Sep 27, 2024
bfe83b6
Inverse condition
Supereg Sep 27, 2024
6394647
Some modifications
Supereg Sep 27, 2024
67d2708
Minor logging changes
Supereg Sep 27, 2024
041591a
Minor changes
Supereg Sep 27, 2024
1548a02
Improve some logging
Supereg Sep 27, 2024
480057c
Some notes
Supereg Sep 27, 2024
bbb1538
Update to sync event handlers
Supereg Oct 2, 2024
b35ad0d
Clear the peripheral property sync
Supereg Oct 2, 2024
159cdf7
Do not retrieve the device for no reason
Supereg Oct 2, 2024
71e5f80
Do not call disconnect ever after accessory removal
Supereg Oct 2, 2024
0ab64d1
Modifications and updates
Supereg Nov 7, 2024
2328379
Resolve some todos
Supereg Nov 17, 2024
d200058
Merge branch 'main' into feature/accessory-setup-kit
PSchmiedmayer Dec 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 9 additions & 31 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,23 @@ on:
workflow_dispatch:

jobs:
packageios:
package_ios:
name: Build and Test Swift Package iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
with:
runsonlabels: '["macOS", "self-hosted"]'
scheme: SpeziDevices-Package
resultBundle: SpeziDevices-iOS.xcresult
artifactname: SpeziDevices-iOS.xcresult
packageios_latest:
name: Build and Test Swift Package iOS Latest
buildandtest_visionos:
name: Build and Test Swift Package visionOS
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
scheme: SpeziBluetooth-Package
destination: 'platform=visionOS Simulator,name=Apple Vision Pro'
resultBundle: SpeziBluetooth-visionOS.xcresult
artifactname: SpeziBluetooth-visionOS.xcresult
ios:
name: Build and Test iOS
uses: StanfordSpezi/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
Expand All @@ -43,32 +42,11 @@ 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
with:
codeql: true
test: false
scheme: SpeziDevices-Package
permissions:
security-events: write
actions: read
uploadcoveragereport:
name: Upload Coverage Report
needs: [packageios, ios]
needs: [package_ios, buildandtest_visionos, ios]
uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
with:
coveragereports: SpeziDevices-iOS.xcresult TestApp-iOS.xcresult
coveragereports: SpeziDevices-iOS.xcresult SpeziBluetooth-visionOS.xcresult TestApp-iOS.xcresult
secrets:
token: ${{ secrets.CODECOV_TOKEN }}
9 changes: 1 addition & 8 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ only_rules:
- comment_spacing
# All elements in a collection literal should be vertically aligned
- collection_alignment
# Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.
# Colons should be next txo the identifier when specifying a type and next to the key in dictionary literals.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Colons should be next txo the identifier when specifying a type and next to the key in dictionary literals.
# Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.

- colon
# There should be no space before and one after any comma.
- comma
Expand Down Expand Up @@ -367,13 +367,6 @@ only_rules:
# The variable should be placed on the left, the constant on the right of a comparison operator.
- yoda_condition

attributes:
attributes_with_arguments_always_on_line_above: false

deployment_target: # Availability checks or attributes shouldn’t be using older versions that are satisfied by the deployment target.
iOSApplicationExtension_deployment_target: 16.0
iOS_deployment_target: 16.0

excluded: # paths to ignore during linting. Takes precedence over `included`.
- .build
- .swiftpm
Expand Down
47 changes: 10 additions & 37 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
// swift-tools-version:5.9
// swift-tools-version:6.0

//
// This source file is part of the Stanford SpeziDevices open source project
//
//
// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md)
//
//
// SPDX-License-Identifier: MIT
//

import class Foundation.ProcessInfo
import PackageDescription


#if swift(<6)
let swiftConcurrency: SwiftSetting = .enableExperimentalFeature("StrictConcurrency")
#else
let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("StrictConcurrency")
#endif


let package = Package(
name: "SpeziDevices",
defaultLocalization: "en",
Expand All @@ -32,13 +25,13 @@ let package = Package(
.library(name: "SpeziOmron", targets: ["SpeziOmron"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-collections", from: "1.1.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation", from: "2.0.0-beta.1"),
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.7.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews", from: "1.5.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziBluetooth", from: "3.0.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziNetworking", from: "2.1.1"),
.package(url: "https://github.com/StanfordBDHG/XCTestExtensions", from: "1.0.0")
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziFoundation.git", from: "2.0.0"),
.package(url: "https://github.com/StanfordSpezi/Spezi.git", from: "1.8.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziViews.git", from: "1.7.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziBluetooth.git", branch: "feature/accessory-setup-kit"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO to get the tagged version in here once it is merged.

.package(url: "https://github.com/StanfordSpezi/SpeziNetworking.git", from: "2.1.1"),
.package(url: "https://github.com/StanfordBDHG/XCTestExtensions.git", from: "1.0.0")
] + swiftLintPackage(),
targets: [
.target(
Expand All @@ -51,10 +44,6 @@ let package = Package(
.product(name: "SpeziViews", package: "SpeziViews"),
.product(name: "Spezi", package: "Spezi")
],
swiftSettings: [
swiftConcurrency,
.enableUpcomingFeature("InferSendableFromCaptures")
],
plugins: [] + swiftLintPlugin()
),
.target(
Expand All @@ -68,10 +57,6 @@ let package = Package(
resources: [
.process("Resources")
],
swiftSettings: [
swiftConcurrency,
.enableUpcomingFeature("InferSendableFromCaptures")
],
plugins: [] + swiftLintPlugin()
),
.target(
Expand All @@ -84,10 +69,6 @@ let package = Package(
resources: [
.process("Resources")
],
swiftSettings: [
swiftConcurrency,
.enableUpcomingFeature("InferSendableFromCaptures")
],
plugins: [] + swiftLintPlugin()
),
.testTarget(
Expand All @@ -100,10 +81,6 @@ let package = Package(
.product(name: "SpeziBluetoothServices", package: "SpeziBluetooth"),
.product(name: "XCTestExtensions", package: "XCTestExtensions")
],
swiftSettings: [
swiftConcurrency,
.enableUpcomingFeature("InferSendableFromCaptures")
],
plugins: [] + swiftLintPlugin()
),
.testTarget(
Expand All @@ -114,10 +91,6 @@ let package = Package(
.product(name: "XCTByteCoding", package: "SpeziNetworking"),
.product(name: "XCTestExtensions", package: "XCTestExtensions")
],
swiftSettings: [
swiftConcurrency,
.enableUpcomingFeature("InferSendableFromCaptures")
],
plugins: [] + swiftLintPlugin()
)
]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ This project is licensed under the MIT License. See [Licenses](https://github.co


## Contributors
This project is developed as part of the Stanford Byers Center for Biodesign at Stanford University.
This project is developed as part of the Stanford Mussallem Center for Biodesign at Stanford University.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

See [CONTRIBUTORS.md](https://github.com/StanfordSpezi/SpeziDevices/tree/main/CONTRIBUTORS.md) for a full list of all TemplatePackage contributors.

![Stanford Byers Center for Biodesign Logo](https://raw.githubusercontent.com/StanfordSpezi/.github/main/assets/Footer.png#gh-light-mode-only)
![Stanford Byers Center for Biodesign Logo](https://raw.githubusercontent.com/StanfordSpezi/.github/main/assets/Footer~dark.png#gh-dark-mode-only)
![Stanford Mussallem Center for Biodesign Logo](https://raw.githubusercontent.com/StanfordSpezi/.github/main/assets/Footer.png#gh-light-mode-only)
![Stanford Mussallem Center for Biodesign Logo](https://raw.githubusercontent.com/StanfordSpezi/.github/main/assets/Footer~dark.png#gh-dark-mode-only)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import AccessorySetupKit
import SpeziViews
import SwiftUI


extension ImageReference {
func uiImageScaledForAccessorySetupKit() -> UIImage {
let image: UIImage
let isSymbol: Bool

if let uiImage {
image = uiImage
isSymbol = isSystemImage
} else {
guard let sensor = UIImage(systemName: "sensor") else {
preconditionFailure("UIImage with systemName 'sensor' is not available.")
}
isSymbol = true
image = sensor
}

if isSymbol {
guard let configuredImage = image
.applyingSymbolConfiguration(.init(font: .systemFont(ofSize: 256), scale: .large))?
.withTintColor(UIColor.tintColor, renderingMode: .alwaysTemplate) else {
preconditionFailure("Failed to apply symbol configuration to UIImage: \(image).")
}

return configuredImage
} else {
return image
}
}
}
19 changes: 19 additions & 0 deletions Sources/SpeziDevices/AccessorySetupKit/LoadAccessorySetupKit.swift
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: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import Spezi
import SpeziBluetooth


@available(iOS 18, *)
final class LoadAccessorySetupKit: Module {
@Dependency(AccessorySetupKit.self)
var accessorySetupKit

init() {}
}
21 changes: 21 additions & 0 deletions Sources/SpeziDevices/Devices/DeviceVariantMigration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import SpeziBluetooth


/// Support migration to the new variant appearance system.
@_spi(Migration)
public protocol DeviceVariantMigration {
/// Select an appearance for an already paired device.
///
/// This method is called when we detect a paired device with variants defined but not variantId associated with the device info (as it is for devices paired before the variant system was introduced).
/// - Parameter deviceInfo: The device info for which to select an appearance.
/// - Returns: Returns the `appearance` and an optional `variantId`.
static func selectAppearance(for deviceInfo: PairedDeviceInfo) -> (appearance: Appearance, variantId: String?)
}
74 changes: 74 additions & 0 deletions Sources/SpeziDevices/Devices/DiscoveredDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import OSLog
import SpeziBluetooth
import SpeziFoundation


@MainActor
final class DiscoveredDevice: Sendable {
private static nonisolated let logger = Logger(subsystem: "edu.stanford.spezi.SpeziDevices", category: "DiscoveredDevice")

let device: any PairableDevice
private(set) var ongoingPairing: PairingContinuation?

init(device: some PairableDevice) {
self.device = device
}

func handleDeviceStateUpdated<Device: PairableDevice>(for device: Device, _ state: PeripheralState) {
guard self.device === device else {
return
}

switch state {
case .disconnected:
if let ongoingPairing {
Self.logger.debug("Device \(device.label), \(device.id) disconnected while pairing was ongoing")
self.ongoingPairing = nil
ongoingPairing.signalDisconnect()
}
default:
break
}
}

func signalDevicePaired(_ device: some PairableDevice) -> Bool {
guard self.device === device else {
return false
}

if let ongoingPairing {
Self.logger.debug("Device \(device.label), \(device.id) signaled it is fully paired.")
self.ongoingPairing = nil
ongoingPairing.signalPaired()
return true
}
return false
}

func assignContinuation(_ continuation: CheckedContinuation<Void, Error>) {
if let ongoingPairing {
ongoingPairing.signalCancellation()
}
self.ongoingPairing = PairingContinuation(continuation)
}

func clearPairingContinuationWithIntentionToResume() -> PairingContinuation? {
if let ongoingPairing {
self.ongoingPairing = nil
return ongoingPairing
}
return nil
}

deinit {
ongoingPairing?.signalCancellation()
}
}
14 changes: 0 additions & 14 deletions Sources/SpeziDevices/Devices/GenericDevice.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ import SpeziBluetoothServices
///
/// A generic Bluetooth device that provides access to basic device information.
public protocol GenericDevice: BluetoothDevice, GenericBluetoothPeripheral, Identifiable, Sendable {
/// A catalog of assets that is used to visually present the device to the user.
///
/// You can provide an array of ``DeviceAsset``s to visually represent the device.
/// Providing multiple assets can be used to support multiple different models of a device kind with a single implementation.
/// The first matching `DeviceAsset` will be used.
static var assets: [DeviceAsset] { get }

/// The device identifier.
///
/// Use the [`DeviceState`](https://swiftpackageindex.com/stanfordspezi/spezibluetooth/documentation/spezibluetooth/devicestate) property wrapper to
Expand Down Expand Up @@ -61,13 +54,6 @@ public protocol GenericDevice: BluetoothDevice, GenericBluetoothPeripheral, Iden


extension GenericDevice {
/// Default `assets` implementation.
///
/// Returns an empty asset catalog by default. Results in a generic icon to be presented.
public static var assets: [DeviceAsset] {
[]
}

/// Default label implementation.
///
/// Returns `"Generic Device"` if the peripheral doesn't expose a ``name``.
Expand Down
Loading