Skip to content

Commit

Permalink
Merge pull request nightscout#251 from Artificial-Pancreas/dev
Browse files Browse the repository at this point in the history
Dev updates
  • Loading branch information
Jon-b-m authored Oct 15, 2023
2 parents 4e5ab1d + 0a47ccf commit dd37f9c
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 18 deletions.
16 changes: 16 additions & 0 deletions FreeAPS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@
CA370FC152BC98B3D1832968 /* BasalProfileEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8BCB0C37DEB5EC377B9612 /* BasalProfileEditorRootView.swift */; };
CC6C406E2ACDD69E009B8058 /* RawFetchedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */; };
CD78BB94E43B249D60CC1A1B /* NotificationsConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22963BD06A9C83959D4914E4 /* NotificationsConfigRootView.swift */; };
CE1856F52ADC4858007E39C7 /* AddCarbPresetIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */; };
CE1856F72ADC4869007E39C7 /* CarbPresetIntentRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1856F62ADC4869007E39C7 /* CarbPresetIntentRequest.swift */; };
CE2FAD38297D69E1001A872C /* ShareClient.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE398D1A297D69A900DF218F /* ShareClient.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */; };
CE398D16297C9D1D00DF218F /* dexcomSourceG7.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE398D15297C9D1D00DF218F /* dexcomSourceG7.swift */; };
Expand Down Expand Up @@ -820,6 +822,8 @@
C377490C77661D75E8C50649 /* ManualTempBasalRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ManualTempBasalRootView.swift; sourceTree = "<group>"; };
C8D1A7CA8C10C4403D4BBFA7 /* BolusDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusDataFlow.swift; sourceTree = "<group>"; };
CC6C406D2ACDD69E009B8058 /* RawFetchedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawFetchedProfile.swift; sourceTree = "<group>"; };
CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCarbPresetIntent.swift; sourceTree = "<group>"; };
CE1856F62ADC4869007E39C7 /* CarbPresetIntentRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbPresetIntentRequest.swift; sourceTree = "<group>"; };
CE2FAD39297D93F0001A872C /* BloodGlucoseExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloodGlucoseExtensions.swift; sourceTree = "<group>"; };
CE398D012977349800DF218F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
CE398D15297C9D1D00DF218F /* dexcomSourceG7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dexcomSourceG7.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2058,9 +2062,19 @@
path = Bolus;
sourceTree = "<group>";
};
CE1856F32ADC4835007E39C7 /* Carbs */ = {
isa = PBXGroup;
children = (
CE1856F42ADC4858007E39C7 /* AddCarbPresetIntent.swift */,
CE1856F62ADC4869007E39C7 /* CarbPresetIntentRequest.swift */,
);
path = Carbs;
sourceTree = "<group>";
};
CE7CA3422A064973004BE681 /* Shortcuts */ = {
isa = PBXGroup;
children = (
CE1856F32ADC4835007E39C7 /* Carbs */,
CE7CA3432A064973004BE681 /* AppShortcuts.swift */,
CE7CA3442A064973004BE681 /* BaseIntentsRequest.swift */,
CE7CA3452A064973004BE681 /* TempPresets */,
Expand Down Expand Up @@ -2590,6 +2604,7 @@
388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */,
3811DE0B25C9D32F00A708ED /* BaseView.swift in Sources */,
3811DE3225C9D49500A708ED /* HomeDataFlow.swift in Sources */,
CE1856F52ADC4858007E39C7 /* AddCarbPresetIntent.swift in Sources */,
38569347270B5DFB0002C50D /* CGMType.swift in Sources */,
3821ED4C25DD18BA00BC42AD /* Constants.swift in Sources */,
384E803425C385E60086DB71 /* JavaScriptWorker.swift in Sources */,
Expand Down Expand Up @@ -2664,6 +2679,7 @@
CE7CA3532A064973004BE681 /* tempPresetIntent.swift in Sources */,
D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */,
38E98A3025F52FF700C0CED0 /* Config.swift in Sources */,
CE1856F72ADC4869007E39C7 /* CarbPresetIntentRequest.swift in Sources */,
BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */,
9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
Expand Down
5 changes: 0 additions & 5 deletions FreeAPS/Sources/APS/APSManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ final class BaseAPSManager: APSManager, Injectable {
@Injected() private var nightscout: NightscoutManager!
@Injected() private var settingsManager: SettingsManager!
@Injected() private var broadcaster: Broadcaster!
@Injected() private var healthKitManager: HealthKitManager!
@Persisted(key: "lastAutotuneDate") private var lastAutotuneDate = Date()
@Persisted(key: "lastStartLoopDate") private var lastStartLoopDate: Date = .distantPast
@Persisted(key: "lastLoopDate") var lastLoopDate: Date = .distantPast {
Expand Down Expand Up @@ -268,10 +267,6 @@ final class BaseAPSManager: APSManager, Injectable {
private func loopCompleted(error: Error? = nil, loopStatRecord: LoopStats) {
isLooping.send(false)

// save AH events
let events = pumpHistoryStorage.recent()
healthKitManager.saveIfNeeded(pumpEvents: events)

if let error = error {
warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
if let backgroundTask = backGroundTaskID {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,38 +158,66 @@ extension NightscoutConfig {
return
}

var areCRsOK = true
let carbratios = fetchedProfile.carbratio
.map { carbratio -> CarbRatioEntry in
CarbRatioEntry(
if carbratio.value <= 0 {
error =
"\nInvalid Carb Ratio settings in Nightscout.\n\nImport aborted. Please check your Nightscout Profile Carb Ratios Settings!"
areCRsOK = false
}
return CarbRatioEntry(
start: carbratio.time,
offset: (carbratio.timeAsSeconds ?? self.offset(carbratio.time)) / 60,
ratio: carbratio.value
) }
let carbratiosProfile = CarbRatios(units: CarbUnit.grams, schedule: carbratios)
guard areCRsOK else {
group.leave()
return
}

var areBasalsOK = true
let pumpName = self.apsManager.pumpName.value
let basals = fetchedProfile.basal
.map { basal -> BasalProfileEntry in
if basal.value <= 0 || basal.value >= self.maxBasal {
if pumpName != "Omnipod DASH", basal.value <= 0
{
error =
"\nInvalid Nightcsout Basal Settings. \n\nImport aborted. Please check your Nightscout Profile Basal Settings!"
"\nInvalid Nightcsout Basal Settings. Some or all of your basal settings are 0 U/h.\n\nImport aborted. Please check your Nightscout Profile Basal Settings before trying to import again. Import has been aborted.)"
areBasalsOK = false
}
return BasalProfileEntry(
start: basal.time,
minutes: (basal.timeAsSeconds ?? self.offset(basal.time)) / 60,
rate: basal.value
) }
// DASH pumps can have 0U/h basal rates but don't import if total basals (24 hours) amount to 0 U.
if pumpName == "Omnipod DASH", basals.map({ each in each.rate }).reduce(0, +) <= 0
{
error =
"\nYour total Basal insulin amount to 0 U or lower in Nightscout Profile settings.\n\n Please check your Nightscout Profile Basal Settings before trying to import again. Import has been aborted.)"
areBasalsOK = false
}
guard areBasalsOK else {
group.leave()
return
}

let sensitivities = fetchedProfile.sens.map { sensitivity -> InsulinSensitivityEntry in
InsulinSensitivityEntry(
sensitivity: self.units == .mmolL ? sensitivity.value : sensitivity.value.asMgdL,
offset: (sensitivity.timeAsSeconds ?? self.offset(sensitivity.time)) / 60,
start: sensitivity.time
) }
)
}
if sensitivities.filter({ $0.sensitivity <= 0 }).isNotEmpty {
error =
"\nInvalid Nightcsout Sensitivities Settings. \n\nImport aborted. Please check your Nightscout Profile Sensitivities Settings!"
group.leave()
return
}

let sensitivitiesProfile = InsulinSensitivities(
units: self.units,
userPrefferedUnits: self.units,
Expand Down Expand Up @@ -238,7 +266,7 @@ extension NightscoutConfig {
debug(.service, "Settings have been imported and the Basals saved to pump!")
// DIA. Save if changed.
let dia = fetchedProfile.dia
if dia != self.dia {
if dia != self.dia, dia <= 0 {
let file = PumpSettings(
insulinActionCurve: dia,
maxBolus: self.maxBolus,
Expand Down
47 changes: 44 additions & 3 deletions FreeAPS/Sources/Services/HealthKit/HealthKitManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protocol HealthKitManager: GlucoseSource {
func deleteInsulin(syncID: String)
}

final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver, PumpHistoryObserver {
private enum Config {
// unwraped HKObjects
static var readPermissions: Set<HKSampleType> {
Expand Down Expand Up @@ -68,7 +68,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
}
get {
guard let data = persistedBGAnchor else { return nil }
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? HKQueryAnchor
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: HKQueryAnchor.self, from: data)
}
}

Expand Down Expand Up @@ -115,6 +115,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
enableBackgroundDelivery()

broadcaster.register(CarbsObserver.self, observer: self)
broadcaster.register(PumpHistoryObserver.self, observer: self)

debug(.service, "HealthKitManager did create")
}
Expand Down Expand Up @@ -226,6 +227,21 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
events.isNotEmpty
else { return }

func delete(syncIds: [String]?) {
syncIds?.forEach { syncID in
let predicate = HKQuery.predicateForObjects(
withMetadataKey: HKMetadataKeySyncIdentifier,
operatorType: .equalTo,
value: syncID
)

self.healthKitStore.deleteObjects(of: sampleType, predicate: predicate) { _, _, error in
guard let error = error else { return }
warning(.service, "Cannot delete sample with syncID: \(syncID)", error: error)
}
}
}

func save(bolus: [InsulinBolus], basal: [InsulinBasal]) {
let bolusSamples = bolus
.map {
Expand Down Expand Up @@ -264,6 +280,26 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
healthKitStore.save(bolusSamples + basalSamples) { _, _ in }
}

// delete existing event in HK where the amount is not the last value in the pumphistory
loadSamplesFromHealth(sampleType: sampleType, withIDs: events.map(\.id))
.receive(on: processQueue)
.compactMap { samples -> [String] in
let sampleIDs = samples.compactMap(\.syncIdentifier)
let bolusToDelete = events
.filter { $0.type == .bolus && sampleIDs.contains($0.id) }
.compactMap { event -> String? in
guard let amount = event.amount else { return nil }
guard let sampleAmount = samples.first(where: { $0.syncIdentifier == event.id }) as? HKQuantitySample
else { return nil }
if Double(amount) != sampleAmount.quantity.doubleValue(for: .internationalUnit()) {
return sampleAmount.syncIdentifier
} else { return nil }
}
return bolusToDelete
}
.sink(receiveValue: delete)
.store(in: &lifetime)

loadSamplesFromHealth(sampleType: sampleType, withIDs: events.map(\.id))
.receive(on: processQueue)
.compactMap { samples -> ([InsulinBolus], [InsulinBasal]) in
Expand All @@ -276,6 +312,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
}
let basalEvents = events
.filter { $0.type == .tempBasal && !sampleIDs.contains($0.id) }
.sorted(by: { $0.timestamp < $1.timestamp })
let basal = basalEvents.enumerated()
.compactMap { item -> InsulinBasal? in
let nextElementEventIndex = item.offset + 1
Expand All @@ -300,7 +337,7 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
}

let id = String(item.element.id.dropFirst())
guard amountRounded >= 0,
guard amountRounded > 0,
id != ""
else { return nil }

Expand All @@ -317,6 +354,10 @@ final class BaseHealthKitManager: HealthKitManager, Injectable, CarbsObserver {
.store(in: &lifetime)
}

func pumpHistoryDidUpdate(_ events: [PumpHistoryEvent]) {
saveIfNeeded(pumpEvents: events)
}

func createBGObserver() {
guard settingsManager.settings.useAppleHealth else { return }

Expand Down
2 changes: 1 addition & 1 deletion FreeAPS/Sources/Services/Network/NightscoutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ final class BaseNightscoutManager: NightscoutManager, Injectable {
self.storage.save(glucose, as: fileToSave)
debug(.nightscout, "Glucose uploaded")
case let .failure(error):
debug(.nightscout, error.localizedDescription)
debug(.nightscout, "Upload of glucose failed: " + error.localizedDescription)
}
} receiveValue: {}
.store(in: &self.lifetime)
Expand Down
7 changes: 7 additions & 0 deletions FreeAPS/Sources/Shortcuts/AppShortcuts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,12 @@ import Foundation
"\(.applicationName) state"
]
)
AppShortcut(
intent: AddCarbPresentIntent(),
phrases: [
"Add carbs in \(.applicationName)",
"\(.applicationName) allows to add carbs"
]
)
}
}
3 changes: 3 additions & 0 deletions FreeAPS/Sources/Shortcuts/BaseIntentsRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Swinject
@Injected() var settingsManager: SettingsManager!
@Injected() var storage: TempTargetsStorage!
@Injected() var fileStorage: FileStorage!
@Injected() var carbsStorage: CarbsStorage!
@Injected() var glucoseStorage: GlucoseStorage!
@Injected() var apsManager: APSManager!

let resolver: Resolver

Expand Down
94 changes: 94 additions & 0 deletions FreeAPS/Sources/Shortcuts/Carbs/AddCarbPresetIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import AppIntents
import Foundation
import Intents
import Swinject

@available(iOS 16.0,*) struct AddCarbPresentIntent: AppIntent {
// Title of the action in the Shortcuts app
static var title: LocalizedStringResource = "Add carbs"

// Description of the action in the Shortcuts app
static var description = IntentDescription("Allow to add carbs in iAPS.")

internal var carbRequest: CarbPresetIntentRequest

init() {
carbRequest = CarbPresetIntentRequest()
dateAdded = Date()
}

@Parameter(
title: "Quantity Carbs",
description: "Quantity of carbs in g",
controlStyle: .field,
inclusiveRange: (lowerBound: 0, upperBound: 200),
requestValueDialog: IntentDialog("What is the numeric value of the carb to add")
) var carbQuantity: Double?

@Parameter(
title: "Quantity fat",
description: "Quantity of fat in g",
default: 0.0,
inclusiveRange: (0, 200)
) var fatQuantity: Double

@Parameter(
title: "Quantity Protein",
description: "Quantity of Protein in g",
default: 0.0,
inclusiveRange: (0, 200)
) var proteinQuantity: Double

@Parameter(
title: "Date",
description: "Date of adding"
) var dateAdded: Date

@Parameter(
title: "Confirm Before applying",
description: "If toggled, you will need to confirm before applying",
default: true
) var confirmBeforeApplying: Bool

static var parameterSummary: some ParameterSummary {
When(\.$confirmBeforeApplying, .equalTo, true, {
Summary("Applying \(\.$carbQuantity) at \(\.$dateAdded)") {
\.$fatQuantity
\.$proteinQuantity
\.$confirmBeforeApplying
}
}, otherwise: {
Summary("Immediately applying \(\.$carbQuantity) at \(\.$dateAdded)") {
\.$fatQuantity
\.$proteinQuantity
\.$confirmBeforeApplying
}
})
}

@MainActor func perform() async throws -> some ProvidesDialog {
do {
let quantityCarbs: Double
if let cq = carbQuantity {
quantityCarbs = cq
} else {
quantityCarbs = try await $carbQuantity.requestValue("How many carbs ?")
}

let quantityCarbsName = quantityCarbs.toString()
if confirmBeforeApplying {
try await requestConfirmation(
result: .result(dialog: "Are you sure to add \(quantityCarbsName) g of carbs ?")
)
}

let finalQuantityCarbsDisplay = try carbRequest.addCarbs(quantityCarbs, fatQuantity, proteinQuantity, dateAdded)
return .result(
dialog: IntentDialog(stringLiteral: finalQuantityCarbsDisplay)
)

} catch {
throw error
}
}
}
Loading

0 comments on commit dd37f9c

Please sign in to comment.