Skip to content

Commit

Permalink
Add initial blood pressure cuff implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Supereg committed Jun 3, 2024
1 parent 3b13d8c commit 675e185
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 16 deletions.
16 changes: 16 additions & 0 deletions ENGAGEHF.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
A92E4DF02BAA001100AC8DE8 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = A92E4DEF2BAA001100AC8DE8 /* OrderedCollections */; };
A96C56B62C0A149B00D6A50B /* WeightScaleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C56B52C0A149B00D6A50B /* WeightScaleTests.swift */; };
A96C56BB2C0DFFCE00D6A50B /* InvitationCodeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C56BA2C0DFFCE00D6A50B /* InvitationCodeModule.swift */; };
A96C56BF2C0E334A00D6A50B /* BloodPressureCuffDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C56BE2C0E334A00D6A50B /* BloodPressureCuffDevice.swift */; };
A96C56C32C0E356900D6A50B /* CurrentTimeService+Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C56C22C0E356900D6A50B /* CurrentTimeService+Update.swift */; };
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */; };
A9D83F962B083794000D0C78 /* SpeziFirebaseAccountStorage in Frameworks */ = {isa = PBXBuildFile; productRef = A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */; };
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; };
Expand Down Expand Up @@ -142,6 +144,8 @@
653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A96C56B52C0A149B00D6A50B /* WeightScaleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightScaleTests.swift; sourceTree = "<group>"; };
A96C56BA2C0DFFCE00D6A50B /* InvitationCodeModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCodeModule.swift; sourceTree = "<group>"; };
A96C56BE2C0E334A00D6A50B /* BloodPressureCuffDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloodPressureCuffDevice.swift; sourceTree = "<group>"; };
A96C56C22C0E356900D6A50B /* CurrentTimeService+Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CurrentTimeService+Update.swift"; sourceTree = "<group>"; };
A9720E422ABB68CC00872D23 /* AccountSetupHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupHeader.swift; sourceTree = "<group>"; };
A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = "<group>"; };
A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSheet.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -288,7 +292,9 @@
4D49AB142BCF6FC400C77310 /* Devices */ = {
isa = PBXGroup;
children = (
A96C56C12C0E356100D6A50B /* Services */,
4D49AB052BC9D56900C77310 /* WeightScaleDevice.swift */,
A96C56BE2C0E334A00D6A50B /* BloodPressureCuffDevice.swift */,
);
path = Devices;
sourceTree = "<group>";
Expand Down Expand Up @@ -371,6 +377,14 @@
name = Frameworks;
sourceTree = "<group>";
};
A96C56C12C0E356100D6A50B /* Services */ = {
isa = PBXGroup;
children = (
A96C56C22C0E356900D6A50B /* CurrentTimeService+Update.swift */,
);
path = Services;
sourceTree = "<group>";
};
A9720E412ABB68B300872D23 /* Account */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -591,6 +605,7 @@
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */,
2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */,
2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */,
A96C56BF2C0E334A00D6A50B /* BloodPressureCuffDevice.swift in Sources */,
2F4E23832989D51F0013F3D9 /* ENGAGEHFTestingSetup.swift in Sources */,
2F5E32BD297E05EA003432F8 /* ENGAGEHFDelegate.swift in Sources */,
4DDFC7702BFAEAD7002B07A1 /* ConfirmMeasurementButton.swift in Sources */,
Expand All @@ -600,6 +615,7 @@
2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */,
2FE5DC2629EDD38A004B9AB4 /* Contacts.swift in Sources */,
4DBDD3462BBFAE2D001FB0CA /* InvitationCodeView.swift in Sources */,
A96C56C32C0E356900D6A50B /* CurrentTimeService+Update.swift in Sources */,
4D49AB062BC9D56900C77310 /* WeightScaleDevice.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
52 changes: 52 additions & 0 deletions ENGAGEHF/Bluetooth/Devices/BloodPressureCuffDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2024 Stanford University
//
// SPDX-License-Identifier: MIT
//

import BluetoothServices
import Foundation
import OSLog
import SpeziBluetooth


class BloodPressureCuffDevice: BluetoothDevice, Identifiable {
private static let logger = Logger(subsystem: "ENGAGEHF", category: "BloodPressureCuffDevice")

@DeviceState(\.id) var id: UUID
@DeviceState(\.name) var name: String?
@DeviceState(\.state) var state: PeripheralState

@Service var deviceInformation = DeviceInformationService()

@Service var time = CurrentTimeService()
@Service var bloodPressure = BloodPressureService()

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

// TODO: user data service?

Check failure on line 30 in ENGAGEHF/Bluetooth/Devices/BloodPressureCuffDevice.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (user data service?) (todo)
// TODO: record access service!

Check failure on line 31 in ENGAGEHF/Bluetooth/Devices/BloodPressureCuffDevice.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (record access service!) (todo)

required init() {
$state
.onChange(perform: handleStateChange)
bloodPressure.$bloodPressureMeasurement
.onChange(perform: processMeasurement)
}

private func handleStateChange(_ state: PeripheralState) {
guard case .connected = state else {
return
}

time.ensureUpdatedTime()
}

private func processMeasurement(_ measurement: BloodPressureMeasurement) {
// TODO: actually do something with it

Check failure on line 49 in ENGAGEHF/Bluetooth/Devices/BloodPressureCuffDevice.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (actually do something with it) (todo)
print("Received new measurement: \(measurement)")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2024 Stanford University
//
// SPDX-License-Identifier: MIT
//

import BluetoothServices
import Foundation
import OSLog


extension CurrentTimeService {
private static var logger: Logger { // TODO: how to deal with that?

Check failure on line 15 in ENGAGEHF/Bluetooth/Devices/Services/CurrentTimeService+Update.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (how to deal with that?) (todo)
Logger(subsystem: "edu.stanford.bluetooth", category: "CurrentTimeService")
}

// TODO: consider moving to SpeziBluetooth!

Check failure on line 19 in ENGAGEHF/Bluetooth/Devices/Services/CurrentTimeService+Update.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (consider moving to SpeziBlueto...) (todo)
func ensureUpdatedTime() { // TODO: better name?

Check failure on line 20 in ENGAGEHF/Bluetooth/Devices/Services/CurrentTimeService+Update.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (better name?) (todo)
// check if time update is necessary
if let currentTime = currentTime,
let deviceTime = currentTime.time.date {
// TODO: what are the default (empty values)? is this check sufficient? (check for zero year, month, day?)

Check failure on line 24 in ENGAGEHF/Bluetooth/Devices/Services/CurrentTimeService+Update.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (what are the default (empty va...) (todo)
let difference = abs(deviceTime.timeIntervalSinceReferenceDate - Date.now.timeIntervalSinceReferenceDate)
if difference < 5 {
// TODO: better value?

Check failure on line 27 in ENGAGEHF/Bluetooth/Devices/Services/CurrentTimeService+Update.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (better value?) (todo)
return // we consider 5 second difference accurate enough?)
}
}


// update time if it isn't present or if it is outdated
Task {
let exactTime = ExactTime256(from: .now)
do {
try await $currentTime.write(CurrentTime(time: exactTime))
Self.logger.debug("Updated weight scale device time to \(String(describing: exactTime))")
} catch {
Self.logger.warning("Failed to update current time: \(error)")
}
}
}
}
40 changes: 26 additions & 14 deletions ENGAGEHF/Bluetooth/Devices/WeightScaleDevice.swift
Original file line number Diff line number Diff line change
@@ -1,43 +1,55 @@
//
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
// SPDX-FileCopyrightText: 2024 Stanford University
//
// SPDX-License-Identifier: MIT
//

import BluetoothServices
import Foundation
import OSLog
@_spi(TestingSupport) import SpeziBluetooth


//
// A bluetooth peripheral representing a Weight Scale
//
// On new measurement, loads the measurement into the MeasurementManager
// as a HealthKit HKQuantitySample
//
/// A bluetooth peripheral representing a Weight Scale
///
/// On new measurement, loads the measurement into the MeasurementManager
/// as a HealthKit HKQuantitySample.
class WeightScaleDevice: BluetoothDevice, Identifiable {
private static let logger = Logger(subsystem: "ENGAGEHF", category: "WeightScale")

@DeviceState(\.id) var id: UUID
@DeviceState(\.name)var name: String?
@DeviceState(\.state) var state: PeripheralState

@Service var deviceInformation = DeviceInformationService()
@Service var service = WeightScaleService()

@Service var time = CurrentTimeService()
@Service var weightScale = WeightScaleService()

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


required init() {
service.$weightMeasurement.onChange(perform: processMeasurement)
$state
.onChange(perform: handleStateChange)
weightScale.$weightMeasurement
.onChange(perform: processMeasurement)
}
private func processMeasurement(_ measurement: WeightMeasurement) {
if !service.$weightMeasurement.isPresent {

private func handleStateChange(_ state: PeripheralState) { // TODO: call from the mock device!

Check failure on line 42 in ENGAGEHF/Bluetooth/Devices/WeightScaleDevice.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (call from the mock device!) (todo)
guard case .connected = state else {
return
}

time.ensureUpdatedTime()
}

private func processMeasurement(_ measurement: WeightMeasurement) {
// TODO: add custom string convertible conformance to all characteristics!

Check failure on line 51 in ENGAGEHF/Bluetooth/Devices/WeightScaleDevice.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Todo Violation: TODOs should be resolved (add custom string convertible ...) (todo)
Self.logger.debug("Received new weight measurement: \(String(describing: measurement))")
MeasurementManager.manager.handleMeasurement(measurement, from: self)
}
}
Expand Down Expand Up @@ -76,8 +88,8 @@ extension WeightScaleDevice {
unit: .si
)

device.service.$features.inject(features)
device.service.$weightMeasurement.inject(measurement)
device.weightScale.$features.inject(features)
device.weightScale.$weightMeasurement.inject(measurement)

device.$id.inject(UUID())
device.$name.inject("Mock Health Scale")
Expand Down
4 changes: 2 additions & 2 deletions ENGAGEHF/Bluetooth/MeasurementManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class MeasurementManager: Module, EnvironmentAccessible {

func handleMeasurement(_ measurement: WeightMeasurement, from device: WeightScaleDevice) {
deviceInformation = device.deviceInformation
weightScaleParams = device.service.features
weightScaleParams = device.weightScale.features
deviceName = device.name

loadMeasurement(measurement)
Expand Down Expand Up @@ -180,7 +180,7 @@ extension MeasurementManager {
func loadMockMeasurement() {
let device = WeightScaleDevice.createMockDevice()

guard let measurement = device.service.weightMeasurement else {
guard let measurement = device.weightScale.weightMeasurement else {
logger.error("Mock measurement was never injected!")
return
}
Expand Down
1 change: 1 addition & 0 deletions ENGAGEHF/ENGAGEHFDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ENGAGEHFDelegate: SpeziAppDelegate {

Bluetooth {
Discover(WeightScaleDevice.self, by: .advertisedService(WeightScaleService.self))
Discover(BloodPressureCuffDevice.self, by: .advertisedService(BloodPressureService.self))
}

OnboardingDataSource()
Expand Down

0 comments on commit 675e185

Please sign in to comment.