Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Common/Locked.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class Locked<T> {
}
}

@discardableResult
public func mutate(_ changes: (_ value: inout T) -> Void) -> T {
os_unfair_lock_lock(&lock)
defer { os_unfair_lock_unlock(&lock) }
Expand Down
728 changes: 366 additions & 362 deletions G6SensorKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1B26058BD31E40741B0058A5"
BlueprintIdentifier = "1FC930737F45A671AE16478F"
BuildableName = "G6SensorKit.framework"
BlueprintName = "G6SensorKit"
ReferencedContainer = "container:G6SensorKit.xcodeproj">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EDCCBEADE99E54A732DD551F"
BlueprintIdentifier = "D59705DF2CF01716CBDBCA1E"
BuildableName = "G6SensorKitPlugin.loopplugin"
BlueprintName = "G6SensorKitPlugin"
ReferencedContainer = "container:G6SensorKit.xcodeproj">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3ADDB0CD055A33C01C7B54FE"
BlueprintIdentifier = "5FDFF54B3F109CF9EB807CE7"
BuildableName = "G6SensorKitUI.framework"
BlueprintName = "G6SensorKitUI"
ReferencedContainer = "container:G6SensorKit.xcodeproj">
Expand Down
52 changes: 49 additions & 3 deletions G6SensorKit/G6Alert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import Foundation
import LoopKit
import G6SensorCore

enum G6Alert: String, CaseIterable {
case sensorExpiringSoon
Expand All @@ -29,6 +30,8 @@ enum G6Alert: String, CaseIterable {
case transmitterBatteryLow
case transmitterBatteryVeryLow
case lastSessionForTransmitter
case commandDelayed
case commandFailed

init?(rawValue: Alert.AlertIdentifier) {
guard let match = Self.allCases.first(where: { $0.rawValue == rawValue }) else {
Expand All @@ -55,7 +58,9 @@ enum G6Alert: String, CaseIterable {
.signalLoss,
.calibrationNeeded,
.transmitterExpired,
.transmitterBatteryVeryLow:
.transmitterBatteryVeryLow,
.commandDelayed,
.commandFailed:
return .timeSensitive
case .sensorFailed:
return .critical
Expand Down Expand Up @@ -86,6 +91,10 @@ enum G6Alert: String, CaseIterable {
return LocalizedString("Transmitter battery very low", comment: "Alert title for a very low transmitter battery")
case .lastSessionForTransmitter:
return LocalizedString("Last session for this transmitter", comment: "Alert title when a transmitter cannot fit another full session")
case .commandDelayed:
return LocalizedString("Transmitter not responding", comment: "Alert title when a queued command has waited several connection cycles")
case .commandFailed:
return LocalizedString("Request not sent", comment: "Alert title when a command failed to send or expired unsent")
}
}

Expand Down Expand Up @@ -113,13 +122,50 @@ enum G6Alert: String, CaseIterable {
return LocalizedString("Your transmitter's battery is very low and it may stop sending readings at any time, even mid-session. Replace the transmitter as soon as you can, and check your glucose with a fingerstick meter if readings stop.", comment: "Alert body for a very low transmitter battery")
case .lastSessionForTransmitter:
return LocalizedString("Your transmitter does not have enough life left for another full sensor session after this one. Have a replacement transmitter ready before this sensor ends.", comment: "Alert body when a transmitter cannot fit another full session")
case .commandDelayed:
return LocalizedString("A request is still waiting for your transmitter. It is sent as soon as the transmitter connects. Keep your phone close to the transmitter; if its battery is low, it may not connect.", comment: "Alert body when a queued command has waited several connection cycles")
case .commandFailed:
return LocalizedString("A request could not be sent to your transmitter and will not be retried. Open the CGM settings and try again.", comment: "Alert body when a command failed to send")
}
}

func alert(managerIdentifier: String) -> Alert {
/// The body, naming the command when the alert is about one. What the
/// user needs to do differs: a stop or start leaves the session in the
/// wrong state, a lost calibration needs a fresh fingerstick.
func body(delayed: Command?, undelivered: UndeliveredCommand?) -> String {
switch self {
case .commandDelayed:
switch delayed {
case .stopSensor?:
return LocalizedString("Your sensor session has not stopped yet: the request is still waiting for your transmitter and is sent as soon as it connects. Keep your phone close to the transmitter; if its battery is low, it may not connect.", comment: "Alert body when a session stop has waited several connection cycles")
case .startSensor?:
return LocalizedString("Your new sensor session has not started yet: the request is still waiting for your transmitter and is sent as soon as it connects. Keep your phone close to the transmitter; if its battery is low, it may not connect.", comment: "Alert body when a session start has waited several connection cycles")
default:
return body
}
case .commandFailed:
guard let undelivered = undelivered else {
return body
}
switch undelivered.entry.command {
case .stopSensor:
return LocalizedString("Your sensor session was not stopped: the request could not be sent to your transmitter and will not be retried. Open the CGM settings and stop the session again, with your phone close to the transmitter.", comment: "Alert body when a session stop failed to send")
case .startSensor:
return LocalizedString("Your new sensor session was not started: the request could not be sent to your transmitter and will not be retried. Open the CGM settings and start the sensor again, with your phone close to the transmitter.", comment: "Alert body when a session start failed to send")
case .calibrateSensor:
return LocalizedString("Your calibration was not applied: it could not be sent to your transmitter in time and will not be retried. If you still want to calibrate, take a new fingerstick and enter it.", comment: "Alert body when a calibration failed to send or expired unsent")
case .resetTransmitter:
return body
}
default:
return body
}
}

func alert(managerIdentifier: String, delayed: Command? = nil, undelivered: UndeliveredCommand? = nil) -> Alert {
let content = Alert.Content(
title: title,
body: body,
body: body(delayed: delayed, undelivered: undelivered),
acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Alert acknowledgment button label")
)

Expand Down
20 changes: 17 additions & 3 deletions G6SensorKit/G6AlertEvaluator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,29 @@ extension G6CGMManager {
}
}

reconcileAlerts(due: due)
// A flagged command still queued well past the usual connection gap.
// Retracted once it goes out or is withdrawn.
let delayed = CommandQueue(rawValues: state.pendingCommands).firstDelayed(now: now)
if delayed != nil {
due.insert(.commandDelayed)
}

// A flagged command that failed or expired, until the user has seen
// the alert or the situation has resolved itself.
let undelivered = state.outstandingUndeliveredCommand
if let undelivered = undelivered, !undelivered.acknowledged {
due.insert(.commandFailed)
}

reconcileAlerts(due: due, delayed: delayed?.command, undelivered: undelivered)
}

private func reconcileAlerts(due: Set<G6Alert>) {
private func reconcileAlerts(due: Set<G6Alert>, delayed: Command?, undelivered: UndeliveredCommand?) {
let previously = raisedAlerts

for alert in due.subtracting(previously) {
log.default("Issuing alert: %{public}@", alert.rawValue)
let issued = alert.alert(managerIdentifier: Self.pluginIdentifier)
let issued = alert.alert(managerIdentifier: Self.pluginIdentifier, delayed: delayed, undelivered: undelivered)
delegateForAlerts.notify { delegate in
delegate?.issueAlert(issued)
}
Expand Down
Loading