Skip to content

Update OmniKit tidepool-merge with improvements from main #51

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

Merged
merged 21 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
976d477
Miscellaneous Omnipod code improvements & cleanup
itsmojo Nov 17, 2024
5162a05
Unacknowledged command handling fixes and PodCommsSession improvements
itsmojo Nov 18, 2024
ef98bb1
Add missing pod suspend test improvement and log type change to PumpM…
itsmojo Nov 20, 2024
0d131a3
return clock icon when isClockOffset is true
marionbarker Nov 28, 2024
c0bdf81
Improved commenting on constants that vary from Eros and Dash
itsmojo Dec 3, 2024
22dd0e9
Update comments to use U/hr instead of U/H for better consistency
itsmojo Dec 4, 2024
9292a66
Merge pull request #44 from itsmojo/Omnipod-improvements
marionbarker Dec 10, 2024
8008147
Merge pull request #45 from itsmojo/unacknowledge-command-improvement
marionbarker Dec 10, 2024
0857ad8
Merge pull request #46 from loopandlearn/omnikit-add-clock-icon-main
marionbarker Dec 10, 2024
b79d85b
Improved & automatic unacknowledged command recovery
itsmojo Dec 23, 2024
2d9959c
Use getStatus() more for unacknowledged command handling & simplified…
itsmojo Dec 24, 2024
2d41740
Rename resolveUnacknowledgedCommand() to tryToResolvePendingCommand()
itsmojo Dec 25, 2024
afa9f62
Merge pull request #47 from itsmojo/improved-unacknowledged-command-r…
marionbarker Jan 15, 2025
f82393c
Logic fix for 049 pod fault with concurrent temp basal commands
itsmojo Jan 18, 2025
48a35ef
Merge pull request #48 from itsmojo/concurrent-TB-logic-fixes
marionbarker Jan 18, 2025
cce7bad
fix for pump manager returns bogus podSuspended;
marionbarker Feb 8, 2025
9d9d499
remove an unneeded connection check, revise some comments and remove …
marionbarker Feb 10, 2025
447caa4
use correct .communication errors instead of .state or .deviceState
marionbarker Feb 11, 2025
39915b0
Merge pull request #49 from LoopKit/try_to_validate_comms
marionbarker Feb 12, 2025
05683a2
Add link dependencies
kingst Mar 11, 2025
92948a7
Merge pull request #50 from kingst/main
marionbarker Mar 13, 2025
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
16 changes: 15 additions & 1 deletion OmniKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

