Skip to content
Merged
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
Binary file added Images/errors_when_building.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/rileylink_ios_omnipod_status.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/rileylink_ios_paired_omnipod.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/rileylink_ios_setup.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,29 @@ public struct RateEntry {
}

public var rate: Double {
return TimeInterval(hours: 1) / delayBetweenPulses * podPulseSize
if totalPulses == 0 {
return 0
} else {
return TimeInterval(hours: 1) / delayBetweenPulses * podPulseSize
}
}

public var duration: TimeInterval {
return delayBetweenPulses * Double(totalPulses)
if totalPulses == 0 {
return delayBetweenPulses / 10
} else {
return delayBetweenPulses * Double(totalPulses)
}
}

public var data: Data {
var data = Data()
data.appendBigEndian(UInt16(totalPulses * 10))
data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds))
if totalPulses == 0 {
data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds) * 10)
} else {
data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds))
}
return data
}

Expand All @@ -105,11 +117,20 @@ public struct RateEntry {

var remainingPulses = rate * duration.hours / podPulseSize
let delayBetweenPulses = TimeInterval(hours: 1) / rate * podPulseSize

var timeRemaining = duration

while (remainingPulses > 0) {
let pulseCount = min(maxPulses, remainingPulses)
entries.append(RateEntry(totalPulses: pulseCount, delayBetweenPulses: delayBetweenPulses))
remainingPulses -= pulseCount
while (remainingPulses > 0 || (rate == 0 && timeRemaining > 0)) {
if rate == 0 {
entries.append(RateEntry(totalPulses: 0, delayBetweenPulses: .minutes(30)))
timeRemaining -= .minutes(30)
} else {
let pulseCount = min(maxPulses, remainingPulses)
let entry = RateEntry(totalPulses: pulseCount, delayBetweenPulses: delayBetweenPulses)
entries.append(entry)
remainingPulses -= pulseCount
timeRemaining -= entry.duration
}
}
return entries
}
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions OmniKit/Pod.swift → OmniKit/Delivery/Pod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@

import Foundation

// Units
let podPulseSize: Double = 0.05

// Units per second
let bolusDeliveryRate: Double = 0.025
99 changes: 99 additions & 0 deletions OmniKit/Delivery/UnfinalizedDose.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// UnfinalizedDose.swift
// OmniKit
//
// Created by Pete Schwamb on 9/5/18.
// Copyright © 2018 Pete Schwamb. All rights reserved.
//

import Foundation
import LoopKit

struct UnfinalizedDose: RawRepresentable, Equatable {
public typealias RawValue = [String: Any]

enum DoseType: Int {
case bolus = 0
case tempBasal
}

enum ScheduledCertainty: Int {
case certain = 0
case uncertain
}

let doseType: DoseType
let units: Double
let startTime: Date
let duration: TimeInterval
let scheduledCertainty: ScheduledCertainty

var finishTime: Date {
return startTime.addingTimeInterval(duration)
}

var rate: Double {
return units / duration.hours
}

init(bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty) {
self.doseType = .bolus
self.units = bolusAmount
self.startTime = startTime
self.duration = TimeInterval(bolusAmount / bolusDeliveryRate)
self.scheduledCertainty = scheduledCertainty
}

init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, scheduledCertainty: ScheduledCertainty) {
self.doseType = .tempBasal
self.units = tempBasalRate * duration.hours
self.startTime = startTime
self.duration = duration
self.scheduledCertainty = scheduledCertainty
}

// RawRepresentable
public init?(rawValue: RawValue) {
guard
let rawDoseType = rawValue["doseType"] as? Int,
let doseType = DoseType(rawValue: rawDoseType),
let units = rawValue["units"] as? Double,
let startTime = rawValue["startTime"] as? Date,
let duration = rawValue["duration"] as? Double,
let rawScheduledCertainty = rawValue["scheduledCertainty"] as? Int,
let scheduledCertainty = ScheduledCertainty(rawValue: rawScheduledCertainty)
else {
return nil
}
self.doseType = doseType
self.units = units
self.startTime = startTime
self.duration = TimeInterval(duration)
self.scheduledCertainty = scheduledCertainty
}

public var rawValue: RawValue {
let rawValue: RawValue = [
"doseType": doseType.rawValue,
"units": units,
"startTime": startTime,
"duration": duration,
"scheduledCertainty": scheduledCertainty.rawValue
]

return rawValue
}
}

extension NewPumpEvent {
init(_ unfinalizedDose: UnfinalizedDose) {
let entry: DoseEntry
switch unfinalizedDose.doseType {
case .bolus:
entry = DoseEntry(type: .bolus, startDate: unfinalizedDose.startTime, value: unfinalizedDose.units, unit: .units)
case .tempBasal:
entry = DoseEntry(type: .tempBasal, startDate: unfinalizedDose.startTime, value: unfinalizedDose.rate, unit: .unitsPerHour)
}
self.init(date: unfinalizedDose.startTime, dose: entry, isMutable: false, raw: Data(), title: String(describing: unfinalizedDose))
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ public struct StatusResponse : MessageBlock {
case bolusInProgress = 5
case bolusAndTempBasal = 6

public var bolusing: Bool {
return self == .bolusInProgress || self == .bolusAndTempBasal
}

public var tempBasalRunning: Bool {
return self == .tempBasalRunning || self == .bolusAndTempBasal
}


public var description: String {
switch self {
case .deliveryInterrupted:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ public struct TempBasalExtraCommand : MessageBlock {
0
])
data.appendBigEndian(UInt16(remainingPulses * 10))
data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds))
if remainingPulses == 0 {
data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds) * 10)
} else {
data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds))
}
for entry in rateEntries {
data.append(entry.data)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class MessageTransport {
log.debug("Recv: %@", String(describing: response))
return response
} catch let error {
print("Error during communication with POD: \(error)")
log.error("Error during communication with POD: %@", String(describing: error))
throw error
}
}
Expand Down
File renamed without changes.
114 changes: 109 additions & 5 deletions OmniKit/PumpManager/OmnipodPumpManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import RileyLinkBLEKit
import os.log