/* Begin PBXBuildFile section */
275A98C02AE456EA0097F476 /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = 275A98BF2AE456EA0097F476 /* SlideButton */; };
3B0FD2A12D803BB000E5E921 /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0FD2A02D803BB000E5E921 /* RileyLinkBLEKit.framework */; };
3B0FD2A32D803BB800E5E921 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0FD2A22D803BB800E5E921 /* RileyLinkKit.framework */; };
3B0FD2A62D803C2000E5E921 /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C124016C29C7D87A00B32844 /* OmniKit.framework */; };
3B0FD2A82D803C2C00E5E921 /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B0FD2A72D803C2C00E5E921 /* RileyLinkKitUI.framework */; };
B60BB2EC2BC757A700D2BB39 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60BB2EA2BC7579E00D2BB39 /* Bundle.swift */; };
C1229C1A29C7E5BC0066A89C /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1229C1729C7E5BC0066A89C /* RileyLinkBLEKit.framework */; };
C1229C1B29C7E5BC0066A89C /* RileyLinkBLEKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1229C1729C7E5BC0066A89C /* RileyLinkBLEKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -192,6 +196,7 @@
D80339982A50085C004FF953 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0329C7DDC800435701 /* TimeInterval.swift */; };
D803399A2A500D3D004FF953 /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12401B229C7D8E900B32844 /* CRC8.swift */; };
D803399B2A50122F004FF953 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12401B329C7D8E900B32844 /* Packet.swift */; };
D803399C2A50128D004FF953 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0129C7DD4700435701 /* LocalizedString.swift */; };
D845A1352AF89DEC00EA0853 /* SilencePodPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1342AF89DEC00EA0853 /* SilencePodPreference.swift */; };
D845A1462AF8A4DA00EA0853 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1452AF8A4DA00EA0853 /* ActivityView.swift */; };
D845A14A2AF8A4EF00EA0853 /* PlayTestBeepsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1492AF8A4EF00EA0853 /* PlayTestBeepsView.swift */; };
Expand All @@ -200,7 +205,6 @@
D845A1522AF8A51000EA0853 /* SilencePodSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1512AF8A51000EA0853 /* SilencePodSelectionView.swift */; };
D85AEAC82B1403C000081044 /* PodDiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEAC72B1403C000081044 /* PodDiagnosticsView.swift */; };
D85AEACA2B1403CB00081044 /* ReadPodInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEAC92B1403CB00081044 /* ReadPodInfoView.swift */; };
D803399C2A50128D004FF953 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12EDA0129C7DD4700435701 /* LocalizedString.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -255,6 +259,9 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
3B0FD2A02D803BB000E5E921 /* RileyLinkBLEKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkBLEKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B0FD2A22D803BB800E5E921 /* RileyLinkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B0FD2A72D803C2C00E5E921 /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B60BB2EA2BC7579E00D2BB39 /* Bundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
C10E1EE62AE59EDC00B4E993 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/Localizable.strings; sourceTree = "<group>"; };
C1229C1729C7E5BC0066A89C /* RileyLinkBLEKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkBLEKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -467,13 +474,17 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3B0FD2A32D803BB800E5E921 /* RileyLinkKit.framework in Frameworks */,
3B0FD2A12D803BB000E5E921 /* RileyLinkBLEKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C124020829C7D92700B32844 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3B0FD2A82D803C2C00E5E921 /* RileyLinkKitUI.framework in Frameworks */,
3B0FD2A62D803C2000E5E921 /* OmniKit.framework in Frameworks */,
275A98C02AE456EA0097F476 /* SlideButton in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -822,6 +833,9 @@
C12ED9F329C7DCE100435701 /* Frameworks */ = {
isa = PBXGroup;
children = (
3B0FD2A72D803C2C00E5E921 /* RileyLinkKitUI.framework */,
3B0FD2A22D803BB800E5E921 /* RileyLinkKit.framework */,
3B0FD2A02D803BB000E5E921 /* RileyLinkBLEKit.framework */,
C1229C1729C7E5BC0066A89C /* RileyLinkBLEKit.framework */,
C1229C1829C7E5BC0066A89C /* RileyLinkKit.framework */,
C1229C1929C7E5BC0066A89C /* RileyLinkKitUI.framework */,
Expand Down
43 changes: 35 additions & 8 deletions OmniKit/OmnipodCommon/Pod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ public struct Pod {
public static let reservoirCapacity: Double = 200

// Supported basal rates
// Eros minimum scheduled basal rate is 0.05 U/H while for Dash supports 0 U/H.
// Would need to have this value based on productID to be able to share this with Eros.
// Eros minimum scheduled basal rate is 0.05 U/hr while Dash supports 0 U/hr.
public static let supportedBasalRates: [Double] = (1...600).map { Double($0) / Double(pulsesPerUnit) }

// Supported temp basal rates
// Both Eros and Dash support a minimum temp basal rate of 0 U/hr.
public static let supportedTempBasalRates: [Double] = (0...600).map { Double($0) / Double(pulsesPerUnit) }

// The internal basal rate used for non-Eros pods
// Would need to have this value based on productID to be able to share this file with Eros.
// The internal basal rate used for zero basal rates
// Eros uses 0.0 while Dash uses a near zero rate
public static let zeroBasalRate: Double = 0.0

// Maximum number of basal schedule entries supported
Expand Down Expand Up @@ -113,19 +113,46 @@ public enum DeliveryStatus: UInt8, CustomStringConvertible {
case extendedBolusAndTempBasal = 10

public var suspended: Bool {
return self == .suspended || self == .priming || self == .extendedBolusWhileSuspended
// returns true if both the tempBasal and basal bits are clear
let suspendedStates: Set<DeliveryStatus> = [
.suspended,
.priming,
.extendedBolusWhileSuspended,
]
return suspendedStates.contains(self)
}

public var bolusing: Bool {
return self == .bolusInProgress || self == .bolusAndTempBasal || self == .extendedBolusRunning || self == .extendedBolusAndTempBasal || self == .priming || self == .extendedBolusWhileSuspended
// returns true if either the immediateBolus or extendedBolus bits are set
let bolusingStates: Set<DeliveryStatus> = [
.priming,
.bolusInProgress,
.bolusAndTempBasal,
.extendedBolusWhileSuspended,
.extendedBolusRunning,
.extendedBolusAndTempBasal,
]
return bolusingStates.contains(self)
}

public var tempBasalRunning: Bool {
return self == .tempBasalRunning || self == .bolusAndTempBasal || self == .extendedBolusAndTempBasal
// returns true if the tempBasal bit is set
let tempBasalRunningStates: Set<DeliveryStatus> = [
.tempBasalRunning,
.bolusAndTempBasal,
.extendedBolusAndTempBasal,
]
return tempBasalRunningStates.contains(self)
}

public var extendedBolusRunning: Bool {
return self == .extendedBolusRunning || self == .extendedBolusAndTempBasal || self == .extendedBolusWhileSuspended
// returns true if the extendedBolus bit is set
let extendedBolusRunningStates: Set<DeliveryStatus> = [
.extendedBolusWhileSuspended,
.extendedBolusRunning,
.extendedBolusAndTempBasal,
]
return extendedBolusRunningStates.contains(self)
}

public var description: String {
Expand Down
77 changes: 70 additions & 7 deletions OmniKit/PumpManager/OmnipodPumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,27 @@ extension OmnipodPumpManager {
}
}

// If the last delivery status received is invalid or there is an unacknowledged command, execute a getStatus command
// for the current PodCommsSession. If the getStatus fails, return its error to be passed on to the higher level.
// Return nil if comms looks OK or the getStatus was successful.
private func tryToValidateComms(session: PodCommsSession) -> LocalizedError? {

// Since we're already connected for this session, if we have a delivery status and no unacknowledged command, return nil
if self.state.podState?.lastDeliveryStatusReceived != nil && self.state.podState?.unacknowledgedCommand == nil {
return nil
}

// Attempt to do a getStatus to try to resolve any outstanding comms issues
do {
let _ = try session.getStatus()
self.log.debug("### tryToValidateComms getStatus resolved all pending comms issues")
return nil
} catch let error {
self.log.debug("### tryToValidateComms getStatus failed, returning: %@", error.localizedDescription)
return error as? LocalizedError
}
}

// MARK: - Pump Commands

public func getPodStatus(completion: ((_ result: PumpManagerResult<StatusResponse>) -> Void)? = nil) {
Expand Down Expand Up @@ -1119,6 +1140,11 @@ extension OmnipodPumpManager {
switch result {
case .success(let session):
do {
if let error = self.tryToValidateComms(session: session) {
completion(.communication(error))
return
}

let beep = self.silencePod ? false : self.beepPreference.shouldBeepForManualCommand
let _ = try session.setTime(timeZone: timeZone, basalSchedule: self.state.basalSchedule, date: Date(), acknowledgementBeep: beep)
self.setState { (state) in
Expand Down Expand Up @@ -1171,6 +1197,11 @@ extension OmnipodPumpManager {
do {
switch result {
case .success(let session):
if let error = self.tryToValidateComms(session: session) {
completion(error)
return
}

let scheduleOffset = timeZone.scheduleOffset(forDate: Date())
let result = session.cancelDelivery(deliveryType: .all)
switch result {
Expand Down Expand Up @@ -1415,6 +1446,11 @@ extension OmnipodPumpManager {
self.podComms.runSession(withName: name, using: rileyLinkSelector) { (result) in
switch result {
case .success(let session):
if let error = self.tryToValidateComms(session: session) {
completion(.communication(error))
return
}

// enable/disable Pod completion beep state for any unfinalized manual insulin delivery
let enabled = newPreference.shouldBeepForManualCommand
let beepType: BeepType = enabled ? .bipBip : .noBeepNonCancel
Expand Down Expand Up @@ -1466,6 +1502,11 @@ extension OmnipodPumpManager {
return
}

if let error = self.tryToValidateComms(session: session) {
completion(.communication(error))
return
}

guard let configuredAlerts = self.state.podState?.configuredAlerts,
let activeAlertSlots = self.state.podState?.activeAlertSlots,
let reservoirLevel = self.state.podState?.lastInsulinMeasurements?.reservoirLevel?.rawValue else
Expand Down Expand Up @@ -1716,6 +1757,11 @@ extension OmnipodPumpManager: PumpManager {
state.suspendEngageState = .engaging
})

if let error = self.tryToValidateComms(session: session) {
completion(error)
return
}

// Use a beepBlock for the confirmation beep to avoid getting 3 beeps using cancel command beeps!
let beepBlock = self.beepMessageBlock(beepType: .beeeeeep)
let result = session.suspendDelivery(suspendReminder: suspendReminder, silent: self.silencePod, beepBlock: beepBlock)
Expand Down Expand Up @@ -1762,6 +1808,11 @@ extension OmnipodPumpManager: PumpManager {
state.suspendEngageState = .disengaging
})

if let error = self.tryToValidateComms(session: session) {
completion(error)
return
}

do {
let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date())
let beep = self.silencePod ? false : self.beepPreference.shouldBeepForManualCommand
Expand Down Expand Up @@ -1870,8 +1921,15 @@ extension OmnipodPumpManager: PumpManager {
state.bolusEngageState = .engaging
})

if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended == true {
self.log.error("Not enacting bolus because podState or last status received indicates pod is suspended")
if let error = self.tryToValidateComms(session: session) {
completion(.communication(error))
return
}

// Use a lastDeliveryStatusReceived?.suspended != true test here to not return a pod suspended failure if
// there is not a valid last delivery status (which shouldn't even happen now with tryToValidateComms()).
guard let podState = self.state.podState, !podState.isSuspended && podState.lastDeliveryStatusReceived?.suspended != true else {
self.log.info("Not enacting bolus because podState or last status received indicates pod is suspended")
completion(.deviceState(PodCommsError.podSuspended))
return
}
Expand Down Expand Up @@ -1968,7 +2026,7 @@ extension OmnipodPumpManager: PumpManager {

public func runTemporaryBasalProgram(unitsPerHour: Double, for duration: TimeInterval, automatic: Bool, completion: @escaping (PumpManagerError?) -> Void) {

guard self.hasActivePod, let podState = self.state.podState else {
guard self.hasActivePod else {
completion(.configuration(OmnipodPumpManagerError.noPodPaired))
return
}
Expand Down Expand Up @@ -2009,7 +2067,14 @@ extension OmnipodPumpManager: PumpManager {
return
}

if let podState = self.state.podState, podState.isSuspended || podState.lastDeliveryStatusReceived?.suspended == true {
if let error = self.tryToValidateComms(session: session) {
completion(.communication(error))
return
}

// Use a lastDeliveryStatusReceived?.suspended != true test here to not return a pod suspended failure if
// there is not a valid last delivery status (which shouldn't even happen now with tryToValidateComms()).
guard let podState = self.state.podState, !podState.isSuspended && podState.lastDeliveryStatusReceived?.suspended != true else {
self.log.info("Not enacting temp basal because podState or last status received indicates pod is suspended")
completion(.deviceState(PodCommsError.podSuspended))
return
Expand Down Expand Up @@ -2053,7 +2118,7 @@ extension OmnipodPumpManager: PumpManager {
return
}

guard status.deliveryStatus != .suspended else {
guard !status.deliveryStatus.suspended else {
self.log.info("Canceling temp basal because status return indicates pod is suspended!")
completion(.communication(PodCommsError.podSuspended))
return
Expand Down Expand Up @@ -2448,12 +2513,10 @@ extension OmnipodPumpManager: PumpManager {

extension OmnipodPumpManager: MessageLogger {
func didSend(_ message: Data) {
log.default("didSend: %{public}@", message.hexadecimalString)
self.logDeviceCommunication(message.hexadecimalString, type: .send)
}

func didReceive(_ message: Data) {
log.default("didReceive: %{public}@", message.hexadecimalString)
self.logDeviceCommunication(message.hexadecimalString, type: .receive)
}

Expand Down
Loading