public class OmnipodPumpManager: RileyLinkPumpManager, PumpManager {

public var pumpBatteryChargeRemaining: Double?

public var pumpRecordsBasalProfileStartEvents = false
Expand All @@ -24,15 +25,97 @@ public class OmnipodPumpManager: RileyLinkPumpManager, PumpManager {
}

public func assertCurrentPumpData() {
return
queue.async {
let semaphore = DispatchSemaphore(value: 0)
let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice
self.podComms.runSession(withName: "Get status for currentPumpData assertion", using: rileyLinkSelector) { (result) in
do {
switch result {
case .success(let session):
let status = try session.getStatus()
self.podStatusReceived(status: status)
case .failure(let error):
throw error
}
} catch let error {
self.log.error("Failed to fetch pump status: %{public}@", String(describing: error))
}
semaphore.signal()
}
semaphore.wait()
self.finalizeDoses {
self.pumpManagerDelegate?.pumpManagerRecommendsLoop(self)
}

}
}

public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (Double, Date) -> Void, completion: @escaping (Error?) -> Void) {
return

finalizeDoses()

let rileyLinkSelector = rileyLinkDeviceProvider.firstConnectedDevice
podComms.runSession(withName: "Bolus", using: rileyLinkSelector) { (result) in

let session: PodCommsSession
switch result {
case .success(let s):
session = s
case .failure(let error):
completion(SetBolusError.certain(error))
return
}

let podStatus: StatusResponse

do {
podStatus = try session.getStatus()
} catch let error {
completion(SetBolusError.certain(error as? PodCommsError ?? PodCommsError.commsError(error: error)))
return
}

guard !podStatus.deliveryStatus.bolusing else {
completion(SetBolusError.certain(PodCommsError.unfinalizedBolus))
return
}

willRequest(units, Date())

let result = session.bolus(units: units)

switch result {
case .success(let status):
self.podStatusReceived(status: status)
completion(nil)
case .certainFailure(let error):
completion(SetBolusError.certain(error))
case .uncertainFailure(let error):
completion(SetBolusError.uncertain(error))
}
}
}

public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerResult<DoseEntry>) -> Void) {
return
finalizeDoses()

let rileyLinkSelector = rileyLinkDeviceProvider.firstConnectedDevice
podComms.runSession(withName: "Enact Temp Basal", using: rileyLinkSelector) { (result) in
do {
switch result {
case .success(let session):
try session.setTempBasal(rate: unitsPerHour, duration: duration, confidenceReminder: false, programReminderInterval: 0)
let basalStart = Date()
let dose = DoseEntry(type: .basal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: unitsPerHour, unit: .unitsPerHour)
completion(PumpManagerResult.success(dose))
case .failure(let error):
self.log.error("Failed to set temp basal: %{public}@", String(describing: error))
throw error
}
} catch (let error) {
completion(PumpManagerResult.failure(error))
}
}
}

public func updateBLEHeartbeatPreference() {
Expand Down Expand Up @@ -88,7 +171,6 @@ public class OmnipodPumpManager: RileyLinkPumpManager, PumpManager {

public let log = OSLog(category: "OmnipodPumpManager")

// MARK: - Pump data
public static let localizedTitle = NSLocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager")

public var localizedTitle: String {
Expand All @@ -99,13 +181,35 @@ public class OmnipodPumpManager: RileyLinkPumpManager, PumpManager {
self.pumpManagerDelegate?.pumpManagerBLEHeartbeatDidFire(self)
}

private func finalizeDoses(_ completion: (() -> Void)? = nil) {
let storageHandler = { (pumpEvents: [NewPumpEvent]) -> Bool in
let semaphore = DispatchSemaphore(value: 0)
var success = false
self.pumpManagerDelegate?.pumpManager(self, didReadPumpEvents: pumpEvents, completion: { (error) in
success = error == nil
semaphore.signal()
})
semaphore.wait()
return success
}
podComms.finalizeDoses(storageHandler: storageHandler) {
completion?()
}
}

private func podStatusReceived(status: StatusResponse) {
pumpManagerDelegate?.pumpManager(self, didReadReservoirValue: status.reservoirLevel, at: Date()) { _ in
// Ignore result
}
}

// MARK: - CustomDebugStringConvertible

override public var debugDescription: String {
return [
"## OmnipodPumpManager",
"pumpBatteryChargeRemaining: \(String(reflecting: pumpBatteryChargeRemaining))",
"state: \(String(reflecting: state))",
"state: \(state.debugDescription)",
"",
"podComms: \(String(reflecting: podComms))",
"",
Expand Down
Loading