From 0e57feed3c26bfb7ee866f054e0e3d7c3e31b815 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 31 Jan 2019 15:49:00 -0800 Subject: [PATCH 01/71] Remove old omnikit --- OmniKit/Delivery/BasalDeliveryTable.swift | 142 --- OmniKit/Delivery/BasalSchedule.swift | 49 -- OmniKit/Delivery/Pod.swift | 15 - OmniKit/Delivery/UnfinalizedDose.swift | 171 ---- OmniKit/Extensions/Notification.swift | 13 - OmniKit/Info.plist | 24 - OmniKit/MessageTransport/CRC16.swift | 56 -- OmniKit/MessageTransport/CRC8.swift | 40 - OmniKit/MessageTransport/Message.swift | 87 -- .../MessageBlocks/AssignAddressCommand.swift | 38 - .../BasalScheduleExtraCommand.swift | 91 -- .../MessageBlocks/BolusExtraCommand.swift | 47 - .../MessageBlocks/CancelDeliveryCommand.swift | 80 -- .../ConfigureAlertsCommand.swift | 191 ---- .../MessageBlocks/DeactivatePodCommand.swift | 39 - .../MessageBlocks/ErrorResponse.swift | 35 - .../MessageBlocks/GetStatusCommand.swift | 52 -- .../MessageBlocks/MessageBlock.swift | 74 -- .../PlaceholderMessageBlock.swift | 29 - .../SetInsulinScheduleCommand.swift | 181 ---- .../MessageBlocks/SetPodTimeCommand.swift | 83 -- .../MessageBlocks/StatusError.swift | 132 --- .../MessageBlocks/StatusResponse.swift | 216 ----- .../MessageBlocks/TempBasalExtraCommand.swift | 78 -- .../MessageBlocks/VersionResponse.swift | 118 --- .../MessageTransport/MessageTransport.swift | 163 ---- OmniKit/MessageTransport/Packet.swift | 86 -- OmniKit/OmniKit.h | 19 - OmniKit/PumpManager/OmnipodPumpManager.swift | 280 ------ .../PumpManager/OmnipodPumpManagerState.swift | 78 -- OmniKit/PumpManager/PodComms.swift | 227 ----- OmniKit/PumpManager/PodCommsSession.swift | 409 --------- .../PumpManager/PodInsulinMeasurements.swift | 57 -- OmniKit/PumpManager/PodState.swift | 268 ------ OmniKitTests/BasalScheduleTests.swift | 139 --- OmniKitTests/BolusTests.swift | 76 -- OmniKitTests/CRC16Tests.swift | 21 - OmniKitTests/CRC8Tests.swift | 19 - OmniKitTests/Info.plist | 22 - OmniKitTests/MessageTests.swift | 238 ----- OmniKitTests/OmniKitTests-Bridging-Header.h | 4 - OmniKitTests/PacketTests.swift | 53 -- OmniKitTests/PodStateTests.swift | 94 -- OmniKitTests/StatusTests.swift | 155 ---- OmniKitTests/TempBasalTests.swift | 300 ------- OmniKitUI/CommandResponseViewController.swift | 65 -- OmniKitUI/Info.plist | 24 - OmniKitUI/OmniKitUI.h | 19 - OmniKitUI/OmniKitUI.xcassets/Contents.json | 6 - .../Pod.imageset/Contents.json | 23 - .../Pod.imageset/pod_1x-1.png | Bin 50805 -> 0 bytes .../Pod.imageset/pod_1x-2.png | Bin 24552 -> 0 bytes .../Pod.imageset/pod_1x.png | Bin 7728 -> 0 bytes .../PodBottom.imageset/Contents.json | 22 - .../PodBottom.imageset/PodBottom1x.jpg | Bin 15239 -> 0 bytes .../PodBottom.imageset/PodBottom2x.jpg | Bin 18436 -> 0 bytes .../PodLarge.imageset/Contents.json | 23 - .../PodLarge.imageset/pod_1x-1.png | Bin 67130 -> 0 bytes .../PodLarge.imageset/pod_1x-2.png | Bin 19537 -> 0 bytes .../PodLarge.imageset/pod_1x.png | Bin 138788 -> 0 bytes OmniKitUI/OmniPodPumpManager+UI.swift | 70 -- OmniKitUI/OmnipodPumpManager.storyboard | 355 -------- OmniKitUI/OmnipodSettingsViewController.swift | 479 ---------- .../InsertCannulaSetupViewController.swift | 206 ----- ...mnipodPumpManagerSetupViewController.swift | 85 -- .../Pairing/PairPodSetupViewController.swift | 298 ------- .../PodSetupCompleteViewController.swift | 26 - RileyLink.xcodeproj/project.pbxproj | 819 ------------------ RileyLink/PumpManagerState.swift | 7 - .../View Controllers/MainViewController.swift | 10 - 70 files changed, 7326 deletions(-) delete mode 100644 OmniKit/Delivery/BasalDeliveryTable.swift delete mode 100644 OmniKit/Delivery/BasalSchedule.swift delete mode 100644 OmniKit/Delivery/Pod.swift delete mode 100644 OmniKit/Delivery/UnfinalizedDose.swift delete mode 100644 OmniKit/Extensions/Notification.swift delete mode 100644 OmniKit/Info.plist delete mode 100644 OmniKit/MessageTransport/CRC16.swift delete mode 100644 OmniKit/MessageTransport/CRC8.swift delete mode 100644 OmniKit/MessageTransport/Message.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/ErrorResponse.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/PlaceholderMessageBlock.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/SetInsulinScheduleCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/StatusError.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift delete mode 100644 OmniKit/MessageTransport/MessageBlocks/VersionResponse.swift delete mode 100644 OmniKit/MessageTransport/MessageTransport.swift delete mode 100644 OmniKit/MessageTransport/Packet.swift delete mode 100644 OmniKit/OmniKit.h delete mode 100644 OmniKit/PumpManager/OmnipodPumpManager.swift delete mode 100644 OmniKit/PumpManager/OmnipodPumpManagerState.swift delete mode 100644 OmniKit/PumpManager/PodComms.swift delete mode 100644 OmniKit/PumpManager/PodCommsSession.swift delete mode 100644 OmniKit/PumpManager/PodInsulinMeasurements.swift delete mode 100644 OmniKit/PumpManager/PodState.swift delete mode 100644 OmniKitTests/BasalScheduleTests.swift delete mode 100644 OmniKitTests/BolusTests.swift delete mode 100644 OmniKitTests/CRC16Tests.swift delete mode 100644 OmniKitTests/CRC8Tests.swift delete mode 100644 OmniKitTests/Info.plist delete mode 100644 OmniKitTests/MessageTests.swift delete mode 100644 OmniKitTests/OmniKitTests-Bridging-Header.h delete mode 100644 OmniKitTests/PacketTests.swift delete mode 100644 OmniKitTests/PodStateTests.swift delete mode 100644 OmniKitTests/StatusTests.swift delete mode 100644 OmniKitTests/TempBasalTests.swift delete mode 100644 OmniKitUI/CommandResponseViewController.swift delete mode 100644 OmniKitUI/Info.plist delete mode 100644 OmniKitUI/OmniKitUI.h delete mode 100644 OmniKitUI/OmniKitUI.xcassets/Contents.json delete mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json delete mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-1.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-2.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.jpg delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.jpg delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-2.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png delete mode 100644 OmniKitUI/OmniPodPumpManager+UI.swift delete mode 100644 OmniKitUI/OmnipodPumpManager.storyboard delete mode 100644 OmniKitUI/OmnipodSettingsViewController.swift delete mode 100644 OmniKitUI/Pairing/InsertCannulaSetupViewController.swift delete mode 100644 OmniKitUI/Pairing/OmnipodPumpManagerSetupViewController.swift delete mode 100644 OmniKitUI/Pairing/PairPodSetupViewController.swift delete mode 100644 OmniKitUI/Pairing/PodSetupCompleteViewController.swift diff --git a/OmniKit/Delivery/BasalDeliveryTable.swift b/OmniKit/Delivery/BasalDeliveryTable.swift deleted file mode 100644 index 3c5f4cd72..000000000 --- a/OmniKit/Delivery/BasalDeliveryTable.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// BasalDeliveryTable.swift -// OmniKit -// -// Created by Pete Schwamb on 4/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct BasalTableEntry { - let segments: Int - let pulses: Int - let alternateSegmentPulse: Bool - - public init(encodedData: Data) { - segments = Int(encodedData[0] >> 4) + 1 - pulses = (Int(encodedData[0] & 0b11) << 8) + Int(encodedData[1]) - alternateSegmentPulse = (encodedData[0] >> 3) & 0x1 == 1 - } - - public init(segments: Int, pulses: Int, alternateSegmentPulse: Bool) { - self.segments = segments - self.pulses = pulses - self.alternateSegmentPulse = alternateSegmentPulse - } - - public var data: Data { - let pulsesHighBits = UInt8((pulses >> 8) & 0b11) - let pulsesLowBits = UInt8(pulses & 0xff) - return Data(bytes: [ - UInt8((segments - 1) << 4) + UInt8((alternateSegmentPulse ? 1 : 0) << 3) + pulsesHighBits, - UInt8(pulsesLowBits) - ]) - } - - public func checksum() -> UInt16 { - let checksumPerSegment = (pulses & 0xff) + (pulses >> 8) - return UInt16(checksumPerSegment * segments + (alternateSegmentPulse ? segments / 2 : 0)) - } - -} - -public struct BasalDeliveryTable { - static let segmentDuration: TimeInterval = .minutes(30) - - let entries: [BasalTableEntry] - - public init(entries: [BasalTableEntry]) { - self.entries = entries - } - - public init(schedule: BasalSchedule) { - var tableEntries = [BasalTableEntry]() - for entry in schedule.entries { - let pulsesPerSegment = entry.rate * BasalDeliveryTable.segmentDuration / TimeInterval(hours: 1) / podPulseSize - let alternateSegmentPulse = pulsesPerSegment - floor(pulsesPerSegment) > 0 - var remaining = Int(entry.duration / BasalDeliveryTable.segmentDuration) - while remaining > 0 { - let segments = min(remaining, 16) - let tableEntry = BasalTableEntry(segments: segments, pulses: Int(pulsesPerSegment), alternateSegmentPulse: alternateSegmentPulse) - tableEntries.append(tableEntry) - remaining -= segments - } - } - self.entries = tableEntries - } - - public init(tempBasalRate: Double, duration: TimeInterval) { - self.init(schedule: BasalSchedule(entries: [BasalScheduleEntry(rate: tempBasalRate, duration: duration)])) - } - - public func numSegments() -> Int { - return entries.reduce(0) { $0 + $1.segments } - } -} - -public struct RateEntry { - let totalPulses: Double - let delayBetweenPulses: TimeInterval - - public init(totalPulses: Double, delayBetweenPulses: TimeInterval) { - self.totalPulses = totalPulses - self.delayBetweenPulses = delayBetweenPulses - } - - public var rate: Double { - if totalPulses == 0 { - return 0 - } else { - return TimeInterval(hours: 1) / delayBetweenPulses * podPulseSize - } - } - - public var duration: TimeInterval { - if totalPulses == 0 { - return delayBetweenPulses / 10 - } else { - return delayBetweenPulses * Double(totalPulses) - } - } - - public var data: Data { - var data = Data() - data.appendBigEndian(UInt16(totalPulses * 10)) - if totalPulses == 0 { - data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds) * 10) - } else { - data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds)) - } - return data - } - - public static func makeEntries(rate: Double, duration: TimeInterval) -> [RateEntry] { - let maxPulses: Double = 6300 - var entries = [RateEntry]() - - var remainingPulses = rate * duration.hours / podPulseSize - let delayBetweenPulses = TimeInterval(hours: 1) / rate * podPulseSize - - var timeRemaining = duration - - 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 - } -} - - - - - diff --git a/OmniKit/Delivery/BasalSchedule.swift b/OmniKit/Delivery/BasalSchedule.swift deleted file mode 100644 index 7b8089f0a..000000000 --- a/OmniKit/Delivery/BasalSchedule.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// BasalSchedule.swift -// OmniKit -// -// Created by Pete Schwamb on 4/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct BasalScheduleEntry { - let rate: Double - let duration: TimeInterval - - public init(rate: Double, duration: TimeInterval) { - self.rate = rate - self.duration = duration - } -} - -// A basal schedule starts at midnight and should contain 24 hours worth of entries -public struct BasalSchedule { - let entries: [BasalScheduleEntry] - - public func rateAt(offset: TimeInterval) -> Double { - let (_, entry, _) = lookup(offset: offset) - return entry.rate - } - - func lookup(offset: TimeInterval) -> (Int, BasalScheduleEntry, TimeInterval) { - guard offset >= 0 && offset < .hours(24) else { - fatalError("Schedule offset out of bounds") - } - - var start: TimeInterval = 0 - for (index, entry) in entries.enumerated() { - let end = start + entry.duration - if end > offset { - return (index, entry, start) - } - start = end - } - fatalError("Schedule incomplete") - } - - public init(entries: [BasalScheduleEntry]) { - self.entries = entries - } -} diff --git a/OmniKit/Delivery/Pod.swift b/OmniKit/Delivery/Pod.swift deleted file mode 100644 index 11b3d7588..000000000 --- a/OmniKit/Delivery/Pod.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Pod.swift -// OmniKit -// -// Created by Pete Schwamb on 4/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -// Units -let podPulseSize: Double = 0.05 - -// Units per second -let bolusDeliveryRate: Double = 0.025 diff --git a/OmniKit/Delivery/UnfinalizedDose.swift b/OmniKit/Delivery/UnfinalizedDose.swift deleted file mode 100644 index f6ccfc682..000000000 --- a/OmniKit/Delivery/UnfinalizedDose.swift +++ /dev/null @@ -1,171 +0,0 @@ -// -// UnfinalizedDose.swift -// OmniKit -// -// Created by Pete Schwamb on 9/5/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation -import LoopKit - -public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConvertible { - public typealias RawValue = [String: Any] - - enum DoseType: Int { - case bolus = 0 - case tempBasal - } - - enum ScheduledCertainty: Int { - case certain = 0 - case uncertain - - public var localizedDescription: String { - switch self { - case .certain: - return LocalizedString("Certain", comment: "String describing a dose that was certainly scheduled") - case .uncertain: - return LocalizedString("Uncertain", comment: "String describing a dose that was possibly scheduled") - } - } - } - - private var insulinFormatter: NumberFormatter { - let formatter = NumberFormatter() - formatter.numberStyle = .decimal - formatter.maximumFractionDigits = 3 - - return formatter - } - - private var dateFormatter: DateFormatter { - let timeFormatter = DateFormatter() - timeFormatter.dateStyle = .short - timeFormatter.timeStyle = .medium - return timeFormatter - } - - - let doseType: DoseType - var units: Double - var scheduledUnits: Double? - let startTime: Date - var duration: TimeInterval - var scheduledCertainty: ScheduledCertainty - - var finishTime: Date { - get { - return startTime.addingTimeInterval(duration) - } - set { - duration = newValue.timeIntervalSince(startTime) - } - } - - // Units per hour - 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 - self.scheduledUnits = nil - } - - 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 - self.scheduledUnits = nil - } - - public mutating func cancel(at date: Date) { - scheduledUnits = units - let oldRate = rate - duration = date.timeIntervalSince(startTime) - units = oldRate * duration.hours - } - - public var description: String { - let unitsStr = insulinFormatter.string(from: units) ?? "?" - let startTimeStr = dateFormatter.string(from: startTime) - let durationStr = duration.format(using: [.minute, .second]) ?? "?" - switch doseType { - case .bolus: - if let scheduledUnits = scheduledUnits { - let scheduledUnitsStr = insulinFormatter.string(from: scheduledUnits) ?? "?" - return String(format: LocalizedString("InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@", comment: "The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty)"), unitsStr, scheduledUnitsStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) - } else { - return String(format: LocalizedString("Bolus: %1$@U %2$@ %3$@ %4$@", comment: "The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty)"), unitsStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) - } - case .tempBasal: - let rateStr = NumberFormatter.localizedString(from: NSNumber(value: rate), number: .decimal) - return String(format: LocalizedString("TempBasal: %1$@ U/hour %2$@ for %3$@ %4$@", comment: "The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: scheduled certainty"), rateStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) - } - } - - // 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 - } -} - -private extension TimeInterval { - func format(using units: NSCalendar.Unit) -> String? { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = units - formatter.unitsStyle = .full - formatter.zeroFormattingBehavior = .dropLeading - formatter.maximumUnitCount = 2 - - return formatter.string(from: self) - } -} - -extension NewPumpEvent { - init(_ unfinalizedDose: UnfinalizedDose) { - let title = String(describing: 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, endDate: unfinalizedDose.finishTime, value: unfinalizedDose.rate, unit: .unitsPerHour) - } - self.init(date: unfinalizedDose.startTime, dose: entry, isMutable: false, raw: title.data(using: .utf8)!, title: title) - } -} diff --git a/OmniKit/Extensions/Notification.swift b/OmniKit/Extensions/Notification.swift deleted file mode 100644 index bc0fdf190..000000000 --- a/OmniKit/Extensions/Notification.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Notification.swift -// OmniKit -// -// Created by Pete Schwamb on 2/22/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -extension Notification.Name { - static let DeviceRadioConfigDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkKit.DeviceRadioConfigDidChange") -} diff --git a/OmniKit/Info.plist b/OmniKit/Info.plist deleted file mode 100644 index 3d809ffe3..000000000 --- a/OmniKit/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 2.1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/OmniKit/MessageTransport/CRC16.swift b/OmniKit/MessageTransport/CRC16.swift deleted file mode 100644 index 33091ec20..000000000 --- a/OmniKit/MessageTransport/CRC16.swift +++ /dev/null @@ -1,56 +0,0 @@ - // -// CRC16.swift -// OmniKit -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation - -let crc16Table: [UInt16] = [ - 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, - 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, - 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, - 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, - 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, - 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, - 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, - 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, - 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, - 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, - 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, - 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, - 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, - 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, - 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, - 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, - 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, - 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, - 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, - 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, - 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, - 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, - 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, - 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, - 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, - 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, - 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, - 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, - 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, - 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, - 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, - 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202] - -public extension Sequence where Element == UInt8 { - - public func crc16() -> UInt16 { - - var acc: UInt16 = 0 - for byte in self { - let idx = (acc ^ UInt16(byte)) & 0xff - acc = (acc >> 8) ^ crc16Table[Int(idx)] - } - return acc - } -} diff --git a/OmniKit/MessageTransport/CRC8.swift b/OmniKit/MessageTransport/CRC8.swift deleted file mode 100644 index 3b541df6b..000000000 --- a/OmniKit/MessageTransport/CRC8.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// CRC8.swift -// OmniKit -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation - -fileprivate let crcTable: [UInt8] = [ - 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, - 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, - 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, - 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, - 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, - 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, - 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, - 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, - 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, - 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, - 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, - 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, - 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, - 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, - 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, - 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 -] - -public extension Sequence where Element == UInt8 { - - public func crc8() -> UInt8 { - - var crc: UInt8 = 0 - for byte in self { - crc = crcTable[Int((crc ^ byte) & 0xff)] - } - return crc - } -} diff --git a/OmniKit/MessageTransport/Message.swift b/OmniKit/MessageTransport/Message.swift deleted file mode 100644 index ac2fde6fc..000000000 --- a/OmniKit/MessageTransport/Message.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// Message.swift -// OmniKit -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation - -public enum MessageError: Error { - case notEnoughData - case invalidCrc - case parsingError(offset: Int, data: Data, error: Error) - case unknownValue(value: UInt8, typeDescription: String) - case validationFailed(description: String) -} - -struct Message { - let address: UInt32 - let messageBlocks: [MessageBlock] - let sequenceNum: Int - - init(address: UInt32, messageBlocks: [MessageBlock], sequenceNum: Int) { - self.address = address - self.messageBlocks = messageBlocks - self.sequenceNum = sequenceNum - } - - init(encodedData: Data) throws { - guard encodedData.count >= 10 else { - throw MessageError.notEnoughData - } - self.address = encodedData[0...].toBigEndian(UInt32.self) - let b9 = encodedData[4] - let bodyLen = encodedData[5] - - if bodyLen > encodedData.count - 8 { - throw MessageError.notEnoughData - } - - self.sequenceNum = Int((b9 >> 2) & 0b11111) - let crc = (UInt16(encodedData[encodedData.count-2]) << 8) + UInt16(encodedData[encodedData.count-1]) - let msgWithoutCrc = encodedData.prefix(encodedData.count - 2) - guard msgWithoutCrc.crc16() == crc else { - throw MessageError.invalidCrc - } - self.messageBlocks = try Message.decodeBlocks(data: Data(msgWithoutCrc.suffix(from: 6))) - } - - static private func decodeBlocks(data: Data) throws -> [MessageBlock] { - var blocks = [MessageBlock]() - var idx = 0 - repeat { - guard let blockType = MessageBlockType(rawValue: data[idx]) else { - throw MessageBlockError.unknownBlockType(rawVal: data[idx]) - } - do { - let block = try blockType.blockType.init(encodedData: data.suffix(from: idx)) - blocks.append(block) - idx += Int(block.data.count) - } catch (let error) { - throw MessageError.parsingError(offset: idx, data: data.suffix(from: idx), error: error) - } - } while idx < data.count - return blocks - } - - func encoded() -> Data { - var bytes = Data(bigEndian: address) - - var cmdData = Data() - for cmd in messageBlocks { - cmdData.append(cmd.data) - } - - let b9: UInt8 = (UInt8(sequenceNum & 0b11111) << 2) + UInt8((cmdData.count >> 8) & 0b11) - bytes.append(b9) - bytes.append(UInt8(cmdData.count & 0xff)) - - var data = Data(bytes) + cmdData - let crc = data.crc16() - data.appendBigEndian(crc) - return data - } -} - diff --git a/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift b/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift deleted file mode 100644 index 21d492edd..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// AssignAddressCommand.swift -// OmniKit -// -// Created by Pete Schwamb on 2/12/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct AssignAddressCommand : MessageBlock { - - public let blockType: MessageBlockType = .assignAddress - public let length: Int = 6 - - let address: UInt32 - - public var data: Data { - var data = Data(bytes: [ - blockType.rawValue, - 4 - ]) - data.appendBigEndian(self.address) - return data - } - - public init(encodedData: Data) throws { - if encodedData.count < length { - throw MessageBlockError.notEnoughData - } - - self.address = encodedData[2...].toBigEndian(UInt32.self) - } - - public init(address: UInt32) { - self.address = address - } -} diff --git a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift deleted file mode 100644 index 2c5648cb9..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// BasalScheduleExtraCommand.swift -// OmniKit -// -// Created by Pete Schwamb on 3/30/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct BasalScheduleExtraCommand : MessageBlock { - - public let blockType: MessageBlockType = .basalScheduleExtra - - public let confidenceReminder: Bool - public let programReminderInterval: TimeInterval - public let currentEntryIndex: UInt8 - public let remainingPulses: Double - public let delayUntilNextPulse: TimeInterval - public let rateEntries: [RateEntry] - - public var data: Data { - let reminders = (UInt8(programReminderInterval.minutes) & 0x3f) + (confidenceReminder ? (1<<6) : 0) - var data = Data(bytes: [ - blockType.rawValue, - UInt8(8 + rateEntries.count * 6), - reminders, - currentEntryIndex - ]) - data.appendBigEndian(UInt16(remainingPulses * 10)) - data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds)) - for entry in rateEntries { - data.append(entry.data) - } - return data - } - - public init(encodedData: Data) throws { - if encodedData.count < 14 { - throw MessageBlockError.notEnoughData - } - let length = encodedData[1] - let numEntries = (length - 8) / 6 - - confidenceReminder = encodedData[2] & (1<<6) != 0 - programReminderInterval = TimeInterval(minutes: Double(encodedData[2] & 0x3f)) - - currentEntryIndex = encodedData[3] - remainingPulses = Double(encodedData[4...].toBigEndian(UInt16.self)) / 10.0 - let timerCounter = encodedData[6...].toBigEndian(UInt32.self) - delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) - var entries = [RateEntry]() - for entryIndex in (0..> 4)! - } - - public init(nonce: UInt32, deliveryType: DeliveryType, beepType: ConfigureAlertsCommand.BeepType) { - self.nonce = nonce - self.deliveryType = deliveryType - self.beepType = beepType - } -} diff --git a/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift b/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift deleted file mode 100644 index 53808a8b2..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift +++ /dev/null @@ -1,191 +0,0 @@ -// -// ConfigureAlertsCommand.swift -// OmniKit -// -// Created by Pete Schwamb on 2/22/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct ConfigureAlertsCommand : NonceResyncableMessageBlock { - - // Pairing ConfigureAlerts #1 - // 4c00 0190 0102 - // 4c00 00c8 0102 - // 4c00 00c8 0102 - // 4c00 0096 0102 - // 4c00 0064 0102 - - // Pairing ConfigureAlerts #2 - // 7837 0005 0802 - // 7837 0005 0802 - // 7837 0005 0802 - // 7837 0005 0802 - // 7837 0005 0802 - - // Pairing ConfigureAlerts #3 - // 3800 0ff0 0302 - // 3800 10a4 0302 - // 3800 10a4 0302 - // 3800 10a4 0302 - // 3800 0ff0 0302 - - public enum AlertType: UInt8 { - case autoOff = 0x00 - case endOfService = 0x02 - case expirationAdvisory = 0x03 - case lowReservoir = 0x04 - case suspendInProgress = 0x05 - case suspendEnded = 0x06 - case timerLimit = 0x07 - } - - public enum ExpirationType { - case reservoir(volume: Double) - case time(TimeInterval) - } - - public enum BeepType: UInt8 { - case noBeep = 0 - case beepBeepBeepBeep = 1 - case bipBeepBipBeepBipBeepBipBeep = 2 - case bipBip = 3 - case beep = 4 - case beepBeepBeep = 5 - case beeeeeep = 6 - case bipBipBipbipBipBip = 7 - case beeepBeeep = 8 - } // Reused in CancelDeliveryCommand - - public struct AlertConfiguration { - let alertType: AlertType - let expirationType: ExpirationType - let audible: Bool - let duration: TimeInterval - let beepType: BeepType - let beepRepeat: UInt8 - let autoOffModifier: Bool - - static let length = 6 - - public var data: Data { - var firstByte = alertType.rawValue << 4 - firstByte += audible ? (1 << 3) : 0 - - if case .reservoir = expirationType { - firstByte += 1 << 2 - } - if autoOffModifier { - firstByte += 1 << 1 - } - // High bit of duration - firstByte += UInt8((Int(duration.minutes) >> 8) & 0x1) - - var data = Data([ - firstByte, - UInt8(Int(duration.minutes) & 0xff) - ]) - - switch expirationType { - case .reservoir(let volume): - let ticks = UInt16(volume / podPulseSize / 2) - data.appendBigEndian(ticks) - case .time(let duration): - let minutes = UInt16(duration.minutes) - data.appendBigEndian(minutes) - } - data.append(beepType.rawValue) - data.append(beepRepeat) - - return data - } - - public init(alertType: AlertType, audible: Bool, autoOffModifier: Bool, duration: TimeInterval, expirationType: ExpirationType, beepType: BeepType, beepRepeat: UInt8) { - self.alertType = alertType - self.audible = audible - self.autoOffModifier = autoOffModifier - self.duration = duration - self.expirationType = expirationType - self.beepType = beepType - self.beepRepeat = beepRepeat - } - - public init(encodedData: Data) throws { - if encodedData.count < 6 { - throw MessageBlockError.notEnoughData - } - - let alertTypeBits = encodedData[0] >> 4 - guard let alertType = AlertType(rawValue: alertTypeBits) else { - throw MessageError.unknownValue(value: alertTypeBits, typeDescription: "AlertType") - } - self.alertType = alertType - - self.audible = encodedData[0] & 0b1000 != 0 - - self.autoOffModifier = encodedData[0] & 0b10 != 0 - - self.duration = TimeInterval(minutes: Double((Int(encodedData[0] & 0b1) << 8) + Int(encodedData[1]))) - - let yyyy = (Int(encodedData[2]) << 8) + (Int(encodedData[3])) & 0x3fff - - if encodedData[0] & 0b100 != 0 { - let volume = Double(yyyy * 2) * podPulseSize - self.expirationType = .reservoir(volume: volume) - } else { - self.expirationType = .time(TimeInterval(minutes: Double(yyyy))) - } - - let beepTypeBits = encodedData[4] - guard let beepType = BeepType(rawValue: beepTypeBits) else { - throw MessageError.unknownValue(value: beepTypeBits, typeDescription: "BeepType") - } - self.beepType = beepType - - self.beepRepeat = encodedData[5] - - } - } - - public let blockType: MessageBlockType = .configureAlerts - - public var nonce: UInt32 - let configurations: [AlertConfiguration] - - public var data: Data { - var data = Data(bytes: [ - blockType.rawValue, - UInt8(4 + configurations.count * AlertConfiguration.length), - ]) - data.appendBigEndian(nonce) - for config in configurations { - data.append(contentsOf: config.data) - } - return data - } - - public init(encodedData: Data) throws { - if encodedData.count < 10 { - throw MessageBlockError.notEnoughData - } - self.nonce = encodedData[2...].toBigEndian(UInt32.self) - - let length = Int(encodedData[1]) - - let numConfigs = (length - 4) / AlertConfiguration.length - - var configs = [AlertConfiguration]() - - for i in 0.. ScheduleTypeCode { - switch self { - case .basalSchedule: - return .basalSchedule - case .tempBasal: - return .tempBasal - case .bolus: - return .bolus - } - } - - fileprivate var data: Data { - switch self { - case .basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table): - var data = Data(bytes: [currentSegment]) - data.appendBigEndian(secondsRemaining << 3) - data.appendBigEndian(pulsesRemaining) - for entry in table.entries { - data.append(entry.data) - } - return data - case .bolus(let units, let multiplier): - let pulseCount = UInt16(units / podPulseSize) - let fieldA = pulseCount * multiplier - let numHalfHourSegments: UInt8 = 1 - var data = Data(bytes: [numHalfHourSegments]) - data.appendBigEndian(fieldA) - data.appendBigEndian(pulseCount) - data.appendBigEndian(pulseCount) - return data - case .tempBasal(let secondsRemaining, let firstSegmentPulses, let table): - var data = Data(bytes: [UInt8(table.numSegments())]) - data.appendBigEndian(secondsRemaining << 3) - data.appendBigEndian(firstSegmentPulses) - for entry in table.entries { - data.append(entry.data) - } - return data - - } - } - - fileprivate func checksum() -> UInt16 { - switch self { - case .basalSchedule( _, _, _, let table): - return data[0..<5].reduce(0) { $0 + UInt16($1) } + - table.entries.reduce(0) { $0 + $1.checksum() } - case .bolus: - return data[0..<7].reduce(0) { $0 + UInt16($1) } - case .tempBasal(_, _, let table): - return data[0..<5].reduce(0) { $0 + UInt16($1) } + - table.entries.reduce(0) { $0 + $1.checksum() } - } - } - } - - public let blockType: MessageBlockType = .setInsulinSchedule - - public var nonce: UInt32 - public let deliverySchedule: DeliverySchedule - - public var data: Data { - var data = Data(bytes: [ - blockType.rawValue, - UInt8(7 + deliverySchedule.data.count), - ]) - data.appendBigEndian(nonce) - data.append(deliverySchedule.typeCode().rawValue) - data.appendBigEndian(deliverySchedule.checksum()) - data.append(deliverySchedule.data) - return data - } - - public init(encodedData: Data) throws { - if encodedData.count < 6 { - throw MessageBlockError.notEnoughData - } - let length = encodedData[1] - - nonce = encodedData[2...].toBigEndian(UInt32.self) - - let checksum = encodedData[7...].toBigEndian(UInt16.self) - - guard let scheduleTypeCode = ScheduleTypeCode(rawValue: encodedData[6]) else { - throw MessageError.unknownValue(value: encodedData[6], typeDescription: "ScheduleTypeCode") - } - - switch scheduleTypeCode { - case .basalSchedule: - var entries = [BasalTableEntry]() - let numEntries = (length - 12) / 2 - for i in 0..> 3 - let pulsesRemaining = encodedData[12...].toBigEndian(UInt16.self) - let table = BasalDeliveryTable(entries: entries) - deliverySchedule = .basalSchedule(currentSegment: currentTableIndex, secondsRemaining: secondsRemaining, pulsesRemaining: pulsesRemaining, table: table) - case .tempBasal: - let secondsRemaining = encodedData[10...].toBigEndian(UInt16.self) >> 3 - let firstSegmentPulses = encodedData[12...].toBigEndian(UInt16.self) - var entries = [BasalTableEntry]() - let numEntries = (length - 12) / 2 - for i in 0.. UInt16 { - return data.reduce(0) { $0 + UInt16($1) } -} - diff --git a/OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift b/OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift deleted file mode 100644 index 491c3af64..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// SetupPodCommand.swift -// OmniKit -// -// Created by Pete Schwamb on 2/17/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct SetupPodCommand : MessageBlock { - - public let blockType: MessageBlockType = .setupPod - - let address: UInt32 - let lot: UInt32 - let tid: UInt32 - let dateComponents: DateComponents - - public static func dateComponents(date: Date, timeZone: TimeZone) -> DateComponents { - var cal = Calendar(identifier: .gregorian) - cal.timeZone = timeZone - return cal.dateComponents([.day, .month, .year, .hour, .minute], from: date) - } - - public static func date(from components: DateComponents, timeZone: TimeZone) -> Date? { - var cal = Calendar(identifier: .gregorian) - cal.timeZone = timeZone - return cal.date(from: components) - } - - // 03 13 1f08ced2 14 04 09 0b 11 0b 08 0000a640 00097c27 83e4 - public var data: Data { - var data = Data(bytes: [ - blockType.rawValue, - 19, - ]) - data.appendBigEndian(self.address) - - let year = UInt8((dateComponents.year ?? 2000) - 2000) - let month = UInt8(dateComponents.month ?? 0) - let day = UInt8(dateComponents.day ?? 0) - let hour = UInt8(dateComponents.hour ?? 0) - let minute = UInt8(dateComponents.minute ?? 0) - - let data2: Data = Data(bytes: [ - UInt8(0x14), // Unknown - UInt8(0x04), // Unknown - day, - month, - year, - hour, - minute - ]) - data.append(data2) - data.appendBigEndian(self.lot) - data.appendBigEndian(self.tid) - return data - } - - public init(encodedData: Data) throws { - if encodedData.count < 21 { - throw MessageBlockError.notEnoughData - } - self.address = encodedData[2...].toBigEndian(UInt32.self) - var components = DateComponents() - components.day = Int(encodedData[8]) - components.month = Int(encodedData[9]) - components.year = Int(encodedData[10]) + 2000 - components.hour = Int(encodedData[11]) - components.minute = Int(encodedData[12]) - self.dateComponents = components - self.lot = encodedData[13...].toBigEndian(UInt32.self) - self.tid = encodedData[17...].toBigEndian(UInt32.self) - } - - public init(address: UInt32, dateComponents: DateComponents, lot: UInt32, tid: UInt32) { - self.address = address - self.dateComponents = dateComponents - self.lot = lot - self.tid = tid - } -} diff --git a/OmniKit/MessageTransport/MessageBlocks/StatusError.swift b/OmniKit/MessageTransport/MessageBlocks/StatusError.swift deleted file mode 100644 index acd5d0b57..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/StatusError.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// StatusError.swift -// OmniKit -// -// Created by Pete Schwamb on 2/23/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct StatusError : MessageBlock { - // https://github.com/openaps/openomni/wiki/Command-02-Status-Error-response - -// public enum lengthType: UInt8{ -// case normal = 0x10 -// case configuredAlerts = 0x13 -// case faultEvents = 0x16 -// case dataLog = 0x04*numberOfWords+0x08 -// case faultDataInitializationTime = 0x11 -// case hardcodedValues = 0x5 -// case resetStatus = numberOfBytes & 0x03 -// case dumpRecentFlashLog = 0x13 -// case dumpOlderFlashlog = 0x14 -// - // public let numberOfWords: UInt8 = 60 - // public let numberOfBytes: UInt8 = 10 - - - public struct DeliveryInProgressType: OptionSet { - public let rawValue: UInt8 - - static let none = DeliveryInProgressType(rawValue: 0) - static let basal = DeliveryInProgressType(rawValue: 1 << 0) - static let tempBasal = DeliveryInProgressType(rawValue: 1 << 1) - static let bolus = DeliveryInProgressType(rawValue: 1 << 2) - static let extendedBolus = DeliveryInProgressType(rawValue: 1 << 3) - - static let all: DeliveryInProgressType = [.none, .basal, .tempBasal, .bolus, .extendedBolus] - - public init(rawValue: UInt8) { - self.rawValue = rawValue - } - } - - public enum InfoLoggedFaultEventType: UInt8 { - case insulinStateCorruptionDuringErrorLogging = 0 - case immediateBolusInProgressDuringError = 1 - // TODO: bb: internal boolean variable initialized to Tab5[$D] != 0 - } - - public let requestedType: GetStatusCommand.StatusType - public let length: UInt8 - public let blockType: MessageBlockType = .statusError - public let deliveryInProgressType: DeliveryInProgressType - public let reservoirStatus: StatusResponse.ReservoirStatus // Reused from StatusResponse - public let insulinNotDelivered: Double - public let podMessageCounter: UInt8 - public let unknownPageCode: Double - public let originalLoggedFaultEvent: UInt8 - public let faultEventTimeSinceActivation: Double - public let insulinRemaining: Double - public let timeActive: TimeInterval - public let secondaryLoggedFaultEvent: UInt8 - public let logEventError: Bool - public let infoLoggedFaultEvent: InfoLoggedFaultEventType - public let reservoirStatusAtFirstLoggedFaultEvent: StatusResponse.ReservoirStatus - public let recieverLowGain: UInt8 - public let radioRSSI: UInt8 - public let reservoirStatusAtFirstLoggedFaultEventCheck: StatusResponse.ReservoirStatus - - public let data: Data - - public init(encodedData: Data) throws { - - if encodedData.count < Int(13) { - throw MessageBlockError.notEnoughData - } - - self.length = encodedData[1] - - guard let requestedType = GetStatusCommand.StatusType(rawValue: encodedData[2]) else { - throw MessageError.unknownValue(value: encodedData[2], typeDescription: "StatusType") - } - self.requestedType = requestedType - - guard let reservoirStatus = StatusResponse.ReservoirStatus(rawValue: encodedData[3]) else { - throw MessageError.unknownValue(value: encodedData[3], typeDescription: "StatusResponse.ReservoirStatus") - } - self.reservoirStatus = reservoirStatus - - self.deliveryInProgressType = DeliveryInProgressType(rawValue: encodedData[4] & 0xf) - - self.insulinNotDelivered = podPulseSize * Double((Int(encodedData[5] & 0x3) << 8) | Int(encodedData[6])) - - self.podMessageCounter = encodedData[7] - self.unknownPageCode = Double(Int(encodedData[8]) | Int(encodedData[9])) - - self.originalLoggedFaultEvent = encodedData[10] - - self.faultEventTimeSinceActivation = TimeInterval(minutes: Double((Int(encodedData[11] & 0b1) << 8) + Int(encodedData[12]))) - - self.insulinRemaining = podPulseSize * Double((Int(encodedData[13] & 0x3) << 8) | Int(encodedData[14])) - - self.timeActive = TimeInterval(minutes: Double((Int(encodedData[15] & 0b1) << 8) + Int(encodedData[16]))) - - self.secondaryLoggedFaultEvent = encodedData[17] - - self.logEventError = encodedData[18] == 2 - - guard let infoLoggedFaultEventType = InfoLoggedFaultEventType(rawValue: encodedData[19] >> 4) else { - throw MessageError.unknownValue(value: encodedData[19] >> 4, typeDescription: "InfoLoggedFaultEventType") - } - self.infoLoggedFaultEvent = infoLoggedFaultEventType - - guard let reservoirStatusAtFirstLoggedFaultEventType = StatusResponse.ReservoirStatus(rawValue: encodedData[19] & 0xF) else { - throw MessageError.unknownValue(value: encodedData[19] & 0xF, typeDescription: "ProgressType") - } - self.reservoirStatusAtFirstLoggedFaultEvent = reservoirStatusAtFirstLoggedFaultEventType - - self.recieverLowGain = encodedData[20] >> 4 - - self.radioRSSI = encodedData[20] & 0xF - - guard let reservoirStatusAtFirstLoggedFaultEventCheckType = StatusResponse.ReservoirStatus(rawValue: encodedData[21] & 0xF) else { - throw MessageError.unknownValue(value: encodedData[21] & 0xF, typeDescription: "ProgressType") - } - self.reservoirStatusAtFirstLoggedFaultEventCheck = reservoirStatusAtFirstLoggedFaultEventCheckType - - // Unknown value: - self.data = Data(encodedData[22]) - } -} diff --git a/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift b/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift deleted file mode 100644 index 6e5789557..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift +++ /dev/null @@ -1,216 +0,0 @@ -// -// StatusResponse.swift -// OmniKit -// -// Created by Pete Schwamb on 10/23/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct StatusResponse : MessageBlock { - - public enum DeliveryStatus: UInt8, CustomStringConvertible { - case deliveryInterrupted = 0 - case normal = 1 - case tempBasalRunning = 2 - case priming = 4 - 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: - return LocalizedString("Interrupted", comment: "Delivery status when delivery is interrupted") - case .normal: - return LocalizedString("Normal", comment: "Delivery status when basal is running") - case .tempBasalRunning: - return LocalizedString("Temp basal running", comment: "Delivery status when temp basal is running") - case .priming: - return LocalizedString("Priming", comment: "Delivery status when pod is priming") - case .bolusInProgress: - return LocalizedString("Bolusing", comment: "Delivery status when bolusing") - case .bolusAndTempBasal: - return LocalizedString("Bolusing with temp basal", comment: "Delivery status when bolusing and temp basal is running") - } - } - } - - public enum ReservoirStatus: UInt8, CustomStringConvertible { - case initialized = 0 - case tankPowerActivated = 1 - case tankFillCompleted = 2 - case pairingSuccess = 3 - case priming = 4 - case readyForInjection = 5 - case injectionStarted = 6 - case injectionDone = 7 - case aboveFiftyUnits = 8 - case belowFiftyUnits = 9 - case oneNotUsedButin33 = 10 - case twoNotUsedButin33 = 11 - case threeNotUsedButin33 = 12 - case errorEventLoggedShuttingDown = 13 - case delayedPrime = 14 // Saw this after delaying prime for a day - case inactive = 15 // ($1C Deactivate Pod or packet header mismatch) - - public var description: String { - switch self { - case .initialized: - return LocalizedString("Initialized", comment: "Pod inititialized") - case .tankPowerActivated: - return LocalizedString("Tank power activated", comment: "Pod power to motor activated") - case .tankFillCompleted: - return LocalizedString("Tank fill completed", comment: "Pod tank fill completed") - case .pairingSuccess: - return LocalizedString("Paired", comment: "Pod status after pairing") - case .priming: - return LocalizedString("Priming", comment: "Pod status when priming") - case .readyForInjection: - return LocalizedString("Ready to insert cannula", comment: "Pod status when ready for cannula insertion") - case .injectionStarted: - return LocalizedString("Cannula inserting", comment: "Pod status when inserting cannula") - case .injectionDone: - return LocalizedString("Cannula inserted", comment: "Pod status when cannula insertion finished") - case .aboveFiftyUnits: - return LocalizedString("Normal", comment: "Pod status when running above fifty units") - case .belowFiftyUnits: - return LocalizedString("Below 50 units", comment: "Pod status when running below fifty units") - case .oneNotUsedButin33: - return LocalizedString("oneNotUsedButin33", comment: "Pod status oneNotUsedButin33") - case .twoNotUsedButin33: - return LocalizedString("twoNotUsedButin33", comment: "Pod status twoNotUsedButin33") - case .threeNotUsedButin33: - return LocalizedString("threeNotUsedButin33", comment: "Pod status threeNotUsedButin33") - case .errorEventLoggedShuttingDown: - return LocalizedString("Error event logged, shutting down", comment: "Pod status error event logged shutting down") - case .delayedPrime: - return LocalizedString("Prime not completed", comment: "Pod status when prime has not completed") - case .inactive: - return LocalizedString("Deactivated", comment: "Pod status when pod has been deactivated") - } - } - } - - public static var maximumReservoirReading: Double = 50.0 - - public enum PodAlarm: UInt8 { - case podExpired = 0b10000000 - case suspendExpired = 0b01000000 - case suspended = 0b00100000 - case belowFiftyUnits = 0b00010000 - case oneHourExpiry = 0b00001000 - case podDeactivated = 0b00000100 - case unknownBit2 = 0b00000010 - case unknownBit1 = 0b00000001 - - public typealias AllCases = [PodAlarm] - - static var allCases: AllCases { - return (0..<8).map { PodAlarm(rawValue: 1<<$0)! } - } - } - - public struct PodAlarmState: RawRepresentable, Collection, CustomStringConvertible { - - public typealias RawValue = UInt8 - public typealias Index = Int - - public let startIndex: Int - public let endIndex: Int - - private let elements: [PodAlarm] - - public var rawValue: UInt8 { - return elements.reduce(0) { $0 & $1.rawValue } - } - - public init(rawValue: UInt8) { - self.elements = PodAlarm.allCases.filter { rawValue & $0.rawValue != 0 } - self.startIndex = 0 - self.endIndex = self.elements.count - } - - public subscript(index: Index) -> PodAlarm { - return elements[index] - } - - public func index(after i: Int) -> Int { - return i+1 - } - - public var description: String { - if elements.count == 0 { - return LocalizedString("No alarms", comment: "Pod alarm state when no alarms are activated") - } else { - let alarmDescriptions = elements.map { String(describing: $0) } - return alarmDescriptions.joined(separator: ", ") - } - } - - } - - public let blockType: MessageBlockType = .statusResponse - public let length: UInt8 = 10 - public let deliveryStatus: DeliveryStatus - public let reservoirStatus: ReservoirStatus - public let timeActive: TimeInterval - public let reservoirLevel: Double? - public let insulin: Double - public let insulinNotDelivered: Double - public let podMessageCounter: UInt8 - public let alarms: PodAlarmState - - - public let data: Data - - public init(encodedData: Data) throws { - if encodedData.count < length { - throw MessageBlockError.notEnoughData - } - - data = encodedData.prefix(upTo: Int(length)) - - guard let deliveryStatus = DeliveryStatus(rawValue: encodedData[1] >> 4) else { - throw MessageError.unknownValue(value: encodedData[1] >> 4, typeDescription: "DeliveryStatus") - } - self.deliveryStatus = deliveryStatus - - guard let reservoirStatus = ReservoirStatus(rawValue: encodedData[1] & 0xf) else { - throw MessageError.unknownValue(value: encodedData[1] & 0xf, typeDescription: "ReservoirStatus") - } - self.reservoirStatus = reservoirStatus - - let minutes = ((Int(encodedData[7]) & 0x7f) << 6) + (Int(encodedData[8]) >> 2) - self.timeActive = TimeInterval(minutes: Double(minutes)) - - let highInsulinBits = Int(encodedData[2] & 0xf) << 9 - let midInsulinBits = Int(encodedData[3]) << 1 - let lowInsulinBits = Int(encodedData[4] >> 7) - self.insulin = podPulseSize * Double(highInsulinBits | midInsulinBits | lowInsulinBits) - - self.podMessageCounter = (encodedData[4] >> 3) & 0xf - - self.insulinNotDelivered = podPulseSize * Double((Int(encodedData[4] & 0x3) << 8) | Int(encodedData[5])) - - self.alarms = PodAlarmState(rawValue: ((encodedData[6] & 0x7f) << 1) | (encodedData[7] >> 7)) - - let resHighBits = Int(encodedData[8] & 0x03) << 6 - let resLowBits = Int(encodedData[9] >> 2) - let reservoirValue = round((Double((resHighBits + resLowBits))*50)/255) - if reservoirValue < StatusResponse.maximumReservoirReading { - self.reservoirLevel = reservoirValue - } else { - self.reservoirLevel = nil - } - } -} diff --git a/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift deleted file mode 100644 index 0ff5c99b8..000000000 --- a/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// TempBasalExtraCommand.swift -// OmniKit -// -// Created by Pete Schwamb on 6/6/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct TempBasalExtraCommand : MessageBlock { - - public let confidenceReminder: Bool - public let programReminderInterval: TimeInterval - public let remainingPulses: Double - public let delayUntilNextPulse: TimeInterval - public let rateEntries: [RateEntry] - - public let blockType: MessageBlockType = .tempBasalExtra - - public var data: Data { - let reminders = (UInt8(programReminderInterval.minutes) & 0x3f) + (confidenceReminder ? (1<<6) : 0) - var data = Data(bytes: [ - blockType.rawValue, - UInt8(8 + rateEntries.count * 6), - reminders, - 0 - ]) - data.appendBigEndian(UInt16(remainingPulses * 10)) - if remainingPulses == 0 { - data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds) * 10) - } else { - data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds)) - } - for entry in rateEntries { - data.append(entry.data) - } - return data - } - - public init(encodedData: Data) throws { - - let length = encodedData[1] - let numEntries = (length - 8) / 6 - - confidenceReminder = encodedData[2] & (1<<6) != 0 - programReminderInterval = TimeInterval(minutes: Double(encodedData[2] & 0x3f)) - - remainingPulses = Double(encodedData[4...].toBigEndian(UInt16.self)) / 10.0 - let timerCounter = encodedData[6...].toBigEndian(UInt32.self) - delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) - var entries = [RateEntry]() - for entryIndex in (0.. Packet { - return Packet(address: address, packetType: .ack, sequenceNum: packetNumber, data: Data(bigEndian: ackAddress)) - } - - func ackUntilQuiet() throws { - - let packetData = makeAckPacket().encoded() - - var quiet = false - while !quiet { - do { - let _ = try session.sendAndListen(packetData, repeatCount: 5, timeout: TimeInterval(milliseconds: 600), retryCount: 0, preambleExtension: TimeInterval(milliseconds: 40)) - } catch RileyLinkDeviceError.responseTimeout { - // Haven't heard anything in 300ms. POD heard our ack. - quiet = true - } - } - incrementPacketNumber() - } - - - func exchangePackets(packet: Packet, repeatCount: Int = 0, packetResponseTimeout: TimeInterval = .milliseconds(165), exchangeTimeout:TimeInterval = .seconds(20), preambleExtension: TimeInterval = .milliseconds(127)) throws -> Packet { - let packetData = packet.encoded() - let radioRetryCount = 20 - - let start = Date() - - while (-start.timeIntervalSinceNow < exchangeTimeout) { - do { - let rfPacket = try session.sendAndListen(packetData, repeatCount: repeatCount, timeout: packetResponseTimeout, retryCount: radioRetryCount, preambleExtension: preambleExtension) - - let candidatePacket: Packet - - do { - candidatePacket = try Packet(rfPacket: rfPacket) - } catch { - continue - } - - guard candidatePacket.address == packet.address else { - continue - } - - guard candidatePacket.sequenceNum == ((packetNumber + 1) & 0b11111) else { - continue - } - - // Once we have verification that the POD heard us, we can increment our counters - incrementPacketNumber(2) - - return candidatePacket - } catch RileyLinkDeviceError.responseTimeout { - continue - } - } - - throw PodCommsError.noResponse - } - - func send(_ messageBlocks: [MessageBlock]) throws -> Message { - let message = Message(address: address, messageBlocks: messageBlocks, sequenceNum: messageNumber) - - do { - let responsePacket = try { () throws -> Packet in - var firstPacket = true - log.debug("Send: %@", String(describing: message)) - var dataRemaining = message.encoded() - while true { - let packetType: PacketType = firstPacket ? .pdm : .con - let sendPacket = Packet(address: address, packetType: packetType, sequenceNum: self.packetNumber, data: dataRemaining) - dataRemaining = dataRemaining.subdata(in: sendPacket.data.count.. Message in - var responseData = responsePacket.data - while true { - do { - return try Message(encodedData: responseData) - } catch MessageError.notEnoughData { - log.debug("Sending ACK for CON") - let conPacket = try self.exchangePackets(packet: makeAckPacket(), repeatCount: 3, preambleExtension:TimeInterval(milliseconds: 40)) - - guard conPacket.packetType == .con else { - log.debug("Expected CON packet, received; %@", String(describing: conPacket)) - throw PodCommsError.unexpectedPacketType(packetType: conPacket.packetType) - } - responseData += conPacket.data - } - } - }() - - try ackUntilQuiet() - - guard response.messageBlocks.count > 0 else { - log.debug("Empty response") - throw PodCommsError.emptyResponse - } - - if response.messageBlocks[0].blockType != .errorResponse { - incrementMessageNumber(2) - } - - log.debug("Recv: %@", String(describing: response)) - return response - } catch let error { - log.error("Error during communication with POD: %@", String(describing: error)) - throw error - } - } - -} diff --git a/OmniKit/MessageTransport/Packet.swift b/OmniKit/MessageTransport/Packet.swift deleted file mode 100644 index fef1a3e58..000000000 --- a/OmniKit/MessageTransport/Packet.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Packet.swift -// OmniKit -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import RileyLinkBLEKit - -public enum PacketType: UInt8 { - case pod = 0b111 - case pdm = 0b101 - case con = 0b100 - case ack = 0b010 - - func maxBodyLen() -> Int { - switch self { - case .ack: - return 4 - case .con, .pdm, .pod: - return 31 - } - } -} - -public struct Packet { - - let address: UInt32 - let packetType: PacketType - let sequenceNum: Int - let data: Data - - init(address: UInt32, packetType: PacketType, sequenceNum: Int, data: Data = Data()) { - self.address = address - self.packetType = packetType - self.sequenceNum = sequenceNum - - let bodyMaxLen = packetType.maxBodyLen() - if data.count > bodyMaxLen { - self.data = data.subdata(in: 0..= 7 else { - // Not enough data for packet - throw PodCommsError.invalidData - } - - self.address = encodedData[0...].toBigEndian(UInt32.self) - - guard let packetType = PacketType(rawValue: encodedData[4] >> 5) else { - throw PodCommsError.unknownPacketType(rawType: encodedData[4]) - } - self.packetType = packetType - self.sequenceNum = Int(encodedData[4] & 0b11111) - - let len = encodedData.count - - // Check crc - guard encodedData[0.. Data { - var output = Data(bigEndian: address) - output.append(UInt8(packetType.rawValue << 5) + UInt8(sequenceNum & 0b11111)) - output.append(data) - output.append(output.crc8()) - return output - } -} - -// Extensions for RFPacket support -extension Packet { - init(rfPacket: RFPacket) throws { - try self.init(encodedData: rfPacket.data) - } -} diff --git a/OmniKit/OmniKit.h b/OmniKit/OmniKit.h deleted file mode 100644 index b353125fd..000000000 --- a/OmniKit/OmniKit.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// OmniKit.h -// OmniKit -// -// Created by Pete Schwamb on 8/26/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -#import - -//! Project version number for OmniKit. -FOUNDATION_EXPORT double OmniKitVersionNumber; - -//! Project version string for OmniKit. -FOUNDATION_EXPORT const unsigned char OmniKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift deleted file mode 100644 index 8c5b0257a..000000000 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ /dev/null @@ -1,280 +0,0 @@ -// -// OmnipodPumpManager.swift -// OmniKit -// -// Created by Pete Schwamb on 8/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import HealthKit -import LoopKit -import RileyLinkKit -import RileyLinkBLEKit -import os.log - -public class OmnipodPumpManager: RileyLinkPumpManager, PumpManager { - - public var pumpBatteryChargeRemaining: Double? - - public var pumpRecordsBasalProfileStartEvents = false - - public var pumpReservoirCapacity: Double = 200 - - public var pumpTimeZone: TimeZone { - return state.podState.timeZone - } - - private var lastPumpDataReportDate: Date? - - public func assertCurrentPumpData() { - - let pumpStatusAgeTolerance = TimeInterval(minutes: 4) - - - guard (lastPumpDataReportDate ?? .distantPast).timeIntervalSinceNow < -pumpStatusAgeTolerance else { - return - } - - queue.async { - 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() - - session.finalizeDoses(deliveryStatus: status.deliveryStatus, storageHandler: { (doses) -> Bool in - return self.store(doses: doses) - }) - - if let reservoirLevel = status.reservoirLevel { - let semaphore = DispatchSemaphore(value: 0) - self.pumpManagerDelegate?.pumpManager(self, didReadReservoirValue: reservoirLevel, at: Date()) { (_) in - semaphore.signal() - } - semaphore.wait() - } - self.pumpManagerDelegate?.pumpManagerRecommendsLoop(self) - case .failure(let error): - throw error - } - } catch let error { - self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) - } - } - } - } - - - public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (Double, Date) -> Void, completion: @escaping (Error?) -> Void) { - - 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 - } - - session.finalizeDoses(deliveryStatus: podStatus.deliveryStatus, storageHandler: { ( _ ) -> Bool in - return false - }) - - willRequest(units, Date()) - - let result = session.bolus(units: units) - - switch result { - case .success: - 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) -> Void) { - //completion(PumpManagerResult.failure(PodCommsError.emptyResponse)) - - let rileyLinkSelector = rileyLinkDeviceProvider.firstConnectedDevice - podComms.runSession(withName: "Enact Temp Basal", using: rileyLinkSelector) { (result) in - self.log.info("Enact temp basal %.03fU/hr for %ds", unitsPerHour, Int(duration)) - let session: PodCommsSession - switch result { - case .success(let s): - session = s - case .failure(let error): - completion(PumpManagerResult.failure(error)) - return - } - - do { - let podStatus = try session.getStatus() - if podStatus.deliveryStatus.tempBasalRunning { - let cancelStatus = try session.cancelDelivery(deliveryType: .tempBasal, beepType: .noBeep) - - guard !cancelStatus.deliveryStatus.tempBasalRunning else { - throw PodCommsError.unfinalizedTempBasal - } - } - - session.finalizeDoses(deliveryStatus: podStatus.deliveryStatus, storageHandler: { ( _ ) -> Bool in - return false - }) - - if duration < .ulpOfOne { - // 0 duration temp basals are used to cancel any existing temp basal - let cancelTime = Date() - let dose = DoseEntry(type: .basal, startDate: cancelTime, endDate: cancelTime, value: 0, unit: .unitsPerHour) - completion(PumpManagerResult.success(dose)) - } else { - let result = 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) - switch result { - case .success: - completion(PumpManagerResult.success(dose)) - case .uncertainFailure(let error): - self.log.error("Temp basal uncertain error: %@", String(describing: error)) - completion(PumpManagerResult.success(dose)) - case .certainFailure(let error): - completion(PumpManagerResult.failure(error)) - } - } - } catch let error { - completion(PumpManagerResult.failure(error)) - } - } - } - - public func updateBLEHeartbeatPreference() { - return - } - - public static let managerIdentifier: String = "Omnipod" - - public init(state: OmnipodPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil) { - self.state = state - - super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager) - - // Pod communication - self.podComms = PodComms(podState: state.podState, delegate: self) - } - - public required convenience init?(rawState: PumpManager.RawStateValue) { - guard let state = OmnipodPumpManagerState(rawValue: rawState), - let connectionManagerState = state.rileyLinkConnectionManagerState else - { - return nil - } - - let rileyLinkConnectionManager = RileyLinkConnectionManager(state: connectionManagerState) - - self.init(state: state, rileyLinkDeviceProvider: rileyLinkConnectionManager.deviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager) - - rileyLinkConnectionManager.delegate = self - } - - public var rawState: PumpManager.RawStateValue { - return state.rawValue - } - - override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { - get { - return state.rileyLinkConnectionManagerState - } - set { - state.rileyLinkConnectionManagerState = newValue - } - } - - // TODO: apply lock - public private(set) var state: OmnipodPumpManagerState { - didSet { - pumpManagerDelegate?.pumpManagerDidUpdateState(self) - } - } - - public weak var pumpManagerDelegate: PumpManagerDelegate? - - public let log = OSLog(category: "OmnipodPumpManager") - - public static let localizedTitle = NSLocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager") - - public var localizedTitle: String { - return String(format: NSLocalizedString("Omnipod", comment: "Omnipod title")) - } - - override public func deviceTimerDidTick(_ device: RileyLinkDevice) { - self.pumpManagerDelegate?.pumpManagerBLEHeartbeatDidFire(self) - } - - - // MARK: - CustomDebugStringConvertible - - override public var debugDescription: String { - return [ - "## OmnipodPumpManager", - "state: \(state.debugDescription)", - "", - "podComms: \(String(reflecting: podComms))", - "", - super.debugDescription, - ].joined(separator: "\n") - } - - // MARK: - Configuration - - // MARK: Pump - - public private(set) var podComms: PodComms! -} - -extension OmnipodPumpManager: PodCommsDelegate { - - public func store(doses: [UnfinalizedDose]) -> Bool { - let semaphore = DispatchSemaphore(value: 0) - var success = false - self.pumpManagerDelegate?.pumpManager(self, didReadPumpEvents: doses.map { NewPumpEvent($0) }, completion: { (error) in - if let error = error { - self.log.error("Error storing pod events: %@", String(describing: error)) - } else { - self.log.error("Stored pod events: %@", String(describing: doses)) - } - success = error == nil - semaphore.signal() - }) - semaphore.wait() - - if success { - self.lastPumpDataReportDate = Date() - } - return success - } - - public func podComms(_ podComms: PodComms, didChange state: PodState) { - self.state.podState = state - } -} - diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift deleted file mode 100644 index 859316697..000000000 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// OmnipodPumpManagerState.swift -// OmniKit -// -// Created by Pete Schwamb on 8/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import RileyLinkKit -import RileyLinkBLEKit -import LoopKit - -public struct OmnipodPumpManagerState: RawRepresentable, Equatable { - public typealias RawValue = PumpManager.RawStateValue - - public static let version = 1 - - public var podState: PodState - - public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? - - public init(podState: PodState, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?) { - self.podState = podState - self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState - } - - public init?(rawValue: RawValue) { - guard - let podStateRaw = rawValue["podState"] as? PodState.RawValue, - let podState = PodState(rawValue: podStateRaw) - else - { - return nil - } - - let rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? - if let rileyLinkConnectionManagerStateRaw = rawValue["rileyLinkConnectionManagerState"] as? RileyLinkConnectionManagerState.RawValue { - rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(rawValue: rileyLinkConnectionManagerStateRaw) - } else { - rileyLinkConnectionManagerState = nil - } - - self.init( - podState: podState, - rileyLinkConnectionManagerState: rileyLinkConnectionManagerState - ) - } - - public var rawValue: RawValue { - var value: [String : Any] = [ - "podState": podState.rawValue, - - "version": OmnipodPumpManagerState.version, - ] - - if let rileyLinkConnectionManagerState = rileyLinkConnectionManagerState { - value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState.rawValue - } - - return value - } -} - - -extension OmnipodPumpManagerState { - static let idleListeningEnabledDefaults: RileyLinkDevice.IdleListeningState = .enabled(timeout: .minutes(4), channel: 0) -} - - -extension OmnipodPumpManagerState: CustomDebugStringConvertible { - public var debugDescription: String { - return [ - "## MinimedPumpManagerState", - String(reflecting: podState), - String(reflecting: rileyLinkConnectionManagerState), - ].joined(separator: "\n") - } -} diff --git a/OmniKit/PumpManager/PodComms.swift b/OmniKit/PumpManager/PodComms.swift deleted file mode 100644 index 2d01dfb78..000000000 --- a/OmniKit/PumpManager/PodComms.swift +++ /dev/null @@ -1,227 +0,0 @@ -// -// PodComms.swift -// OmniKit -// -// Created by Pete Schwamb on 10/7/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation -import RileyLinkBLEKit -import LoopKit -import os.log - -public protocol PodCommsDelegate: class { - func podComms(_ podComms: PodComms, didChange state: PodState) -} - -public class PodComms { - - private var configuredDevices: Set = Set() - - private weak var delegate: PodCommsDelegate? - - private let sessionQueue = DispatchQueue(label: "com.rileylink.OmniKit.PodComms", qos: .utility) - - public let log = OSLog(category: "PodComms") - - private var podState: PodState { - didSet { - self.delegate?.podComms(self, didChange: podState) - } - } - - public init(podState: PodState, delegate: PodCommsDelegate?) { - self.podState = podState - self.delegate = delegate - } - - public enum PairResults { - case success(podState: PodState) - case failure(Error) - } - - public class func pair(using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, timeZone: TimeZone, completion: @escaping (PairResults) -> Void) - { - deviceSelector { (device) in - guard let device = device else { - completion(.failure(PodCommsError.noRileyLinkAvailable)) - return - } - - device.runSession(withName: "Pair Pod") { (commandSession) in - - do { - try commandSession.configureRadio() - - // Create random address with 20 bits. Can we use all 24 bits? - let newAddress = 0x1f000000 | (arc4random() & 0x000fffff) - - let transport = MessageTransport(session: commandSession, address: 0xffffffff, ackAddress: newAddress) - - // Assign Address - let assignAddress = AssignAddressCommand(address: newAddress) - - let response = try transport.send([assignAddress]) - guard let config1 = response.messageBlocks[0] as? VersionResponse else { - let responseType = response.messageBlocks[0].blockType - throw PodCommsError.unexpectedResponse(response: responseType) - } - - // Verify address is set - let activationDate = Date() - let dateComponents = SetupPodCommand.dateComponents(date: activationDate, timeZone: timeZone) - let setupPod = SetupPodCommand(address: newAddress, dateComponents: dateComponents, lot: config1.lot, tid: config1.tid) - - let response2 = try transport.send([setupPod]) - guard let config2 = response2.messageBlocks[0] as? VersionResponse else { - let responseType = response.messageBlocks[0].blockType - throw PodCommsError.unexpectedResponse(response: responseType) - } - - guard config2.pairingState == .paired else { - throw PodCommsError.invalidData - } - let newPodState = PodState( - address: newAddress, - activatedAt: activationDate, - timeZone: timeZone, - piVersion: String(describing: config2.piVersion), - pmVersion: String(describing: config2.pmVersion), - lot: config2.lot, - tid: config2.tid - ) - completion(.success(podState: newPodState)) - } catch let error { - completion(.failure(error)) - } - } - } - } - - public enum SessionRunResult { - case success(session: PodCommsSession) - case failure(PodCommsError) - } - - public func runSession(withName name: String, using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, _ block: @escaping (_ result: SessionRunResult) -> Void) { - sessionQueue.async { - let semaphore = DispatchSemaphore(value: 0) - - deviceSelector { (device) in - guard let device = device else { - block(.failure(PodCommsError.noRileyLinkAvailable)) - semaphore.signal() - return - } - - device.runSession(withName: name) { (commandSession) in - self.configureDevice(device, with: commandSession) - let transport = MessageTransport(session: commandSession, address: self.podState.address) - let podSession = PodCommsSession(podState: self.podState, transport: transport, delegate: self) - block(.success(session: podSession)) - semaphore.signal() - } - } - - semaphore.wait() - } - } - - // Must be called from within the RileyLinkDevice sessionQueue - private func configureDevice(_ device: RileyLinkDevice, with session: CommandSession) { - guard !self.configuredDevices.contains(device) else { - return - } - - do { - log.debug("configureRadio (omnipod)") - _ = try session.configureRadio() - } catch let error { - log.error("configure Radio failed with error: %{public}@", String(describing: error)) - // Ignore the error and let the block run anyway - return - } - - NotificationCenter.default.post(name: .DeviceRadioConfigDidChange, object: device) - NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceRadioConfigDidChange, object: device) - NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceConnectionStateDidChange, object: device) - - log.debug("added device %{public}@ to configuredDevices", device.name ?? "unknown") - configuredDevices.insert(device) - } - - @objc private func deviceRadioConfigDidChange(_ note: Notification) { - guard let device = note.object as? RileyLinkDevice else { - return - } - log.debug("removing device %{public}@ from configuredDevices", device.name ?? "unknown") - - NotificationCenter.default.removeObserver(self, name: .DeviceRadioConfigDidChange, object: device) - NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device) - configuredDevices.remove(device) - } -} - -private extension CommandSession { - - func configureRadio() throws { - - // SYNC1 |0xDF00|0x54|Sync Word, High Byte - // SYNC0 |0xDF01|0xC3|Sync Word, Low Byte - // PKTLEN |0xDF02|0x32|Packet Length - // PKTCTRL1 |0xDF03|0x24|Packet Automation Control - // PKTCTRL0 |0xDF04|0x00|Packet Automation Control - // FSCTRL1 |0xDF07|0x06|Frequency Synthesizer Control - // FREQ2 |0xDF09|0x12|Frequency Control Word, High Byte - // FREQ1 |0xDF0A|0x14|Frequency Control Word, Middle Byte - // FREQ0 |0xDF0B|0x5F|Frequency Control Word, Low Byte - // MDMCFG4 |0xDF0C|0xCA|Modem configuration - // MDMCFG3 |0xDF0D|0xBC|Modem Configuration - // MDMCFG2 |0xDF0E|0x0A|Modem Configuration - // MDMCFG1 |0xDF0F|0x13|Modem Configuration - // MDMCFG0 |0xDF10|0x11|Modem Configuration - // MCSM0 |0xDF14|0x18|Main Radio Control State Machine Configuration - // FOCCFG |0xDF15|0x17|Frequency Offset Compensation Configuration - // AGCCTRL1 |0xDF18|0x70|AGC Control - // FSCAL3 |0xDF1C|0xE9|Frequency Synthesizer Calibration - // FSCAL2 |0xDF1D|0x2A|Frequency Synthesizer Calibration - // FSCAL1 |0xDF1E|0x00|Frequency Synthesizer Calibration - // FSCAL0 |0xDF1F|0x1F|Frequency Synthesizer Calibration - // TEST1 |0xDF24|0x31|Various Test Settings - // TEST0 |0xDF25|0x09|Various Test Settings - // PA_TABLE0 |0xDF2E|0x60|PA Power Setting 0 - // VERSION |0xDF37|0x04|Chip ID[7:0] - - try setSoftwareEncoding(.manchester) - try setPreamble(0x6665) - try setBaseFrequency(Measurement(value: 433.91, unit: .megahertz)) - try updateRegister(.pktctrl1, value: 0x20) - try updateRegister(.pktctrl0, value: 0x00) - try updateRegister(.fsctrl1, value: 0x06) - try updateRegister(.mdmcfg4, value: 0xCA) - try updateRegister(.mdmcfg3, value: 0xBC) // 0xBB for next lower bitrate - try updateRegister(.mdmcfg2, value: 0x06) - try updateRegister(.mdmcfg1, value: 0x70) - try updateRegister(.mdmcfg0, value: 0x11) - try updateRegister(.deviatn, value: 0x44) - try updateRegister(.mcsm0, value: 0x18) - try updateRegister(.foccfg, value: 0x17) - try updateRegister(.fscal3, value: 0xE9) - try updateRegister(.fscal2, value: 0x2A) - try updateRegister(.fscal1, value: 0x00) - try updateRegister(.fscal0, value: 0x1F) - - try updateRegister(.test1, value: 0x31) - try updateRegister(.test0, value: 0x09) - try updateRegister(.paTable0, value: 0x84) - try updateRegister(.sync1, value: 0xA5) - try updateRegister(.sync0, value: 0x5A) - } -} - -extension PodComms: PodCommsSessionDelegate { - public func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) { - self.podState = state - } -} diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift deleted file mode 100644 index 11a05b334..000000000 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ /dev/null @@ -1,409 +0,0 @@ -// -// PodCommsSession.swift -// OmniKit -// -// Created by Pete Schwamb on 10/13/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation -import RileyLinkBLEKit -import LoopKit -import os.log - -public enum PodCommsError: Error { - case invalidData - case crcMismatch - case unknownPacketType(rawType: UInt8) - case noResponse - case emptyResponse - case podAckedInsteadOfReturningResponse - case unexpectedPacketType(packetType: PacketType) - case unexpectedResponse(response: MessageBlockType) - case unknownResponseType(rawType: UInt8) - case noRileyLinkAvailable - case unfinalizedBolus - case unfinalizedTempBasal - case nonceResyncFailed - case commsError(error: Error) -} - -extension PodCommsError: LocalizedError { - public var errorDescription: String? { - switch self { - case .invalidData: - return nil - case .crcMismatch: - return nil - case .unknownPacketType: - return nil - case .noResponse: - return LocalizedString("No response from pod", comment: "Error message shown when no response from pod was received") - case .emptyResponse: - return LocalizedString("Empty response from pod", comment: "Error message shown when empty response from pod was received") - case .podAckedInsteadOfReturningResponse: - return nil - case .unexpectedPacketType: - return nil - case .unexpectedResponse: - return nil - case .unknownResponseType: - return nil - case .noRileyLinkAvailable: - return LocalizedString("No RileyLink available", comment: "Error message shown when no response from pod was received") - case .unfinalizedBolus: - return LocalizedString("Bolus in progress", comment: "Error message shown when bolus could not be completed due to exiting bolus in progress") - case .unfinalizedTempBasal: - return LocalizedString("Temp basal in progress", comment: "Error message shown when temp basal could not be set due to existing temp basal in progress") - case .nonceResyncFailed: - return nil - case .commsError: - return nil - } - } - - public var failureReason: String? { - switch self { - case .invalidData: - return nil - case .crcMismatch: - return nil - case .unknownPacketType: - return nil - case .noResponse: - return nil - case .emptyResponse: - return nil - case .podAckedInsteadOfReturningResponse: - return nil - case .unexpectedPacketType: - return nil - case .unexpectedResponse: - return nil - case .unknownResponseType: - return nil - case .noRileyLinkAvailable: - return nil - case .unfinalizedBolus: - return LocalizedString("Unable to issue concurrent boluses", comment: "Failure reason when bolus could not be completed due to existing bolus in progress") - case .unfinalizedTempBasal: - return LocalizedString("Unable to issue concurrent temp basals", comment: "Failure reason when temp basal could not be set due to existing temp basal in progress") - case .nonceResyncFailed: - return nil - case .commsError: - return nil - } - } - - public var recoverySuggestion: String? { - switch self { - case .invalidData: - return nil - case .crcMismatch: - return nil - case .unknownPacketType: - return nil - case .noResponse: - return LocalizedString("Please bring your pod closer to the RileyLink and try again", comment: "Recovery suggestion when no response is received from pod") - case .emptyResponse: - return nil - case .podAckedInsteadOfReturningResponse: - return nil - case .unexpectedPacketType: - return nil - case .unexpectedResponse: - return nil - case .unknownResponseType: - return nil - case .noRileyLinkAvailable: - return LocalizedString("Make sure your RileyLink is nearby and powered on", comment: "Recovery suggestion when no RileyLink is available") - case .unfinalizedBolus: - return LocalizedString("Wait for existing bolus to finish, or suspend to cancel", comment: "Recovery suggestion when bolus could not be completed due to existing bolus in progress") - case .unfinalizedTempBasal: - return LocalizedString("Wait for existing temp basal to finish, or suspend to cancel", comment: "Recovery suggestion when temp basal could not be completed due to existing temp basal in progress") - case .nonceResyncFailed: - return nil - case .commsError: - return nil - } - } -} - - - -public protocol PodCommsSessionDelegate: class { - func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) -} - -public class PodCommsSession { - - public let log = OSLog(category: "PodCommsSession") - - private var podState: PodState { - didSet { - delegate.podCommsSession(self, didChange: podState) - } - } - - private unowned let delegate: PodCommsSessionDelegate - private let transport: MessageTransport - - init(podState: PodState, transport: MessageTransport, delegate: PodCommsSessionDelegate) { - self.podState = podState - self.transport = transport - self.delegate = delegate - } - - - func send(_ messageBlocks: [MessageBlock]) throws -> T { - - var triesRemaining = 2 - - var blocksToSend = messageBlocks - - while (triesRemaining > 0) { - triesRemaining -= 1 - let response = try transport.send(blocksToSend) - - if let responseMessageBlock = response.messageBlocks[0] as? T { - log.info("POD Response: %@", String(describing: responseMessageBlock)) - return responseMessageBlock - } else { - let responseType = response.messageBlocks[0].blockType - - if responseType == .errorResponse, - let errorResponse = response.messageBlocks[0] as? ErrorResponse, - errorResponse.errorReponseType == .badNonce - { - let sentNonce = podState.currentNonce - self.podState.resyncNonce(syncWord: errorResponse.nonceSearchKey, sentNonce: sentNonce, messageSequenceNum: transport.messageNumber) - log.info("resyncNonce(syncWord: %02X, sentNonce: %04X, messageSequenceNum: %d) -> %04X", errorResponse.nonceSearchKey, sentNonce, transport.messageNumber, podState.currentNonce) - - blocksToSend = blocksToSend.map({ (block) -> MessageBlock in - if var resyncableBlock = block as? NonceResyncableMessageBlock { - resyncableBlock.nonce = podState.currentNonce - return resyncableBlock - } else { - return block - } - }) - } else { - log.error("Unexpected response: %@", String(describing: response.messageBlocks[0])) - throw PodCommsError.unexpectedResponse(response: responseType) - } - } - } - throw PodCommsError.nonceResyncFailed - } - - public func configurePod() throws { - //4c00 00c8 0102 - let alertConfig1 = ConfigureAlertsCommand.AlertConfiguration(alertType: .lowReservoir, audible: true, autoOffModifier: false, duration: 0, expirationType: .reservoir(volume: 20), beepType: .beepBeepBeepBeep, beepRepeat: 2) - - let configureAlerts1 = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations:[alertConfig1]) - let _: StatusResponse = try send([configureAlerts1]) - podState.advanceToNextNonce() - - //7837 0005 0802 - let alertConfig2 = ConfigureAlertsCommand.AlertConfiguration(alertType: .timerLimit, audible:true, autoOffModifier: false, duration: .minutes(55), expirationType: .time(.minutes(5)), beepType: .beeepBeeep, beepRepeat: 2) - let configureAlerts2 = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations:[alertConfig2]) - let _: StatusResponse = try send([configureAlerts2]) - podState.advanceToNextNonce() - - // Mark 2.6U delivery for prime - - // 1a0e bed2e16b 02 010a 01 01a0 0034 0034 170d 00 0208 000186a0 - let primeUnits = 2.6 - let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: primeUnits, multiplier: 8) - let scheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) - let bolusExtraCommand = BolusExtraCommand(units: primeUnits, byte2: 0, unknownSection: Data(hexadecimalString: "000186a0")!) - let _: StatusResponse = try send([scheduleCommand, bolusExtraCommand]) - podState.advanceToNextNonce() - } - - public func finishPrime() throws { - // 3800 0ff0 0302 - let alertConfig = ConfigureAlertsCommand.AlertConfiguration(alertType: .expirationAdvisory, audible: false, autoOffModifier: false, duration: .minutes(0), expirationType: .time(.hours(68)), beepType: .bipBip, beepRepeat: 2) - let configureAlerts = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations:[alertConfig]) - let _: StatusResponse = try send([configureAlerts]) - podState.advanceToNextNonce() - } - - // Throws SetBolusError - public enum DeliveryCommandResult { - case success(statusResponse: StatusResponse) - case certainFailure(error: PodCommsError) - case uncertainFailure(error: PodCommsError) - } - - public func bolus(units: Double) -> DeliveryCommandResult { - - let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: units, multiplier: 16) - let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) - - guard podState.unfinalizedBolus == nil else { - return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) - } - - // 17 0d 00 0064 0001 86a0000000000000 - let bolusExtraCommand = BolusExtraCommand(units: units, byte2: 0, unknownSection: Data(hexadecimalString: "00030d40")!) - do { - let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), scheduledCertainty: .certain) - podState.advanceToNextNonce() - return DeliveryCommandResult.success(statusResponse: statusResponse) - } catch let error { - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), scheduledCertainty: .uncertain) - return DeliveryCommandResult.uncertainFailure(error: error as? PodCommsError ?? PodCommsError.commsError(error: error)) - } - } - - public func setTempBasal(rate: Double, duration: TimeInterval, confidenceReminder: Bool, programReminderInterval: TimeInterval) -> DeliveryCommandResult { - - let tempBasalCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, tempBasalRate: rate, duration: duration) - let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, confidenceReminder: confidenceReminder, programReminderInterval: programReminderInterval) - - guard podState.unfinalizedBolus == nil else { - return DeliveryCommandResult.certainFailure(error: .unfinalizedTempBasal) - } - - do { - let statusResponse: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) - podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: Date(), duration: duration, scheduledCertainty: .certain) - podState.advanceToNextNonce() - return DeliveryCommandResult.success(statusResponse: statusResponse) - } catch let error { - podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: Date(), duration: duration, scheduledCertainty: .uncertain) - return DeliveryCommandResult.uncertainFailure(error: error as? PodCommsError ?? PodCommsError.commsError(error: error)) - } - } - - public func cancelDelivery(deliveryType: CancelDeliveryCommand.DeliveryType, beepType:ConfigureAlertsCommand.BeepType) throws -> StatusResponse { - - let cancelDelivery = CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: deliveryType, beepType: beepType) - - let status: StatusResponse = try send([cancelDelivery]) - - let now = Date() - - if let unfinalizedTempBasal = podState.unfinalizedTempBasal, - deliveryType.contains(.tempBasal), - unfinalizedTempBasal.finishTime.compare(now) == .orderedDescending - { - podState.unfinalizedTempBasal?.cancel(at: now) - log.info("Interrupted temp basal: %@", String(describing: unfinalizedTempBasal)) - } - - if let unfinalizedBolus = podState.unfinalizedBolus, - deliveryType.contains(.bolus), - unfinalizedBolus.finishTime.compare(now) == .orderedDescending - { - podState.unfinalizedBolus?.cancel(at: now) - log.info("Interrupted bolus: %@", String(describing: unfinalizedBolus)) - } - - podState.advanceToNextNonce() - - return status - } - - public func testingCommands() throws { - //try setTempBasal(rate: 2.5, duration: .minutes(30), confidenceReminder: false, programReminderInterval: .minutes(0)) - //try cancelDelivery(deliveryType: .tempBasal, beepType: .noBeep) - let _ = bolus(units: 20) - //try cancelDelivery(deliveryType: .bolus, beepType: .bipBip) - } - - public func setTime(basalSchedule: BasalSchedule, timeZone: TimeZone, date: Date) throws { - let scheduleOffset = timeZone.scheduleOffset(forDate: date) - try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, confidenceReminder: false, programReminderInterval: .minutes(0)) - self.podState.timeZone = timeZone - } - - public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, confidenceReminder: Bool, programReminderInterval: TimeInterval) throws { - - let basalScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, basalSchedule: schedule, scheduleOffset: scheduleOffset) - let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, confidenceReminder: confidenceReminder, programReminderInterval: programReminderInterval) - - let _: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) - podState.advanceToNextNonce() - } - - public func insertCannula(basalSchedule: BasalSchedule, scheduleOffset: TimeInterval) throws { - - // Set basal schedule - try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, confidenceReminder: false, programReminderInterval: .minutes(0)) - - // Configure Alerts - // 79a4 10df 0502 - // Pod expires 1 minute short of 3 days - let podSoftExpirationTime = TimeInterval(hours:72) - TimeInterval(minutes:1) - let alertConfig1 = ConfigureAlertsCommand.AlertConfiguration(alertType: .timerLimit, audible: true, autoOffModifier: false, duration: .minutes(164), expirationType: .time(podSoftExpirationTime), beepType: .beepBeepBeep, beepRepeat: 2) - - // 2800 1283 0602 - let podHardExpirationTime = TimeInterval(hours:79) - TimeInterval(minutes:1) - let alertConfig2 = ConfigureAlertsCommand.AlertConfiguration(alertType: .endOfService, audible: true, autoOffModifier: false, duration: .minutes(0), expirationType: .time(podHardExpirationTime), beepType: .beeeeeep, beepRepeat: 2) - - // 020f 0000 0202 - let alertConfig3 = ConfigureAlertsCommand.AlertConfiguration(alertType: .autoOff, audible: false, autoOffModifier: true, duration: .minutes(15), expirationType: .time(0), beepType: .bipBeepBipBeepBipBeepBipBeep, beepRepeat: 2) // Would like to change this to be less annoying, for example .bipBipBipbipBipBip - - let configureAlerts = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations:[alertConfig1, alertConfig2, alertConfig3]) - - do { - let _: StatusResponse = try send([configureAlerts]) - } catch PodCommsError.podAckedInsteadOfReturningResponse { - print("pod acked?") - } - - podState.advanceToNextNonce() - - // Insert Cannula - // 1a0e7e30bf16020065010050000a000a - let insertionBolusAmount = 0.5 - let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: insertionBolusAmount, multiplier: 8) - let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) - - // 17 0d 00 0064 0001 86a0000000000000 - let bolusExtraCommand = BolusExtraCommand(units: insertionBolusAmount, byte2: 0, unknownSection: Data(hexadecimalString: "000186a0")!) - let _: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) - podState.advanceToNextNonce() - } - - public func getStatus() throws -> StatusResponse { - - do { - let response: StatusResponse = try send([GetStatusCommand()]) - podStatusUpdated(response) - return response - } catch PodCommsError.podAckedInsteadOfReturningResponse { - let response: StatusResponse = try send([GetStatusCommand()]) - return response - } - } - - public func changePod() throws -> StatusResponse { - - let cancelDelivery = CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: .all, beepType: .beeepBeeep) - let _: StatusResponse = try send([cancelDelivery]) - podState.advanceToNextNonce() - - // PDM at this point makes a few get status requests, for logs and other details, presumably. - // We don't know what to do with them, so skip for now. - - let deactivatePod = DeactivatePodCommand(nonce: podState.currentNonce) - return try send([deactivatePod]) - } - - func finalizeDoses(deliveryStatus: StatusResponse.DeliveryStatus, storageHandler: ([UnfinalizedDose]) -> Bool) { - self.podState.finalizeDoses(deliveryStatus: deliveryStatus) - if storageHandler(podState.finalizedDoses) { - log.info("Finalized %@", String(describing: podState.finalizedDoses)) - self.podState.finalizedDoses.removeAll() - } - } - - func podStatusUpdated(_ status: StatusResponse) { - podState.lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: status, validTime: Date()) - } -} - diff --git a/OmniKit/PumpManager/PodInsulinMeasurements.swift b/OmniKit/PumpManager/PodInsulinMeasurements.swift deleted file mode 100644 index 26aa2e171..000000000 --- a/OmniKit/PumpManager/PodInsulinMeasurements.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// PodInsulinMeasurements.swift -// OmniKit -// -// Created by Pete Schwamb on 9/5/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct PodInsulinMeasurements: RawRepresentable, Equatable { - public typealias RawValue = [String: Any] - - public let validTime: Date - public let delivered: Double - public let notDelivered: Double - public let reservoirVolume: Double? - - public init(statusResponse: StatusResponse, validTime: Date) { - self.validTime = validTime - self.delivered = statusResponse.insulin - self.notDelivered = statusResponse.insulinNotDelivered - self.reservoirVolume = statusResponse.reservoirLevel - } - - // RawRepresentable - public init?(rawValue: RawValue) { - guard - let validTime = rawValue["validTime"] as? Date, - let delivered = rawValue["delivered"] as? Double, - let notDelivered = rawValue["notDelivered"] as? Double, - let reservoirVolume = rawValue["reservoirVolume"] as? Double - else { - return nil - } - self.validTime = validTime - self.delivered = delivered - self.notDelivered = notDelivered - self.reservoirVolume = reservoirVolume - } - - public var rawValue: RawValue { - var rawValue: RawValue = [ - "validTime": validTime, - "delivered": delivered, - "notDelivered": notDelivered - ] - - if let reservoirVolume = reservoirVolume { - rawValue["reservoirVolume"] = reservoirVolume - } - - return rawValue - } - -} - diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift deleted file mode 100644 index 8166e03a9..000000000 --- a/OmniKit/PumpManager/PodState.swift +++ /dev/null @@ -1,268 +0,0 @@ -// -// PodState.swift -// OmniKit -// -// Created by Pete Schwamb on 10/13/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import Foundation - -public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertible { - - public typealias RawValue = [String: Any] - - public let address: UInt32 - fileprivate var nonceState: NonceState - public let activatedAt: Date - public var timeZone: TimeZone - public let piVersion: String - public let pmVersion: String - public let lot: UInt32 - public let tid: UInt32 - public var lastInsulinMeasurements: PodInsulinMeasurements? - var unfinalizedBolus: UnfinalizedDose? - var unfinalizedTempBasal: UnfinalizedDose? - var finalizedDoses: [UnfinalizedDose] - - public var expiresAt: Date { - return activatedAt + .days(3) - } - - public var deliveryScheduleUncertain: Bool { - return unfinalizedBolus?.scheduledCertainty == .uncertain || unfinalizedTempBasal?.scheduledCertainty == .uncertain - } - - public init(address: UInt32, activatedAt: Date, timeZone: TimeZone, piVersion: String, pmVersion: String, lot: UInt32, tid: UInt32) { - self.address = address - self.nonceState = NonceState(lot: lot, tid: tid) - self.activatedAt = activatedAt - self.timeZone = timeZone - self.piVersion = piVersion - self.pmVersion = pmVersion - self.lot = lot - self.tid = tid - self.lastInsulinMeasurements = nil - self.unfinalizedBolus = nil - self.unfinalizedTempBasal = nil - self.finalizedDoses = [] - } - - public mutating func advanceToNextNonce() { - nonceState.advanceToNextNonce() - } - - public var currentNonce: UInt32 { - return nonceState.currentNonce - } - - public mutating func resyncNonce(syncWord: UInt16, sentNonce: UInt32, messageSequenceNum: Int) { - let sum = (sentNonce & 0xffff) + UInt32(crc16Table[messageSequenceNum]) + (lot & 0xffff) + (tid & 0xffff) - let seed = UInt16(sum & 0xffff) ^ syncWord - nonceState = NonceState(lot: lot, tid: tid, seed: UInt8(seed & 0xff)) - } - - public mutating func finalizeDoses(deliveryStatus: StatusResponse.DeliveryStatus) { - let now = Date() - - if let bolus = unfinalizedBolus { - if bolus.finishTime <= now { - finalizedDoses.append(bolus) - unfinalizedBolus = nil - } else if bolus.scheduledCertainty == .uncertain { - if deliveryStatus.bolusing { - // Bolus did schedule - unfinalizedBolus?.scheduledCertainty = .certain - } else { - // Bolus didn't happen - unfinalizedBolus = nil - } - } - } - - if let tempBasal = unfinalizedTempBasal { - if tempBasal.finishTime <= now { - finalizedDoses.append(tempBasal) - unfinalizedTempBasal = nil - } else if tempBasal.scheduledCertainty == .uncertain { - if deliveryStatus.tempBasalRunning { - // Temp basal did schedule - unfinalizedTempBasal?.scheduledCertainty = .certain - } else { - // Temp basal didn't happen - unfinalizedTempBasal = nil - } - } - } - } - - // MARK: - RawRepresentable - public init?(rawValue: RawValue) { - - guard - let address = rawValue["address"] as? UInt32, - let nonceStateRaw = rawValue["nonceState"] as? NonceState.RawValue, - let nonceState = NonceState(rawValue: nonceStateRaw), - let activatedAt = rawValue["activatedAt"] as? Date, - let timeZoneSeconds = rawValue["timeZone"] as? Int, - let timeZone = TimeZone(secondsFromGMT: timeZoneSeconds), - let piVersion = rawValue["piVersion"] as? String, - let pmVersion = rawValue["pmVersion"] as? String, - let lot = rawValue["lot"] as? UInt32, - let tid = rawValue["tid"] as? UInt32 - else { - return nil - } - - self.address = address - self.nonceState = nonceState - self.activatedAt = activatedAt - self.timeZone = timeZone - self.piVersion = piVersion - self.pmVersion = pmVersion - self.lot = lot - self.tid = tid - - if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue, - let unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus) - { - self.unfinalizedBolus = unfinalizedBolus - } else { - self.unfinalizedBolus = nil - } - - if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue, - let unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) - { - self.unfinalizedTempBasal = unfinalizedTempBasal - } else { - self.unfinalizedTempBasal = nil - } - - if let rawLastInsulinMeasurements = rawValue["lastInsulinMeasurements"] as? PodInsulinMeasurements.RawValue - { - self.lastInsulinMeasurements = PodInsulinMeasurements(rawValue: rawLastInsulinMeasurements) - } else { - self.lastInsulinMeasurements = nil - } - - if let rawFinalizedDoses = rawValue["finalizedDoses"] as? [UnfinalizedDose.RawValue] - { - self.finalizedDoses = rawFinalizedDoses.compactMap( { UnfinalizedDose(rawValue: $0) } ) - } else { - self.finalizedDoses = [] - } - - } - - public var rawValue: RawValue { - var rawValue: RawValue = [ - "address": address, - "nonceState": nonceState.rawValue, - "activatedAt": activatedAt, - "timeZone": timeZone.secondsFromGMT(), - "piVersion": piVersion, - "pmVersion": pmVersion, - "lot": lot, - "tid": tid, - "finalizedDoses": finalizedDoses.map( { $0.rawValue }) - ] - - if let unfinalizedBolus = self.unfinalizedBolus { - rawValue["unfinalizedBolus"] = unfinalizedBolus.rawValue - } - - if let unfinalizedTempBasal = self.unfinalizedTempBasal { - rawValue["unfinalizedTempBasal"] = unfinalizedTempBasal.rawValue - } - - if let lastInsulinMeasurements = self.lastInsulinMeasurements { - rawValue["lastInsulinMeasurements"] = lastInsulinMeasurements.rawValue - } - - return rawValue - } - - // MARK: - CustomDebugStringConvertible - - public var debugDescription: String { - return [ - "## PodState", - "address: \(String(format: "%04X", address))", - "activatedAt: \(String(reflecting: activatedAt))", - "timeZone: \(timeZone)", - "piVersion: \(piVersion)", - "pmVersion: \(pmVersion)", - "lot: \(lot)", - "tid: \(tid)", - "unfinalizedBolus: \(String(describing: unfinalizedBolus))", - "unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))", - "finalizedDoses: \(String(describing: finalizedDoses))", - "", - ].joined(separator: "\n") - } -} - -fileprivate struct NonceState: RawRepresentable, Equatable { - public typealias RawValue = [String: Any] - - var table: [UInt32] - var idx: UInt8 - - public init(lot: UInt32 = 0, tid: UInt32 = 0, seed: UInt8 = 0) { - table = Array(repeating: UInt32(0), count: 21) - table[0] = (lot & 0xFFFF) + 0x55543DC3 + (lot >> 16) - table[0] = table[0] & 0xFFFFFFFF - table[1] = (tid & 0xFFFF) + 0xAAAAE44E + (tid >> 16) - table[1] = table[1] & 0xFFFFFFFF - - idx = 0 - - table[0] += UInt32(seed) - - for i in 0..<16 { - table[2 + i] = generateEntry() - } - - idx = UInt8((table[0] + table[1]) & 0x0F) - } - - private mutating func generateEntry() -> UInt32 { - table[0] = ((table[0] >> 16) + (table[0] & 0xFFFF) * 0x5D7F) & 0xFFFFFFFF - table[1] = ((table[1] >> 16) + (table[1] & 0xFFFF) * 0x8CA0) & 0xFFFFFFFF - return UInt32((UInt64(table[1]) + (UInt64(table[0]) << 16)) & 0xFFFFFFFF) - } - - public mutating func advanceToNextNonce() { - let nonce = currentNonce - table[Int(2 + idx)] = generateEntry() - idx = UInt8(nonce & 0x0F) - } - - public var currentNonce: UInt32 { - return table[Int(2 + idx)] - } - - // RawRepresentable - public init?(rawValue: RawValue) { - guard - let table = rawValue["table"] as? [UInt32], - let idx = rawValue["idx"] as? UInt8 - else { - return nil - } - self.table = table - self.idx = idx - } - - public var rawValue: RawValue { - let rawValue: RawValue = [ - "table": table, - "idx": idx, - ] - - return rawValue - } -} - - diff --git a/OmniKitTests/BasalScheduleTests.swift b/OmniKitTests/BasalScheduleTests.swift deleted file mode 100644 index 32e1ad1bd..000000000 --- a/OmniKitTests/BasalScheduleTests.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// BasalScheduleTests.swift -// OmniKitTests -// -// Created by Pete Schwamb on 4/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import XCTest -@testable import OmniKit - -class BasalScheduleTests: XCTestCase { - - func testBasalTableEntry() { - let entry = BasalTableEntry(segments: 2, pulses: 300, alternateSegmentPulse: false) - // $01 $2c $01 $2c = 1 + 44 + 1 + 44 = 90 = $5a - XCTAssertEqual(0x5a, entry.checksum()) - - let entry2 = BasalTableEntry(segments: 2, pulses: 260, alternateSegmentPulse: true) - // $01 $04 $01 $04 = 1 + 4 + 1 + 5 = 1 = $0b - XCTAssertEqual(0x0b, entry2.checksum()) - } - - func testSetBasalScheduleCommand() { - do { - // Decode 1a 12 77a05551 00 0062 2b 1708 0000 f800 f800 f800 - let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a1277a055510000622b17080000f800f800f800")!) - - XCTAssertEqual(0x77a05551, cmd.nonce) - if case SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table) = cmd.deliverySchedule { - XCTAssertEqual(0x2b, currentSegment) - XCTAssertEqual(737, secondsRemaining) - XCTAssertEqual(0, pulsesRemaining) - XCTAssertEqual(3, table.entries.count) - } else { - XCTFail("Expected ScheduleEntry.basalSchedule type") - } - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - - // Encode - let scheduleEntry = BasalTableEntry(segments: 16, pulses: 0, alternateSegmentPulse: true) - let table = BasalDeliveryTable(entries: [scheduleEntry, scheduleEntry, scheduleEntry]) - let deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(currentSegment: 0x2b, secondsRemaining: 737, pulsesRemaining: 0, table: table) - let cmd = SetInsulinScheduleCommand(nonce: 0x77a05551, deliverySchedule: deliverySchedule) - XCTAssertEqual("1a1277a055510000622b17080000f800f800f800", cmd.data.hexadecimalString) - } - - func testBasalScheduleCommandFromSchedule() { - // Encode from schedule - let entry = BasalScheduleEntry(rate: 0.05, duration: .hours(24)) - let schedule = BasalSchedule(entries: [entry]) - - let cmd = SetInsulinScheduleCommand(nonce: 0x01020304, basalSchedule: schedule, scheduleOffset: .hours(8.25)) - - XCTAssertEqual(0x01020304, cmd.nonce) - if case SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table) = cmd.deliverySchedule { - XCTAssertEqual(16, currentSegment) - XCTAssertEqual(UInt16(TimeInterval(minutes: 15)), secondsRemaining) - XCTAssertEqual(0, pulsesRemaining) - XCTAssertEqual(3, table.entries.count) - let tableEntry = table.entries[0] - XCTAssertEqual(true, tableEntry.alternateSegmentPulse) - XCTAssertEqual(0, tableEntry.pulses) - XCTAssertEqual(16, tableEntry.segments) - } else { - XCTFail("Expected ScheduleEntry.basalSchedule type") - } - XCTAssertEqual("1a1201020304000064101c200000f800f800f800", cmd.data.hexadecimalString) - } - - - func testBasalScheduleExtraCommand() { - do { - // Decode 130e40 00 1aea 001e8480 3840005b8d80 - - let cmd = try BasalScheduleExtraCommand(encodedData: Data(hexadecimalString: "130e40001aea001e84803840005b8d80")!) - - XCTAssertEqual(true, cmd.confidenceReminder) - XCTAssertEqual(0, cmd.programReminderInterval) - XCTAssertEqual(0, cmd.currentEntryIndex) - XCTAssertEqual(689, cmd.remainingPulses) - XCTAssertEqual(TimeInterval(seconds: 20), cmd.delayUntilNextPulse) - XCTAssertEqual(1, cmd.rateEntries.count) - let entry = cmd.rateEntries[0] - XCTAssertEqual(TimeInterval(seconds: 60), entry.delayBetweenPulses) - XCTAssertEqual(1440, entry.totalPulses) - XCTAssertEqual(3.0, entry.rate) - XCTAssertEqual(TimeInterval(hours: 24), entry.duration) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - - // Encode - let rateEntries = RateEntry.makeEntries(rate: 3.0, duration: TimeInterval(hours: 24)) - let cmd = BasalScheduleExtraCommand(confidenceReminder: true, programReminderInterval: 0, currentEntryIndex: 0, remainingPulses: 689, delayUntilNextPulse: TimeInterval(seconds: 20), rateEntries: rateEntries) - - - XCTAssertEqual("130e40001aea001e84803840005b8d80", cmd.data.hexadecimalString) - } - - func testBasalScheduleExtraCommandFromSchedule() { - // Encode from schedule - let entry = BasalScheduleEntry(rate: 0.05, duration: .hours(24)) - let schedule = BasalSchedule(entries: [entry]) - - let cmd = BasalScheduleExtraCommand(schedule: schedule, scheduleOffset: .hours(8.25), confidenceReminder: true, programReminderInterval: 60) - - XCTAssertEqual(true, cmd.confidenceReminder) - XCTAssertEqual(60, cmd.programReminderInterval) - XCTAssertEqual(0, cmd.currentEntryIndex) - XCTAssertEqual(16, cmd.remainingPulses) - XCTAssertEqual(TimeInterval(minutes: 45), cmd.delayUntilNextPulse) - XCTAssertEqual(1, cmd.rateEntries.count) - let rateEntry = cmd.rateEntries[0] - XCTAssertEqual(TimeInterval(minutes: 60), rateEntry.delayBetweenPulses) - XCTAssertEqual(24, rateEntry.totalPulses, accuracy: 0.001) - XCTAssertEqual(0.05, rateEntry.rate) - XCTAssertEqual(TimeInterval(hours: 24), rateEntry.duration, accuracy: 0.001) - } - - func testSuspendBasalCommand() { - do { - // Decode 1f 05 6fede14a 01 - let cmd = try CancelDeliveryCommand(encodedData: Data(hexadecimalString: "1f056fede14a01")!) - XCTAssertEqual(0x6fede14a, cmd.nonce) - XCTAssertEqual(.noBeep, cmd.beepType) - XCTAssertEqual(.basal, cmd.deliveryType) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - - // Encode - let cmd = CancelDeliveryCommand(nonce: 0x6fede14a, deliveryType: .basal, beepType: .noBeep) - XCTAssertEqual("1f056fede14a01", cmd.data.hexadecimalString) - } -} - diff --git a/OmniKitTests/BolusTests.swift b/OmniKitTests/BolusTests.swift deleted file mode 100644 index db338cc0e..000000000 --- a/OmniKitTests/BolusTests.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// BolusTests.swift -// OmniKitTests -// -// Created by Eelke Jager on 04/09/2018. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -import XCTest -@testable import OmniKit - -class BolusTests: XCTestCase { - func testSetBolusCommand() { - // 2017-09-11T11:07:57.476872 ID1:1f08ced2 PTYPE:PDM SEQ:18 ID2:1f08ced2 B9:18 BLEN:31 MTYPE:1a0e BODY:bed2e16b02010a0101a000340034170d000208000186a0 CRC:fd - // 2017-09-11T11:07:57.552574 ID1:1f08ced2 PTYPE:ACK SEQ:19 ID2:1f08ced2 CRC:b8 - // 2017-09-11T11:07:57.734557 ID1:1f08ced2 PTYPE:CON SEQ:20 CON:00000000000003c0 CRC:a9 - - do { - // Decode - let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0ebed2e16b02010a0101a000340034")!) - XCTAssertEqual(0xbed2e16b, cmd.nonce) - - if case SetInsulinScheduleCommand.DeliverySchedule.bolus(let units, let multiplier) = cmd.deliverySchedule { - XCTAssertEqual(2.6, units) - XCTAssertEqual(0x8, multiplier) - } else { - XCTFail("Expected ScheduleEntry.bolus type") - } - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - - // Encode - let scheduleEntry = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: 2.6, multiplier: 0x8) - let cmd = SetInsulinScheduleCommand(nonce: 0xbed2e16b, deliverySchedule: scheduleEntry) - XCTAssertEqual("1a0ebed2e16b02010a0101a000340034", cmd.data.hexadecimalString) - } - - func testBolusExtraCommand() { - // 30U bolus - // 170d 7c 1770 00030d40 000000000000 - - do { - // Decode - let cmd = try BolusExtraCommand(encodedData: Data(hexadecimalString: "170d7c177000030d40000000000000")!) - XCTAssertEqual(30.0, cmd.units) - XCTAssertEqual(0x7c, cmd.byte2) - XCTAssertEqual(Data(hexadecimalString: "00030d40"), cmd.unknownSection) - - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - - // Encode - let cmd = BolusExtraCommand(units: 2.6, byte2: 0, unknownSection: Data(hexadecimalString: "000186a0")!) - XCTAssertEqual("170d000208000186a0000000000000", cmd.data.hexadecimalString) - } - - func testCancelBolusCommand() { - do { - // Decode 1f 05 4d91f8ff 64 - let cmd = try CancelDeliveryCommand(encodedData: Data(hexadecimalString: "1f054d91f8ff64")!) - XCTAssertEqual(0x4d91f8ff, cmd.nonce) - XCTAssertEqual(.beeeeeep, cmd.beepType) - XCTAssertEqual(.bolus, cmd.deliveryType) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - - // Encode - let cmd = CancelDeliveryCommand(nonce: 0x4d91f8ff, deliveryType: .bolus, beepType: .beeeeeep) - XCTAssertEqual("1f054d91f8ff64", cmd.data.hexadecimalString) - } -} diff --git a/OmniKitTests/CRC16Tests.swift b/OmniKitTests/CRC16Tests.swift deleted file mode 100644 index 491f959e2..000000000 --- a/OmniKitTests/CRC16Tests.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// CRC16Tests.swift -// OmniKitTests -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import XCTest -@testable import OmniKit - -class CRC16Tests: XCTestCase { - - func testComputeCRC16() { - let input = Data(hexadecimalString: "1f01482a10030e0100")! - XCTAssertEqual(0x802c, input.crc16()) - } -} - - - diff --git a/OmniKitTests/CRC8Tests.swift b/OmniKitTests/CRC8Tests.swift deleted file mode 100644 index cc4c3e009..000000000 --- a/OmniKitTests/CRC8Tests.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// CRC8Tests.swift -// OmniKitTests -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import XCTest -@testable import OmniKit - -class CRC8Tests: XCTestCase { - - func testComputeCRC8() { - let input = Data(hexadecimalString: "1f07b1eeae1f07b1ee181f1a0eeb5701b202010a0101a000340034170d000208000186a0")! - XCTAssertEqual(0x19, input.crc8()) - } -} - diff --git a/OmniKitTests/Info.plist b/OmniKitTests/Info.plist deleted file mode 100644 index 9366bbbb5..000000000 --- a/OmniKitTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 2.1.0 - CFBundleVersion - 1 - - diff --git a/OmniKitTests/MessageTests.swift b/OmniKitTests/MessageTests.swift deleted file mode 100644 index ff1f263fa..000000000 --- a/OmniKitTests/MessageTests.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// MessageTests.swift -// OmniKitTests -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import XCTest -@testable import OmniKit - -class MessageTests: XCTestCase { - - func testMessageData() { - // 2016-06-26T20:33:28.412197 ID1:1f01482a PTYPE:PDM SEQ:13 ID2:1f01482a B9:10 BLEN:3 BODY:0e0100802c CRC:88 - - let msg = Message(address: 0x1f01482a, messageBlocks: [GetStatusCommand()], sequenceNum: 4) - - XCTAssertEqual("1f01482a10030e0100802c", msg.encoded().hexadecimalString) - } - - func testMessageDecoding() { - do { - let msg = try Message(encodedData: Data(hexadecimalString: "1f00ee84300a1d18003f1800004297ff8128")!) - - XCTAssertEqual(0x1f00ee84, msg.address) - XCTAssertEqual(12, msg.sequenceNum) - - let messageBlocks = msg.messageBlocks - - XCTAssertEqual(1, messageBlocks.count) - - let statusResponse = messageBlocks[0] as! StatusResponse - - XCTAssertEqual(nil, statusResponse.reservoirLevel) - XCTAssertEqual(TimeInterval(minutes: 4261), statusResponse.timeActive) - - XCTAssertEqual(.normal, statusResponse.deliveryStatus) - XCTAssertEqual(.aboveFiftyUnits, statusResponse.reservoirStatus) - XCTAssertEqual(6.3, statusResponse.insulin, accuracy: 0.01) - XCTAssertEqual(0, statusResponse.insulinNotDelivered) - XCTAssertEqual(3, statusResponse.podMessageCounter) - XCTAssert(statusResponse.alarms.isEmpty) - - XCTAssertEqual("1f00ee84300a1d18003f1800004297ff8128", msg.encoded().hexadecimalString) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testAssemblingMultiPacketMessage() { - do { - let packet1 = try Packet(encodedData: Data(hexadecimalString: "ffffffffe4ffffffff041d011b13881008340a5002070002070002030000a62b0004479420")!) - XCTAssertEqual(packet1.data.hexadecimalString, "ffffffff041d011b13881008340a5002070002070002030000a62b00044794") - XCTAssertEqual(packet1.packetType, .pod) - - XCTAssertThrowsError(try Message(encodedData: packet1.data)) { error in - XCTAssertEqual(String(describing: error), "notEnoughData") - } - - let packet2 = try Packet(encodedData: Data(hexadecimalString: "ffffffff861f00ee878352ff")!) - XCTAssertEqual(packet2.address, 0xffffffff) - XCTAssertEqual(packet2.data.hexadecimalString, "1f00ee878352") - XCTAssertEqual(packet2.packetType, .con) - - let messageBody = packet1.data + packet2.data - XCTAssertEqual(messageBody.hexadecimalString, "ffffffff041d011b13881008340a5002070002070002030000a62b000447941f00ee878352") - - let message = try Message(encodedData: messageBody) - XCTAssertEqual(message.messageBlocks.count, 1) - - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testParsingVersionResponse() { - do { - let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a64000097c279c1f08ced2")!) - XCTAssertEqual(23, config.data.count) - XCTAssertEqual(0x1f08ced2, config.address) - XCTAssertEqual(42560, config.lot) - XCTAssertEqual(621607, config.tid) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testParsingLongVersionResponse() { - do { - let message = try Message(encodedData: Data(hexadecimalString: "ffffffff041d011b13881008340a5002070002070002030000a62b000447941f00ee878352")!) - let config = message.messageBlocks[0] as! VersionResponse - XCTAssertEqual(29, config.data.count) - XCTAssertEqual(0x1f00ee87, config.address) - XCTAssertEqual(42539, config.lot) - XCTAssertEqual(280468, config.tid) - XCTAssertEqual("2.7.0", String(describing: config.piVersion)) - XCTAssertEqual("2.7.0", String(describing: config.pmVersion)) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testParsingConfigWithPairingExpired() { - do { - let message = try Message(encodedData: Data(hexadecimalString: "ffffffff04170115020700020700020e0000a5ad00053030971f08686301fd")!) - let config = message.messageBlocks[0] as! VersionResponse - XCTAssertEqual(.pairingExpired, config.pairingState) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testAssignAddressCommand() { - do { - // Encode - let encoded = AssignAddressCommand(address: 0x1f01482a) - XCTAssertEqual("07041f01482a", encoded.data.hexadecimalString) - - // Decode - let decoded = try AssignAddressCommand(encodedData: Data(hexadecimalString: "07041f01482a")!) - XCTAssertEqual(0x1f01482a, decoded.address) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testSetupPodCommand() { - do { - var components = DateComponents() - components.day = 6 - components.month = 12 - components.year = 2016 - components.hour = 13 - components.minute = 47 - - // Decode - let decoded = try SetupPodCommand(encodedData: Data(hexadecimalString: "03131f0218c31404060c100d2f0000a4be0004e4a1")!) - XCTAssertEqual(0x1f0218c3, decoded.address) - XCTAssertEqual(components, decoded.dateComponents) - XCTAssertEqual(0x0000a4be, decoded.lot) - XCTAssertEqual(0x0004e4a1, decoded.tid) - - // Encode - let encoded = SetupPodCommand(address: 0x1f0218c3, dateComponents: components, lot: 0x0000a4be, tid: 0x0004e4a1) - XCTAssertEqual("03131f0218c31404060c100d2f0000a4be0004e4a1", encoded.data.hexadecimalString) - - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testInsertCannula() { -// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:PDM SEQ:17 ID2:1f00ee85 B9:38 BLEN:31 BODY:1a0e7e30bf16020065010050000a000a170d000064000186a0 CRC:33 -// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:ACK SEQ:18 ID2:1f00ee85 CRC:89 -// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:CON SEQ:19 CON:000000000000808c CRC:6f -// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:POD SEQ:20 ID2:1f00ee85 B9:3c BLEN:10 BODY:1d570016f00a00000bff8099 CRC:86 -// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:ACK SEQ:21 ID2:1f00ee85 CRC:a0 - - do { - // Decode - let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0ebed2e16b02010a0101a000340034")!) - XCTAssertEqual(0xbed2e16b, cmd.nonce) - - if case SetInsulinScheduleCommand.DeliverySchedule.bolus(let units, let multiplier) = cmd.deliverySchedule { - XCTAssertEqual(2.6, units) - XCTAssertEqual(0x8, multiplier) - } else { - XCTFail("Expected ScheduleEntry.bolus type") - } - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testStatusResponseAlarmsParsing() { - // 1d 28 0082 00 0044 46eb ff - - do { - // Decode - let status = try StatusResponse(encodedData: Data(hexadecimalString: "1d28008200004446ebff")!) - XCTAssert(status.alarms.contains(.oneHourExpiry)) - XCTAssert(status.alarms.contains(.podExpired)) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testConfigureAlertsCommand() { - // 79a4 10df 0502 - // Pod expires 1 minute short of 3 days - let podSoftExpirationTime = TimeInterval(hours:72) - TimeInterval(minutes:1) - let alertConfig1 = ConfigureAlertsCommand.AlertConfiguration(alertType: .timerLimit, audible: true, autoOffModifier: false, duration: .hours(7), expirationType: .time(podSoftExpirationTime), beepType: .beepBeepBeep, beepRepeat: 2) - XCTAssertEqual("79a410df0502", alertConfig1.data.hexadecimalString) - - // 2800 1283 0602 - let podHardExpirationTime = TimeInterval(hours:79) - TimeInterval(minutes:1) - let alertConfig2 = ConfigureAlertsCommand.AlertConfiguration(alertType: .endOfService, audible: true, autoOffModifier: false, duration: .minutes(0), expirationType: .time(podHardExpirationTime), beepType: .beeeeeep, beepRepeat: 2) - XCTAssertEqual("280012830602", alertConfig2.data.hexadecimalString) - - // 020f 0000 0202 - let alertConfig3 = ConfigureAlertsCommand.AlertConfiguration(alertType: .autoOff, audible: false, autoOffModifier: true, duration: .minutes(15), expirationType: .time(0), beepType: .bipBeepBipBeepBipBeepBipBeep, beepRepeat: 2) - XCTAssertEqual("020f00000202", alertConfig3.data.hexadecimalString) - - let configureAlerts = ConfigureAlertsCommand(nonce: 0xfeb6268b, configurations:[alertConfig1, alertConfig2, alertConfig3]) - XCTAssertEqual("1916feb6268b79a410df0502280012830602020f00000202", configureAlerts.data.hexadecimalString) - - do { - let decoded = try ConfigureAlertsCommand(encodedData: Data(hexadecimalString: "1916feb6268b79a410df0502280012830602020f00000202")!) - XCTAssertEqual(3, decoded.configurations.count) - - let config1 = decoded.configurations[0] - XCTAssertEqual(.timerLimit, config1.alertType) - XCTAssertEqual(true, config1.audible) - XCTAssertEqual(false, config1.autoOffModifier) - XCTAssertEqual(.hours(7), config1.duration) - if case ConfigureAlertsCommand.ExpirationType.time(let duration) = config1.expirationType { - XCTAssertEqual(podSoftExpirationTime, duration) - } - XCTAssertEqual(.beepBeepBeep, config1.beepType) - - let cfg = try ConfigureAlertsCommand.AlertConfiguration(encodedData: Data(hexadecimalString: "4c0000640102")!) - XCTAssertEqual(.lowReservoir, cfg.alertType) - XCTAssertEqual(true, cfg.audible) - XCTAssertEqual(false, cfg.autoOffModifier) - XCTAssertEqual(0, cfg.duration) - if case ConfigureAlertsCommand.ExpirationType.reservoir(let volume) = cfg.expirationType { - XCTAssertEqual(10, volume) - } - XCTAssertEqual(.beepBeepBeepBeep, cfg.beepType) - - - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } -} - diff --git a/OmniKitTests/OmniKitTests-Bridging-Header.h b/OmniKitTests/OmniKitTests-Bridging-Header.h deleted file mode 100644 index 1b2cb5d6d..000000000 --- a/OmniKitTests/OmniKitTests-Bridging-Header.h +++ /dev/null @@ -1,4 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - diff --git a/OmniKitTests/PacketTests.swift b/OmniKitTests/PacketTests.swift deleted file mode 100644 index 3b97bf458..000000000 --- a/OmniKitTests/PacketTests.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// PacketTests.swift -// OmniKitTests -// -// Created by Pete Schwamb on 10/14/17. -// Copyright © 2017 Pete Schwamb. All rights reserved. -// - -import XCTest -@testable import OmniKit - -class PacketTests: XCTestCase { - - func testPacketData() { - // 2016-06-26T20:33:28.412197 ID1:1f01482a PTYPE:PDM SEQ:13 ID2:1f01482a B9:10 BLEN:3 BODY:0e0100802c CRC:88 - - let msg = Message(address: 0x1f01482a, messageBlocks: [GetStatusCommand()], sequenceNum: 4) - - let packet = Packet(address: 0x1f01482a, packetType: .pdm, sequenceNum: 13, data: msg.encoded()) - - XCTAssertEqual("1f01482aad1f01482a10030e0100802c88", packet.encoded().hexadecimalString) - - XCTAssertEqual("1f01482a10030e0100802c", packet.data.hexadecimalString) - - } - - func testPacketDecoding() { - do { - let packet = try Packet(encodedData: Data(hexadecimalString:"1f01482aad1f01482a10030e0100802c88")!) - XCTAssertEqual(0x1f01482a, packet.address) - XCTAssertEqual(13, packet.sequenceNum) - XCTAssertEqual(.pdm, packet.packetType) - XCTAssertEqual("1f01482a10030e0100802c", packet.data.hexadecimalString) - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - - func testPacketFragmenting() { - let longMessageData = Data(hexadecimalString:"02cb5000c92162368024632d8029623f002c62320031623b003463320039633d003c63310041623e0044633200496340004c6333005163448101627c8104627c8109627c810c62198111627c811460198103fe")! - let packet = Packet(address: 0x1f01482a, packetType: .pdm, sequenceNum: 13, data: longMessageData) - XCTAssertEqual(31, packet.data.count) - XCTAssertEqual("02cb5000c92162368024632d8029623f002c62320031623b00346332003963", packet.data.hexadecimalString) - let con1 = Packet(address: 0x1f01482a, packetType: .con, sequenceNum: 14, data: longMessageData.subdata(in: 31.. T { - return T { (completionHandler) -> String in - podComms?.runSession(withName: "Set time", using: rileyLinkDeviceProvider.firstConnectedDevice) { (result) in - let response: String - switch result { - case .success(let session): - do { - try session.setTime(basalSchedule: temporaryBasalSchedule, timeZone: .currentFixed, date: Date()) - response = self.successText - } catch let error { - response = String(describing: error) - } - case .failure(let error): - response = String(describing: error) - } - DispatchQueue.main.async { - completionHandler(response) - } - } - return LocalizedString("Changing time…", comment: "Progress message for changing pod time.") - } - } - - - static func testCommand(podComms: PodComms?, rileyLinkDeviceProvider: RileyLinkDeviceProvider) -> T { - return T { (completionHandler) -> String in - podComms?.runSession(withName: "Testing Commands", using: rileyLinkDeviceProvider.firstConnectedDevice) { (result) in - let response: String - switch result { - case .success(let session): - do { - try session.testingCommands() - response = self.successText - } catch let error { - response = String(describing: error) - } - case .failure(let error): - response = String(describing: error) - } - DispatchQueue.main.async { - completionHandler(response) - } - } - return LocalizedString("Testing Commands…", comment: "Progress message for testing commands.") - } - } -} diff --git a/OmniKitUI/Info.plist b/OmniKitUI/Info.plist deleted file mode 100644 index 3d809ffe3..000000000 --- a/OmniKitUI/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 2.1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/OmniKitUI/OmniKitUI.h b/OmniKitUI/OmniKitUI.h deleted file mode 100644 index 02da72a59..000000000 --- a/OmniKitUI/OmniKitUI.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// OmniKitUI.h -// OmniKitUI -// -// Created by Pete Schwamb on 8/26/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -#import - -//! Project version number for OmniKitUI. -FOUNDATION_EXPORT double OmniKitUIVersionNumber; - -//! Project version string for OmniKitUI. -FOUNDATION_EXPORT const unsigned char OmniKitUIVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/OmniKitUI/OmniKitUI.xcassets/Contents.json b/OmniKitUI/OmniKitUI.xcassets/Contents.json deleted file mode 100644 index da4a164c9..000000000 --- a/OmniKitUI/OmniKitUI.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json deleted file mode 100644 index 54267cedb..000000000 --- a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "pod_1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pod_1x-2.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pod_1x-1.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-1.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-1.png deleted file mode 100644 index e17525d5fecde24b3838004e198028164d65f39f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50805 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 diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-2.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-2.png deleted file mode 100644 index 07bbbf95874d64fe0820f083d050dc33cb7a417b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24552 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 MxNJt zVOLl8f;^l*{;m3W_&hK3diSXg=(rzOvt#s;=Lh(7_+?Axu7=&LB74{1=nfWRCAC|4 zSG1lddVPKAYyHCNTiX!Noktgo%I5V$YRPx4+r+oXybLc^pXptYk43a?i7$T-Ibwy` zjBkuTjyoP{+eJ9_BWpQ%(S+sx*KDTIlb%&psjR&o1rmoDJiNZv3nqwtaL`e!?$l{* z;e5*Xd zg;^P^$ouFo|L~Set$ed6ySz>)Pq9&IHCQ$QYU~w%wB(8!xux+Og~pdP-0O?5lYTm3BhnRYoVLW z>kPba%}nAuZV(rbGd=3iXc;TJ!a|$VF=TM{Gu@q}P|NJ{&fAJ!^h%em#C@zizLfCR zw*1r&vrmf=xn;L#3MXN(KC|$ihUoVq=pHSzo4K4W*#{avi?VatAh_1UL`Smpm{i8U z-^@K*KW!vfM_0@mvp8NrTcF%F+>2KmS2%f7j#gb-^RczzjFZ%9ev5FNTZss;{xw1M zq_|kGFBb}Tt&Xs&7Q6_3;@r%^4b|jJU$)qf znX=P#X1rncUL7eh^B6lhsyTzgpUs>OePB&l1OZ<>w$MeTRPgO z?yy9u=5*l)_)P8=^aVhX*)}UXS9ap1A8#ZPk3C6GR=2u6vr>GQb6?JA-y>gh9=94U z*xAcmf`bX;BNdufHEqd#G}G_Ry|4$GdQPd0ZrQS47ff!}$%-|$iFVKtEj*Uamhl1> zf8f~rQhnd6VehJd&Bxs|LYyDuNZRUCUssB9xUNFd+0WT-1d8PAr)SPL1@l$&EovP+ z*LIcIUmtk}(nMevnIKx!2WoX$(;a5x_*kAH#Je(n3zaa=*%n*h`gu$Jd-we|&Z2t1 z8z{GHSFWZHIWx#kWf2mrS)ce={>EF%CEND1I!C%uU65tGVP2Q@-0t;Yfe8bNG1Jzldt7Mz zmer%~EB5|e>{$=qzNuk;$f|y$qvuaiC48E7RO@Ghfitjk=@@c>2&wgHgM4Ia&k=Pjq%08{1c;7YUcb?-ATGM zw7pRgPr=)=>jEqz#=hN5!`i$j9*=A0bw|g*wgKIIG{Z(biJ)G>=n`%&kzl&-NGsQP zwAkUW4wL4?AW#2ucMSoU!ir8}=2nHXbE7ZEy6RFn`6a*ktana^N0wcN-sQ;ZX@slU z;f&IE9DE%}Rq|0P_TRUP5Z?H>m%YpNv&Z(Y+zmdmPZ_mUF<6i#nC4cx)yZENUWcso zFXpgyKw#IL-p|?Z?~`=i9KNYh#gN!hdc(v%(5yrai&!TgdXRvanK9lgIIOZsHalx{ zZGg+JrBDuXIzB#lS<#AzDsJL1zc?-48JjxTTShzFMYUYysQK6*-Kd6*81J%@TL>Q| z4@IlAu0QAsXzo9C2G&Fh-3GV~8yK%eCro4q7Ta@IFh!R$Dw#EZLyV8y7s+wRZBMUK z)|uAK>22@r-v;C%adQfxy3eauO1Z)P)XrsNoS25!+11Ou&q<6~_oM^g2uRWtU*Zv) zyqeJ@M`-hhN$Z72>--RJcRc&agI1nN4gGF&Hn9J$&dXwxFPEOS6(l-Ippu>s^YXit z9{TZ!wrScq64iULh4*vCZCC+6R5th{=*0R~U`_gux@v&ue;Eql{j?-6ieud3>q^PHb+N2IM=!viEoBo-f9qZ_y}!pHfP(J5uzd&&+8_Z+PQPR(teeg$s042 zjVCSc}f?>6EcfQgzhqox{b(?WhSpJ1;TBRxz(&G7XA7_<} z)O{6l^mEcqW?454+4gu~*SvbUh1$cc9W!$Hr=3oJaZ-BdKb)9^+vo!=eEPSaHsUlZ z`t|)DLyCqv-aI`;ccJ2?^-!L#0F<2Nw<@VwaZdltTHbdg9clVu!>yxoeNpEW<%XTg T^x09id;nVo_-l#%_M86#Su|$e diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.jpg b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.jpg deleted file mode 100644 index fe0682182c71e85f94e478c421be40c31a04000f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18436 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

)|c9mOc{%3@c2f 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 OyrH7sWN+x|&Hn>;VnojX diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json deleted file mode 100644 index c1722d577..000000000 --- a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "pod_1x-2.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "pod_1x-1.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "pod_1x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png deleted file mode 100644 index b89405d93442b07eb1116c7463790e9583af9470..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67130 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 z

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 Qj{pDw07*qoM6N<$g7mTL7ytkO diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png deleted file mode 100644 index 086e4a4c9bb964e24d12d0380be767ae4e32c86c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138788 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 z 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 diff --git a/OmniKitUI/OmniPodPumpManager+UI.swift b/OmniKitUI/OmniPodPumpManager+UI.swift deleted file mode 100644 index eada0cb67..000000000 --- a/OmniKitUI/OmniPodPumpManager+UI.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// OmniPodPumpManager+UI.swift -// OmniKitUI -// -// Created by Pete Schwamb on 8/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -import UIKit -import LoopKit -import LoopKitUI -import OmniKit - -// TEMPORARY solution to providing basal schedule; will eventually provided by PumpManagerDelegate -// Used during pairing, and during timezone change. -let temporaryBasalSchedule = BasalSchedule(entries: [BasalScheduleEntry(rate: 0.05, duration: .hours(24))]) - - -extension OmnipodPumpManager: PumpManagerUI { - static public func setupViewController() -> (UIViewController & PumpManagerSetupViewController) { - return OmnipodPumpManagerSetupViewController.instantiateFromStoryboard() - } - - public func settingsViewController() -> UIViewController { - return OmnipodSettingsViewController(pumpManager: self) - } - - public var smallImage: UIImage? { - return UIImage(named: "Pod", in: Bundle(for: OmnipodSettingsViewController.self), compatibleWith: nil)! - } -} - -// MARK: - DeliveryLimitSettingsTableViewControllerSyncSource -extension OmnipodPumpManager { - public func syncDeliveryLimitSettings(for viewController: DeliveryLimitSettingsTableViewController, completion: @escaping (DeliveryLimitSettingsResult) -> Void) { - return - } - - public func syncButtonTitle(for viewController: DeliveryLimitSettingsTableViewController) -> String { - return NSLocalizedString("Save", comment: "Title of button to save delivery limit settings") } - - public func syncButtonDetailText(for viewController: DeliveryLimitSettingsTableViewController) -> String? { - return nil - } - - public func deliveryLimitSettingsTableViewControllerIsReadOnly(_ viewController: DeliveryLimitSettingsTableViewController) -> Bool { - return true - } -} - -// MARK: - SingleValueScheduleTableViewControllerSyncSource -extension OmnipodPumpManager { - public func syncScheduleValues(for viewController: SingleValueScheduleTableViewController, completion: @escaping (RepeatingScheduleValueResult) -> Void) { - return // TODO: store basal schedule - } - - public func syncButtonTitle(for viewController: SingleValueScheduleTableViewController) -> String { - return NSLocalizedString("Sync With Pod", comment: "Title of button to sync basal profile from pod") - } - - public func syncButtonDetailText(for viewController: SingleValueScheduleTableViewController) -> String? { - return nil - } - - public func singleValueScheduleTableViewControllerIsReadOnly(_ viewController: SingleValueScheduleTableViewController) -> Bool { - return false - } -} diff --git a/OmniKitUI/OmnipodPumpManager.storyboard b/OmniKitUI/OmnipodPumpManager.storyboard deleted file mode 100644 index 39f08cfa1..000000000 --- a/OmniKitUI/OmnipodPumpManager.storyboard +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/OmniKitUI/OmnipodSettingsViewController.swift b/OmniKitUI/OmnipodSettingsViewController.swift deleted file mode 100644 index cf4881db2..000000000 --- a/OmniKitUI/OmnipodSettingsViewController.swift +++ /dev/null @@ -1,479 +0,0 @@ -// -// OmnipodSettingsViewController.swift -// OmniKitUI -// -// Created by Pete Schwamb on 8/5/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import UIKit -import RileyLinkKitUI -import OmniKit -import LoopKitUI - -class OmnipodSettingsViewController: RileyLinkSettingsViewController { - - let pumpManager: OmnipodPumpManager - - var statusError: Error? - - var podStatus: StatusResponse? - - init(pumpManager: OmnipodPumpManager) { - self.pumpManager = pumpManager - super.init(rileyLinkPumpManager: pumpManager, devicesSectionIndex: Section.rileyLinks.rawValue, style: .grouped) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public var podImage: UIImage? { - return UIImage(named: "PodLarge", in: Bundle(for: OmnipodSettingsViewController.self), compatibleWith: nil)! - } - - - override func viewDidLoad() { - super.viewDidLoad() - - title = NSLocalizedString("Pod Settings", comment: "Title of the pod settings view controller") - - tableView.rowHeight = UITableView.automaticDimension - tableView.estimatedRowHeight = 44 - - tableView.sectionHeaderHeight = UITableView.automaticDimension - tableView.estimatedSectionHeaderHeight = 55 - - tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className) - tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className) - - let imageView = UIImageView(image: podImage) - imageView.contentMode = .center - imageView.frame.size.height += 18 // feels right - tableView.tableHeaderView = imageView - tableView.tableHeaderView?.backgroundColor = UIColor.white - - updateStatus() - } - - override func viewWillAppear(_ animated: Bool) { - if clearsSelectionOnViewWillAppear { - // Manually invoke the delegate for rows deselecting on appear - for indexPath in tableView.indexPathsForSelectedRows ?? [] { - _ = tableView(tableView, willDeselectRowAt: indexPath) - } - } - - super.viewWillAppear(animated) - } - - private func updateStatus() { - let deviceSelector = pumpManager.rileyLinkDeviceProvider.firstConnectedDevice - pumpManager.podComms.runSession(withName: "Update Status", using: deviceSelector) { (result) in - do { - switch result { - case .success(let session): - self.podStatus = try session.getStatus() - case.failure(let error): - throw error - } - } catch let error { - self.statusError = error - } - DispatchQueue.main.async { - self.tableView.reloadSections([Section.status.rawValue], with: .none) - } - } - } - - // MARK: - Formatters - - private lazy var dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.timeStyle = .short - dateFormatter.dateStyle = .medium - dateFormatter.doesRelativeDateFormatting = true - //dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "EEEE 'at' hm", options: 0, locale: nil) - return dateFormatter - }() - - - // MARK: - Data Source - - private enum Section: Int { - case info = 0 - case configuration - case status - case rileyLinks - case deactivate - - static let count = 5 - } - - private enum InfoRow: Int { - case activatedAt = 0 - case expiresAt - case podAddress - case podLot - case podTid - case piVersion - case pmVersion - - static let count = 7 - } - - private enum ConfigurationRow: Int { - case timeZoneOffset = 0 - case testCommand - - static let count = 2 - } - - fileprivate enum StatusRow: Int { - case deliveryStatus = 0 - case podStatus - case alarms - case reservoirLevel - case deliveredInsulin - case insulinNotDelivered - - static let count = 6 - } - - // MARK: UITableViewDataSource - - override func numberOfSections(in tableView: UITableView) -> Int { - return Section.count - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch Section(rawValue: section)! { - case .info: - return InfoRow.count - case .configuration: - return ConfigurationRow.count - case .status: - return StatusRow.count - case .rileyLinks: - return super.tableView(tableView, numberOfRowsInSection: section) - case .deactivate: - return 1 - } - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch Section(rawValue: section)! { - case .info: - return NSLocalizedString("Device Information", comment: "The title of the device information section in settings") - case .configuration: - return NSLocalizedString("Configuration", comment: "The title of the configuration section in settings") - case .status: - return NSLocalizedString("Status", comment: "The title of the status section in settings") - case .rileyLinks: - return super.tableView(tableView, titleForHeaderInSection: section) - case .deactivate: - return " " // Use an empty string for more dramatic spacing - } - } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - switch Section(rawValue: section)! { - case .rileyLinks: - return super.tableView(tableView, viewForHeaderInSection: section) - case .info, .configuration, .status, .deactivate: - return nil - } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch Section(rawValue: indexPath.section)! { - case .info: - switch InfoRow(rawValue: indexPath.row)! { - case .activatedAt: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("Active Time", comment: "The title of the cell showing the pod activated at time") - cell.setDetailAge(-pumpManager.state.podState.activatedAt.timeIntervalSinceNow) - return cell - case .expiresAt: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("Expires", comment: "The title of the cell showing the pod expiration") - cell.setDetailDate(pumpManager.state.podState.expiresAt, formatter: dateFormatter) - return cell - case .podAddress: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("Assigned Address", comment: "The title text for the address assigned to the pod") - cell.detailTextLabel?.text = String(format:"%04X", pumpManager.state.podState.address) - return cell - case .podLot: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("Lot", comment: "The title of the cell showing the pod lot id") - cell.detailTextLabel?.text = String(format:"L%d", pumpManager.state.podState.lot) - return cell - case .podTid: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("TID", comment: "The title of the cell showing the pod TID") - cell.detailTextLabel?.text = String(format:"%07d", pumpManager.state.podState.tid) - return cell - case .piVersion: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("PI Version", comment: "The title of the cell showing the pod pi version") - cell.detailTextLabel?.text = pumpManager.state.podState.piVersion - return cell - case .pmVersion: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = NSLocalizedString("PM Version", comment: "The title of the cell showing the pod pm version") - cell.detailTextLabel?.text = pumpManager.state.podState.pmVersion - return cell - } - case .configuration: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - - switch ConfigurationRow(rawValue: indexPath.row)! { - case .timeZoneOffset: - cell.textLabel?.text = NSLocalizedString("Change Time Zone", comment: "The title of the command to change pump time zone") - - let localTimeZone = TimeZone.current - let localTimeZoneName = localTimeZone.abbreviation() ?? localTimeZone.identifier - - let timeZoneDiff = TimeInterval(pumpManager.state.podState.timeZone.secondsFromGMT() - localTimeZone.secondsFromGMT()) - let formatter = DateComponentsFormatter() - formatter.allowedUnits = [.hour, .minute] - let diffString = timeZoneDiff != 0 ? formatter.string(from: abs(timeZoneDiff)) ?? String(abs(timeZoneDiff)) : "" - - cell.detailTextLabel?.text = String(format: NSLocalizedString("%1$@%2$@%3$@", comment: "The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00)"), localTimeZoneName, timeZoneDiff != 0 ? (timeZoneDiff < 0 ? "-" : "+") : "", diffString) - case .testCommand: - cell.textLabel?.text = NSLocalizedString("Test Command", comment: "The title of the command to run the test command") - } - - cell.accessoryType = .disclosureIndicator - return cell - case .status: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - - switch StatusRow(rawValue: indexPath.row)! { - case .deliveryStatus: - cell.textLabel?.text = NSLocalizedString("Delivery", comment: "The title of the cell showing delivery status") - case .podStatus: - cell.textLabel?.text = NSLocalizedString("Pod Status", comment: "The title of the cell showing pod status") - case .alarms: - cell.textLabel?.text = NSLocalizedString("Alarms", comment: "The title of the cell showing alarm status") - case .reservoirLevel: - cell.textLabel?.text = NSLocalizedString("Reservoir", comment: "The title of the cell showing reservoir status") - case .deliveredInsulin: - cell.textLabel?.text = NSLocalizedString("Delivery", comment: "The title of the cell showing delivered insulin") - case .insulinNotDelivered: - cell.textLabel?.text = NSLocalizedString("Insulin Not Delivered", comment: "The title of the cell showing insulin not delivered") - } - cell.setStatusDetail(podStatus: podStatus, statusRow: StatusRow(rawValue: indexPath.row)!) - return cell - case .rileyLinks: - return super.tableView(tableView, cellForRowAt: indexPath) - case .deactivate: - let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell - - cell.textLabel?.text = NSLocalizedString("Deactivate Pod", comment: "Title text for the button to remove a pod from Loop") - cell.textLabel?.textAlignment = .center - cell.tintColor = .deleteColor - cell.isEnabled = true - return cell - } - } - - override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - switch Section(rawValue: indexPath.section)! { - case .info, .status: - return false - case .configuration, .rileyLinks, .deactivate: - return true - } - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let sender = tableView.cellForRow(at: indexPath) - - switch Section(rawValue: indexPath.section)! { - case .info, .status: - break - case .configuration: - switch ConfigurationRow(rawValue: indexPath.row)! { - case .timeZoneOffset: - let vc = CommandResponseViewController.changeTime(podComms: pumpManager.podComms, rileyLinkDeviceProvider: pumpManager.rileyLinkDeviceProvider) - vc.title = sender?.textLabel?.text - show(vc, sender: indexPath) - case .testCommand: - let vc = CommandResponseViewController.testCommand(podComms: pumpManager.podComms, rileyLinkDeviceProvider: pumpManager.rileyLinkDeviceProvider) - vc.title = sender?.textLabel?.text - show(vc, sender: indexPath) - } - case .rileyLinks: - let device = devicesDataSource.devices[indexPath.row] - let vc = RileyLinkDeviceTableViewController(device: device) - self.show(vc, sender: sender) - case .deactivate: - let confirmVC = UIAlertController(pumpDeletionHandler: { - let deviceSelector = self.pumpManager.rileyLinkDeviceProvider.firstConnectedDevice - self.pumpManager.podComms.runSession(withName: "Deactivate Pod", using: deviceSelector, { (result) in - do { - switch result { - case .success(let session): - let _ = try session.changePod() - DispatchQueue.main.async { - self.pumpManager.pumpManagerDelegate?.pumpManagerWillDeactivate(self.pumpManager) - self.navigationController?.popViewController(animated: true) - } - case.failure(let error): - throw error - } - } catch let error { - self.statusError = error - DispatchQueue.main.async { - self.tableView.reloadSections([Section.status.rawValue], with: .none) - } - } - }) - }) - - present(confirmVC, animated: true) { - tableView.deselectRow(at: indexPath, animated: true) - } - } - } - - override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { - switch Section(rawValue: indexPath.section)! { - case .info, .status: - break - case .configuration: - switch ConfigurationRow(rawValue: indexPath.row)! { - case .timeZoneOffset, .testCommand: - tableView.reloadRows(at: [indexPath], with: .fade) - } - case .rileyLinks: - break - case .deactivate: - break - } - - return indexPath - } -} - - -extension OmnipodSettingsViewController: RadioSelectionTableViewControllerDelegate { - func radioSelectionTableViewControllerDidChangeSelectedIndex(_ controller: RadioSelectionTableViewController) { - guard let indexPath = self.tableView.indexPathForSelectedRow else { - return - } - - switch Section(rawValue: indexPath.section)! { - case .configuration: - switch ConfigurationRow(rawValue: indexPath.row)! { - default: - assertionFailure() - } - default: - assertionFailure() - } - - tableView.reloadRows(at: [indexPath], with: .none) - } -} - -private extension UIAlertController { - convenience init(pumpDeletionHandler handler: @escaping () -> Void) { - self.init( - title: nil, - message: NSLocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"), - preferredStyle: .actionSheet - ) - - addAction(UIAlertAction( - title: NSLocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"), - style: .destructive, - handler: { (_) in - handler() - } - )) - - let cancel = NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet") - addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil)) - } -} - -private extension TimeInterval { - func format(using units: NSCalendar.Unit) -> String? { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = units - formatter.unitsStyle = .full - formatter.zeroFormattingBehavior = .dropLeading - formatter.maximumUnitCount = 2 - - return formatter.string(from: self) - } -} - - -private extension UITableViewCell { - func setDetailDate(_ date: Date?, formatter: DateFormatter) { - if let date = date { - detailTextLabel?.text = formatter.string(from: date) - } else { - detailTextLabel?.text = "-" - } - } - - func setDetailAge(_ age: TimeInterval?) { - if let age = age { - detailTextLabel?.text = age.format(using: [.day, .hour, .minute]) - } else { - detailTextLabel?.text = "" - } - } - - private var insulinFormatter: NumberFormatter { - let formatter = NumberFormatter() - formatter.numberStyle = .decimal - formatter.maximumFractionDigits = 3 - - return formatter - } - - func setStatusDetail(podStatus: StatusResponse?, statusRow: OmnipodSettingsViewController.StatusRow) { - if let podStatus = podStatus { - switch statusRow { - case .deliveryStatus: - detailTextLabel?.text = String(describing: podStatus.deliveryStatus) - case .podStatus: - detailTextLabel?.text = String(describing: podStatus.reservoirStatus) - case .alarms: - detailTextLabel?.text = String(describing: podStatus.alarms) - case .reservoirLevel: - if podStatus.reservoirLevel == StatusResponse.maximumReservoirReading { - if let units = insulinFormatter.string(from: StatusResponse.maximumReservoirReading) { - detailTextLabel?.text = String(format: LocalizedString(">= %@U", comment: "Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount)"), units) - } - } else { - if let reservoirValue = podStatus.reservoirLevel, - let units = insulinFormatter.string(from: reservoirValue) - { - detailTextLabel?.text = String(format: LocalizedString("%@ U", comment: "Format string for insulin remaining in reservoir. (1: The localized amount)"), units) - } else { - detailTextLabel?.text = String(format: LocalizedString(">50 U", comment: "String shown when reservoir is above its max measurement capacity")) - } - } - case .deliveredInsulin: - if let units = insulinFormatter.string(from: podStatus.insulin) { - detailTextLabel?.text = String(format: LocalizedString("%@U", comment: "Format string for delivered insulin. (1: The localized amount)"), units) - } - case .insulinNotDelivered: - if let units = insulinFormatter.string(from: podStatus.insulinNotDelivered) { - detailTextLabel?.text = String(format: LocalizedString("%@U", comment: "Format string for insulin not delivered. (1: The localized amount)"), units) - } - } - } else { - detailTextLabel?.text = "" - } - } - -} - diff --git a/OmniKitUI/Pairing/InsertCannulaSetupViewController.swift b/OmniKitUI/Pairing/InsertCannulaSetupViewController.swift deleted file mode 100644 index fbc7b2019..000000000 --- a/OmniKitUI/Pairing/InsertCannulaSetupViewController.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// InsertCannulaSetupViewController.swift -// OmniKitUI -// -// Created by Pete Schwamb on 9/18/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import UIKit -import LoopKit -import LoopKitUI -import RileyLinkKit -import OmniKit - -class InsertCannulaSetupViewController: SetupTableViewController { - - var pumpManager: OmnipodPumpManager! - - // MARK: - - - @IBOutlet weak var activityIndicator: SetupIndicatorView! - - @IBOutlet weak var loadingLabel: UILabel! - - private var cancelErrorCount = 0 - - override func viewDidLoad() { - super.viewDidLoad() - - continueState = .initial - } - - override func setEditing(_ editing: Bool, animated: Bool) { - super.setEditing(editing, animated: animated) - } - - // MARK: - UITableViewDelegate - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard continueState != .inserting else { - return - } - - tableView.deselectRow(at: indexPath, animated: true) - } - - // MARK: - Navigation - - private enum State { - case initial - case inserting - case ready - } - - private var continueState: State = .initial { - didSet { - switch continueState { - case .initial: - activityIndicator.state = .hidden - footerView.primaryButton.isEnabled = true - footerView.primaryButton.setConnectTitle() - case .inserting: - activityIndicator.state = .loading - footerView.primaryButton.isEnabled = false - footerView.primaryButton.setConnectTitle() - lastError = nil - case .ready: - activityIndicator.state = .completed - footerView.primaryButton.isEnabled = true - footerView.primaryButton.resetTitle() - lastError = nil - } - } - } - - private var lastError: Error? { - didSet { - guard oldValue != nil || lastError != nil else { - return - } - - var errorText = lastError?.localizedDescription - - if let error = lastError as? LocalizedError { - let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ") + "." - - if !localizedText.isEmpty { - errorText = localizedText - } - } - - tableView.beginUpdates() - loadingLabel.text = errorText - - let isHidden = (errorText == nil) - loadingLabel.isHidden = isHidden - tableView.endUpdates() - - // If we changed the error text, update the continue state - if !isHidden { - continueState = .initial - } - } - } - - override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { - return continueState == .ready - } - - override func continueButtonPressed(_ sender: Any) { - - if case .ready = continueState { - super.continueButtonPressed(sender) - } else if case .initial = continueState { - continueState = .inserting - insertCannula() - } - } - - override func cancelButtonPressed(_ sender: Any) { - let confirmVC = UIAlertController(pumpDeletionHandler: { - let deviceSelector = self.pumpManager.rileyLinkDeviceProvider.firstConnectedDevice - self.pumpManager.podComms.runSession(withName: "Deactivate Pod", using: deviceSelector, { (result) in - do { - switch result { - case .success(let session): - let _ = try session.changePod() - DispatchQueue.main.async { - super.cancelButtonPressed(sender) - } - case.failure(let error): - throw error - } - } catch let error { - DispatchQueue.main.async { - self.cancelErrorCount += 1 - self.lastError = error - if self.cancelErrorCount >= 2 { - super.cancelButtonPressed(sender) - } - } - } - }) - }) - present(confirmVC, animated: true) {} - } - - func insertCannula() { - - guard let podComms = pumpManager.podComms else { - return - } - - let deviceSelector = pumpManager.rileyLinkDeviceProvider.firstConnectedDevice - - podComms.runSession(withName: "Insert cannula", using: deviceSelector) { (result) in - switch result { - case .success(let session): - do { - // TODO: Need to get schedule from PumpManagerDelegate - let scheduleOffset = self.pumpManager.state.podState.timeZone.scheduleOffset(forDate: Date()) - try session.insertCannula(basalSchedule: temporaryBasalSchedule, scheduleOffset: scheduleOffset) - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(10)) { - self.continueState = .ready - } - } catch let error { - DispatchQueue.main.async { - self.lastError = error - } - } - case .failure(let error): - DispatchQueue.main.async { - self.lastError = error - } - } - } - } -} - -private extension SetupButton { - func setConnectTitle() { - setTitle(LocalizedString("Insert Cannula", comment: "Button title to insert cannula during setup"), for: .normal) - } -} - -private extension UIAlertController { - convenience init(pumpDeletionHandler handler: @escaping () -> Void) { - self.init( - title: nil, - message: NSLocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"), - preferredStyle: .actionSheet - ) - - addAction(UIAlertAction( - title: NSLocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"), - style: .destructive, - handler: { (_) in - handler() - } - )) - - let exit = NSLocalizedString("Continue", comment: "The title of the continue action in an action sheet") - addAction(UIAlertAction(title: exit, style: .default, handler: nil)) - } -} - diff --git a/OmniKitUI/Pairing/OmnipodPumpManagerSetupViewController.swift b/OmniKitUI/Pairing/OmnipodPumpManagerSetupViewController.swift deleted file mode 100644 index a3eaef610..000000000 --- a/OmniKitUI/Pairing/OmnipodPumpManagerSetupViewController.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// OmnipodPumpManagerSetupViewController.swift -// OmniKitUI -// -// Created by Pete Schwamb on 8/4/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import Foundation - -import UIKit -import LoopKit -import LoopKitUI -import OmniKit -import RileyLinkBLEKit -import RileyLinkKit -import RileyLinkKitUI - -// PumpManagerSetupViewController -public class OmnipodPumpManagerSetupViewController: RileyLinkManagerSetupViewController { - - class func instantiateFromStoryboard() -> OmnipodPumpManagerSetupViewController { - return UIStoryboard(name: "OmnipodPumpManager", bundle: Bundle(for: OmnipodPumpManagerSetupViewController.self)).instantiateInitialViewController() as! OmnipodPumpManagerSetupViewController - } - - override public func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .white - navigationBar.shadowImage = UIImage() - - if let omnipodPairingViewController = topViewController as? PairPodSetupViewController, let rileyLinkPumpManager = rileyLinkPumpManager { - omnipodPairingViewController.rileyLinkPumpManager = rileyLinkPumpManager - } - } - - private(set) var pumpManager: OmnipodPumpManager? - - /* - 1. RileyLink - - RileyLinkPumpManagerState - - 2. Basal Rates & Delivery Limits - - 3. Pod Pairing/Priming - - 4. Cannula Insertion - - 5. Pod Setup Complete - */ - - override public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { - super.navigationController(navigationController, willShow: viewController, animated: animated) - - // Read state values - let viewControllers = navigationController.viewControllers - let count = navigationController.viewControllers.count - - if count >= 2 { - switch viewControllers[count - 2] { - case let vc as PairPodSetupViewController: - pumpManager = vc.pumpManager - default: - break - } - } - - // Set state values - switch viewController { - case let vc as PairPodSetupViewController: - vc.rileyLinkPumpManager = rileyLinkPumpManager - case let vc as InsertCannulaSetupViewController: - vc.pumpManager = pumpManager - default: - break - } - } - - - func completeSetup() { - if let pumpManager = pumpManager { - setupDelegate?.pumpManagerSetupViewController(self, didSetUpPumpManager: pumpManager) - } - } -} diff --git a/OmniKitUI/Pairing/PairPodSetupViewController.swift b/OmniKitUI/Pairing/PairPodSetupViewController.swift deleted file mode 100644 index 497aab70f..000000000 --- a/OmniKitUI/Pairing/PairPodSetupViewController.swift +++ /dev/null @@ -1,298 +0,0 @@ -// -// PairPodSetupViewController.swift -// OmniKitUI -// -// Created by Pete Schwamb on 9/18/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import UIKit -import LoopKit -import LoopKitUI -import RileyLinkKit -import OmniKit - -class PairPodSetupViewController: SetupTableViewController { - - var rileyLinkPumpManager: RileyLinkPumpManager! - - private var podComms: PodComms? - - private var podState: PodState? - - private var cancelErrorCount = 0 - - var pumpManagerState: OmnipodPumpManagerState? { - get { - guard let podState = podState else { - return nil - } - - return OmnipodPumpManagerState( - podState: podState, - rileyLinkConnectionManagerState: self.rileyLinkPumpManager.rileyLinkConnectionManagerState - ) - } - } - - var pumpManager: OmnipodPumpManager? { - guard let pumpManagerState = pumpManagerState else { - return nil - } - - return OmnipodPumpManager( - state: pumpManagerState, - rileyLinkDeviceProvider: rileyLinkPumpManager.rileyLinkDeviceProvider, - rileyLinkConnectionManager: rileyLinkPumpManager.rileyLinkConnectionManager) - } - - // MARK: - - - @IBOutlet weak var activityIndicator: SetupIndicatorView! - - @IBOutlet weak var loadingLabel: UILabel! - - override func viewDidLoad() { - super.viewDidLoad() - - continueState = .initial - } - - override func setEditing(_ editing: Bool, animated: Bool) { - super.setEditing(editing, animated: animated) - } - - // MARK: - UITableViewDelegate - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard continueState != .pairing else { - return - } - - tableView.deselectRow(at: indexPath, animated: true) - } - - // MARK: - Navigation - - private enum State { - case initial - case pairing - case paired - } - - private var continueState: State = .initial { - didSet { - switch continueState { - case .initial: - activityIndicator.state = .hidden - footerView.primaryButton.isEnabled = true - footerView.primaryButton.setConnectTitle() - case .pairing: - activityIndicator.state = .loading - footerView.primaryButton.isEnabled = false - footerView.primaryButton.setConnectTitle() - lastError = nil - case .paired: - activityIndicator.state = .completed - footerView.primaryButton.isEnabled = true - footerView.primaryButton.resetTitle() - lastError = nil - } - } - } - - private var lastError: Error? { - didSet { - guard oldValue != nil || lastError != nil else { - return - } - - var errorText = lastError?.localizedDescription - - if let error = lastError as? LocalizedError { - let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ") + "." - - if !localizedText.isEmpty { - errorText = localizedText - } - } - - tableView.beginUpdates() - loadingLabel.text = errorText - - let isHidden = (errorText == nil) - loadingLabel.isHidden = isHidden - tableView.endUpdates() - - // If we changed the error text, update the continue state - if !isHidden { - continueState = .initial - } - } - } - - override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { - return continueState == .paired - } - - override func continueButtonPressed(_ sender: Any) { - - if case .paired = continueState { - super.continueButtonPressed(sender) - } else if case .initial = continueState { - if podState == nil { - continueState = .pairing - pair() - } else { - configurePod() - } - } - } - - override func cancelButtonPressed(_ sender: Any) { - if case .paired = continueState, let pumpManager = self.pumpManager { - let confirmVC = UIAlertController(pumpDeletionHandler: { - let deviceSelector = pumpManager.rileyLinkDeviceProvider.firstConnectedDevice - pumpManager.podComms.runSession(withName: "Deactivate Pod", using: deviceSelector, { (result) in - do { - switch result { - case .success(let session): - let _ = try session.changePod() - DispatchQueue.main.async { - super.cancelButtonPressed(sender) - } - case.failure(let error): - throw error - } - } catch let error { - DispatchQueue.main.async { - self.cancelErrorCount += 1 - self.lastError = error - if self.cancelErrorCount >= 2 { - super.cancelButtonPressed(sender) - } - } - } - }) - }) - present(confirmVC, animated: true) {} - } else { - super.cancelButtonPressed(sender) - } - } - - func pair() { - - guard podComms == nil else { - return - } - - let deviceSelector = rileyLinkPumpManager.rileyLinkDeviceProvider.firstConnectedDevice - - // TODO: Let user choose between current and previously used timezone? - PodComms.pair(using: deviceSelector, timeZone: .currentFixed, completion: { (result) in - DispatchQueue.main.async { - switch result { - case .success(let podState): - self.podState = podState - self.podComms = PodComms(podState: podState, delegate: self) - self.configurePod() - case .failure(let error): - self.lastError = error - } - } - }) - } - - func configurePod() { - guard let podComms = podComms else { - return - } - - let deviceSelector = rileyLinkPumpManager.rileyLinkDeviceProvider.firstConnectedDevice - - podComms.runSession(withName: "Configure pod", using: deviceSelector) { (result) in - switch result { - case .success(let session): - do { - try session.configurePod() - - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(55)) { - self.finishPrime() - } - } catch let error { - DispatchQueue.main.async { - self.lastError = error - } - } - case .failure(let error): - DispatchQueue.main.async { - self.lastError = error - } - } - } - } - - func finishPrime() { - guard let podComms = podComms else { - return - } - - let deviceSelector = rileyLinkPumpManager.rileyLinkDeviceProvider.firstConnectedDevice - - podComms.runSession(withName: "Finish Prime", using: deviceSelector) { (result) in - switch result { - case .success(let session): - do { - try session.finishPrime() - DispatchQueue.main.async { - self.continueState = .paired - } - } catch let error { - DispatchQueue.main.async { - self.lastError = error - } - } - case .failure(let error): - DispatchQueue.main.async { - self.lastError = error - } - } - } - } - - -} - -extension PairPodSetupViewController: PodCommsDelegate { - public func podComms(_ podComms: PodComms, didChange state: PodState) { - self.podState = state - } -} - -private extension SetupButton { - func setConnectTitle() { - setTitle(LocalizedString("Pair", comment: "Button title to pair with pod during setup"), for: .normal) - } -} - -private extension UIAlertController { - convenience init(pumpDeletionHandler handler: @escaping () -> Void) { - self.init( - title: nil, - message: NSLocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"), - preferredStyle: .actionSheet - ) - - addAction(UIAlertAction( - title: NSLocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"), - style: .destructive, - handler: { (_) in - handler() - } - )) - - let exit = NSLocalizedString("Continue", comment: "The title of the continue action in an action sheet") - addAction(UIAlertAction(title: exit, style: .default, handler: nil)) - } -} diff --git a/OmniKitUI/Pairing/PodSetupCompleteViewController.swift b/OmniKitUI/Pairing/PodSetupCompleteViewController.swift deleted file mode 100644 index d0da070f3..000000000 --- a/OmniKitUI/Pairing/PodSetupCompleteViewController.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// PodSetupCompleteViewController.swift -// OmniKitUI -// -// Created by Pete Schwamb on 9/18/18. -// Copyright © 2018 Pete Schwamb. All rights reserved. -// - -import UIKit -import LoopKitUI - -class PodSetupCompleteViewController: SetupTableViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - self.navigationItem.hidesBackButton = true - self.navigationItem.rightBarButtonItem = nil - } - - override func continueButtonPressed(_ sender: Any) { - if let setupViewController = setupViewController as? OmnipodPumpManagerSetupViewController { - setupViewController.completeSetup() - } - } -} diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index b7e5144d5..da0b4837e 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -316,8 +316,6 @@ C12EA260198B436900309FA4 /* RileyLinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C12EA25F198B436900309FA4 /* RileyLinkTests.m */; }; C1330F431DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */; }; C133CF931D5943780034B82D /* PredictedBG.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133CF921D5943780034B82D /* PredictedBG.swift */; }; - C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */; }; - C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */; }; C13D155A1DAACE8400ADC044 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13D15591DAACE8400ADC044 /* Either.swift */; }; C14303161C97C98000A40450 /* PumpAckMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303151C97C98000A40450 /* PumpAckMessageBody.swift */; }; C14303181C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */; }; @@ -346,8 +344,6 @@ C178845D1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */; }; C178845F1D5166BE00405663 /* COBStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178845E1D5166BE00405663 /* COBStatus.swift */; }; C17884611D519F1E00405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884601D519F1E00405663 /* BatteryIndicator.swift */; }; - C17C5C0F21447383002A06F8 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; }; - C17EDC4F2134D0CC0031D9F0 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBA1C8E184300DB42AC /* PumpModel.swift */; }; C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */; }; C1842BBF1C8E855A00DB42AC /* PumpEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */; }; @@ -431,13 +427,8 @@ C1C3578F1C927303009BDD4F /* MeterMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C3578E1C927303009BDD4F /* MeterMessage.swift */; }; C1C357911C92733A009BDD4F /* MeterMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C357901C92733A009BDD4F /* MeterMessageTests.swift */; }; C1C73F1D1DE6306A0022FC89 /* BatteryChemistryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C73F1C1DE6306A0022FC89 /* BatteryChemistryType.swift */; }; - C1CB13A521383F1E00F9EEDA /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; - C1CB13A72138453B00F9EEDA /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; }; C1D00E9D1E8986A400B733B7 /* PumpSuspendTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D00E9C1E8986A400B733B7 /* PumpSuspendTreatment.swift */; }; C1D00EA11E8986F900B733B7 /* PumpResumeTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D00EA01E8986F900B733B7 /* PumpResumeTreatment.swift */; }; - C1E163D52135EC0100EB89AE /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; }; - C1E163D62135ED0300EB89AE /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; - C1E163D72135FF9A00EB89AE /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; C1E5BEAD1D5E26F200BD4390 /* RileyLinkStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E5BEAC1D5E26F200BD4390 /* RileyLinkStatus.swift */; }; C1EAD6B31C826B6D006DBA60 /* MySentryAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6AE1C826B6D006DBA60 /* MySentryAlertType.swift */; }; C1EAD6B41C826B6D006DBA60 /* MessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6AF1C826B6D006DBA60 /* MessageBody.swift */; }; @@ -467,84 +458,16 @@ C1F0004C1EBE68A600F65163 /* DataFrameMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0004B1EBE68A600F65163 /* DataFrameMessageBody.swift */; }; C1F000501EBE727C00F65163 /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0004F1EBE727C00F65163 /* BasalScheduleTests.swift */; }; C1F000521EBE73F400F65163 /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F000511EBE73F400F65163 /* BasalSchedule.swift */; }; - C1F1A5EA215164FA00F0B820 /* PairPodSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */; }; - C1F1A5EC2151F73F00F0B820 /* InsertCannulaSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */; }; - C1F1A5EE2151FBA700F0B820 /* PodSetupCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */; }; C1F6EB871F89C3B100CFE393 /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6DD1C82B78C006DBA60 /* CRC8.swift */; }; C1F6EB891F89C3E200CFE393 /* FourByteSixByteEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB881F89C3E200CFE393 /* FourByteSixByteEncoding.swift */; }; C1F6EB8B1F89C41200CFE393 /* MinimedPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB8A1F89C41200CFE393 /* MinimedPacket.swift */; }; C1F6EB8D1F89C45500CFE393 /* MinimedPacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */; }; C1FC49EC2135CB2D007D0788 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1FC49EB2135CB2D007D0788 /* LaunchScreen.storyboard */; }; - C1FC49EE2135DD56007D0788 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */; }; C1FDFCA91D964A3E00ADBC31 /* BolusReminderPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */; }; C1FFAF4D212944F600C50C1D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF4B212944F600C50C1D /* Localizable.strings */; }; C1FFAF64212B126E00C50C1D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF62212B126E00C50C1D /* InfoPlist.strings */; }; C1FFAF6F212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF6E212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift */; }; C1FFAF72212FAAEF00C50C1D /* RileyLink.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF71212FAAEF00C50C1D /* RileyLink.xcassets */; }; - C1FFAF81213323CC00C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; - C1FFAF8A213323CC00C50C1D /* OmniKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FFAF7A213323CC00C50C1D /* OmniKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C1FFAF8D213323CC00C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; - C1FFAF8E213323CC00C50C1D /* OmniKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C1FFAFB6213323E900C50C1D /* OmnipodPumpManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF96213323E600C50C1D /* OmnipodPumpManagerState.swift */; }; - C1FFAFB7213323E900C50C1D /* OmnipodPumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF97213323E600C50C1D /* OmnipodPumpManager.swift */; }; - C1FFAFB8213323E900C50C1D /* Pod.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF98213323E600C50C1D /* Pod.swift */; }; - C1FFAFB9213323E900C50C1D /* PodCommsSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF99213323E600C50C1D /* PodCommsSession.swift */; }; - C1FFAFBA213323E900C50C1D /* BasalDeliveryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */; }; - C1FFAFBB213323E900C50C1D /* MessageTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9B213323E700C50C1D /* MessageTransport.swift */; }; - C1FFAFBC213323E900C50C1D /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9C213323E700C50C1D /* Message.swift */; }; - C1FFAFBD213323E900C50C1D /* PodState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9D213323E700C50C1D /* PodState.swift */; }; - C1FFAFBE213323E900C50C1D /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9E213323E700C50C1D /* CRC16.swift */; }; - C1FFAFBF213323E900C50C1D /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */; }; - C1FFAFC0213323E900C50C1D /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA0213323E800C50C1D /* CRC8.swift */; }; - C1FFAFC1213323E900C50C1D /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA2213323E800C50C1D /* Notification.swift */; }; - C1FFAFC2213323E900C50C1D /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA4213323E800C50C1D /* VersionResponse.swift */; }; - C1FFAFC3213323E900C50C1D /* StatusError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA5213323E800C50C1D /* StatusError.swift */; }; - C1FFAFC4213323E900C50C1D /* TempBasalExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */; }; - C1FFAFC5213323E900C50C1D /* DeactivatePodCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */; }; - C1FFAFC6213323E900C50C1D /* MessageBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA8213323E800C50C1D /* MessageBlock.swift */; }; - C1FFAFC7213323E900C50C1D /* PlaceholderMessageBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */; }; - C1FFAFC8213323E900C50C1D /* BolusExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */; }; - C1FFAFC9213323E900C50C1D /* StatusResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAB213323E800C50C1D /* StatusResponse.swift */; }; - C1FFAFCA213323E900C50C1D /* GetStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */; }; - C1FFAFCB213323E900C50C1D /* BasalScheduleExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */; }; - C1FFAFCC213323E900C50C1D /* CancelDeliveryCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */; }; - C1FFAFCD213323E900C50C1D /* SetPodTimeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAF213323E800C50C1D /* SetPodTimeCommand.swift */; }; - C1FFAFCE213323E900C50C1D /* AssignAddressCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */; }; - C1FFAFCF213323E900C50C1D /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */; }; - C1FFAFD0213323E900C50C1D /* SetInsulinScheduleCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */; }; - C1FFAFD1213323E900C50C1D /* ConfigureAlertsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */; }; - C1FFAFD2213323E900C50C1D /* PodComms.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB4213323E800C50C1D /* PodComms.swift */; }; - C1FFAFD3213323E900C50C1D /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB5213323E900C50C1D /* Packet.swift */; }; - C1FFAFEB213323FA00C50C1D /* OmniKitUI.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FFAFDB213323F900C50C1D /* OmniKitUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C1FFAFEE213323FA00C50C1D /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; }; - C1FFAFEF213323FA00C50C1D /* OmniKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - C1FFAFFE2133241700C50C1D /* MessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF62133241500C50C1D /* MessageTests.swift */; }; - C1FFAFFF2133241700C50C1D /* PodStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF72133241500C50C1D /* PodStateTests.swift */; }; - C1FFB0002133241700C50C1D /* TempBasalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF82133241500C50C1D /* TempBasalTests.swift */; }; - C1FFB0012133241700C50C1D /* CRC8Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF92133241600C50C1D /* CRC8Tests.swift */; }; - C1FFB0022133241700C50C1D /* PacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFA2133241600C50C1D /* PacketTests.swift */; }; - C1FFB0032133241700C50C1D /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */; }; - C1FFB0042133241700C50C1D /* CRC16Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */; }; - C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */; }; - C1FFB00E2133242C00C50C1D /* OmniPodPumpManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */; }; - C1FFB00F2133242C00C50C1D /* OmniKitUI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */; }; - C1FFB0102133242C00C50C1D /* OmnipodSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */; }; - C1FFB0132133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */; }; - C1FFB0142133249300C50C1D /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; - C1FFB0152133249900C50C1D /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; }; - C1FFB016213324BC00C50C1D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; - C1FFB01721332A1F00C50C1D /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; - C1FFB01821332A2A00C50C1D /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; }; - C1FFB01921332A7100C50C1D /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; }; - C1FFB01A21332AFE00C50C1D /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; }; - C1FFB01B21332DD900C50C1D /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; - C1FFB01C21332E0900C50C1D /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */; }; - C1FFB01D21332E1700C50C1D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; - C1FFB01E2133860E00C50C1D /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC601D3D75470049CF85 /* UIColor.swift */; }; - C1FFB02121343E6D00C50C1D /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; - C1FFB02421343EC200C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; - C1FFB02521343F0300C50C1D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; - C1FFB02621343F0600C50C1D /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610A20DDF55E002B996B /* LoopKitUI.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -702,48 +625,6 @@ remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; - C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C1FFAF77213323CC00C50C1D; - remoteInfo = OmniKit; - }; - C1FFAF84213323CC00C50C1D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; - }; - C1FFAF8B213323CC00C50C1D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C1FFAF77213323CC00C50C1D; - remoteInfo = OmniKit; - }; - C1FFAFEC213323FA00C50C1D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C1FFAFD8213323F900C50C1D; - remoteInfo = OmniKitUI; - }; - C1FFB01F21343B9700C50C1D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; - C1FFB02221343EB500C50C1D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -764,12 +645,10 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - C1FFAF8E213323CC00C50C1D /* OmniKit.framework in Embed Frameworks */, 43722FC41CB9F7640038B7F2 /* RileyLinkKit.framework in Embed Frameworks */, 43C246AA1D8A31540031F8D1 /* Crypto.framework in Embed Frameworks */, C10D9BD71C8269D500378342 /* MinimedKit.framework in Embed Frameworks */, 43D5E7961FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Embed Frameworks */, - C1FFAFEF213323FA00C50C1D /* OmniKitUI.framework in Embed Frameworks */, C1B383211CD0665D00CE7782 /* NightscoutUploadKit.framework in Embed Frameworks */, 4352A72D20DEC9B700CAC200 /* MinimedKitUI.framework in Embed Frameworks */, 431CE7851F98564200255374 /* RileyLinkBLEKit.framework in Embed Frameworks */, @@ -1160,8 +1039,6 @@ C12EA25F198B436900309FA4 /* RileyLinkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RileyLinkTests.m; sourceTree = ""; }; C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeSensorAlarmSilenceConfigPumpEvent.swift; sourceTree = ""; }; C133CF921D5943780034B82D /* PredictedBG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictedBG.swift; sourceTree = ""; }; - C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInsulinMeasurements.swift; sourceTree = ""; }; - C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfinalizedDose.swift; sourceTree = ""; }; C13D15591DAACE8400ADC044 /* Either.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; C14303151C97C98000A40450 /* PumpAckMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpAckMessageBody.swift; sourceTree = ""; }; C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBodyTests.swift; sourceTree = ""; }; @@ -1314,14 +1191,10 @@ C1F0004B1EBE68A600F65163 /* DataFrameMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataFrameMessageBody.swift; sourceTree = ""; }; C1F0004F1EBE727C00F65163 /* BasalScheduleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleTests.swift; sourceTree = ""; }; C1F000511EBE73F400F65163 /* BasalSchedule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalSchedule.swift; sourceTree = ""; }; - C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairPodSetupViewController.swift; sourceTree = ""; }; - C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertCannulaSetupViewController.swift; sourceTree = ""; }; - C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodSetupCompleteViewController.swift; sourceTree = ""; }; C1F6EB881F89C3E200CFE393 /* FourByteSixByteEncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourByteSixByteEncoding.swift; sourceTree = ""; }; C1F6EB8A1F89C41200CFE393 /* MinimedPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPacket.swift; sourceTree = ""; }; C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPacketTests.swift; sourceTree = ""; }; C1FC49EB2135CB2D007D0788 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = ""; }; C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusReminderPumpEvent.swift; sourceTree = ""; }; C1FFAF4C212944F600C50C1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; C1FFAF4F212944FF00C50C1D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; @@ -1343,57 +1216,6 @@ C1FFAF6D212B128C00C50C1D /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; C1FFAF6E212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkConnectionManagerState.swift; sourceTree = ""; }; C1FFAF71212FAAEF00C50C1D /* RileyLink.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = RileyLink.xcassets; sourceTree = ""; }; - C1FFAF78213323CC00C50C1D /* OmniKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OmniKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C1FFAF7A213323CC00C50C1D /* OmniKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OmniKit.h; sourceTree = ""; }; - C1FFAF7B213323CC00C50C1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OmniKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - C1FFAF89213323CC00C50C1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C1FFAF96213323E600C50C1D /* OmnipodPumpManagerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodPumpManagerState.swift; sourceTree = ""; }; - C1FFAF97213323E600C50C1D /* OmnipodPumpManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodPumpManager.swift; sourceTree = ""; }; - C1FFAF98213323E600C50C1D /* Pod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pod.swift; sourceTree = ""; }; - C1FFAF99213323E600C50C1D /* PodCommsSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodCommsSession.swift; sourceTree = ""; }; - C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalDeliveryTable.swift; sourceTree = ""; }; - C1FFAF9B213323E700C50C1D /* MessageTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTransport.swift; sourceTree = ""; }; - C1FFAF9C213323E700C50C1D /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; - C1FFAF9D213323E700C50C1D /* PodState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodState.swift; sourceTree = ""; }; - C1FFAF9E213323E700C50C1D /* CRC16.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC16.swift; sourceTree = ""; }; - C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalSchedule.swift; sourceTree = ""; }; - C1FFAFA0213323E800C50C1D /* CRC8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC8.swift; sourceTree = ""; }; - C1FFAFA2213323E800C50C1D /* Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; - C1FFAFA4213323E800C50C1D /* VersionResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionResponse.swift; sourceTree = ""; }; - C1FFAFA5213323E800C50C1D /* StatusError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusError.swift; sourceTree = ""; }; - C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempBasalExtraCommand.swift; sourceTree = ""; }; - C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeactivatePodCommand.swift; sourceTree = ""; }; - C1FFAFA8213323E800C50C1D /* MessageBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageBlock.swift; sourceTree = ""; }; - C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderMessageBlock.swift; sourceTree = ""; }; - C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusExtraCommand.swift; sourceTree = ""; }; - C1FFAFAB213323E800C50C1D /* StatusResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusResponse.swift; sourceTree = ""; }; - C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetStatusCommand.swift; sourceTree = ""; }; - C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleExtraCommand.swift; sourceTree = ""; }; - C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelDeliveryCommand.swift; sourceTree = ""; }; - C1FFAFAF213323E800C50C1D /* SetPodTimeCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetPodTimeCommand.swift; sourceTree = ""; }; - C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssignAddressCommand.swift; sourceTree = ""; }; - C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = ""; }; - C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetInsulinScheduleCommand.swift; sourceTree = ""; }; - C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigureAlertsCommand.swift; sourceTree = ""; }; - C1FFAFB4213323E800C50C1D /* PodComms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodComms.swift; sourceTree = ""; }; - C1FFAFB5213323E900C50C1D /* Packet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Packet.swift; sourceTree = ""; }; - C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OmniKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C1FFAFDB213323F900C50C1D /* OmniKitUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OmniKitUI.h; sourceTree = ""; }; - C1FFAFDC213323F900C50C1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C1FFAFF62133241500C50C1D /* MessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTests.swift; sourceTree = ""; }; - C1FFAFF72133241500C50C1D /* PodStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodStateTests.swift; sourceTree = ""; }; - C1FFAFF82133241500C50C1D /* TempBasalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempBasalTests.swift; sourceTree = ""; }; - C1FFAFF92133241600C50C1D /* CRC8Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC8Tests.swift; sourceTree = ""; }; - C1FFAFFA2133241600C50C1D /* PacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTests.swift; sourceTree = ""; }; - C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleTests.swift; sourceTree = ""; }; - C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC16Tests.swift; sourceTree = ""; }; - C1FFAFFD2133241600C50C1D /* OmniKitTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OmniKitTests-Bridging-Header.h"; sourceTree = ""; }; - C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = OmnipodPumpManager.storyboard; sourceTree = ""; }; - C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OmniPodPumpManager+UI.swift"; sourceTree = ""; }; - C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OmniKitUI.xcassets; sourceTree = ""; }; - C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodSettingsViewController.swift; sourceTree = ""; }; - C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodPumpManagerSetupViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1482,7 +1304,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C1FFAFEE213323FA00C50C1D /* OmniKitUI.framework in Frameworks */, 43C246A91D8A31540031F8D1 /* Crypto.framework in Frameworks */, C1B383201CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */, C12EA23D198B436800309FA4 /* CoreGraphics.framework in Frameworks */, @@ -1493,7 +1314,6 @@ 431CE7841F98564200255374 /* RileyLinkBLEKit.framework in Frameworks */, C10D9BD61C8269D500378342 /* MinimedKit.framework in Frameworks */, 4352A72C20DEC9B700CAC200 /* MinimedKitUI.framework in Frameworks */, - C1FFAF8D213323CC00C50C1D /* OmniKit.framework in Frameworks */, 4352A74720DED4AF00CAC200 /* LoopKit.framework in Frameworks */, 4352A74620DED4AB00CAC200 /* LoopKitUI.framework in Frameworks */, ); @@ -1530,35 +1350,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C1FFAF74213323CC00C50C1D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFB0152133249900C50C1D /* RileyLinkBLEKit.framework in Frameworks */, - C1FFB0142133249300C50C1D /* RileyLinkKit.framework in Frameworks */, - C1FFB016213324BC00C50C1D /* LoopKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAF7D213323CC00C50C1D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFAF81213323CC00C50C1D /* OmniKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAFD5213323F900C50C1D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C1E163D52135EC0100EB89AE /* RileyLinkKitUI.framework in Frameworks */, - C1FFB02421343EC200C50C1D /* OmniKit.framework in Frameworks */, - C1FFB02521343F0300C50C1D /* LoopKit.framework in Frameworks */, - C1FFB02621343F0600C50C1D /* LoopKitUI.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -1943,9 +1734,6 @@ 431CE77D1F98564200255374 /* RileyLinkBLEKitTests */, 43D5E78F1FAF7BFB004ACDB7 /* RileyLinkKitUI */, 4352A72620DEC9B700CAC200 /* MinimedKitUI */, - C1FFAF79213323CC00C50C1D /* OmniKit */, - C1FFAF86213323CC00C50C1D /* OmniKitTests */, - C1FFAFDA213323F900C50C1D /* OmniKitUI */, C12EA239198B436800309FA4 /* Frameworks */, C12EA238198B436800309FA4 /* Products */, ); @@ -1967,9 +1755,6 @@ 431CE7771F98564200255374 /* RileyLinkBLEKitTests.xctest */, 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */, 4352A72520DEC9B700CAC200 /* MinimedKitUI.framework */, - C1FFAF78213323CC00C50C1D /* OmniKit.framework */, - C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */, - C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */, ); name = Products; sourceTree = ""; @@ -2041,30 +1826,6 @@ name = "Supporting Files"; sourceTree = ""; }; - C13BD64021403362006D7F19 /* MessageTransport */ = { - isa = PBXGroup; - children = ( - C1FFAFA0213323E800C50C1D /* CRC8.swift */, - C1FFAF9E213323E700C50C1D /* CRC16.swift */, - C1FFAFB5213323E900C50C1D /* Packet.swift */, - C1FFAF9B213323E700C50C1D /* MessageTransport.swift */, - C1FFAF9C213323E700C50C1D /* Message.swift */, - C1FFAFA3213323E800C50C1D /* MessageBlocks */, - ); - path = MessageTransport; - sourceTree = ""; - }; - C13BD641214033B0006D7F19 /* Delivery */ = { - isa = PBXGroup; - children = ( - C1FFAF98213323E600C50C1D /* Pod.swift */, - C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */, - C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */, - C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */, - ); - path = Delivery; - sourceTree = ""; - }; C14FFC4D1D3D6D8E0049CF85 /* Models */ = { isa = PBXGroup; children = ( @@ -2299,105 +2060,6 @@ path = Messages; sourceTree = ""; }; - C1FFAF79213323CC00C50C1D /* OmniKit */ = { - isa = PBXGroup; - children = ( - C13BD641214033B0006D7F19 /* Delivery */, - C13BD64021403362006D7F19 /* MessageTransport */, - C1FFAFA1213323E800C50C1D /* Extensions */, - C1FFAF95213323E600C50C1D /* PumpManager */, - C1FFAF7A213323CC00C50C1D /* OmniKit.h */, - C1FFAF7B213323CC00C50C1D /* Info.plist */, - ); - path = OmniKit; - sourceTree = ""; - }; - C1FFAF86213323CC00C50C1D /* OmniKitTests */ = { - isa = PBXGroup; - children = ( - C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */, - C1FFAFF92133241600C50C1D /* CRC8Tests.swift */, - C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */, - C1FFAFF62133241500C50C1D /* MessageTests.swift */, - C1FFAFFD2133241600C50C1D /* OmniKitTests-Bridging-Header.h */, - C1FFAFFA2133241600C50C1D /* PacketTests.swift */, - C1FFAFF72133241500C50C1D /* PodStateTests.swift */, - C1FFAFF82133241500C50C1D /* TempBasalTests.swift */, - C1FFAF89213323CC00C50C1D /* Info.plist */, - ); - path = OmniKitTests; - sourceTree = ""; - }; - C1FFAF95213323E600C50C1D /* PumpManager */ = { - isa = PBXGroup; - children = ( - C1FFAFB4213323E800C50C1D /* PodComms.swift */, - C1FFAF99213323E600C50C1D /* PodCommsSession.swift */, - C1FFAF96213323E600C50C1D /* OmnipodPumpManagerState.swift */, - C1FFAF97213323E600C50C1D /* OmnipodPumpManager.swift */, - C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */, - C1FFAF9D213323E700C50C1D /* PodState.swift */, - ); - path = PumpManager; - sourceTree = ""; - }; - C1FFAFA1213323E800C50C1D /* Extensions */ = { - isa = PBXGroup; - children = ( - C1FFAFA2213323E800C50C1D /* Notification.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - C1FFAFA3213323E800C50C1D /* MessageBlocks */ = { - isa = PBXGroup; - children = ( - C1FFAFA4213323E800C50C1D /* VersionResponse.swift */, - C1FFAFA5213323E800C50C1D /* StatusError.swift */, - C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */, - C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */, - C1FFAFA8213323E800C50C1D /* MessageBlock.swift */, - C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */, - C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */, - C1FFAFAB213323E800C50C1D /* StatusResponse.swift */, - C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */, - C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */, - C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */, - C1FFAFAF213323E800C50C1D /* SetPodTimeCommand.swift */, - C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */, - C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */, - C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */, - C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */, - ); - path = MessageBlocks; - sourceTree = ""; - }; - C1FFAFDA213323F900C50C1D /* OmniKitUI */ = { - isa = PBXGroup; - children = ( - C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */, - C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */, - C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */, - C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */, - C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */, - C1FFB0092133242C00C50C1D /* Pairing */, - C1FFAFDB213323F900C50C1D /* OmniKitUI.h */, - C1FFAFDC213323F900C50C1D /* Info.plist */, - ); - path = OmniKitUI; - sourceTree = ""; - }; - C1FFB0092133242C00C50C1D /* Pairing */ = { - isa = PBXGroup; - children = ( - C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */, - C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */, - C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */, - C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */, - ); - path = Pairing; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2457,22 +2119,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C1FFAF75213323CC00C50C1D /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFAF8A213323CC00C50C1D /* OmniKit.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAFD6213323F900C50C1D /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFAFEB213323FA00C50C1D /* OmniKitUI.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -2666,8 +2312,6 @@ 431CE7831F98564200255374 /* PBXTargetDependency */, 43D5E7941FAF7BFB004ACDB7 /* PBXTargetDependency */, 4352A72B20DEC9B700CAC200 /* PBXTargetDependency */, - C1FFAF8C213323CC00C50C1D /* PBXTargetDependency */, - C1FFAFED213323FA00C50C1D /* PBXTargetDependency */, ); name = RileyLink; productName = RileyLink; @@ -2731,63 +2375,6 @@ productReference = C1B383141CD0665D00CE7782 /* NightscoutUploadKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - C1FFAF77213323CC00C50C1D /* OmniKit */ = { - isa = PBXNativeTarget; - buildConfigurationList = C1FFAF93213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKit" */; - buildPhases = ( - C1FFAF73213323CC00C50C1D /* Sources */, - C1FFAF74213323CC00C50C1D /* Frameworks */, - C1FFAF75213323CC00C50C1D /* Headers */, - C1FFAF76213323CC00C50C1D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C1FFB02021343B9700C50C1D /* PBXTargetDependency */, - ); - name = OmniKit; - productName = OmniKit; - productReference = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; - productType = "com.apple.product-type.framework"; - }; - C1FFAF7F213323CC00C50C1D /* OmniKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C1FFAF94213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitTests" */; - buildPhases = ( - C1FFAF7C213323CC00C50C1D /* Sources */, - C1FFAF7D213323CC00C50C1D /* Frameworks */, - C1FFAF7E213323CC00C50C1D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C1FFAF83213323CC00C50C1D /* PBXTargetDependency */, - C1FFAF85213323CC00C50C1D /* PBXTargetDependency */, - ); - name = OmniKitTests; - productName = OmniKitTests; - productReference = C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - C1FFAFD8213323F900C50C1D /* OmniKitUI */ = { - isa = PBXNativeTarget; - buildConfigurationList = C1FFAFF0213323FA00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitUI" */; - buildPhases = ( - C1FFAFD4213323F900C50C1D /* Sources */, - C1FFAFD5213323F900C50C1D /* Frameworks */, - C1FFAFD6213323F900C50C1D /* Headers */, - C1FFAFD7213323F900C50C1D /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - C1FFB02321343EB500C50C1D /* PBXTargetDependency */, - ); - name = OmniKitUI; - productName = OmniKitUI; - productReference = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; - productType = "com.apple.product-type.framework"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -2862,22 +2449,6 @@ CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1000; }; - C1FFAF77213323CC00C50C1D = { - CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; - ProvisioningStyle = Manual; - }; - C1FFAF7F213323CC00C50C1D = { - CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; - ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; - }; - C1FFAFD8213323F900C50C1D = { - CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; - ProvisioningStyle = Manual; - }; }; }; buildConfigurationList = C12EA232198B436800309FA4 /* Build configuration list for PBXProject "RileyLink" */; @@ -2916,9 +2487,6 @@ 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */, 4352A72420DEC9B700CAC200 /* MinimedKitUI */, 43FB610120DDEF26002B996B /* Cartfile */, - C1FFAF77213323CC00C50C1D /* OmniKit */, - C1FFAF7F213323CC00C50C1D /* OmniKitTests */, - C1FFAFD8213323F900C50C1D /* OmniKitUI */, ); }; /* End PBXProject section */ @@ -3039,29 +2607,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C1FFAF76213323CC00C50C1D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAF7E213323CC00C50C1D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAFD7213323F900C50C1D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */, - C1FFB00F2133242C00C50C1D /* OmniKitUI.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -3558,88 +3103,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C1FFAF73213323CC00C50C1D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFAFBA213323E900C50C1D /* BasalDeliveryTable.swift in Sources */, - C1FFAFC5213323E900C50C1D /* DeactivatePodCommand.swift in Sources */, - C1FFAFB8213323E900C50C1D /* Pod.swift in Sources */, - C1FFAFB7213323E900C50C1D /* OmnipodPumpManager.swift in Sources */, - C1FFAFBB213323E900C50C1D /* MessageTransport.swift in Sources */, - C1FFAFD0213323E900C50C1D /* SetInsulinScheduleCommand.swift in Sources */, - C1FFAFC4213323E900C50C1D /* TempBasalExtraCommand.swift in Sources */, - C1FFAFB9213323E900C50C1D /* PodCommsSession.swift in Sources */, - C1FFAFC8213323E900C50C1D /* BolusExtraCommand.swift in Sources */, - C1FFAFC7213323E900C50C1D /* PlaceholderMessageBlock.swift in Sources */, - C17C5C0F21447383002A06F8 /* NumberFormatter.swift in Sources */, - C1FFB01821332A2A00C50C1D /* OSLog.swift in Sources */, - C1FFAFC6213323E900C50C1D /* MessageBlock.swift in Sources */, - C1FFAFC2213323E900C50C1D /* VersionResponse.swift in Sources */, - C1FFB01D21332E1700C50C1D /* Data.swift in Sources */, - C1FFAFBD213323E900C50C1D /* PodState.swift in Sources */, - C1FFAFB6213323E900C50C1D /* OmnipodPumpManagerState.swift in Sources */, - C1FFAFC3213323E900C50C1D /* StatusError.swift in Sources */, - C1E163D72135FF9A00EB89AE /* TimeZone.swift in Sources */, - C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */, - C1FFAFD1213323E900C50C1D /* ConfigureAlertsCommand.swift in Sources */, - C1FFAFBE213323E900C50C1D /* CRC16.swift in Sources */, - C1FFAFCD213323E900C50C1D /* SetPodTimeCommand.swift in Sources */, - C1FFAFD2213323E900C50C1D /* PodComms.swift in Sources */, - C1FFB01921332A7100C50C1D /* IdentifiableClass.swift in Sources */, - C1FFAFC1213323E900C50C1D /* Notification.swift in Sources */, - C1FFAFC0213323E900C50C1D /* CRC8.swift in Sources */, - C1FFAFCB213323E900C50C1D /* BasalScheduleExtraCommand.swift in Sources */, - C1CB13A521383F1E00F9EEDA /* LocalizedString.swift in Sources */, - C1FFAFBF213323E900C50C1D /* BasalSchedule.swift in Sources */, - C1FFAFC9213323E900C50C1D /* StatusResponse.swift in Sources */, - C1FFAFCE213323E900C50C1D /* AssignAddressCommand.swift in Sources */, - C1FFAFCC213323E900C50C1D /* CancelDeliveryCommand.swift in Sources */, - C1FFAFCA213323E900C50C1D /* GetStatusCommand.swift in Sources */, - C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */, - C1FFAFD3213323E900C50C1D /* Packet.swift in Sources */, - C1FFAFBC213323E900C50C1D /* Message.swift in Sources */, - C1FFAFCF213323E900C50C1D /* ErrorResponse.swift in Sources */, - C1FFB01721332A1F00C50C1D /* TimeInterval.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAF7C213323CC00C50C1D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C1FFB0032133241700C50C1D /* BasalScheduleTests.swift in Sources */, - C1FFAFFE2133241700C50C1D /* MessageTests.swift in Sources */, - C1FFAFFF2133241700C50C1D /* PodStateTests.swift in Sources */, - C1FFB0022133241700C50C1D /* PacketTests.swift in Sources */, - C17EDC4F2134D0CC0031D9F0 /* TimeZone.swift in Sources */, - C1FFB0042133241700C50C1D /* CRC16Tests.swift in Sources */, - C1FFB0012133241700C50C1D /* CRC8Tests.swift in Sources */, - C1FFB0002133241700C50C1D /* TempBasalTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1FFAFD4213323F900C50C1D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C1F1A5EE2151FBA700F0B820 /* PodSetupCompleteViewController.swift in Sources */, - C1FFB01B21332DD900C50C1D /* TimeZone.swift in Sources */, - C1CB13A72138453B00F9EEDA /* NumberFormatter.swift in Sources */, - C1FFB02121343E6D00C50C1D /* TimeInterval.swift in Sources */, - C1E163D62135ED0300EB89AE /* LocalizedString.swift in Sources */, - C1FFB0132133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift in Sources */, - C1F1A5EC2151F73F00F0B820 /* InsertCannulaSetupViewController.swift in Sources */, - C1FFB01A21332AFE00C50C1D /* IdentifiableClass.swift in Sources */, - C1FFB0102133242C00C50C1D /* OmnipodSettingsViewController.swift in Sources */, - C1F1A5EA215164FA00F0B820 /* PairPodSetupViewController.swift in Sources */, - C1FFB01E2133860E00C50C1D /* UIColor.swift in Sources */, - C1FFB01C21332E0900C50C1D /* UITableViewCell.swift in Sources */, - C1FFB00E2133242C00C50C1D /* OmniPodPumpManager+UI.swift in Sources */, - C1FC49EE2135DD56007D0788 /* CommandResponseViewController.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -3753,36 +3216,6 @@ target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; - C1FFAF83213323CC00C50C1D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C1FFAF77213323CC00C50C1D /* OmniKit */; - targetProxy = C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */; - }; - C1FFAF85213323CC00C50C1D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1FFAF84213323CC00C50C1D /* PBXContainerItemProxy */; - }; - C1FFAF8C213323CC00C50C1D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C1FFAF77213323CC00C50C1D /* OmniKit */; - targetProxy = C1FFAF8B213323CC00C50C1D /* PBXContainerItemProxy */; - }; - C1FFAFED213323FA00C50C1D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C1FFAFD8213323F900C50C1D /* OmniKitUI */; - targetProxy = C1FFAFEC213323FA00C50C1D /* PBXContainerItemProxy */; - }; - C1FFB02021343B9700C50C1D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = C1FFB01F21343B9700C50C1D /* PBXContainerItemProxy */; - }; - C1FFB02321343EB500C50C1D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = C1FFB02221343EB500C50C1D /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -5055,231 +4488,6 @@ }; name = Release; }; - C1FFAF8F213323CC00C50C1D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 44; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - C1FFAF90213323CC00C50C1D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 44; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - C1FFAF91213323CC00C50C1D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Debug; - }; - C1FFAF92213323CC00C50C1D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Release; - }; - C1FFAFF1213323FA00C50C1D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 44; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitUI/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitUI; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - C1FFAFF2213323FA00C50C1D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 44; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 44; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitUI/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitUI; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -5418,33 +4626,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C1FFAF93213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKit" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C1FFAF8F213323CC00C50C1D /* Debug */, - C1FFAF90213323CC00C50C1D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C1FFAF94213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C1FFAF91213323CC00C50C1D /* Debug */, - C1FFAF92213323CC00C50C1D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C1FFAFF0213323FA00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitUI" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C1FFAFF1213323FA00C50C1D /* Debug */, - C1FFAFF2213323FA00C50C1D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = C12EA22F198B436800309FA4 /* Project object */; diff --git a/RileyLink/PumpManagerState.swift b/RileyLink/PumpManagerState.swift index 8a7e758a4..52f69c53e 100644 --- a/RileyLink/PumpManagerState.swift +++ b/RileyLink/PumpManagerState.swift @@ -9,8 +9,6 @@ import Foundation import LoopKit import MinimedKit import RileyLinkBLEKit -import OmniKit - let allPumpManagers: [String: PumpManager.Type] = [ MinimedPumpManager.managerIdentifier: MinimedPumpManager.self @@ -29,11 +27,6 @@ func PumpManagerFromRawValue(_ rawValue: [String: Any], rileyLinkDeviceProvider: return nil } return MinimedPumpManager(state: state, rileyLinkDeviceProvider: rileyLinkDeviceProvider) - case OmnipodPumpManager.managerIdentifier: - guard let state = OmnipodPumpManagerState(rawValue: rawState) else { - return nil - } - return OmnipodPumpManager(state: state, rileyLinkDeviceProvider: rileyLinkDeviceProvider) default: return nil } diff --git a/RileyLink/View Controllers/MainViewController.swift b/RileyLink/View Controllers/MainViewController.swift index 11d11fbdc..7e09a220c 100644 --- a/RileyLink/View Controllers/MainViewController.swift +++ b/RileyLink/View Controllers/MainViewController.swift @@ -13,7 +13,6 @@ import RileyLinkKit import RileyLinkKitUI import LoopKit import LoopKitUI -import OmniKitUI class MainViewController: RileyLinkSettingsViewController { @@ -75,7 +74,6 @@ class MainViewController: RileyLinkSettingsViewController { fileprivate enum PumpActionRow: Int, CaseCountable { case addMinimedPump = 0 - case setupOmnipod } weak var rileyLinkManager: RileyLinkDeviceManager! @@ -130,12 +128,6 @@ class MainViewController: RileyLinkSettingsViewController { textButtonCell?.isEnabled = shouldAllowAddingPump textButtonCell?.isUserInteractionEnabled = shouldAllowAddingPump cell.textLabel?.text = NSLocalizedString("Add Minimed Pump", comment: "Title text for button to set up a new minimed pump") - case .setupOmnipod: - cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) - let textButtonCell = cell as? TextButtonTableViewCell - textButtonCell?.isEnabled = shouldAllowAddingPump - textButtonCell?.isUserInteractionEnabled = shouldAllowAddingPump - cell.textLabel?.text = NSLocalizedString("Setup Omnipod", comment: "Title text for button to set up omnipod") } } } @@ -182,8 +174,6 @@ class MainViewController: RileyLinkSettingsViewController { switch PumpActionRow(rawValue: indexPath.row)! { case .addMinimedPump: setupViewController = UIStoryboard(name: "MinimedPumpManager", bundle: Bundle(for: MinimedPumpManagerSetupViewController.self)).instantiateViewController(withIdentifier: "DevelopmentPumpSetup") as! MinimedPumpManagerSetupViewController - case .setupOmnipod: - setupViewController = UIStoryboard(name: "OmnipodPumpManager", bundle: Bundle(for: OmnipodPumpManagerSetupViewController.self)).instantiateViewController(withIdentifier: "DevelopmentPumpSetup") as! OmnipodPumpManagerSetupViewController } if let rileyLinkManagerViewController = setupViewController as? RileyLinkManagerSetupViewController { rileyLinkManagerViewController.rileyLinkPumpManager = RileyLinkPumpManager(rileyLinkDeviceProvider: deviceDataManager.rileyLinkConnectionManager.deviceProvider) From 46ed2c845e7c4b88c721994d8e8432e86be4c699 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 18 Feb 2019 21:55:28 -0600 Subject: [PATCH 02/71] MinimedPumpManager changes for PumpManager protocol updates (#494) * MinimedPumpManager changes for PumpManager protocol updates * devicesSectionIndex is not changed after initialization * Bump LoopKit rev * Fix handling settings completion * Don't push root viewcontroller * Wrap unsuspend for bolus errors in SetBolusError.certain * Allow PumpState mock creation * Create suspend resume cell via cell reuse * Remove unused argument * Use higher res delivery for x23 pumps for roundToDeliveryIncrement * Guard access to MinimedPumpManagerState * Code organization * dequeue SuspendResumeTableViewCell * Remove unneeded conditional let * Fix typo * Make some properties immutable on MinimedPumpManagerState * Add notes about thread issues * Include dose in enactBolus completion * Dismiss settings controller on minimed pumpmanager delete * Bump LoopKit rev * Setup controller is presented modally * x23 pumps speed up bolus delivery to fit in 5 minute window * present instead of show settings vc * Remove zero bolus error; use assertionFailure instead * Fix vc present() call * ReservoirVolumeHUDView interface changed * Use active flag instead of appear/disapper methods * Bump loopkit rev * HUDProvider active -> visible * Add Comparable.clamped(to:) method * Updates from @mpangburn's review * Remove another unnecessary optional let * Bump LoopKit rev * use dev LoopKit --- Cartfile | 2 +- Cartfile.resolved | 2 +- Common/Comparable.swift | 21 + .../GetPumpFirmwareVersionMessageBody.swift | 27 ++ MinimedKit/Messages/MessageType.swift | 5 + .../Messages/Models/BasalSchedule.swift | 1 - .../Messages/SuspendResumeMessageBody.swift | 25 ++ MinimedKit/Models/PumpModel.swift | 12 + .../PumpManager/MinimedPumpManager.swift | 422 +++++++++++------- .../PumpManager/MinimedPumpManagerState.swift | 66 ++- .../PumpManager/PumpOpsSession+LoopKit.swift | 1 - MinimedKit/PumpManager/PumpState.swift | 5 + MinimedKit/PumpManager/ReservoirReading.swift | 52 +++ .../Base.lproj/MinimedPumpManager.storyboard | 10 +- MinimedKitUI/MinimedHUDProvider.swift | 131 ++++++ MinimedKitUI/MinimedPumpManager+UI.swift | 18 +- .../MinimedPumpSettingsViewController.swift | 125 +++++- .../MinimedPumpIDSetupViewController.swift | 21 +- ...inimedPumpManagerSetupViewController.swift | 24 +- ...MinimedPumpSentrySetupViewController.swift | 2 +- ...nimedPumpSettingsSetupViewController.swift | 21 +- ...nimedPumpSetupCompleteViewController.swift | 4 +- RileyLink.xcodeproj/project.pbxproj | 26 +- .../xcshareddata/xcschemes/Crypto.xcscheme | 2 +- .../xcschemes/MinimedKit.xcscheme | 2 +- .../xcschemes/MinimedKitUI.xcscheme | 2 +- .../xcschemes/NightscoutUploadKit.xcscheme | 2 +- .../xcshareddata/xcschemes/RileyLink.xcscheme | 2 +- .../xcschemes/RileyLinkBLEKit.xcscheme | 2 +- .../xcschemes/RileyLinkKit.xcscheme | 2 +- .../xcschemes/RileyLinkKitUI.xcscheme | 2 +- RileyLink/DeviceDataManager.swift | 7 +- .../View Controllers/MainViewController.swift | 21 +- RileyLinkBLEKit/PeripheralManagerError.swift | 4 +- RileyLinkBLEKit/RFPacket.swift | 2 +- RileyLinkBLEKit/RileyLinkDevice.swift | 18 +- RileyLinkKit/PumpOpsSession.swift | 29 +- RileyLinkKit/RileyLinkPumpManager.swift | 1 + .../RileyLinkManagerSetupViewController.swift | 8 +- .../RileyLinkSetupTableViewController.swift | 5 + 40 files changed, 877 insertions(+), 257 deletions(-) create mode 100644 Common/Comparable.swift create mode 100644 MinimedKit/Messages/GetPumpFirmwareVersionMessageBody.swift create mode 100644 MinimedKit/Messages/SuspendResumeMessageBody.swift create mode 100644 MinimedKit/PumpManager/ReservoirReading.swift create mode 100644 MinimedKitUI/MinimedHUDProvider.swift diff --git a/Cartfile b/Cartfile index 5abe0d1bc..05195c51b 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "LoopKit/LoopKit" ~> 2.0 +github "LoopKit/LoopKit" "dev" diff --git a/Cartfile.resolved b/Cartfile.resolved index ea5e0dd01..d43c89f95 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "LoopKit/LoopKit" "v2.2.1" +github "LoopKit/LoopKit" "bf4166bc77f89e22971f2030ad006967881ff082" diff --git a/Common/Comparable.swift b/Common/Comparable.swift new file mode 100644 index 000000000..70e8121ad --- /dev/null +++ b/Common/Comparable.swift @@ -0,0 +1,21 @@ +// +// Comparable.swift +// RileyLink +// +// Created by Pete Schwamb on 2/17/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation + +extension Comparable { + func clamped(to range: ClosedRange) -> Self { + if self < range.lowerBound { + return range.lowerBound + } else if self > range.upperBound { + return range.upperBound + } else { + return self + } + } +} diff --git a/MinimedKit/Messages/GetPumpFirmwareVersionMessageBody.swift b/MinimedKit/Messages/GetPumpFirmwareVersionMessageBody.swift new file mode 100644 index 000000000..2abb0c63a --- /dev/null +++ b/MinimedKit/Messages/GetPumpFirmwareVersionMessageBody.swift @@ -0,0 +1,27 @@ +// +// GetPumpFirmwareVersionMessageBody.swift +// MinimedKit +// +// Created by Pete Schwamb on 10/10/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public class GetPumpFirmwareVersionMessageBody: CarelinkLongMessageBody { + public let version: String + + public required init?(rxData: Data) { + let stringEnd = rxData.firstIndex(of: 0) ?? rxData.count + guard rxData.count == type(of: self).length, + let vsn = String(data: rxData.subdata(in: 1..= 23 + } + + public var pulsesPerUnit: Int { + if generation >= 23 { + return 40 + } else { + return 20 + } + } + /// Even though this is capped by the system at 250 / 10 U, the message takes a UInt16. var usesTwoBytesForMaxBolus: Bool { return generation >= 23 diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 4039a36c4..02b979725 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -11,13 +11,39 @@ import RileyLinkKit import RileyLinkBLEKit import os.log +public protocol MinimedPumpManagerStateObserver: class { + func didUpdatePumpManagerState(_ state: MinimedPumpManagerState) +} public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { + public static let managerIdentifier: String = "Minimed500" + + public func roundToDeliveryIncrement(units: Double) -> Double { + return round(units * Double(state.pumpModel.pulsesPerUnit)) / Double(state.pumpModel.pulsesPerUnit) + } + + /* + It takes a MM pump about 40s to deliver 1 Unit while bolusing + See: http://www.healthline.com/diabetesmine/ask-dmine-speed-insulin-pumps#3 + */ + private static let deliveryUnitsPerMinute = 1.5 public init(state: MinimedPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil, pumpOps: PumpOps? = nil) { - self.state = state - + self.lockedState = Locked(state) + self.bolusState = .none + + self.device = HKDevice( + name: type(of: self).managerIdentifier, + manufacturer: "Medtronic", + model: state.pumpModel.rawValue, + hardwareVersion: nil, + firmwareVersion: state.pumpFirmwareVersion, + softwareVersion: String(MinimedKitVersionNumber), + localIdentifier: state.pumpID, + udiDeviceIdentifier: nil + ) + super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager) // Pump communication @@ -45,12 +71,35 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return state.rawValue } - // TODO: apply lock - public private(set) var state: MinimedPumpManagerState { - didSet { + // TODO: Accessed (various queues) and set (main) on different threads + public weak var stateObserver: MinimedPumpManagerStateObserver? + + private(set) public var state: MinimedPumpManagerState { + get { + return lockedState.value + } + set { + let oldValue = lockedState.value + lockedState.value = newValue + + if oldValue.timeZone != newValue.timeZone || + oldValue.batteryPercentage != newValue.batteryPercentage { + self.notifyStatusObservers() + } + pumpManagerDelegate?.pumpManagerDidUpdateState(self) + stateObserver?.didUpdatePumpManagerState(newValue) } } + private let lockedState: Locked + + // TODO: Accessed and set on different threads + private var basalDeliveryStateTransitioning: Bool = false { + didSet { + notifyStatusObservers() + } + } + override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { get { @@ -60,6 +109,28 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { state.rileyLinkConnectionManagerState = newValue } } + + private var statusObservers = WeakSet() + + public func addStatusObserver(_ observer: PumpManagerStatusObserver) { + queue.async { + self.statusObservers.insert(observer) + } + } + + public func removeStatusObserver(_ observer: PumpManagerStatusObserver) { + queue.async { + self.statusObservers.remove(observer) + } + } + + private func notifyStatusObservers() { + let status = self.status + pumpManagerDelegate?.pumpManager(self, didUpdate: status) + for observer in statusObservers { + observer.pumpManager(self, didUpdate: status) + } + } public weak var cgmManagerDelegate: CGMManagerDelegate? @@ -70,6 +141,8 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { // MARK: - CGMManager public private(set) var sensorState: SensorDisplayable? + + public var device: HKDevice? // MARK: - Pump data @@ -79,36 +152,59 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { if let sensorState = latestPumpStatusFromMySentry { self.sensorState = sensorState } + + notifyStatusObservers() + storeBatteryPercentage() } } - + // TODO: Isolate to queue - private var latestPumpStatus: PumpStatus? + private var latestPumpStatus: PumpStatus? { + didSet { + notifyStatusObservers() + storeBatteryPercentage() + } + } // TODO: Isolate to queue private var lastAddedPumpEvents: Date = .distantPast - // Battery monitor - private func observeBatteryDuring(_ block: () -> Void) { - let oldVal = pumpBatteryChargeRemaining - block() - pumpManagerDelegate?.pumpManagerDidUpdatePumpBatteryChargeRemaining(self, oldValue: oldVal) - } - - // MARK: - PumpManager - // TODO: Isolate to queue - // Returns a value in the range 0 - 1 - public var pumpBatteryChargeRemaining: Double? { + private func storeBatteryPercentage() { if let status = latestPumpStatusFromMySentry { - return Double(status.batteryRemainingPercent) / 100 + state.batteryPercentage = Double(status.batteryRemainingPercent) / 100 } else if let status = latestPumpStatus { - return batteryChemistry.chargeRemaining(at: status.batteryVolts) + state.batteryPercentage = batteryChemistry.chargeRemaining(at: status.batteryVolts) } else { - return nil + state.batteryPercentage = nil + } + } + + // MARK: - PumpManager + + private var basalDeliveryState: PumpManagerStatus.BasalDeliveryState { + if basalDeliveryStateTransitioning { + return state.isPumpSuspended ? .resuming : .suspending + } else { + return state.isPumpSuspended ? .suspended : .active } } + private var bolusState: PumpManagerStatus.BolusState { + didSet { + notifyStatusObservers() + } + } + + public var status: PumpManagerStatus { + return PumpManagerStatus( + timeZone: state.timeZone, + device: device!, + pumpBatteryChargeRemaining: state.batteryPercentage, + basalDeliveryState: basalDeliveryState, + bolusState: bolusState) + } + public func updateBLEHeartbeatPreference() { queue.async { /// Controls the management of the RileyLink timer tick, which is a reliably-changing BLE @@ -136,6 +232,49 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { public var localizedTitle: String { return String(format: LocalizedString("Minimed %@", comment: "Pump title (1: model number)"), state.pumpModel.rawValue) } + + public func suspendDelivery(completion: @escaping (Error?) -> Void) { + setSuspendResumeState(state: .suspend, completion: completion) + } + + public func resumeDelivery(completion: @escaping (Error?) -> Void) { + setSuspendResumeState(state: .resume, completion: completion) + } + + private func setSuspendResumeState(state: SuspendResumeMessageBody.SuspendResumeState, completion: @escaping (Error?) -> Void) { + rileyLinkDeviceProvider.getDevices { (devices) in + guard let device = devices.firstConnected else { + completion(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)) + return + } + + let sessionName: String = { + switch state { + case .suspend: + return "Suspend Delivery" + case .resume: + return "Resume Delivery" + } + }() + + self.pumpOps.runSession(withName: sessionName, using: device) { (session) in + do { + + defer { self.basalDeliveryStateTransitioning = false } + self.basalDeliveryStateTransitioning = true + + try session.setSuspendResumeState(state) + self.state.isPumpSuspended = state == .suspend + completion(nil) + } catch let error { + self.troubleshootPumpComms(using: device) + completion(PumpManagerError.communication(error as? LocalizedError)) + } + } + } + } + + // MARK: - RileyLink Updates @@ -171,10 +310,9 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return [ "## MinimedPumpManager", "isPumpDataStale: \(isPumpDataStale)", - "latestPumpStatus: \(String(describing: latestPumpStatus))", + "status: \(String(describing: status))", "latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry))", "lastAddedPumpEvents: \(lastAddedPumpEvents)", - "pumpBatteryChargeRemaining: \(String(reflecting: pumpBatteryChargeRemaining))", "state: \(String(reflecting: state))", "sensorState: \(String(describing: sensorState))", "", @@ -249,54 +387,32 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { log.error("Ignored MySentry status due to date mismatch: %{public}@ in %{public}", String(describing: pumpDate), String(describing: timeZone)) return } - - observeBatteryDuring { - latestPumpStatusFromMySentry = status - } - - device.getStatus { (deviceStatus) in - // Trigger device status upload, even if something is wrong with pumpStatus - self.queue.async { - - let pumpManagerStatus = PumpManagerStatus( - date: pumpDate, - timeZone: timeZone, - device: deviceStatus.device(pumpID: self.state.pumpID, pumpModel: self.state.pumpModel), - lastValidFrequency: self.state.lastValidFrequency, - lastTuned: self.state.lastTuned, - battery: PumpManagerStatus.BatteryStatus(percent: Double(status.batteryRemainingPercent) / 100), - isSuspended: nil, - isBolusing: nil, - remainingReservoir: HKQuantity(unit: .internationalUnit(), doubleValue: status.reservoirRemainingUnits) + + latestPumpStatusFromMySentry = status + + switch status.glucose { + case .active(glucose: let glucose): + // Enlite data is included + if let date = glucoseDateComponents?.date { + let sample = NewGlucoseSample( + date: date, + quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: Double(glucose)), + isDisplayOnly: false, + syncIdentifier: status.glucoseSyncIdentifier ?? UUID().uuidString, + device: self.device ) - self.pumpManagerDelegate?.pumpManager(self, didUpdateStatus: pumpManagerStatus) - - switch status.glucose { - case .active(glucose: let glucose): - // Enlite data is included - if let date = glucoseDateComponents?.date { - let sample = NewGlucoseSample( - date: date, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: Double(glucose)), - isDisplayOnly: false, - syncIdentifier: status.glucoseSyncIdentifier ?? UUID().uuidString, - device: deviceStatus.device(pumpID: self.state.pumpID, pumpModel: self.state.pumpModel) - ) - - self.cgmManagerDelegate?.cgmManager(self, didUpdateWith: .newData([sample])) - } - case .off: - // Enlite is disabled, so assert glucose from another source - self.pumpManagerDelegate?.pumpManagerBLEHeartbeatDidFire(self) - default: - // Anything else is an Enlite error - // TODO: Provide info about status.glucose - self.cgmManagerDelegate?.cgmManager(self, didUpdateWith: .error(PumpManagerError.deviceState(nil))) - } + self.cgmManagerDelegate?.cgmManager(self, didUpdateWith: .newData([sample])) } + case .off: + // Enlite is disabled, so assert glucose from another source + self.pumpManagerDelegate?.pumpManagerBLEHeartbeatDidFire(self) + default: + // Anything else is an Enlite error + // TODO: Provide info about status.glucose + self.cgmManagerDelegate?.cgmManager(self, didUpdateWith: .error(PumpManagerError.deviceState(nil))) } - + // Sentry packets are sent in groups of 3, 5s apart. Wait 11s before allowing the loop data to continue to avoid conflicting comms. queue.asyncAfter(deadline: .now() + .seconds(11)) { self.updateReservoirVolume(status.reservoirRemainingUnits, at: pumpDate, withTimeLeft: TimeInterval(minutes: Double(status.reservoirRemainingMinutes))) @@ -311,6 +427,9 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { - parameter timeLeft: The approximate time before the reservoir is empty */ private func updateReservoirVolume(_ units: Double, at date: Date, withTimeLeft timeLeft: TimeInterval?) { + + self.state.lastReservoirReading = ReservoirReading(units: units, validAt: date) + pumpManagerDelegate?.pumpManager(self, didReadReservoirValue: units, at: date) { (result) in /// TODO: Isolate to queue @@ -401,93 +520,65 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { /** Ensures pump data is current by either waking and polling, or ensuring we're listening to sentry packets. */ - /// TODO: Isolate to queue public func assertCurrentPumpData() { - rileyLinkDeviceProvider.assertIdleListening(forcingRestart: true) - - guard isPumpDataStale else { - return - } - - self.log.default("Pump data is stale, fetching.") - - rileyLinkDeviceProvider.getDevices { (devices) in - guard let device = devices.firstConnected else { - let error = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink) - self.log.error("No devices found while fetching pump data") - self.pumpManagerDelegate?.pumpManager(self, didError: error) + queue.async { + self.rileyLinkDeviceProvider.assertIdleListening(forcingRestart: true) + + guard self.isPumpDataStale else { return } - - self.pumpOps.runSession(withName: "Get Pump Status", using: device) { (session) in - do { - let status = try session.getCurrentPumpStatus() - guard var date = status.clock.date else { - assertionFailure("Could not interpret a valid date from \(status.clock) in the system calendar") - throw PumpManagerError.configuration(MinimedPumpManagerError.noDate) - } - - // Check if the clock should be reset - if abs(date.timeIntervalSinceNow) > .seconds(20) { - self.log.error("Pump clock is more than 20 seconds off. Resetting.") - self.pumpManagerDelegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) - try session.setTimeToNow() - - guard let newDate = try session.getTime().date else { + + self.log.default("Pump data is stale, fetching.") + + self.rileyLinkDeviceProvider.getDevices { (devices) in + guard let device = devices.firstConnected else { + let error = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink) + self.log.error("No devices found while fetching pump data") + self.pumpManagerDelegate?.pumpManager(self, didError: error) + return + } + + self.pumpOps.runSession(withName: "Get Pump Status", using: device) { (session) in + do { + let status = try session.getCurrentPumpStatus() + guard var date = status.clock.date else { + assertionFailure("Could not interpret a valid date from \(status.clock) in the system calendar") throw PumpManagerError.configuration(MinimedPumpManagerError.noDate) } - - date = newDate - } - - self.observeBatteryDuring { - self.latestPumpStatus = status - } - - self.updateReservoirVolume(status.reservoir, at: date, withTimeLeft: nil) - - device.getStatus { (deviceStatus) in - self.queue.async { - let pumpManagerStatus = PumpManagerStatus( - date: date, - timeZone: session.pump.timeZone, - device: deviceStatus.device(pumpID: self.state.pumpID, pumpModel: self.state.pumpModel), - lastValidFrequency: self.state.lastValidFrequency, - lastTuned: self.state.lastTuned, - battery: PumpManagerStatus.BatteryStatus( - voltage: status.batteryVolts, - state: { - switch status.batteryStatus { - case .normal: - return .normal - case .low: - return .low - case .unknown: - return nil - } - }() - ), - isSuspended: status.suspended, - isBolusing: status.bolusing, - remainingReservoir: HKQuantity(unit: .internationalUnit(), doubleValue: status.reservoir) - ) - - self.pumpManagerDelegate?.pumpManager(self, didUpdateStatus: pumpManagerStatus) + + // Check if the clock should be reset + if abs(date.timeIntervalSinceNow) > .seconds(20) { + self.log.error("Pump clock is more than 20 seconds off. Resetting.") + self.pumpManagerDelegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) + try session.setTimeToNow() + + guard let newDate = try session.getTime().date else { + throw PumpManagerError.configuration(MinimedPumpManagerError.noDate) + } + + date = newDate } + + self.state.isPumpSuspended = status.suspended + + self.latestPumpStatus = status + + self.updateReservoirVolume(status.reservoir, at: date, withTimeLeft: nil) + + } catch let error { + self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + self.pumpManagerDelegate?.pumpManager(self, didError: PumpManagerError.communication(error as? LocalizedError)) + self.troubleshootPumpComms(using: device) } - } catch let error { - self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) - self.pumpManagerDelegate?.pumpManager(self, didError: PumpManagerError.communication(error as? LocalizedError)) - self.troubleshootPumpComms(using: device) } } } } // TODO: Isolate to queue - public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (_ units: Double, _ date: Date) -> Void, completion: @escaping (_ error: Error?) -> Void) { + public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (_ dose: DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { guard units > 0 else { - completion(nil) + assertionFailure("Invalid zero unit bolus") return } @@ -496,7 +587,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { pumpOps.runSession(withName: "Bolus", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in guard let session = session else { - completion(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)) + completion(.failure(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink))) return } @@ -509,30 +600,60 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } catch let error as PumpOpsError { self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) - completion(SetBolusError.certain(error)) + completion(.failure(SetBolusError.certain(error))) return } catch let error as PumpCommandError { self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) switch error { case .arguments(let error): - completion(SetBolusError.certain(error)) + completion(.failure(SetBolusError.certain(error))) case .command(let error): - completion(SetBolusError.certain(error)) + completion(.failure(SetBolusError.certain(error))) } return } catch let error { - completion(error) + completion(.failure(error)) return } } do { - willRequest(units, Date()) + if self.state.isPumpSuspended { + do { + try session.setSuspendResumeState(.resume) + } catch let error as PumpOpsError { + self.log.error("Failed to resume pump for bolus: %{public}@", String(describing: error)) + completion(.failure(SetBolusError.certain(error))) + return + } catch let error as PumpCommandError { + self.log.error("Failed to resume pump for bolus: %{public}@", String(describing: error)) + switch error { + case .arguments(let error): + completion(.failure(SetBolusError.certain(error))) + case .command(let error): + completion(.failure(SetBolusError.certain(error))) + } + return + } catch let error { + completion(.failure(error)) + return + } + } + + let date = Date() + var deliveryTime = TimeInterval(minutes: units / MinimedPumpManager.deliveryUnitsPerMinute) + if self.state.pumpModel.constrainsBolusDeliveryTimeTo5Minutes { + deliveryTime = min(TimeInterval(minutes: 5), deliveryTime) + } + let endDate = date.addingTimeInterval(deliveryTime) + let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: units, unit: .units) + willRequest(dose) + try session.setNormalBolus(units: units) - completion(nil) + completion(.success(dose)) } catch let error { self.log.error("Failed to bolus: %{public}@", String(describing: error)) - completion(error) + completion(.failure(error)) } } } @@ -621,6 +742,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { state.batteryChemistry = newValue } } + } @@ -644,10 +766,6 @@ extension MinimedPumpManager: CGMManager { return nil } - public var device: HKDevice? { - return nil - } - public func fetchNewDataIfNeeded(_ completion: @escaping (CGMResult) -> Void) { rileyLinkDeviceProvider.getDevices { (devices) in guard let device = devices.firstConnected else { diff --git a/MinimedKit/PumpManager/MinimedPumpManagerState.swift b/MinimedKit/PumpManager/MinimedPumpManagerState.swift index dfeb8222f..84434393c 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerState.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerState.swift @@ -9,7 +9,6 @@ import LoopKit import RileyLinkKit import RileyLinkBLEKit - public struct MinimedPumpManagerState: RawRepresentable, Equatable { public typealias RawValue = PumpManager.RawStateValue @@ -19,26 +18,30 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public var preferredInsulinDataSource: InsulinDataSource - public var pumpColor: PumpColor + public let pumpColor: PumpColor - public var pumpModel: PumpModel + public let pumpModel: PumpModel + + public let pumpFirmwareVersion: String - public var pumpID: String + public let pumpID: String - public var pumpRegion: PumpRegion + public let pumpRegion: PumpRegion + public var isPumpSuspended: Bool + public var lastValidFrequency: Measurement? public var lastTuned: Date? + + public var batteryPercentage: Double? + public var lastReservoirReading: ReservoirReading? + public var pumpSettings: PumpSettings { get { return PumpSettings(pumpID: pumpID, pumpRegion: pumpRegion) } - set { - pumpID = newValue.pumpID - pumpRegion = newValue.pumpRegion - } } public var pumpState: PumpState { @@ -51,9 +54,6 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { return state } set { - if let model = newValue.pumpModel { - pumpModel = model - } lastValidFrequency = newValue.lastValidFrequency lastTuned = newValue.lastTuned timeZone = newValue.timeZone @@ -64,16 +64,20 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public var timeZone: TimeZone - public init(batteryChemistry: BatteryChemistryType = .alkaline, preferredInsulinDataSource: InsulinDataSource = .pumpHistory, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, lastValidFrequency: Measurement? = nil) { + public init(batteryChemistry: BatteryChemistryType = .alkaline, preferredInsulinDataSource: InsulinDataSource = .pumpHistory, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, lastValidFrequency: Measurement? = nil, isPumpSuspended: Bool = false, batteryPercentage: Double? = nil, lastReservoirReading: ReservoirReading? = nil) { self.batteryChemistry = batteryChemistry self.preferredInsulinDataSource = preferredInsulinDataSource self.pumpColor = pumpColor self.pumpID = pumpID self.pumpModel = pumpModel + self.pumpFirmwareVersion = pumpFirmwareVersion self.pumpRegion = pumpRegion self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState self.timeZone = timeZone + self.isPumpSuspended = isPumpSuspended self.lastValidFrequency = lastValidFrequency + self.batteryPercentage = batteryPercentage + self.lastReservoirReading = lastReservoirReading } public init?(rawValue: RawValue) { @@ -112,6 +116,8 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(rawValue: rawState) } } + + let isPumpSuspended = (rawValue["isPumpSuspended"] as? Bool) ?? false let lastValidFrequency: Measurement? if let frequencyRaw = rawValue["lastValidFrequency"] as? Double { @@ -119,17 +125,31 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { } else { lastValidFrequency = nil } - + + let pumpFirmwareVersion = (rawValue["pumpFirmwareVersion"] as? String) ?? "" + let batteryPercentage = rawValue["batteryPercentage"] as? Double + + let lastReservoirReading: ReservoirReading? + if let rawLastReservoirReading = rawValue["lastReservoirReading"] as? ReservoirReading.RawValue { + lastReservoirReading = ReservoirReading(rawValue: rawLastReservoirReading) + } else { + lastReservoirReading = nil + } + self.init( batteryChemistry: batteryChemistry, preferredInsulinDataSource: insulinDataSource, pumpColor: pumpColor, pumpID: pumpID, pumpModel: pumpModel, + pumpFirmwareVersion: pumpFirmwareVersion, pumpRegion: pumpRegion, rileyLinkConnectionManagerState: rileyLinkConnectionManagerState, timeZone: timeZone, - lastValidFrequency: lastValidFrequency + lastValidFrequency: lastValidFrequency, + isPumpSuspended: isPumpSuspended, + batteryPercentage: batteryPercentage, + lastReservoirReading: lastReservoirReading ) } @@ -140,9 +160,10 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { "pumpColor": pumpColor.rawValue, "pumpID": pumpID, "pumpModel": pumpModel.rawValue, + "pumpFirmwareVersion": pumpFirmwareVersion, "pumpRegion": pumpRegion.rawValue, "timeZone": timeZone.secondsFromGMT(), - + "isPumpSuspended": isPumpSuspended, "version": MinimedPumpManagerState.version, ] @@ -153,6 +174,14 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { if let frequency = lastValidFrequency?.converted(to: .megahertz) { value["lastValidFrequency"] = frequency.value } + + if let batteryPercentage = batteryPercentage { + value["batteryPercentage"] = batteryPercentage + } + + if let lastReservoirReading = lastReservoirReading { + value["lastReservoirReading"] = lastReservoirReading.rawValue + } return value } @@ -173,9 +202,14 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible { "pumpColor: \(pumpColor)", "pumpID: ✔︎", "pumpModel: \(pumpModel.rawValue)", + "pumpFirmwareVersion: \(pumpFirmwareVersion)", "pumpRegion: \(pumpRegion)", "lastValidFrequency: \(String(describing: lastValidFrequency))", "timeZone: \(timeZone)", + "isPumpSuspended: \(isPumpSuspended)", + "batteryPercentage: \(String(describing: batteryPercentage))", + "reservoirUnits: \(String(describing: lastReservoirReading?.units))", + "reservoirValidAt: \(String(describing: lastReservoirReading?.validAt))", String(reflecting: rileyLinkConnectionManagerState), ].joined(separator: "\n") } diff --git a/MinimedKit/PumpManager/PumpOpsSession+LoopKit.swift b/MinimedKit/PumpManager/PumpOpsSession+LoopKit.swift index 00f5949b4..6dfad9807 100644 --- a/MinimedKit/PumpManager/PumpOpsSession+LoopKit.swift +++ b/MinimedKit/PumpManager/PumpOpsSession+LoopKit.swift @@ -18,7 +18,6 @@ extension PumpOpsSession { } - extension BasalSchedule { public init(repeatingScheduleValues: [LoopKit.RepeatingScheduleValue]) { self.init(entries: repeatingScheduleValues.enumerated().map({ (index, value) -> BasalScheduleEntry in diff --git a/MinimedKit/PumpManager/PumpState.swift b/MinimedKit/PumpManager/PumpState.swift index 48a90d340..3d17bb390 100644 --- a/MinimedKit/PumpManager/PumpState.swift +++ b/MinimedKit/PumpManager/PumpState.swift @@ -36,6 +36,11 @@ public struct PumpState: RawRepresentable, Equatable { self.timeZone = .currentFixed } + public init(timeZone: TimeZone, pumpModel: PumpModel) { + self.timeZone = timeZone + self.pumpModel = pumpModel + } + public init?(rawValue: RawValue) { guard let timeZoneSeconds = rawValue["timeZone"] as? Int, diff --git a/MinimedKit/PumpManager/ReservoirReading.swift b/MinimedKit/PumpManager/ReservoirReading.swift new file mode 100644 index 000000000..e25921126 --- /dev/null +++ b/MinimedKit/PumpManager/ReservoirReading.swift @@ -0,0 +1,52 @@ +// +// ReservoirReading.swift +// MinimedKit +// +// Created by Pete Schwamb on 2/4/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +public struct ReservoirReading: RawRepresentable, Equatable { + + public typealias RawValue = [String: Any] + + public let units: Double + public let validAt: Date + + public init(units: Double, validAt: Date) { + self.units = units + self.validAt = validAt + } + + public init?(rawValue: RawValue) { + guard + let units = rawValue["units"] as? Double, + let validAt = rawValue["validAt"] as? Date + else { + return nil + } + + self.units = units + self.validAt = validAt + } + + public var rawValue: RawValue { + return [ + "units": units, + "validAt": validAt + ] + } +} + +extension ReservoirReading: ReservoirValue { + public var startDate: Date { + return validAt + } + + public var unitVolume: Double { + return units + } +} diff --git a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard index 7acfa46f5..474f4db6d 100644 --- a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard +++ b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard @@ -1,13 +1,11 @@ - + - - - + @@ -344,7 +342,7 @@ - + @@ -597,7 +595,7 @@ - + diff --git a/MinimedKitUI/MinimedHUDProvider.swift b/MinimedKitUI/MinimedHUDProvider.swift new file mode 100644 index 000000000..135fc0a0f --- /dev/null +++ b/MinimedKitUI/MinimedHUDProvider.swift @@ -0,0 +1,131 @@ +// +// MinimedHUDProvider.swift +// MinimedKitUI +// +// Created by Pete Schwamb on 2/4/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit +import LoopKitUI +import MinimedKit + +class MinimedHUDProvider: HUDProvider, MinimedPumpManagerStateObserver { + + var managerIdentifier: String { + return MinimedPumpManager.managerIdentifier + } + + private var state: MinimedPumpManagerState { + didSet { + guard visible else { + return + } + + if oldValue.batteryPercentage != state.batteryPercentage { + self.updateBatteryView() + } + + if oldValue.lastReservoirReading != state.lastReservoirReading { + self.updateReservoirView() + } + } + } + + private let pumpManager: MinimedPumpManager + + public init(pumpManager: MinimedPumpManager) { + self.pumpManager = pumpManager + self.state = pumpManager.state + pumpManager.stateObserver = self + } + + var visible: Bool = false { + didSet { + if oldValue != visible && visible { + self.updateBatteryView() + self.updateReservoirView() + } + } + } + + private weak var reservoirView: ReservoirVolumeHUDView? + + private weak var batteryView: BatteryLevelHUDView? + + private func updateReservoirView() { + if let lastReservoirVolume = state.lastReservoirReading, + let reservoirView = reservoirView + { + let reservoirLevel = (lastReservoirVolume.units / pumpManager.pumpReservoirCapacity).clamped(to: 0...1.0) + reservoirView.level = reservoirLevel + reservoirView.setReservoirVolume(volume: lastReservoirVolume.units, at: lastReservoirVolume.validAt) + } + } + + private func updateBatteryView() { + if let batteryView = batteryView { + batteryView.batteryLevel = state.batteryPercentage + } + } + + public func createHUDViews() -> [BaseHUDView] { + + reservoirView = ReservoirVolumeHUDView.instantiate() + batteryView = BatteryLevelHUDView.instantiate() + + if visible { + updateReservoirView() + updateBatteryView() + } + + return [reservoirView, batteryView].compactMap { $0 } + } + + public func didTapOnHUDView(_ view: BaseHUDView) -> HUDTapAction? { + return nil + } + + public var hudViewsRawState: HUDProvider.HUDViewsRawState { + var rawValue: HUDProvider.HUDViewsRawState = [ + "pumpReservoirCapacity": pumpManager.pumpReservoirCapacity + ] + + rawValue["batteryPercentage"] = state.batteryPercentage + + if let lastReservoirReading = state.lastReservoirReading { + rawValue["lastReservoirReading"] = lastReservoirReading.rawValue + } + + return rawValue + } + + public static func createHUDViews(rawValue: HUDProvider.HUDViewsRawState) -> [BaseHUDView] { + guard let pumpReservoirCapacity = rawValue["pumpReservoirCapacity"] as? Double else { + return [] + } + + let batteryPercentage = rawValue["batteryPercentage"] as? Double + + let reservoirVolumeHUDView = ReservoirVolumeHUDView.instantiate() + if let rawLastReservoirReading = rawValue["lastReservoirReading"] as? ReservoirReading.RawValue, + let lastReservoirReading = ReservoirReading(rawValue: rawLastReservoirReading) + { + let reservoirLevel = (lastReservoirReading.units / pumpReservoirCapacity).clamped(to: 0...1.0) + reservoirVolumeHUDView.level = reservoirLevel + reservoirVolumeHUDView.setReservoirVolume(volume: lastReservoirReading.units, at: lastReservoirReading.validAt) + } + + let batteryLevelHUDView = BatteryLevelHUDView.instantiate() + batteryLevelHUDView.batteryLevel = batteryPercentage + + return [reservoirVolumeHUDView, batteryLevelHUDView] + } + + func didUpdatePumpManagerState(_ state: MinimedPumpManagerState) { + DispatchQueue.main.async { + self.state = state + } + } +} diff --git a/MinimedKitUI/MinimedPumpManager+UI.swift b/MinimedKitUI/MinimedPumpManager+UI.swift index 36485b663..a01849e5e 100644 --- a/MinimedKitUI/MinimedPumpManager+UI.swift +++ b/MinimedKitUI/MinimedPumpManager+UI.swift @@ -12,20 +12,30 @@ import MinimedKit extension MinimedPumpManager: PumpManagerUI { - static public func setupViewController() -> (UIViewController & PumpManagerSetupViewController) { + + static public func setupViewController() -> (UIViewController & PumpManagerSetupViewController & CompletionNotifying) { return MinimedPumpManagerSetupViewController.instantiateFromStoryboard() } - public func settingsViewController() -> UIViewController { - return MinimedPumpSettingsViewController(pumpManager: self) + public func settingsViewController() -> (UIViewController & CompletionNotifying) { + let settings = MinimedPumpSettingsViewController(pumpManager: self) + let nav = SettingsNavigationViewController(rootViewController: settings) + return nav } public var smallImage: UIImage? { return state.smallPumpImage } + + public func hudProvider() -> HUDProvider? { + return MinimedHUDProvider(pumpManager: self) + } + + public static func createHUDViews(rawValue: HUDProvider.HUDViewsRawState) -> [BaseHUDView] { + return MinimedHUDProvider.createHUDViews(rawValue: rawValue) + } } - // MARK: - DeliveryLimitSettingsTableViewControllerSyncSource extension MinimedPumpManager { public func syncDeliveryLimitSettings(for viewController: DeliveryLimitSettingsTableViewController, completion: @escaping (DeliveryLimitSettingsResult) -> Void) { diff --git a/MinimedKitUI/MinimedPumpSettingsViewController.swift b/MinimedKitUI/MinimedPumpSettingsViewController.swift index e20230667..af47cbad4 100644 --- a/MinimedKitUI/MinimedPumpSettingsViewController.swift +++ b/MinimedKitUI/MinimedPumpSettingsViewController.swift @@ -9,12 +9,12 @@ import UIKit import LoopKitUI import MinimedKit import RileyLinkKitUI - +import LoopKit class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { let pumpManager: MinimedPumpManager - + init(pumpManager: MinimedPumpManager) { self.pumpManager = pumpManager super.init(rileyLinkPumpManager: pumpManager, devicesSectionIndex: Section.rileyLinks.rawValue, style: .grouped) @@ -37,11 +37,31 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className) tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className) + tableView.register(SuspendResumeTableViewCell.self, forCellReuseIdentifier: SuspendResumeTableViewCell.className) let imageView = UIImageView(image: pumpManager.state.largePumpImage) imageView.contentMode = .bottom imageView.frame.size.height += 18 // feels right tableView.tableHeaderView = imageView + + pumpManager.addStatusObserver(self) + + let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:))) + self.navigationItem.setRightBarButton(button, animated: false) + + } + + @objc func doneTapped(_ sender: Any) { + done() + } + + private func done() { + if let nav = navigationController as? SettingsNavigationViewController { + nav.notifyComplete() + } + if let nav = navigationController as? MinimedPumpManagerSetupViewController { + nav.finishedSettingsDisplay() + } } override func viewWillAppear(_ animated: Bool) { @@ -57,42 +77,45 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { // MARK: - Data Source - private enum Section: Int { + private enum Section: Int, CaseIterable { case info = 0 + case actions case settings case rileyLinks case delete - - static let count = 4 } - private enum InfoRow: Int { + private enum InfoRow: Int, CaseIterable { case pumpID = 0 case pumpModel + case pumpFirmware + case pumpRegion + } - static let count = 2 + private enum ActionsRow: Int, CaseIterable { + case suspendResume = 0 } - private enum SettingsRow: Int { + private enum SettingsRow: Int, CaseIterable { case timeZoneOffset = 0 case batteryChemistry case preferredInsulinDataSource - - static let count = 3 } // MARK: UITableViewDataSource override func numberOfSections(in tableView: UITableView) -> Int { - return Section.count + return Section.allCases.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Section(rawValue: section)! { case .info: - return InfoRow.count + return InfoRow.allCases.count + case .actions: + return ActionsRow.allCases.count case .settings: - return SettingsRow.count + return SettingsRow.allCases.count case .rileyLinks: return super.tableView(tableView, numberOfRowsInSection: section) case .delete: @@ -102,14 +125,14 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { switch Section(rawValue: section)! { - case .info: - return nil case .settings: return LocalizedString("Configuration", comment: "The title of the configuration section in settings") case .rileyLinks: return super.tableView(tableView, titleForHeaderInSection: section) case .delete: return " " // Use an empty string for more dramatic spacing + case .info, .actions: + return nil } } @@ -117,7 +140,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { switch Section(rawValue: section)! { case .rileyLinks: return super.tableView(tableView, viewForHeaderInSection: section) - case .info, .settings, .delete: + case .info, .settings, .delete, .actions: return nil } } @@ -136,6 +159,23 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { cell.textLabel?.text = LocalizedString("Pump Model", comment: "The title of the cell showing the pump model number") cell.detailTextLabel?.text = String(describing: pumpManager.state.pumpModel) return cell + case .pumpFirmware: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Firmware Version", comment: "The title of the cell showing the pump firmware version") + cell.detailTextLabel?.text = String(describing: pumpManager.state.pumpFirmwareVersion) + return cell + case .pumpRegion: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Region", comment: "The title of the cell showing the pump region") + cell.detailTextLabel?.text = String(describing: pumpManager.state.pumpRegion) + return cell + } + case .actions: + switch ActionsRow(rawValue: indexPath.row)! { + case .suspendResume: + let cell = tableView.dequeueReusableCell(withIdentifier: SuspendResumeTableViewCell.className, for: indexPath) as! SuspendResumeTableViewCell + cell.basalDeliveryState = pumpManager.status.basalDeliveryState + return cell } case .settings: let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) @@ -180,7 +220,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { switch Section(rawValue: indexPath.section)! { case .info: return false - case .settings, .rileyLinks, .delete: + case .actions, .settings, .rileyLinks, .delete: return true } } @@ -191,6 +231,12 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { switch Section(rawValue: indexPath.section)! { case .info: break + case .actions: + switch ActionsRow(rawValue: indexPath.row)! { + case .suspendResume: + suspendResumeCellTapped(sender as! SuspendResumeTableViewCell) + tableView.deselectRow(at: indexPath, animated: true) + } case .settings: switch SettingsRow(rawValue: indexPath.row)! { case .timeZoneOffset: @@ -227,7 +273,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { case .delete: let confirmVC = UIAlertController(pumpDeletionHandler: { self.pumpManager.pumpManagerDelegate?.pumpManagerWillDeactivate(self.pumpManager) - self.navigationController?.popViewController(animated: true) + self.done() }) present(confirmVC, animated: true) { @@ -238,8 +284,6 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { switch Section(rawValue: indexPath.section)! { - case .info: - break case .settings: switch SettingsRow(rawValue: indexPath.row)! { case .timeZoneOffset: @@ -249,9 +293,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { case .preferredInsulinDataSource: break } - case .rileyLinks: - break - case .delete: + case .info, .actions, .rileyLinks, .delete: break } @@ -286,8 +328,45 @@ extension MinimedPumpSettingsViewController: RadioSelectionTableViewControllerDe tableView.reloadRows(at: [indexPath], with: .none) } + + private func suspendResumeCellTapped(_ cell: SuspendResumeTableViewCell) { + guard cell.isEnabled else { + return + } + + switch cell.shownAction { + case .resume: + pumpManager.resumeDelivery { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error Resuming", comment: "The alert title for a resume error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + } + case .suspend: + pumpManager.suspendDelivery { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error Suspending", comment: "The alert title for a suspend error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + } + } + } } +extension MinimedPumpSettingsViewController: PumpManagerStatusObserver { + public func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { + DispatchQueue.main.async { + let suspendResumeTableViewCell = self.tableView?.cellForRow(at: IndexPath(row: ActionsRow.suspendResume.rawValue, section: Section.actions.rawValue)) as! SuspendResumeTableViewCell + suspendResumeTableViewCell.basalDeliveryState = status.basalDeliveryState + } + } +} + + private extension UIAlertController { convenience init(pumpDeletionHandler handler: @escaping () -> Void) { diff --git a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift index c2b49749e..9db9a918b 100644 --- a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift @@ -60,6 +60,8 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { private var pumpOps: PumpOps? private var pumpState: PumpState? + + private var pumpFirmwareVersion: String? var maxBasalRateUnitsPerHour: Double? @@ -75,19 +77,19 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { let pumpID = pumpID, let pumpModel = pumpState?.pumpModel, let pumpRegion = pumpRegionCode?.region, - let timeZone = pumpState?.timeZone + let timeZone = pumpState?.timeZone, + let pumpFirmwareVersion = pumpFirmwareVersion else { return nil } - return MinimedPumpManagerState( pumpColor: pumpColor, pumpID: pumpID, pumpModel: pumpModel, + pumpFirmwareVersion: pumpFirmwareVersion, pumpRegion: pumpRegion, rileyLinkConnectionManagerState: rileyLinkPumpManager.rileyLinkConnectionManagerState, - timeZone: timeZone - ) + timeZone: timeZone) } } @@ -178,7 +180,7 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { footerView.primaryButton.setConnectTitle() case .reading: pumpIDTextField.isEnabled = false - activityIndicator.state = .loading + activityIndicator.state = .indeterminantProgress footerView.primaryButton.isEnabled = false footerView.primaryButton.setConnectTitle() lastError = nil @@ -248,6 +250,8 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { _ = try session.tuneRadio() let model = try session.getPumpModel() var isSentrySetUpNeeded = false + + self.pumpFirmwareVersion = try session.getPumpFirmwareVersion() // Radio if model.hasMySentry { @@ -309,7 +313,14 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { super.continueButtonPressed(sender) } } else if case .readyToRead = continueState, let pumpID = pumpID, let pumpRegion = pumpRegionCode?.region { +#if targetEnvironment(simulator) + self.continueState = .completed + self.pumpState = PumpState(timeZone: .currentFixed, pumpModel: PumpModel(rawValue: + "523")!) + self.pumpFirmwareVersion = "2.4Mock" +#else readPumpState(with: PumpSettings(pumpID: pumpID, pumpRegion: pumpRegion)) +#endif } } diff --git a/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift index 92d79b601..edfe67fdf 100644 --- a/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift @@ -52,6 +52,7 @@ public class MinimedPumpManagerSetupViewController: RileyLinkManagerSetupViewCon 5. Basal Rates & Delivery Limits 6. Pump Setup Complete + */ override public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { @@ -73,6 +74,10 @@ public class MinimedPumpManagerSetupViewController: RileyLinkManagerSetupViewCon } } + if let setupViewController = viewController as? SetupTableViewController { + setupViewController.delegate = self + } + // Set state values switch viewController { case let vc as MinimedPumpIDSetupViewController: @@ -112,9 +117,24 @@ public class MinimedPumpManagerSetupViewController: RileyLinkManagerSetupViewCon } } - func completeSetup() { + public func pumpManagerSetupComplete(_ pumpManager: PumpManagerUI) { + setupDelegate?.pumpManagerSetupViewController(self, didSetUpPumpManager: pumpManager) + } + + override open func finishedSetup() { if let pumpManager = pumpManager { - setupDelegate?.pumpManagerSetupViewController(self, didSetUpPumpManager: pumpManager) + let settings = MinimedPumpSettingsViewController(pumpManager: pumpManager) + setViewControllers([settings], animated: true) } } + + public func finishedSettingsDisplay() { + completionDelegate?.completionNotifyingDidComplete(self) + } +} + +extension MinimedPumpManagerSetupViewController: SetupTableViewControllerDelegate { + public func setupTableViewControllerCancelButtonPressed(_ viewController: SetupTableViewController) { + completionDelegate?.completionNotifyingDidComplete(self) + } } diff --git a/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift index b083c0134..c485f69ad 100644 --- a/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift @@ -64,7 +64,7 @@ class MinimedPumpSentrySetupViewController: SetupTableViewController { footerView.primaryButton.setTitle(LocalizedString("Retry", comment: "Button title to retry sentry setup"), for: .normal) case .listening: lastError = nil - activityIndicator.state = .loading + activityIndicator.state = .indeterminantProgress footerView.primaryButton.isEnabled = false case .completed: lastError = nil diff --git a/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift index da4465acb..4bc4e91b6 100644 --- a/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift @@ -16,7 +16,7 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { var pumpManager: MinimedPumpManager? private var pumpManagerSetupViewController: MinimedPumpManagerSetupViewController? { - return setupViewController as? MinimedPumpManagerSetupViewController + return navigationController as? MinimedPumpManagerSetupViewController } override func viewDidLoad() { @@ -81,7 +81,7 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { case .basalRates: cell.textLabel?.text = LocalizedString("Basal Rates", comment: "The title text for the basal rate schedule") - if let basalRateSchedule = setupViewController?.basalSchedule { + if let basalRateSchedule = pumpManagerSetupViewController?.basalSchedule { let unit = HKUnit.internationalUnit() let total = HKQuantity(unit: unit, doubleValue: basalRateSchedule.total()) cell.detailTextLabel?.text = quantityFormatter.string(from: total, for: unit) @@ -91,7 +91,7 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { case .deliveryLimits: cell.textLabel?.text = LocalizedString("Delivery Limits", comment: "Title text for delivery limits") - if setupViewController?.maxBolusUnits == nil || setupViewController?.maxBasalRateUnitsPerHour == nil { + if pumpManagerSetupViewController?.maxBolusUnits == nil || pumpManagerSetupViewController?.maxBasalRateUnitsPerHour == nil { cell.detailTextLabel?.text = SettingsTableViewCell.TapToSetString } else { cell.detailTextLabel?.text = SettingsTableViewCell.EnabledString @@ -124,7 +124,7 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { case .basalRates: let vc = SingleValueScheduleTableViewController(style: .grouped) - if let profile = setupViewController?.basalSchedule { + if let profile = pumpManagerSetupViewController?.basalSchedule { vc.scheduleItems = profile.items vc.timeZone = profile.timeZone } else if let timeZone = pumpManager?.pumpTimeZone { @@ -139,8 +139,8 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { case .deliveryLimits: let vc = DeliveryLimitSettingsTableViewController(style: .grouped) - vc.maximumBasalRatePerHour = setupViewController?.maxBasalRateUnitsPerHour - vc.maximumBolus = setupViewController?.maxBolusUnits + vc.maximumBasalRatePerHour = pumpManagerSetupViewController?.maxBasalRateUnitsPerHour + vc.maximumBolus = pumpManagerSetupViewController?.maxBolusUnits vc.title = sender?.textLabel?.text vc.delegate = self @@ -150,6 +150,15 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { } } } + + override func continueButtonPressed(_ sender: Any) { + if let setupViewController = navigationController as? MinimedPumpManagerSetupViewController, + let pumpManager = pumpManager + { + super.continueButtonPressed(sender) + setupViewController.pumpManagerSetupComplete(pumpManager) + } + } } extension MinimedPumpSettingsSetupViewController: DailyValueScheduleTableViewControllerDelegate { diff --git a/MinimedKitUI/Setup/MinimedPumpSetupCompleteViewController.swift b/MinimedKitUI/Setup/MinimedPumpSetupCompleteViewController.swift index 8314f1a3f..3131eeacb 100644 --- a/MinimedKitUI/Setup/MinimedPumpSetupCompleteViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpSetupCompleteViewController.swift @@ -30,8 +30,8 @@ class MinimedPumpSetupCompleteViewController: SetupTableViewController { } override func continueButtonPressed(_ sender: Any) { - if let setupViewController = setupViewController as? MinimedPumpManagerSetupViewController { - setupViewController.completeSetup() + if let setupViewController = navigationController as? MinimedPumpManagerSetupViewController { + setupViewController.finishedSetup() } } } diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index da0b4837e..4a0f3f705 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -320,6 +320,7 @@ C14303161C97C98000A40450 /* PumpAckMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303151C97C98000A40450 /* PumpAckMessageBody.swift */; }; C14303181C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */; }; C143031A1C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */; }; + C145BF9F2219F37200A977CB /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C145BF9D2219F2EC00A977CB /* Comparable.swift */; }; C14D2B051C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D2B041C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift */; }; C14D2B091C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D2B081C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift */; }; C14FFC4A1D3AF1AC0049CF85 /* JournalEntryInsulinMarkerPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC491D3AF1AC0049CF85 /* JournalEntryInsulinMarkerPumpEvent.swift */; }; @@ -337,6 +338,12 @@ C15AF2AF1D7498930031FC9D /* RestoreMystery54PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */; }; C15AF2B11D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */; }; C16A08311D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16A08301D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift */; }; + C16E611D2203CB7A0069F357 /* SuspendResumeMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */; }; + C16E611E2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */; }; + C16E611F22065B8E0069F357 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; + C16E6120220779210069F357 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26B720DC83E400891C17 /* Locked.swift */; }; + C16E61222208C7A80069F357 /* ReservoirReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E61212208C7A80069F357 /* ReservoirReading.swift */; }; + C16E61262208EC580069F357 /* MinimedHUDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E61252208EC580069F357 /* MinimedHUDProvider.swift */; }; C1711A561C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */; }; C1711A5A1C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1711A591C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift */; }; C1711A5C1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1711A5B1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift */; }; @@ -1043,6 +1050,7 @@ C14303151C97C98000A40450 /* PumpAckMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpAckMessageBody.swift; sourceTree = ""; }; C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBodyTests.swift; sourceTree = ""; }; C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBatteryCarelinkMessageBodyTests.swift; sourceTree = ""; }; + C145BF9D2219F2EC00A977CB /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = ""; }; C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NightscoutPumpEventsTests.swift; path = ../RileyLinkTests/NightscoutPumpEventsTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14D2B041C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TempBasalDurationPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14D2B081C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChangeTempBasalTypePumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -1060,6 +1068,10 @@ C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreMystery54PumpEvent.swift; sourceTree = ""; }; C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreMystery55PumpEvent.swift; sourceTree = ""; }; C16A08301D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JournalEntryMealMarkerPumpEvent.swift; sourceTree = ""; }; + C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuspendResumeMessageBody.swift; sourceTree = ""; }; + C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpFirmwareVersionMessageBody.swift; sourceTree = ""; }; + C16E61212208C7A80069F357 /* ReservoirReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservoirReading.swift; sourceTree = ""; }; + C16E61252208EC580069F357 /* MinimedHUDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedHUDProvider.swift; sourceTree = ""; }; C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = ""; }; C170C9961CECD80000F3D8E5 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = ""; }; C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceTableViewController.swift; sourceTree = ""; }; @@ -1455,6 +1467,7 @@ 43709AC220DF1C8B00F941B3 /* MinimedPumpSettingsViewController.swift */, 43709AC020DF1C8B00F941B3 /* PumpModel.swift */, 43709AC120DF1C8B00F941B3 /* RadioSelectionTableViewController.swift */, + C16E61252208EC580069F357 /* MinimedHUDProvider.swift */, ); path = MinimedKitUI; sourceTree = ""; @@ -1549,6 +1562,7 @@ 4384C8C71FB937E500D916E6 /* PumpSettings.swift */, 434AB0941CBA0DF600422F4A /* PumpState.swift */, 43D8709420DE1C91006B549E /* RileyLinkDevice.swift */, + C16E61212208C7A80069F357 /* ReservoirReading.swift */, ); path = PumpManager; sourceTree = ""; @@ -1577,6 +1591,7 @@ 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */, C14FFC601D3D75470049CF85 /* UIColor.swift */, 7D2366EF212527DA0028B67D /* LocalizedString.swift */, + C145BF9D2219F2EC00A977CB /* Comparable.swift */, ); path = Common; sourceTree = ""; @@ -2015,6 +2030,8 @@ C1EAD6BC1C826B92006DBA60 /* Messages */ = { isa = PBXGroup; children = ( + C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */, + C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */, C1EAD6B21C826B6D006DBA60 /* PumpMessage.swift */, C1EAD6B11C826B6D006DBA60 /* PacketType.swift */, C1EAD6B01C826B6D006DBA60 /* MessageType.swift */, @@ -2382,7 +2399,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Pete Schwamb"; TargetAttributes = { 431CE76E1F98564100255374 = { @@ -2692,12 +2709,15 @@ 7D2366F7212527DA0028B67D /* LocalizedString.swift in Sources */, C154EA7F2146F41900B24AF8 /* TimeInterval.swift in Sources */, 43709AD420DF1CF800F941B3 /* MinimedPumpIDSetupViewController.swift in Sources */, + C16E611F22065B8E0069F357 /* TimeZone.swift in Sources */, + C16E61262208EC580069F357 /* MinimedHUDProvider.swift in Sources */, 43709AD520DF1CF800F941B3 /* MinimedPumpManagerSetupViewController.swift in Sources */, 43709AD320DF1CF800F941B3 /* MinimedPumpClockSetupViewController.swift in Sources */, 43709AC420DF1C8B00F941B3 /* PumpModel.swift in Sources */, 43709AD820DF1CF800F941B3 /* MinimedPumpSettingsSetupViewController.swift in Sources */, 43709AD720DF1CF800F941B3 /* MinimedPumpSetupCompleteViewController.swift in Sources */, 43709AD620DF1CF800F941B3 /* MinimedPumpSentrySetupViewController.swift in Sources */, + C145BF9F2219F37200A977CB /* Comparable.swift in Sources */, 43709AE720DF22E400F941B3 /* UIColor.swift in Sources */, 43709AE320DF20D400F941B3 /* OSLog.swift in Sources */, 43709AE120DF1F0D00F941B3 /* IdentifiableClass.swift in Sources */, @@ -2845,6 +2865,7 @@ C1842C0F1C8FA45100DB42AC /* ChangeSensorRateOfChangeAlertSetupPumpEvent.swift in Sources */, C1842BFE1C8FA45100DB42AC /* ResultDailyTotalPumpEvent.swift in Sources */, C1FDFCA91D964A3E00ADBC31 /* BolusReminderPumpEvent.swift in Sources */, + C16E611D2203CB7A0069F357 /* SuspendResumeMessageBody.swift in Sources */, 43CA93291CB8CF22000026B5 /* ChangeTempBasalCarelinkMessageBody.swift in Sources */, C1EAD6CD1C826B92006DBA60 /* PowerOnCarelinkMessageBody.swift in Sources */, 43CEC07620D099B400F1BC19 /* SetRemoteControlEnabledMessageBody.swift in Sources */, @@ -2860,6 +2881,7 @@ 54BC44821DB476BB00340EED /* CalBGForGHGlucoseEvent.swift in Sources */, C1EAD6CF1C826B92006DBA60 /* UnknownMessageBody.swift in Sources */, 43D8708B20DE1BCA006B549E /* PumpColor.swift in Sources */, + C16E6120220779210069F357 /* Locked.swift in Sources */, C1842C161C8FA45100DB42AC /* ChangeCarbUnitsPumpEvent.swift in Sources */, C1842C241C8FA45100DB42AC /* BatteryPumpEvent.swift in Sources */, C1842C1F1C8FA45100DB42AC /* ChangeBasalProfilePatternPumpEvent.swift in Sources */, @@ -2898,6 +2920,7 @@ C1842C251C8FA45100DB42AC /* AlarmSensorPumpEvent.swift in Sources */, 43D8709320DE1C80006B549E /* PumpOpsSession+LoopKit.swift in Sources */, C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */, + C16E61222208C7A80069F357 /* ReservoirReading.swift in Sources */, C14D2B091C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift in Sources */, C1842C1E1C8FA45100DB42AC /* ChangeBasalProfilePumpEvent.swift in Sources */, C14D2B051C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift in Sources */, @@ -2909,6 +2932,7 @@ C1842C001C8FA45100DB42AC /* JournalEntryPumpLowReservoirPumpEvent.swift in Sources */, 54A840D11DB85D0600B1F202 /* UnknownGlucoseEvent.swift in Sources */, 4352A71A20DEC7CB00CAC200 /* CommandSession.swift in Sources */, + C16E611E2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift in Sources */, C1842BCF1C8F9E5100DB42AC /* PumpAlarmPumpEvent.swift in Sources */, 43D8708F20DE1C23006B549E /* EnliteSensorDisplayable.swift in Sources */, C1EAD6C61C826B92006DBA60 /* Data.swift in Sources */, diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme index f975dc656..cd8f3e11a 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme @@ -1,6 +1,6 @@ Date { return Date().addingTimeInterval(.minutes(-15)) - } + } } diff --git a/RileyLink/View Controllers/MainViewController.swift b/RileyLink/View Controllers/MainViewController.swift index 7e09a220c..2bd21b9e1 100644 --- a/RileyLink/View Controllers/MainViewController.swift +++ b/RileyLink/View Controllers/MainViewController.swift @@ -167,10 +167,11 @@ class MainViewController: RileyLinkSettingsViewController { show(vc, sender: indexPath) case .pump: if let pumpManager = deviceDataManager.pumpManager { - let settings = pumpManager.settingsViewController() - show(settings, sender: sender) + var settings = pumpManager.settingsViewController() + settings.completionDelegate = self + present(settings, animated: true) } else { - var setupViewController: PumpManagerSetupViewController & UIViewController + var setupViewController: PumpManagerSetupViewController & UIViewController & CompletionNotifying switch PumpActionRow(rawValue: indexPath.row)! { case .addMinimedPump: setupViewController = UIStoryboard(name: "MinimedPumpManager", bundle: Bundle(for: MinimedPumpManagerSetupViewController.self)).instantiateViewController(withIdentifier: "DevelopmentPumpSetup") as! MinimedPumpManagerSetupViewController @@ -179,6 +180,7 @@ class MainViewController: RileyLinkSettingsViewController { rileyLinkManagerViewController.rileyLinkPumpManager = RileyLinkPumpManager(rileyLinkDeviceProvider: deviceDataManager.rileyLinkConnectionManager.deviceProvider) } setupViewController.setupDelegate = self + setupViewController.completionDelegate = self present(setupViewController, animated: true, completion: nil) } } @@ -196,15 +198,18 @@ class MainViewController: RileyLinkSettingsViewController { } } +extension MainViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController { + vc.dismiss(animated: true, completion: nil) + } + } +} + extension MainViewController: PumpManagerSetupViewControllerDelegate { func pumpManagerSetupViewController(_ pumpManagerSetupViewController: PumpManagerSetupViewController, didSetUpPumpManager pumpManager: PumpManagerUI) { deviceDataManager.pumpManager = pumpManager show(pumpManager.settingsViewController(), sender: nil) tableView.reloadSections(IndexSet([Section.pump.rawValue]), with: .none) - dismiss(animated: true, completion: nil) - } - - func pumpManagerSetupViewControllerDidCancel(_ pumpManagerSetupViewController: PumpManagerSetupViewController) { - dismiss(animated: true, completion: nil) } } diff --git a/RileyLinkBLEKit/PeripheralManagerError.swift b/RileyLinkBLEKit/PeripheralManagerError.swift index aed11b075..618625533 100644 --- a/RileyLinkBLEKit/PeripheralManagerError.swift +++ b/RileyLinkBLEKit/PeripheralManagerError.swift @@ -22,9 +22,9 @@ extension PeripheralManagerError: LocalizedError { case .cbPeripheralError(let error): return error.localizedDescription case .notReady: - return LocalizedString("Peripheral isnʼt connected", comment: "Not ready error description") + return LocalizedString("RileyLink is not connected", comment: "Not ready error description") case .timeout: - return LocalizedString("Peripheral did not respond in time", comment: "Timeout error description") + return LocalizedString("RileyLink did not respond in time", comment: "Timeout error description") case .unknownCharacteristic: return LocalizedString("Unknown characteristic", comment: "Error description") } diff --git a/RileyLinkBLEKit/RFPacket.swift b/RileyLinkBLEKit/RFPacket.swift index a69e10ba9..d5dc904be 100644 --- a/RileyLinkBLEKit/RFPacket.swift +++ b/RileyLinkBLEKit/RFPacket.swift @@ -14,7 +14,7 @@ public struct RFPacket { public let rssi: Int init?(rfspyResponse: Data) { - guard rfspyResponse.count > 2 else { + guard rfspyResponse.count >= 2 else { return nil } diff --git a/RileyLinkBLEKit/RileyLinkDevice.swift b/RileyLinkBLEKit/RileyLinkDevice.swift index 6e682faf5..5c734a9cd 100644 --- a/RileyLinkBLEKit/RileyLinkDevice.swift +++ b/RileyLinkBLEKit/RileyLinkDevice.swift @@ -364,15 +364,15 @@ extension RileyLinkDevice: CustomDebugStringConvertible { public var debugDescription: String { return [ "## RileyLinkDevice", - "name: \(name ?? "")", - "lastIdle: \(lastIdle ?? .distantPast)", - "isIdleListeningPending: \(isIdleListeningPending)", - "isTimerTickEnabled: \(isTimerTickEnabled)", - "isTimerTickNotifying: \(manager.timerTickEnabled)", - "radioFirmware: \(String(describing: radioFirmwareVersion))", - "bleFirmware: \(String(describing: bleFirmwareVersion))", - "peripheralManager: \(String(reflecting: manager))", - "sessionQueue.operationCount: \(sessionQueue.operationCount)" + "* name: \(name ?? "")", + "* lastIdle: \(lastIdle ?? .distantPast)", + "* isIdleListeningPending: \(isIdleListeningPending)", + "* isTimerTickEnabled: \(isTimerTickEnabled)", + "* isTimerTickNotifying: \(manager.timerTickEnabled)", + "* radioFirmware: \(String(describing: radioFirmwareVersion))", + "* bleFirmware: \(String(describing: bleFirmwareVersion))", + "* peripheralManager: \(manager)", + "* sessionQueue.operationCount: \(sessionQueue.operationCount)" ].joined(separator: "\n") } } diff --git a/RileyLinkKit/PumpOpsSession.swift b/RileyLinkKit/PumpOpsSession.swift index 8c8d70887..ae4dddf62 100644 --- a/RileyLinkKit/PumpOpsSession.swift +++ b/RileyLinkKit/PumpOpsSession.swift @@ -134,7 +134,7 @@ extension PumpOpsSession { // MARK: - Single reads extension PumpOpsSession { - /// Retrieves the pump model from either the state or from the + /// Retrieves the pump model from either the state or from the cache /// /// - Parameter usingCache: Whether the pump state should be checked first for a known pump model /// - Returns: The pump model @@ -164,6 +164,26 @@ extension PumpOpsSession { return pumpModel } + /// Retrieves the pump firmware version + /// + /// - Returns: The pump firmware version as string + /// - Throws: + /// - PumpCommandError.command + /// - PumpCommandError.arguments + /// - PumpOpsError.couldNotDecode + /// - PumpOpsError.crosstalk + /// - PumpOpsError.deviceError + /// - PumpOpsError.noResponse + /// - PumpOpsError.unexpectedResponse + /// - PumpOpsError.unknownResponse + public func getPumpFirmwareVersion() throws -> String { + + try wakeup() + let body: GetPumpFirmwareVersionMessageBody = try session.getResponse(to: PumpMessage(settings: settings, type: .readFirmwareVersion), responseType: .readFirmwareVersion) + + return body.version + } + /// - Throws: /// - PumpCommandError.command /// - PumpCommandError.arguments @@ -393,6 +413,13 @@ extension PumpOpsSession { let _: PumpAckMessageBody = try runCommandWithArguments(message) } + + /// - Throws: `PumpCommandError` specifying the failure sequence + public func setSuspendResumeState(_ state: SuspendResumeMessageBody.SuspendResumeState) throws { + let message = PumpMessage(settings: settings, type: .suspendResume, body: SuspendResumeMessageBody(state: state)) + + let _: PumpAckMessageBody = try runCommandWithArguments(message) + } /// - Throws: PumpCommandError public func selectBasalProfile(_ profile: BasalProfile) throws { diff --git a/RileyLinkKit/RileyLinkPumpManager.swift b/RileyLinkKit/RileyLinkPumpManager.swift index 9cd8f3465..0e72166da 100644 --- a/RileyLinkKit/RileyLinkPumpManager.swift +++ b/RileyLinkKit/RileyLinkPumpManager.swift @@ -14,6 +14,7 @@ open class RileyLinkPumpManager { self.rileyLinkDeviceProvider = rileyLinkDeviceProvider self.rileyLinkConnectionManager = rileyLinkConnectionManager + self.rileyLinkConnectionManagerState = rileyLinkConnectionManager?.state // Listen for device notifications NotificationCenter.default.addObserver(self, selector: #selector(receivedRileyLinkPacketNotification(_:)), name: .DevicePacketReceived, object: nil) diff --git a/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift b/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift index 2f0e1859e..5bf384b58 100644 --- a/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift +++ b/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift @@ -11,7 +11,7 @@ import LoopKitUI import RileyLinkKit -open class RileyLinkManagerSetupViewController: UINavigationController, PumpManagerSetupViewController, UINavigationControllerDelegate { +open class RileyLinkManagerSetupViewController: UINavigationController, PumpManagerSetupViewController, UINavigationControllerDelegate, CompletionNotifying { open var maxBasalRateUnitsPerHour: Double? @@ -21,6 +21,8 @@ open class RileyLinkManagerSetupViewController: UINavigationController, PumpMana open weak var setupDelegate: PumpManagerSetupViewControllerDelegate? + open weak var completionDelegate: CompletionDelegate? + open var rileyLinkPumpManager: RileyLinkPumpManager? open override func viewDidLoad() { @@ -37,4 +39,8 @@ open class RileyLinkManagerSetupViewController: UINavigationController, PumpMana rileyLinkPumpManager = setupViewController.rileyLinkPumpManager } } + + open func finishedSetup() { + completionDelegate?.completionNotifyingDidComplete(self) + } } diff --git a/RileyLinkKitUI/RileyLinkSetupTableViewController.swift b/RileyLinkKitUI/RileyLinkSetupTableViewController.swift index 20c0f34b1..0adc2db1a 100644 --- a/RileyLinkKitUI/RileyLinkSetupTableViewController.swift +++ b/RileyLinkKitUI/RileyLinkSetupTableViewController.swift @@ -140,11 +140,16 @@ public class RileyLinkSetupTableViewController: SetupTableViewController { // MARK: - Navigation private var shouldContinue: Bool { + #if targetEnvironment(simulator) + return true + #else + guard let connectionManager = rileyLinkPumpManager.rileyLinkConnectionManager else { return false } return connectionManager.connectingCount > 0 + #endif } @objc private func deviceConnectionStateDidChange() { From fa8f6016f9eba7db4308e90a3c28c67240673ee6 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2019 12:15:31 -0600 Subject: [PATCH 03/71] Basal picker (#499) * Define allowed rates for x23 and x22 pumps, and integrate new basal picker * Point cartfile at tidepool branch * Use tidepool-org/LoopKit basal-picker branch --- Cartfile | 2 +- Cartfile.resolved | 2 +- MinimedKit/Models/PumpModel.swift | 23 +++++++++++++++++++ .../PumpManager/MinimedPumpManager.swift | 12 ++++++++++ MinimedKitUI/MinimedPumpManager+UI.swift | 10 ++++---- ...nimedPumpSettingsSetupViewController.swift | 9 +++++--- 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/Cartfile b/Cartfile index 05195c51b..e3f36a17f 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "LoopKit/LoopKit" "dev" +github "tidepool-org/LoopKit" "basal-picker" diff --git a/Cartfile.resolved b/Cartfile.resolved index d43c89f95..304aedffc 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "LoopKit/LoopKit" "bf4166bc77f89e22971f2030ad006967881ff082" +github "tidepool-org/LoopKit" "27c3916468fc5308f7aac54725a88d0762828c95" diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 6566a2999..1f5a5bbc5 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -99,6 +99,29 @@ public enum PumpModel: String { var usesTwoBytesForMaxBolus: Bool { return generation >= 23 } + + public var supportedBasalRates: [Double] { + if generation >= 23 { + // 0.025 units (for rates between 0.0-0.975 U/h) + let rateGroup1 = ((0...38).map { Double($0) / Double(pulsesPerUnit) }) + // 0.05 units (for rates between 1-9.95 U/h) + let rateGroup2 = ((20...199).map { Double($0) / Double(pulsesPerUnit/2) }) + // 0.1 units (for rates between 10-35 U/h) + let rateGroup3 = ((100...350).map { Double($0) / Double(pulsesPerUnit/4) }) + return rateGroup1 + rateGroup2 + rateGroup3 + } else { + // 0.05 units for rates between 0.0-35U/hr + return (0...700).map { Double($0) / Double(pulsesPerUnit) } + } + } + + public var maximumBasalScheduleEntryCount: Int { + return 48 + } + + public var minimumBasalScheduleEntryDuration: TimeInterval { + return .minutes(30) + } } diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 02b979725..72088f085 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -16,6 +16,18 @@ public protocol MinimedPumpManagerStateObserver: class { } public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { + + public var supportedBasalRates: [Double] { + return state.pumpModel.supportedBasalRates + } + + public var maximumBasalScheduleEntryCount: Int { + return state.pumpModel.maximumBasalScheduleEntryCount + } + + public var minimumBasalScheduleEntryDuration: TimeInterval { + return state.pumpModel.minimumBasalScheduleEntryDuration + } public static let managerIdentifier: String = "Minimed500" diff --git a/MinimedKitUI/MinimedPumpManager+UI.swift b/MinimedKitUI/MinimedPumpManager+UI.swift index a01849e5e..714c2f283 100644 --- a/MinimedKitUI/MinimedPumpManager+UI.swift +++ b/MinimedKitUI/MinimedPumpManager+UI.swift @@ -77,9 +77,9 @@ extension MinimedPumpManager { } -// MARK: - SingleValueScheduleTableViewControllerSyncSource +// MARK: - BasalScheduleTableViewControllerSyncSource extension MinimedPumpManager { - public func syncScheduleValues(for viewController: SingleValueScheduleTableViewController, completion: @escaping (RepeatingScheduleValueResult) -> Void) { + public func syncScheduleValues(for viewController: BasalScheduleTableViewController, completion: @escaping (SyncBasalScheduleResult) -> Void) { pumpOps.runSession(withName: "Save Basal Profile", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in guard let session = session else { completion(.failure(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink))) @@ -98,15 +98,15 @@ extension MinimedPumpManager { } } - public func syncButtonTitle(for viewController: SingleValueScheduleTableViewController) -> String { + public func syncButtonTitle(for viewController: BasalScheduleTableViewController) -> String { return LocalizedString("Save to Pump…", comment: "Title of button to save basal profile to pump") } - public func syncButtonDetailText(for viewController: SingleValueScheduleTableViewController) -> String? { + public func syncButtonDetailText(for viewController: BasalScheduleTableViewController) -> String? { return nil } - public func singleValueScheduleTableViewControllerIsReadOnly(_ viewController: SingleValueScheduleTableViewController) -> Bool { + public func basalScheduleTableViewControllerIsReadOnly(_ viewController: BasalScheduleTableViewController) -> Bool { return false } } diff --git a/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift index 4bc4e91b6..38e71a1dd 100644 --- a/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift @@ -122,13 +122,16 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { case .basalRates: - let vc = SingleValueScheduleTableViewController(style: .grouped) + guard let pumpManager = pumpManager else { + return + } + let vc = BasalScheduleTableViewController(allowedBasalRates: pumpManager.supportedBasalRates, maximumScheduleItemCount: pumpManager.maximumBasalScheduleEntryCount, minimumTimeInterval: pumpManager.minimumBasalScheduleEntryDuration) if let profile = pumpManagerSetupViewController?.basalSchedule { vc.scheduleItems = profile.items vc.timeZone = profile.timeZone - } else if let timeZone = pumpManager?.pumpTimeZone { - vc.timeZone = timeZone + } else { + vc.timeZone = pumpManager.pumpTimeZone } vc.title = sender?.textLabel?.text From 817875dd746dd69823253405fe5745c20d002423 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2019 14:01:17 -0600 Subject: [PATCH 04/71] Update to dev LoopKit --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index e3f36a17f..fdede6a12 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "tidepool-org/LoopKit" "basal-picker" +github "tidepool-org/LoopKit" "dev" diff --git a/Cartfile.resolved b/Cartfile.resolved index 304aedffc..af77a0bc2 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "tidepool-org/LoopKit" "27c3916468fc5308f7aac54725a88d0762828c95" +github "tidepool-org/LoopKit" "35b68972d5188fa78dd7757533038b4fd0a31cc7" From e1a8ffe6839e18fe3517e33b7b146ef64ed15351 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2019 14:22:57 -0600 Subject: [PATCH 05/71] Point back to LoopKit/LoopKit --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index fdede6a12..05195c51b 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "tidepool-org/LoopKit" "dev" +github "LoopKit/LoopKit" "dev" diff --git a/Cartfile.resolved b/Cartfile.resolved index af77a0bc2..f1c591756 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "tidepool-org/LoopKit" "35b68972d5188fa78dd7757533038b4fd0a31cc7" +github "LoopKit/LoopKit" "797a4fffeb0b56c8ce47543ea6516b3b8c8e0857" From 23af71639041ca3a2a1b4120d3cd0a06c5d3fa01 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2019 19:50:57 -0600 Subject: [PATCH 06/71] fix off by one error --- MinimedKit/Models/PumpModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 1f5a5bbc5..1be57fc73 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -103,7 +103,7 @@ public enum PumpModel: String { public var supportedBasalRates: [Double] { if generation >= 23 { // 0.025 units (for rates between 0.0-0.975 U/h) - let rateGroup1 = ((0...38).map { Double($0) / Double(pulsesPerUnit) }) + let rateGroup1 = ((0...39).map { Double($0) / Double(pulsesPerUnit) }) // 0.05 units (for rates between 1-9.95 U/h) let rateGroup2 = ((20...199).map { Double($0) / Double(pulsesPerUnit/2) }) // 0.1 units (for rates between 10-35 U/h) From faecc1a8a46e4432231b2e3dc123ffa815e1210b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 6 Mar 2019 10:19:42 -0600 Subject: [PATCH 07/71] Remove a few queues and the Session semaphore (#496) --- .../PumpManager/MinimedPumpManager.swift | 4 +- MinimedKit/PumpManager/PumpOps.swift | 102 ++++++-------- RileyLinkBLEKit/PeripheralManager.swift | 5 +- RileyLinkBLEKit/RileyLinkDevice.swift | 125 ++++++++++-------- RileyLinkBLEKit/RileyLinkDeviceManager.swift | 6 +- RileyLinkKit/RileyLinkPumpManager.swift | 5 +- 6 files changed, 126 insertions(+), 121 deletions(-) diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 72088f085..13ec5b216 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -121,7 +121,8 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { state.rileyLinkConnectionManagerState = newValue } } - + + // Isolated to queue private var statusObservers = WeakSet() public func addStatusObserver(_ observer: PumpManagerStatusObserver) { @@ -139,6 +140,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { private func notifyStatusObservers() { let status = self.status pumpManagerDelegate?.pumpManager(self, didUpdate: status) + // TODO: Not thread-safe for observer in statusObservers { observer.pumpManager(self, didUpdate: status) } diff --git a/MinimedKit/PumpManager/PumpOps.swift b/MinimedKit/PumpManager/PumpOps.swift index 6b6ff57b9..7f3dca51e 100644 --- a/MinimedKit/PumpManager/PumpOps.swift +++ b/MinimedKit/PumpManager/PumpOps.swift @@ -13,6 +13,7 @@ import os.log public protocol PumpOpsDelegate: class { + // TODO: Audit clients of this as its called on the session queue func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) } @@ -23,24 +24,13 @@ public class PumpOps { public let pumpSettings: PumpSettings - private var pumpState: PumpState { - didSet { - delegate?.pumpOps(self, didChange: pumpState) + private let pumpState: Locked - NotificationCenter.default.post( - name: .PumpOpsStateDidChange, - object: self, - userInfo: [PumpOps.notificationPumpStateKey: pumpState] - ) - } - } + private let configuredDevices: Locked> = Locked(Set()) - private var configuredDevices: Set = Set() - + // Isolated to RileyLinkDeviceManager.sessionQueue private var sessionDevice: RileyLinkDevice? - private let sessionQueue = DispatchQueue(label: "com.rileylink.RileyLinkKit.PumpOps", qos: .utility) - private weak var delegate: PumpOpsDelegate? public init(pumpSettings: PumpSettings, pumpState: PumpState?, delegate: PumpOpsDelegate?) { @@ -48,58 +38,50 @@ public class PumpOps { self.delegate = delegate if let pumpState = pumpState { - self.pumpState = pumpState + self.pumpState = Locked(pumpState) } else { - self.pumpState = PumpState() - self.delegate?.pumpOps(self, didChange: self.pumpState) + let pumpState = PumpState() + self.pumpState = Locked(pumpState) + self.delegate?.pumpOps(self, didChange: pumpState) } } public func runSession(withName name: String, using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, _ block: @escaping (_ session: PumpOpsSession?) -> Void) { - sessionQueue.async { - deviceSelector { (device) in - guard let device = device else { - block(nil) - return - } - - self.runSession(withName: name, using: device, block) + deviceSelector { (device) in + guard let device = device else { + block(nil) + return } + + self.runSession(withName: name, using: device, block) } } public func runSession(withName name: String, using device: RileyLinkDevice, _ block: @escaping (_ session: PumpOpsSession) -> Void) { - sessionQueue.async { - let semaphore = DispatchSemaphore(value: 0) - - device.runSession(withName: name) { (commandSession) in - let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState, session: commandSession, delegate: self) - self.sessionDevice = device - if !commandSession.firmwareVersion.isUnknown { - self.configureDevice(device, with: session) - } else { - self.log.error("Skipping device configuration due to unknown firmware version") - } - - block(session) - self.sessionDevice = nil - semaphore.signal() + device.runSession(withName: name) { (commandSession) in + let session = PumpOpsSession(settings: self.pumpSettings, pumpState: self.pumpState.value, session: commandSession, delegate: self) + self.sessionDevice = device + if !commandSession.firmwareVersion.isUnknown { + self.configureDevice(device, with: session) + } else { + self.log.error("Skipping device configuration due to unknown firmware version") } - semaphore.wait() + block(session) + self.sessionDevice = nil } } // Must be called from within the RileyLinkDevice sessionQueue private func configureDevice(_ device: RileyLinkDevice, with session: PumpOpsSession) { - guard !self.configuredDevices.contains(device) else { + guard !self.configuredDevices.value.contains(device) else { return } log.default("Configuring RileyLinkDevice: %{public}@", String(describing: device.deviceURI)) do { - _ = try session.configureRadio(for: pumpSettings.pumpRegion, frequency: pumpState.lastValidFrequency) + _ = try session.configureRadio(for: pumpSettings.pumpRegion, frequency: pumpState.value.lastValidFrequency) } catch let error { // Ignore the error and let the block run anyway log.error("Error configuring device: %{public}@", String(describing: error)) @@ -109,7 +91,9 @@ public class PumpOps { NotificationCenter.default.post(name: .DeviceRadioConfigDidChange, object: device) NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceRadioConfigDidChange, object: device) NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceConnectionStateDidChange, object: device) - configuredDevices.insert(device) + _ = configuredDevices.mutate { (value) in + value.insert(device) + } } @objc private func deviceRadioConfigDidChange(_ note: Notification) { @@ -120,30 +104,32 @@ public class PumpOps { NotificationCenter.default.removeObserver(self, name: .DeviceRadioConfigDidChange, object: device) NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device) - // TODO: Unsafe access - self.configuredDevices.remove(device) + _ = configuredDevices.mutate { (value) in + value.remove(device) + } } public func getPumpState(_ completion: @escaping (_ state: PumpState) -> Void) { - sessionQueue.async { - completion(self.pumpState) - } + completion(self.pumpState.value) } } - +// Delivered on RileyLinkDeviceManager.sessionQueue extension PumpOps: PumpOpsSessionDelegate { - func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession) { - sessionQueue.async { - if let sessionDevice = self.sessionDevice { - self.configuredDevices = [sessionDevice] - } + if let sessionDevice = self.sessionDevice { + self.configuredDevices.value = [sessionDevice] } } func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState) { - self.pumpState = state + self.pumpState.value = state + delegate?.pumpOps(self, didChange: state) + NotificationCenter.default.post( + name: .PumpOpsStateDidChange, + object: self, + userInfo: [PumpOps.notificationPumpStateKey: pumpState] + ) } } @@ -153,8 +139,8 @@ extension PumpOps: CustomDebugStringConvertible { return [ "### PumpOps", "pumpSettings: \(String(reflecting: pumpSettings))", - "pumpState: \(String(reflecting: pumpState))", - "configuredDevices: \(configuredDevices.map({ $0.peripheralIdentifier.uuidString }))", + "pumpState: \(String(reflecting: pumpState.value))", + "configuredDevices: \(configuredDevices.value.map({ $0.peripheralIdentifier.uuidString }))", ].joined(separator: "\n") } } diff --git a/RileyLinkBLEKit/PeripheralManager.swift b/RileyLinkBLEKit/PeripheralManager.swift index c845733f0..c991c8750 100644 --- a/RileyLinkBLEKit/PeripheralManager.swift +++ b/RileyLinkBLEKit/PeripheralManager.swift @@ -34,7 +34,7 @@ class PeripheralManager: NSObject { } /// The dispatch queue used to serialize operations on the peripheral - let queue = DispatchQueue(label: "com.loopkit.PeripheralManager.queue", qos: .utility) + let queue: DispatchQueue /// The condition used to signal command completion private let commandLock = NSCondition() @@ -61,10 +61,11 @@ class PeripheralManager: NSObject { } // Called from RileyLinkDeviceManager.managerQueue - init(peripheral: CBPeripheral, configuration: Configuration, centralManager: CBCentralManager) { + init(peripheral: CBPeripheral, configuration: Configuration, centralManager: CBCentralManager, queue: DispatchQueue) { self.peripheral = peripheral self.central = centralManager self.configuration = configuration + self.queue = queue super.init() diff --git a/RileyLinkBLEKit/RileyLinkDevice.swift b/RileyLinkBLEKit/RileyLinkDevice.swift index 5c734a9cd..e43e87066 100644 --- a/RileyLinkBLEKit/RileyLinkDevice.swift +++ b/RileyLinkBLEKit/RileyLinkDevice.swift @@ -21,7 +21,7 @@ public class RileyLinkDevice { // Confined to `manager.queue` private var radioFirmwareVersion: RadioFirmwareVersion? - // Confined to `queue` + // Confined to `lock` private var idleListeningState: IdleListeningState = .disabled { didSet { switch (oldValue, idleListeningState) { @@ -35,24 +35,23 @@ public class RileyLinkDevice { } } - // Confined to `queue` + // Confined to `lock` private var lastIdle: Date? - // Confined to `queue` + // Confined to `lock` // TODO: Tidy up this state/preference machine private var isIdleListeningPending = false - // Confined to `queue` + // Confined to `lock` private var isTimerTickEnabled = true /// Serializes access to device state - private let queue = DispatchQueue(label: "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.queue", qos: .userInitiated) + private var lock = os_unfair_lock() /// The queue used to serialize sessions and observe when they've drained private let sessionQueue: OperationQueue = { let queue = OperationQueue() queue.name = "com.rileylink.RileyLinkBLEKit.RileyLinkDevice.sessionQueue" - queue.qualityOfService = .utility queue.maxConcurrentOperationCount = 1 return queue @@ -135,17 +134,17 @@ extension RileyLinkDevice { } public func getStatus(_ completion: @escaping (_ status: Status) -> Void) { - queue.async { - let lastIdle = self.lastIdle - - self.manager.queue.async { - completion(Status( - lastIdle: lastIdle, - name: self.name, - bleFirmwareVersion: self.bleFirmwareVersion, - radioFirmwareVersion: self.radioFirmwareVersion - )) - } + os_unfair_lock_lock(&lock) + let lastIdle = self.lastIdle + os_unfair_lock_unlock(&lock) + + self.manager.queue.async { + completion(Status( + lastIdle: lastIdle, + name: self.name, + bleFirmwareVersion: self.bleFirmwareVersion, + radioFirmwareVersion: self.radioFirmwareVersion + )) } } } @@ -178,42 +177,48 @@ extension RileyLinkDevice { } func setIdleListeningState(_ state: IdleListeningState) { - queue.async { - self.idleListeningState = state - } + os_unfair_lock_lock(&lock) + self.idleListeningState = state + os_unfair_lock_unlock(&lock) } public func assertIdleListening(forceRestart: Bool = false) { - queue.async { - guard case .enabled(timeout: let timeout, channel: let channel) = self.idleListeningState else { - return - } + os_unfair_lock_lock(&lock) + guard case .enabled(timeout: let timeout, channel: let channel) = self.idleListeningState else { + os_unfair_lock_unlock(&lock) + return + } - guard case .connected = self.manager.peripheral.state, case .poweredOn? = self.manager.central?.state else { - return - } + guard case .connected = self.manager.peripheral.state, case .poweredOn? = self.manager.central?.state else { + os_unfair_lock_unlock(&lock) + return + } - guard forceRestart || (self.lastIdle ?? .distantPast).timeIntervalSinceNow < -timeout else { - return - } - - guard !self.isIdleListeningPending else { - return - } - - self.isIdleListeningPending = true - self.log.debug("Enqueuing idle listening") - - self.manager.startIdleListening(idleTimeout: timeout, channel: channel) { (error) in - self.queue.async { - if let error = error { - self.log.error("Unable to start idle listening: %@", String(describing: error)) - } else { - self.lastIdle = Date() - NotificationCenter.default.post(name: .DeviceDidStartIdle, object: self) - } - self.isIdleListeningPending = false - } + guard forceRestart || (self.lastIdle ?? .distantPast).timeIntervalSinceNow < -timeout else { + os_unfair_lock_unlock(&lock) + return + } + + guard !self.isIdleListeningPending else { + os_unfair_lock_unlock(&lock) + return + } + + self.isIdleListeningPending = true + os_unfair_lock_unlock(&lock) + self.log.debug("Enqueuing idle listening") + + self.manager.startIdleListening(idleTimeout: timeout, channel: channel) { (error) in + os_unfair_lock_lock(&self.lock) + self.isIdleListeningPending = false + + if let error = error { + self.log.error("Unable to start idle listening: %@", String(describing: error)) + os_unfair_lock_unlock(&self.lock) + } else { + self.lastIdle = Date() + os_unfair_lock_unlock(&self.lock) + NotificationCenter.default.post(name: .DeviceDidStartIdle, object: self) } } } @@ -223,17 +228,19 @@ extension RileyLinkDevice { // MARK: - Timer tick management extension RileyLinkDevice { func setTimerTickEnabled(_ enabled: Bool) { - queue.async { - self.isTimerTickEnabled = enabled - self.assertTimerTick() - } + os_unfair_lock_lock(&lock) + self.isTimerTickEnabled = enabled + os_unfair_lock_unlock(&lock) + self.assertTimerTick() } func assertTimerTick() { - queue.async { - if self.isTimerTickEnabled != self.manager.timerTickEnabled { - self.manager.setTimerTickEnabled(self.isTimerTickEnabled) - } + os_unfair_lock_lock(&self.lock) + let isTimerTickEnabled = self.isTimerTickEnabled + os_unfair_lock_unlock(&self.lock) + + if isTimerTickEnabled != self.manager.timerTickEnabled { + self.manager.setTimerTickEnabled(isTimerTickEnabled) } } } @@ -362,6 +369,12 @@ extension RileyLinkDevice: PeripheralManagerDelegate { extension RileyLinkDevice: CustomDebugStringConvertible { public var debugDescription: String { + os_unfair_lock_lock(&lock) + let lastIdle = self.lastIdle + let isIdleListeningPending = self.isIdleListeningPending + let isTimerTickEnabled = self.isTimerTickEnabled + os_unfair_lock_unlock(&lock) + return [ "## RileyLinkDevice", "* name: \(name ?? "")", diff --git a/RileyLinkBLEKit/RileyLinkDeviceManager.swift b/RileyLinkBLEKit/RileyLinkDeviceManager.swift index f76423464..e21f74f6a 100644 --- a/RileyLinkBLEKit/RileyLinkDeviceManager.swift +++ b/RileyLinkBLEKit/RileyLinkDeviceManager.swift @@ -15,7 +15,9 @@ public class RileyLinkDeviceManager: NSObject { // Isolated to centralQueue private var central: CBCentralManager! - private let centralQueue = DispatchQueue(label: "com.rileylink.RileyLinkBLEKit.BluetoothManager.centralQueue", qos: .utility) + private let centralQueue = DispatchQueue(label: "com.rileylink.RileyLinkBLEKit.BluetoothManager.centralQueue", qos: .unspecified) + + internal let sessionQueue = DispatchQueue(label: "com.rileylink.RileyLinkBLEKit.RileyLinkDeviceManager.sessionQueue", qos: .unspecified) // Isolated to centralQueue private var devices: [RileyLinkDevice] = [] { @@ -159,7 +161,7 @@ extension RileyLinkDeviceManager { if let device = device { device.manager.peripheral = peripheral } else { - device = RileyLinkDevice(peripheralManager: PeripheralManager(peripheral: peripheral, configuration: .rileyLink, centralManager: central)) + device = RileyLinkDevice(peripheralManager: PeripheralManager(peripheral: peripheral, configuration: .rileyLink, centralManager: central, queue: sessionQueue)) device.setTimerTickEnabled(timerTickEnabled) device.setIdleListeningState(idleListeningState) diff --git a/RileyLinkKit/RileyLinkPumpManager.swift b/RileyLinkKit/RileyLinkPumpManager.swift index 0e72166da..6a6a1eedc 100644 --- a/RileyLinkKit/RileyLinkPumpManager.swift +++ b/RileyLinkKit/RileyLinkPumpManager.swift @@ -23,14 +23,15 @@ open class RileyLinkPumpManager { /// Manages all the RileyLinks - access to management is optional public let rileyLinkConnectionManager: RileyLinkConnectionManager? - + + // TODO: Not thread-safe open var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? /// Access to rileylink devices public let rileyLinkDeviceProvider: RileyLinkDeviceProvider // TODO: Evaluate if this is necessary - public let queue = DispatchQueue(label: "com.loopkit.RileyLinkPumpManager", qos: .utility) + public let queue = DispatchQueue(label: "com.loopkit.RileyLinkPumpManager", qos: .unspecified) /// Isolated to queue // TODO: Put this on each RileyLinkDevice? From 752909a2c1dbf01975128644b25ad7cb4682fe9f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 8 Mar 2019 16:00:46 -0600 Subject: [PATCH 08/71] Bug fixes (#500) - Fixes a deadlock case in RileyLinkDevice - Marks frameworks as using only extension-safe API - Fixes Swift warnings - Reduces the number of tune retries when tuning automatically --- .../MySentryPumpStatusMessageBody.swift | 9 +- MinimedKit/PumpEvents/PumpEvent.swift | 2 +- .../PumpManager/MinimedPumpManager.swift | 5 +- MinimedKit/Radio/CRC8.swift | 2 +- .../Radio/FourByteSixByteEncoding.swift | 4 +- RileyLink.xcodeproj/project.pbxproj | 10 +- RileyLink/Extensions/BatteryIndicator.swift | 2 +- .../View Controllers/MainViewController.swift | 2 - RileyLinkBLEKit/PeripheralManager.swift | 12 +- RileyLinkBLEKit/RileyLinkDevice.swift | 29 +++-- RileyLinkKit/PumpOpsSession.swift | 7 +- .../CommandResponseViewController.swift | 116 ------------------ 12 files changed, 41 insertions(+), 159 deletions(-) delete mode 100644 RileyLinkKitUI/CommandResponseViewController.swift diff --git a/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift b/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift index 5ef79cd40..686ae8687 100644 --- a/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift +++ b/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift @@ -184,10 +184,11 @@ public struct MySentryPumpStatusMessageBody: MessageBody, DictionaryRepresentabl self.sensorRemainingHours = Int(sensorRemainingHours) let matchingHour: UInt8 = rxData[20] - nextSensorCalibrationDateComponents = DateComponents() - nextSensorCalibrationDateComponents?.hour = Int(matchingHour) - nextSensorCalibrationDateComponents?.minute = Int(rxData[21]) - nextSensorCalibrationDateComponents?.calendar = calendar + var nextSensorCalibrationDateComponents = DateComponents() + nextSensorCalibrationDateComponents.hour = Int(matchingHour) + nextSensorCalibrationDateComponents.minute = Int(rxData[21]) + nextSensorCalibrationDateComponents.calendar = calendar + self.nextSensorCalibrationDateComponents = nextSensorCalibrationDateComponents } public var dictionaryRepresentation: [String: Any] { diff --git a/MinimedKit/PumpEvents/PumpEvent.swift b/MinimedKit/PumpEvents/PumpEvent.swift index e74f20e57..0e7ffe5ed 100644 --- a/MinimedKit/PumpEvents/PumpEvent.swift +++ b/MinimedKit/PumpEvents/PumpEvent.swift @@ -23,7 +23,7 @@ public protocol PumpEvent : DictionaryRepresentable { } public extension PumpEvent { - public func isDelayedAppend(with pumpModel: PumpModel) -> Bool { + func isDelayedAppend(with pumpModel: PumpModel) -> Bool { // Delays only occur for bolus events guard let bolus = self as? BolusNormalPumpEvent else { return false diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 13ec5b216..cfd8a5a78 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -112,7 +112,6 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } - override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { get { return state.rileyLinkConnectionManagerState @@ -261,7 +260,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { completion(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)) return } - + let sessionName: String = { switch state { case .suspend: @@ -354,7 +353,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { if lastTuned.timeIntervalSinceNow <= -tuneTolerance { pumpOps.runSession(withName: "Tune pump", using: device) { (session) in do { - let scanResult = try session.tuneRadio() + let scanResult = try session.tuneRadio(attempts: 1) self.log.default("Device %{public}@ auto-tuned to %{public}@ MHz", device.name ?? "", String(describing: scanResult.bestFrequency)) } catch let error { self.log.error("Device %{public}@ auto-tune failed with error: %{public}@", device.name ?? "", String(describing: error)) diff --git a/MinimedKit/Radio/CRC8.swift b/MinimedKit/Radio/CRC8.swift index e60054e6c..30a9eeafc 100644 --- a/MinimedKit/Radio/CRC8.swift +++ b/MinimedKit/Radio/CRC8.swift @@ -12,7 +12,7 @@ fileprivate let crcTable: [UInt8] = [0x0, 0x9B, 0xAD, 0x36, 0xC1, 0x5A, 0x6C, 0x public extension Sequence where Element == UInt8 { - public func crc8() -> UInt8 { + func crc8() -> UInt8 { var crc: UInt8 = 0 for byte in self { diff --git a/MinimedKit/Radio/FourByteSixByteEncoding.swift b/MinimedKit/Radio/FourByteSixByteEncoding.swift index 82bb13a50..6d03f6b94 100644 --- a/MinimedKit/Radio/FourByteSixByteEncoding.swift +++ b/MinimedKit/Radio/FourByteSixByteEncoding.swift @@ -14,7 +14,7 @@ fileprivate let codesRev = Dictionary(uniqueKeysWithValues: codes.en public extension Sequence where Element == UInt8 { - public func decode4b6b() -> [UInt8]? { + func decode4b6b() -> [UInt8]? { var buffer = [UInt8]() var availBits = 0 var bitAccumulator = 0 @@ -40,7 +40,7 @@ public extension Sequence where Element == UInt8 { return buffer } - public func encode4b6b() -> [UInt8] { + func encode4b6b() -> [UInt8] { var buffer = [UInt8]() var bitAccumulator = 0x0 var bitcount = 0 diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 4a0f3f705..63d66b296 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -172,7 +172,6 @@ 43D5E7951FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; }; 43D5E7961FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 43D5E79A1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */; }; - 43D5E79B1FAF7C47004ACDB7 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C170C9961CECD80000F3D8E5 /* CommandResponseViewController.swift */; }; 43D5E79C1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */; }; 43D5E79F1FAF7C98004ACDB7 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; }; 43D5E7A01FAF7CCA004ACDB7 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; }; @@ -1073,7 +1072,6 @@ C16E61212208C7A80069F357 /* ReservoirReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservoirReading.swift; sourceTree = ""; }; C16E61252208EC580069F357 /* MinimedHUDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedHUDProvider.swift; sourceTree = ""; }; C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = ""; }; - C170C9961CECD80000F3D8E5 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = ""; }; C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDeviceTableViewController.swift; sourceTree = ""; }; C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonPressCarelinkMessageBody.swift; sourceTree = ""; }; C1711A591C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBody.swift; sourceTree = ""; }; @@ -1526,7 +1524,6 @@ 7D23679521252EBC0028B67D /* Localizable.strings */, 43709AEB20E0056F00F941B3 /* RileyLinkKitUI.xcassets */, C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */, - C170C9961CECD80000F3D8E5 /* CommandResponseViewController.swift */, 439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */, C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */, 435D26B320DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift */, @@ -2782,7 +2779,6 @@ 43D5E79A1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewCell.swift in Sources */, 43D5E79F1FAF7C98004ACDB7 /* IdentifiableClass.swift in Sources */, 435D26B420DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift in Sources */, - 43D5E79B1FAF7C47004ACDB7 /* CommandResponseViewController.swift in Sources */, 43709ABF20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift in Sources */, 43709ABD20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift in Sources */, 43D5E79C1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewController.swift in Sources */, @@ -3689,6 +3685,7 @@ 4352A72F20DEC9B700CAC200 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3730,6 +3727,7 @@ 4352A73020DEC9B700CAC200 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -3960,7 +3958,7 @@ 43D5E7981FAF7BFB004ACDB7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -4000,7 +3998,7 @@ 43D5E7991FAF7BFB004ACDB7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; diff --git a/RileyLink/Extensions/BatteryIndicator.swift b/RileyLink/Extensions/BatteryIndicator.swift index f049bf0ed..5bc2b38fc 100644 --- a/RileyLink/Extensions/BatteryIndicator.swift +++ b/RileyLink/Extensions/BatteryIndicator.swift @@ -10,7 +10,7 @@ import NightscoutUploadKit import MinimedKit public extension BatteryIndicator { - public init?(batteryStatus: MinimedKit.BatteryStatus) { + init?(batteryStatus: MinimedKit.BatteryStatus) { switch batteryStatus { case .low: self = .low diff --git a/RileyLink/View Controllers/MainViewController.swift b/RileyLink/View Controllers/MainViewController.swift index 2bd21b9e1..e78ebdc8f 100644 --- a/RileyLink/View Controllers/MainViewController.swift +++ b/RileyLink/View Controllers/MainViewController.swift @@ -158,8 +158,6 @@ class MainViewController: RileyLinkSettingsViewController { // MARK: - UITableViewDelegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let sender = tableView.cellForRow(at: indexPath) - switch Section(rawValue: indexPath.section)! { case .rileyLinks: let device = devicesDataSource.devices[indexPath.row] diff --git a/RileyLinkBLEKit/PeripheralManager.swift b/RileyLinkBLEKit/PeripheralManager.swift index c991c8750..55f5413d2 100644 --- a/RileyLinkBLEKit/PeripheralManager.swift +++ b/RileyLinkBLEKit/PeripheralManager.swift @@ -400,6 +400,8 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { commandLock.lock() + var notifyDelegate = false + if let index = commandConditions.index(where: { (condition) -> Bool in if case .valueUpdate(characteristic: characteristic, matching: let matching) = condition { return matching?(characteristic.value) ?? true @@ -416,13 +418,15 @@ extension PeripheralManager: CBPeripheralDelegate { } else if let macro = configuration.valueUpdateMacros[characteristic.uuid] { macro(self) } else if commandConditions.isEmpty { - defer { // execute after the unlock - // If we weren't expecting this notification, pass it along to the delegate - delegate?.peripheralManager(self, didUpdateValueFor: characteristic) - } + notifyDelegate = true // execute after the unlock } commandLock.unlock() + + if notifyDelegate { + // If we weren't expecting this notification, pass it along to the delegate + delegate?.peripheralManager(self, didUpdateValueFor: characteristic) + } } func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { diff --git a/RileyLinkBLEKit/RileyLinkDevice.swift b/RileyLinkBLEKit/RileyLinkDevice.swift index e43e87066..dc0e5ca83 100644 --- a/RileyLinkBLEKit/RileyLinkDevice.swift +++ b/RileyLinkBLEKit/RileyLinkDevice.swift @@ -22,18 +22,7 @@ public class RileyLinkDevice { private var radioFirmwareVersion: RadioFirmwareVersion? // Confined to `lock` - private var idleListeningState: IdleListeningState = .disabled { - didSet { - switch (oldValue, idleListeningState) { - case (.disabled, .enabled): - assertIdleListening(forceRestart: true) - case (.enabled, .enabled): - assertIdleListening(forceRestart: false) - default: - break - } - } - } + private var idleListeningState: IdleListeningState = .disabled // Confined to `lock` private var lastIdle: Date? @@ -115,8 +104,8 @@ extension RileyLinkDevice: Equatable, Hashable { return lhs === rhs } - public var hashValue: Int { - return peripheralIdentifier.hashValue + public func hash(into hasher: inout Hasher) { + hasher.combine(peripheralIdentifier) } } @@ -178,8 +167,18 @@ extension RileyLinkDevice { func setIdleListeningState(_ state: IdleListeningState) { os_unfair_lock_lock(&lock) - self.idleListeningState = state + let oldValue = idleListeningState + idleListeningState = state os_unfair_lock_unlock(&lock) + + switch (oldValue, state) { + case (.disabled, .enabled): + assertIdleListening(forceRestart: true) + case (.enabled, .enabled): + assertIdleListening(forceRestart: false) + default: + break + } } public func assertIdleListening(forceRestart: Bool = false) { diff --git a/RileyLinkKit/PumpOpsSession.swift b/RileyLinkKit/PumpOpsSession.swift index ae4dddf62..877d4c7ad 100644 --- a/RileyLinkKit/PumpOpsSession.swift +++ b/RileyLinkKit/PumpOpsSession.swift @@ -834,11 +834,11 @@ extension PumpOpsSession { /// - PumpOpsError.deviceError /// - PumpOpsError.noResponse /// - PumpOpsError.rfCommsFailure - public func tuneRadio() throws -> FrequencyScanResults { + public func tuneRadio(attempts: Int = 3) throws -> FrequencyScanResults { let region = self.settings.pumpRegion do { - let results = try scanForPump(in: region.scanFrequencies, fallback: pump.lastValidFrequency) + let results = try scanForPump(in: region.scanFrequencies, fallback: pump.lastValidFrequency, tries: attempts) pump.lastValidFrequency = results.bestFrequency pump.lastTuned = Date() @@ -908,7 +908,7 @@ extension PumpOpsSession { /// - PumpOpsError.noResponse /// - PumpOpsError.rfCommsFailure /// - LocalizedError - private func scanForPump(in frequencies: [Measurement], fallback: Measurement?) throws -> FrequencyScanResults { + private func scanForPump(in frequencies: [Measurement], fallback: Measurement?, tries: Int = 3) throws -> FrequencyScanResults { var trials = [FrequencyTrial]() @@ -923,7 +923,6 @@ extension PumpOpsSession { } for freq in frequencies { - let tries = 3 var trial = FrequencyTrial(frequency: freq) try session.setBaseFrequency(freq) diff --git a/RileyLinkKitUI/CommandResponseViewController.swift b/RileyLinkKitUI/CommandResponseViewController.swift deleted file mode 100644 index 79052e191..000000000 --- a/RileyLinkKitUI/CommandResponseViewController.swift +++ /dev/null @@ -1,116 +0,0 @@ -// -// CommandResponseViewController.swift -// Naterade -// -// Created by Nathan Racklyeft on 3/5/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import UIKit -import os.log - - -class CommandResponseViewController: UIViewController { - typealias Command = (_ completionHandler: @escaping (_ responseText: String) -> Void) -> String - - init(command: @escaping Command) { - self.command = command - - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public var fileName: String? - - private let uuid = UUID() - - private let command: Command - - private lazy var textView = UITextView() - - override func loadView() { - self.view = textView - } - - override func viewDidLoad() { - super.viewDidLoad() - - if #available(iOS 11.0, *) { - textView.contentInsetAdjustmentBehavior = .always - } - - let font = UIFont(name: "Menlo-Regular", size: 14) - if #available(iOS 11.0, *), let font = font { - let metrics = UIFontMetrics(forTextStyle: .body) - textView.font = metrics.scaledFont(for: font) - } else { - textView.font = font - } - - textView.text = command { [weak self] (responseText) -> Void in - var newText = self?.textView.text ?? "" - newText += "\n\n" - newText += responseText - self?.textView.text = newText - } - textView.isEditable = false - - navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareText(_:))) - } - - @objc func shareText(_: AnyObject?) { - let title = fileName ?? "\(self.title ?? uuid.uuidString).txt" - - guard let item = SharedResponse(text: textView.text, title: title) else { - return - } - - let activityVC = UIActivityViewController(activityItems: [item], applicationActivities: nil) - - present(activityVC, animated: true, completion: nil) - } -} - - -private class SharedResponse: NSObject, UIActivityItemSource { - - let title: String - let fileURL: URL - - init?(text: String, title: String) { - self.title = title - - var url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - url.appendPathComponent(title, isDirectory: false) - - do { - try text.write(to: url, atomically: true, encoding: .utf8) - } catch let error { - os_log("Failed to write to file %{public}@: %{public}@", log: .default, type: .error, title, String(describing: error)) - return nil - } - - fileURL = url - - super.init() - } - - public func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { - return fileURL - } - - public func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - return fileURL - } - - public func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { - return title - } - - public func activityViewController(_ activityViewController: UIActivityViewController, dataTypeIdentifierForActivityType activityType: UIActivity.ActivityType?) -> String { - return "public.utf8-plain-text" - } -} From 930c586eca2487864d3611a50485602530794270 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 9 Mar 2019 06:53:13 -0600 Subject: [PATCH 09/71] Add new pump precision methods required by PumpManager protocol (#501) * Add new pump precision methods required by PumpManager protocol * Fix boundary bug, and update to LoopKit changes --- MinimedKit/Models/PumpModel.swift | 12 ++++++++++++ MinimedKit/PumpManager/MinimedPumpManager.swift | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 1be57fc73..a9ea53ef5 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -115,6 +115,18 @@ public enum PumpModel: String { } } + public var maximumBolusVolume: Double { + return 25 + } + + public var maximumBasalRate: Double { + return 35 + } + + public var supportedBolusVolumes: [Double] { + return supportedBasalRates.filter { $0 <= maximumBolusVolume } + } + public var maximumBasalScheduleEntryCount: Int { return 48 } diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index cfd8a5a78..9a14c150b 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -16,11 +16,22 @@ public protocol MinimedPumpManagerStateObserver: class { } public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { + public func roundToSupportedBasalRate(unitsPerHour: Double) -> Double { + return supportedBasalRates.filter({$0 <= unitsPerHour}).max() ?? 0 + } + + public func roundToSupportedBolusVolume(units: Double) -> Double { + return supportedBolusVolumes.filter({$0 <= units}).max() ?? 0 + } public var supportedBasalRates: [Double] { return state.pumpModel.supportedBasalRates } + public var supportedBolusVolumes: [Double] { + return state.pumpModel.supportedBolusVolumes + } + public var maximumBasalScheduleEntryCount: Int { return state.pumpModel.maximumBasalScheduleEntryCount } @@ -28,7 +39,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { public var minimumBasalScheduleEntryDuration: TimeInterval { return state.pumpModel.minimumBasalScheduleEntryDuration } - + public static let managerIdentifier: String = "Minimed500" public func roundToDeliveryIncrement(units: Double) -> Double { From 8418b57c1983bdefaec7ccb456c86d5efacf40e7 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 9 Mar 2019 07:05:40 -0600 Subject: [PATCH 10/71] Update loopkit rev --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index f1c591756..03db6147a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "LoopKit/LoopKit" "797a4fffeb0b56c8ce47543ea6516b3b8c8e0857" +github "LoopKit/LoopKit" "55658718091c4676557c2d65293395e276218037" From 6294968c8d7ae8c77e740ee75bd19eabe5a26726 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 27 Mar 2019 13:00:43 -0500 Subject: [PATCH 11/71] Bolus progress and cancelling support (#503) * Bolus progress and cancelling support * Point to LoopKit with bolus progress changes * Support multiple observers of dose progress * Notify outside of lock * subclass DoseProgressTimerEstimator * Protocol naming update * PumpManager createBolusProgressReporter implementation for minimed * Updates from reviews * Use LoopKit dev * Add LoopKit dependency for RileyLinkBLEKit * Update tests with new initializer signature for BolusCarelinkMessageBody --- Cartfile.resolved | 2 +- Common/Locked.swift | 40 --------- .../Messages/BolusCarelinkMessageBody.swift | 6 +- MinimedKit/Messages/PumpMessage.swift | 2 +- .../ReadRemainingInsulinMessageBody.swift | 6 +- MinimedKit/Models/PumpModel.swift | 40 ++++++--- .../PumpEvents/BolusNormalPumpEvent.swift | 2 +- .../MinimedDoseProgressEstimator.swift | 78 +++++++++++++++++ .../PumpManager/MinimedPumpManager.swift | 85 +++++++++++++------ MinimedKit/PumpManager/PumpOps.swift | 2 +- .../BolusCarelinkMessageBodyTests.swift | 10 +-- ...ReadRemainingInsulinMessageBodyTests.swift | 4 +- RileyLink.xcodeproj/project.pbxproj | 23 +++-- RileyLinkBLEKit/CommandSession.swift | 2 +- RileyLinkBLEKit/RFPacket.swift | 7 +- RileyLinkBLEKit/RileyLinkDevice.swift | 2 +- RileyLinkBLEKit/RileyLinkDeviceManager.swift | 1 + RileyLinkKit/PumpOpsSession.swift | 4 +- 18 files changed, 205 insertions(+), 111 deletions(-) delete mode 100644 Common/Locked.swift create mode 100644 MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift diff --git a/Cartfile.resolved b/Cartfile.resolved index 03db6147a..267678eda 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "LoopKit/LoopKit" "55658718091c4676557c2d65293395e276218037" +github "LoopKit/LoopKit" "e386a24577244ee6b2add52e8b2aff0385c2200b" diff --git a/Common/Locked.swift b/Common/Locked.swift deleted file mode 100644 index fd6e35b10..000000000 --- a/Common/Locked.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Locked.swift -// LoopKit -// -// Copyright © 2018 LoopKit Authors. All rights reserved. -// - -import os.lock - - -internal class Locked { - private var lock = os_unfair_lock() - private var _value: T - - init(_ value: T) { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - _value = value - } - - var value: T { - get { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - return _value - } - set { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - _value = newValue - } - } - - func mutate(_ changes: (_ value: inout T) -> Void) -> T { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - changes(&_value) - return _value - } -} diff --git a/MinimedKit/Messages/BolusCarelinkMessageBody.swift b/MinimedKit/Messages/BolusCarelinkMessageBody.swift index 61362d30e..c43eda9b3 100644 --- a/MinimedKit/Messages/BolusCarelinkMessageBody.swift +++ b/MinimedKit/Messages/BolusCarelinkMessageBody.swift @@ -11,12 +11,12 @@ import Foundation public class BolusCarelinkMessageBody: CarelinkLongMessageBody { - public convenience init(units: Double, strokesPerUnit: Int = 10) { + public convenience init(units: Double, insulinBitPackingScale: Int = 10) { let length: Int let scrollRate: Int - if strokesPerUnit >= 40 { + if insulinBitPackingScale >= 40 { length = 2 // 40-stroke pumps scroll faster for higher unit values @@ -33,7 +33,7 @@ public class BolusCarelinkMessageBody: CarelinkLongMessageBody { scrollRate = 1 } - let strokes = Int(units * Double(strokesPerUnit / scrollRate)) * scrollRate + let strokes = Int(units * Double(insulinBitPackingScale / scrollRate)) * scrollRate let data = Data(hexadecimalString: String(format: "%02x%0\(2 * length)x", length, strokes))! diff --git a/MinimedKit/Messages/PumpMessage.swift b/MinimedKit/Messages/PumpMessage.swift index 471ae79aa..700c5b5cc 100644 --- a/MinimedKit/Messages/PumpMessage.swift +++ b/MinimedKit/Messages/PumpMessage.swift @@ -48,7 +48,7 @@ public struct PumpMessage : CustomStringConvertible { } public var description: String { - return String(format: LocalizedString("PumpMessage(%1$@, %2$@, %3$@, %4$@)", comment: "The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data"), String(describing: packetType), String(describing: messageType), String(describing: address), String(describing: self.messageBody.txData)) + return String(format: "PumpMessage(%1$@, %2$@, %3$@, %4$@)", String(describing: packetType), String(describing: messageType), address.hexadecimalString, self.messageBody.txData.hexadecimalString) } } diff --git a/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift b/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift index b220082a7..9ea0fbbdf 100644 --- a/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift +++ b/MinimedKit/Messages/ReadRemainingInsulinMessageBody.swift @@ -10,18 +10,18 @@ import Foundation public class ReadRemainingInsulinMessageBody: CarelinkLongMessageBody { - public func getUnitsRemainingForStrokes(_ strokesPerUnit: Int) -> Double { + public func getUnitsRemaining(insulinBitPackingScale: Int) -> Double { let strokes: Data - switch strokesPerUnit { + switch insulinBitPackingScale { case let x where x > 10: strokes = rxData.subdata(in: 3..<5) default: strokes = rxData.subdata(in: 1..<3) } - return Double(Int(bigEndianBytes: strokes)) / Double(strokesPerUnit) + return Double(Int(bigEndianBytes: strokes)) / Double(insulinBitPackingScale) } public required init?(rxData: Data) { diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index a9ea53ef5..d478cb7e2 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -68,10 +68,15 @@ public enum PumpModel: String { } /// Newer models allow higher precision delivery, and have bit packing to accomodate this. - public var strokesPerUnit: Int { + public var insulinBitPackingScale: Int { return (generation >= 23) ? 40 : 10 } + /// Pulses per unit is the inverse of the minimum volume of delivery. + public var pulsesPerUnit: Int { + return (generation >= 23) ? 40 : 20 + } + public var reservoirCapacity: Int { switch size { case 5: @@ -83,18 +88,6 @@ public enum PumpModel: String { } } - public var constrainsBolusDeliveryTimeTo5Minutes: Bool { - return generation >= 23 - } - - public var pulsesPerUnit: Int { - if generation >= 23 { - return 40 - } else { - return 20 - } - } - /// Even though this is capped by the system at 250 / 10 U, the message takes a UInt16. var usesTwoBytesForMaxBolus: Bool { return generation >= 23 @@ -134,6 +127,27 @@ public enum PumpModel: String { public var minimumBasalScheduleEntryDuration: TimeInterval { return .minutes(30) } + + public var isDeliveryRateVariable: Bool { + return generation >= 23 + } + + public func bolusDeliveryTime(units: Double) -> TimeInterval { + let unitsPerMinute: Double + if isDeliveryRateVariable { + switch units { + case let u where u < 1.0: + unitsPerMinute = 0.75 + case let u where u > 7.5: + unitsPerMinute = units / 5 + default: + unitsPerMinute = 1.5 + } + } else { + unitsPerMinute = 1.5 + } + return TimeInterval(minutes: units / unitsPerMinute) + } } diff --git a/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift b/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift index a0424099d..4c98bba58 100644 --- a/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift +++ b/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift @@ -68,7 +68,7 @@ public struct BolusNormalPumpEvent: TimestampedPumpEvent { } func decodeInsulin(from bytes: Data) -> Double { - return Double(Int(bigEndianBytes: bytes)) / Double(pumpModel.strokesPerUnit) + return Double(Int(bigEndianBytes: bytes)) / Double(pumpModel.insulinBitPackingScale) } length = BolusNormalPumpEvent.calculateLength(pumpModel.larger) diff --git a/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift b/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift new file mode 100644 index 000000000..dc66427fc --- /dev/null +++ b/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift @@ -0,0 +1,78 @@ +// +// MinimedDoseProgressEstimator.swift +// MinimedKit +// +// Created by Pete Schwamb on 3/14/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +class MinimedDoseProgressEstimator: DoseProgressTimerEstimator { + + let dose: DoseEntry + + public let pumpModel: PumpModel + + override var progress: DoseProgress { + let elapsed = -dose.startDate.timeIntervalSinceNow + let duration = dose.endDate.timeIntervalSince(dose.startDate) + let timeProgress = min(elapsed / duration, 1) + + let updateResolution: Double + let unroundedVolume: Double + + if pumpModel.isDeliveryRateVariable { + if dose.units < 1 { + updateResolution = 40 // Resolution = 0.025 + unroundedVolume = timeProgress * dose.units + } else { + var remainingUnits = dose.units + var baseDuration: TimeInterval = 0 + var overlay1Duration: TimeInterval = 0 + var overlay2Duration: TimeInterval = 0 + let baseDeliveryRate = 1.5 / TimeInterval(minutes: 1) + + baseDuration = min(duration, remainingUnits / baseDeliveryRate) + remainingUnits -= baseDuration * baseDeliveryRate + + overlay1Duration = min(duration, remainingUnits / baseDeliveryRate) + remainingUnits -= overlay1Duration * baseDeliveryRate + + overlay2Duration = min(duration, remainingUnits / baseDeliveryRate) + remainingUnits -= overlay2Duration * baseDeliveryRate + + unroundedVolume = (min(elapsed, baseDuration) + min(elapsed, overlay1Duration) + min(elapsed, overlay2Duration)) * baseDeliveryRate + + if overlay1Duration > elapsed { + updateResolution = 10 // Resolution = 0.1 + } else { + updateResolution = 20 // Resolution = 0.05 + } + } + + } else { + updateResolution = 20 // Resolution = 0.05 + unroundedVolume = timeProgress * dose.units + } + let roundedVolume = round(unroundedVolume * updateResolution) / updateResolution + return DoseProgress(deliveredUnits: roundedVolume, percentComplete: roundedVolume / dose.units) + } + + init(dose: DoseEntry, pumpModel: PumpModel, reportingQueue: DispatchQueue) { + self.dose = dose + self.pumpModel = pumpModel + super.init(reportingQueue: reportingQueue) + } + + override func timerParameters() -> (delay: TimeInterval, repeating: TimeInterval) { + let timeSinceStart = -dose.startDate.timeIntervalSinceNow + let duration = dose.endDate.timeIntervalSince(dose.startDate) + let timeBetweenPulses = duration / (Double(pumpModel.pulsesPerUnit) * dose.units) + + let delayUntilNextPulse = timeBetweenPulses - timeSinceStart.remainder(dividingBy: timeBetweenPulses) + + return (delay: delayUntilNextPulse, repeating: timeBetweenPulses) + } +} diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 9a14c150b..6ba3eddcc 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -41,16 +41,15 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } public static let managerIdentifier: String = "Minimed500" - - public func roundToDeliveryIncrement(units: Double) -> Double { - return round(units * Double(state.pumpModel.pulsesPerUnit)) / Double(state.pumpModel.pulsesPerUnit) + + + public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? { + if case .inProgress(let dose) = bolusState { + return MinimedDoseProgressEstimator(dose: dose, pumpModel: state.pumpModel, reportingQueue: dispatchQueue) + } + return nil } - - /* - It takes a MM pump about 40s to deliver 1 Unit while bolusing - See: http://www.healthline.com/diabetesmine/ask-dmine-speed-insulin-pumps#3 - */ - private static let deliveryUnitsPerMinute = 1.5 + public init(state: MinimedPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil, pumpOps: PumpOps? = nil) { self.lockedState = Locked(state) @@ -116,13 +115,6 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } private let lockedState: Locked - // TODO: Accessed and set on different threads - private var basalDeliveryStateTransitioning: Bool = false { - didSet { - notifyStatusObservers() - } - } - override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { get { return state.rileyLinkConnectionManagerState @@ -205,11 +197,25 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } // MARK: - PumpManager + private enum SuspendTransition { + case suspending + case resuming + } + + // TODO: Accessed and set on different threads + private var suspendTransition: SuspendTransition? { + didSet { + notifyStatusObservers() + } + } private var basalDeliveryState: PumpManagerStatus.BasalDeliveryState { - if basalDeliveryStateTransitioning { - return state.isPumpSuspended ? .resuming : .suspending - } else { + switch suspendTransition { + case .suspending?: + return .suspending + case .resuming?: + return .resuming + case .none: return state.isPumpSuspended ? .suspended : .active } } @@ -284,8 +290,8 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { self.pumpOps.runSession(withName: sessionName, using: device) { (session) in do { - defer { self.basalDeliveryStateTransitioning = false } - self.basalDeliveryStateTransitioning = true + defer { self.suspendTransition = nil } + self.suspendTransition = state == .suspend ? .suspending : .resuming try session.setSuspendResumeState(state) self.state.isPumpSuspended = state == .suspend @@ -610,11 +616,14 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { let shouldReadReservoir = isReservoirDataOlderThan(timeIntervalSinceNow: .minutes(-6)) pumpOps.runSession(withName: "Bolus", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in + guard let session = session else { completion(.failure(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink))) return } + self.bolusState = .initiating + if shouldReadReservoir { do { let reservoir = try session.getRemainingInsulin() @@ -636,6 +645,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } return } catch let error { + self.bolusState = .none completion(.failure(error)) return } @@ -659,29 +669,50 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } return } catch let error { + self.bolusState = .none completion(.failure(error)) return } } let date = Date() - var deliveryTime = TimeInterval(minutes: units / MinimedPumpManager.deliveryUnitsPerMinute) - if self.state.pumpModel.constrainsBolusDeliveryTimeTo5Minutes { - deliveryTime = min(TimeInterval(minutes: 5), deliveryTime) - } + let deliveryTime = self.state.pumpModel.bolusDeliveryTime(units: units) let endDate = date.addingTimeInterval(deliveryTime) let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: units, unit: .units) willRequest(dose) try session.setNormalBolus(units: units) - completion(.success(dose)) + + // Between bluetooth and the radio and firmware, about 2s on average passes before we start tracking + let commsOffset = TimeInterval(seconds: -2) + let acknowledgedDate = Date().addingTimeInterval(commsOffset) + let acknowledgedDose = DoseEntry(type: .bolus, startDate: acknowledgedDate, endDate: acknowledgedDate.addingTimeInterval(deliveryTime), value: units, unit: .units) + + self.bolusState = .inProgress(acknowledgedDose) + completion(.success(acknowledgedDose)) } catch let error { self.log.error("Failed to bolus: %{public}@", String(describing: error)) + self.bolusState = .none completion(.failure(error)) } } } + public func cancelBolus(completion: @escaping (PumpManagerResult) -> Void) { + let oldState = self.bolusState + self.bolusState = .canceling + setSuspendResumeState(state: .suspend) { (error) in + if let error = error { + self.bolusState = oldState + completion(.failure(error)) + } else { + self.bolusState = .none + completion(.success(nil)) + } + } + } + + public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerResult) -> Void) { pumpOps.runSession(withName: "Set Temp Basal", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in guard let session = session else { @@ -769,14 +800,12 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } - extension MinimedPumpManager: PumpOpsDelegate { public func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) { self.state.pumpState = state } } - extension MinimedPumpManager: CGMManager { public var shouldSyncToRemoteService: Bool { return true diff --git a/MinimedKit/PumpManager/PumpOps.swift b/MinimedKit/PumpManager/PumpOps.swift index 7f3dca51e..f6c7845f7 100644 --- a/MinimedKit/PumpManager/PumpOps.swift +++ b/MinimedKit/PumpManager/PumpOps.swift @@ -10,6 +10,7 @@ import Foundation import RileyLinkKit import RileyLinkBLEKit import os.log +import LoopKit public protocol PumpOpsDelegate: class { @@ -19,7 +20,6 @@ public protocol PumpOpsDelegate: class { public class PumpOps { - private let log = OSLog(category: "PumpOps") public let pumpSettings: PumpSettings diff --git a/MinimedKitTests/Messages/BolusCarelinkMessageBodyTests.swift b/MinimedKitTests/Messages/BolusCarelinkMessageBodyTests.swift index ea41005e7..b501fe286 100644 --- a/MinimedKitTests/Messages/BolusCarelinkMessageBodyTests.swift +++ b/MinimedKitTests/Messages/BolusCarelinkMessageBodyTests.swift @@ -14,7 +14,7 @@ import XCTest class BolusCarelinkMessageBodyTests: XCTestCase { func testBolusMessageBody() { - let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 1.1, strokesPerUnit: 40)) + let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 1.1, insulinBitPackingScale: 40)) XCTAssertEqual( Data(hexadecimalString: "a71234564202002C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), @@ -23,7 +23,7 @@ class BolusCarelinkMessageBodyTests: XCTestCase { } func testBolusMessageBody522() { - let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 1.1, strokesPerUnit: 10)) + let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 1.1, insulinBitPackingScale: 10)) XCTAssertEqual( Data(hexadecimalString: "a712345642010B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), @@ -32,7 +32,7 @@ class BolusCarelinkMessageBodyTests: XCTestCase { } func testBolusMessageBodyRounding() { - let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 1.475, strokesPerUnit: 40)) + let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 1.475, insulinBitPackingScale: 40)) XCTAssertEqual( Data(hexadecimalString: "a71234564202003A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), @@ -41,7 +41,7 @@ class BolusCarelinkMessageBodyTests: XCTestCase { } func testBolusMessageBodyTwoByte() { - let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 7.9, strokesPerUnit: 40)) + let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 7.9, insulinBitPackingScale: 40)) XCTAssertEqual( Data(hexadecimalString: "a71234564202013C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), @@ -50,7 +50,7 @@ class BolusCarelinkMessageBodyTests: XCTestCase { } func testBolusMessageBodyGreaterThanTenUnits() { - let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 10.25, strokesPerUnit: 40)) + let message = PumpMessage(packetType: .carelink, address: "123456", messageType: .bolus, messageBody: BolusCarelinkMessageBody(units: 10.25, insulinBitPackingScale: 40)) XCTAssertEqual( Data(hexadecimalString: "a7123456420201980000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), diff --git a/MinimedKitTests/Messages/ReadRemainingInsulinMessageBodyTests.swift b/MinimedKitTests/Messages/ReadRemainingInsulinMessageBodyTests.swift index 20fb1a418..46119cff2 100644 --- a/MinimedKitTests/Messages/ReadRemainingInsulinMessageBodyTests.swift +++ b/MinimedKitTests/Messages/ReadRemainingInsulinMessageBodyTests.swift @@ -16,7 +16,7 @@ class ReadRemainingInsulinMessageBodyTests: XCTestCase { let body = message?.messageBody as! ReadRemainingInsulinMessageBody - XCTAssertEqual(80.875, body.getUnitsRemainingForStrokes(PumpModel.model723.strokesPerUnit)) + XCTAssertEqual(80.875, body.getUnitsRemaining(insulinBitPackingScale: PumpModel.model723.insulinBitPackingScale)) } func testReservoir522() { @@ -24,7 +24,7 @@ class ReadRemainingInsulinMessageBodyTests: XCTestCase { let body = message?.messageBody as! ReadRemainingInsulinMessageBody - XCTAssertEqual(135.0, body.getUnitsRemainingForStrokes(PumpModel.model522.strokesPerUnit)) + XCTAssertEqual(135.0, body.getUnitsRemaining(insulinBitPackingScale: PumpModel.model522.insulinBitPackingScale)) } } diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 63d66b296..689760434 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -51,7 +51,6 @@ 431CE7A51F9D78F500255374 /* RFPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7A41F9D78F500255374 /* RFPacket.swift */; }; 431CE7A71F9D98F700255374 /* CommandSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7A61F9D98F700255374 /* CommandSession.swift */; }; 4322B75620282DA60002837D /* ResponseBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4322B75520282DA60002837D /* ResponseBufferTests.swift */; }; - 43260F6F21C9B21000DD6837 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26B720DC83E400891C17 /* Locked.swift */; }; 432847C11FA1737400CDE69C /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; }; 432847C31FA57C0F00CDE69C /* RadioFirmwareVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432847C21FA57C0F00CDE69C /* RadioFirmwareVersion.swift */; }; 432CF9061FF74CCB003AB446 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; @@ -101,7 +100,6 @@ 435D26B020DA08CE00891C17 /* RileyLinkPumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26AF20DA08CE00891C17 /* RileyLinkPumpManager.swift */; }; 435D26B420DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26B320DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift */; }; 435D26B620DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26B520DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift */; }; - 435D26B920DC83F300891C17 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26B720DC83E400891C17 /* Locked.swift */; }; 43709ABD20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43709ABA20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift */; }; 43709ABE20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43709ABB20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift */; }; 43709ABF20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43709ABC20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift */; }; @@ -340,7 +338,6 @@ C16E611D2203CB7A0069F357 /* SuspendResumeMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */; }; C16E611E2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */; }; C16E611F22065B8E0069F357 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; - C16E6120220779210069F357 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435D26B720DC83E400891C17 /* Locked.swift */; }; C16E61222208C7A80069F357 /* ReservoirReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E61212208C7A80069F357 /* ReservoirReading.swift */; }; C16E61262208EC580069F357 /* MinimedHUDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E61252208EC580069F357 /* MinimedHUDProvider.swift */; }; C1711A561C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */; }; @@ -429,6 +426,7 @@ C1B383291CD0668600CE7782 /* NightscoutPumpEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */; }; C1B383301CD0680800CE7782 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; C1B383361CD1BA8100CE7782 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B383351CD1BA8100CE7782 /* DeviceDataManager.swift */; }; + C1B44CA7224BDFDF00DE47E5 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; C1BAD1181E63984C009BA1C6 /* RadioAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BAD1171E63984C009BA1C6 /* RadioAdapter.swift */; }; C1C3578F1C927303009BDD4F /* MeterMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C3578E1C927303009BDD4F /* MeterMessage.swift */; }; C1C357911C92733A009BDD4F /* MeterMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C357901C92733A009BDD4F /* MeterMessageTests.swift */; }; @@ -468,6 +466,7 @@ C1F6EB891F89C3E200CFE393 /* FourByteSixByteEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB881F89C3E200CFE393 /* FourByteSixByteEncoding.swift */; }; C1F6EB8B1F89C41200CFE393 /* MinimedPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB8A1F89C41200CFE393 /* MinimedPacket.swift */; }; C1F6EB8D1F89C45500CFE393 /* MinimedPacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */; }; + C1F8B1DF223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */; }; C1FC49EC2135CB2D007D0788 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1FC49EB2135CB2D007D0788 /* LaunchScreen.storyboard */; }; C1FDFCA91D964A3E00ADBC31 /* BolusReminderPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */; }; C1FFAF4D212944F600C50C1D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF4B212944F600C50C1D /* Localizable.strings */; }; @@ -715,7 +714,6 @@ 435D26AF20DA08CE00891C17 /* RileyLinkPumpManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkPumpManager.swift; sourceTree = ""; }; 435D26B320DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkDevicesHeaderView.swift; sourceTree = ""; }; 435D26B520DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkDevicesTableViewDataSource.swift; sourceTree = ""; }; - 435D26B720DC83E400891C17 /* Locked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Locked.swift; sourceTree = ""; }; 436CCEF11FB953E800A6822B /* CommandSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandSession.swift; sourceTree = ""; }; 43709ABA20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkSetupTableViewController.swift; sourceTree = ""; }; 43709ABB20DF1C6400F941B3 /* RileyLinkManagerSetupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RileyLinkManagerSetupViewController.swift; sourceTree = ""; }; @@ -1204,6 +1202,7 @@ C1F6EB881F89C3E200CFE393 /* FourByteSixByteEncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourByteSixByteEncoding.swift; sourceTree = ""; }; C1F6EB8A1F89C41200CFE393 /* MinimedPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPacket.swift; sourceTree = ""; }; C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPacketTests.swift; sourceTree = ""; }; + C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedDoseProgressEstimator.swift; sourceTree = ""; }; C1FC49EB2135CB2D007D0788 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusReminderPumpEvent.swift; sourceTree = ""; }; C1FFAF4C212944F600C50C1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; @@ -1233,6 +1232,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1B44CA7224BDFDF00DE47E5 /* LoopKit.framework in Frameworks */, 43C0196C1FA6B8AE007ABFA1 /* CoreBluetooth.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1560,6 +1560,7 @@ 434AB0941CBA0DF600422F4A /* PumpState.swift */, 43D8709420DE1C91006B549E /* RileyLinkDevice.swift */, C16E61212208C7A80069F357 /* ReservoirReading.swift */, + C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */, ); path = PumpManager; sourceTree = ""; @@ -1580,7 +1581,6 @@ C1EAD6BA1C826B92006DBA60 /* Data.swift */, 4352A73D20DED01700CAC200 /* HKUnit.swift */, 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */, - 435D26B720DC83E400891C17 /* Locked.swift */, C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */, 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */, 431CE7941F9B0DAE00255374 /* OSLog.swift */, @@ -2470,6 +2470,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, es, ru, @@ -2678,7 +2679,6 @@ 431CE7A11F9D195600255374 /* CBCentralManager.swift in Sources */, 431CE7911F985D8D00255374 /* RileyLinkDeviceManager.swift in Sources */, 431CE78D1F985B5400255374 /* PeripheralManager.swift in Sources */, - 43260F6F21C9B21000DD6837 /* Locked.swift in Sources */, 431CE79E1F9BE73900255374 /* BLEFirmwareVersion.swift in Sources */, 432847C31FA57C0F00CDE69C /* RadioFirmwareVersion.swift in Sources */, 431CE7931F985DE700255374 /* PeripheralManager+RileyLink.swift in Sources */, @@ -2740,7 +2740,6 @@ 4352A74120DED23100CAC200 /* RileyLinkDeviceManager.swift in Sources */, 431CE7961F9B0F0200255374 /* OSLog.swift in Sources */, 435D26B020DA08CE00891C17 /* RileyLinkPumpManager.swift in Sources */, - 435D26B920DC83F300891C17 /* Locked.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2877,7 +2876,6 @@ 54BC44821DB476BB00340EED /* CalBGForGHGlucoseEvent.swift in Sources */, C1EAD6CF1C826B92006DBA60 /* UnknownMessageBody.swift in Sources */, 43D8708B20DE1BCA006B549E /* PumpColor.swift in Sources */, - C16E6120220779210069F357 /* Locked.swift in Sources */, C1842C161C8FA45100DB42AC /* ChangeCarbUnitsPumpEvent.swift in Sources */, C1842C241C8FA45100DB42AC /* BatteryPumpEvent.swift in Sources */, C1842C1F1C8FA45100DB42AC /* ChangeBasalProfilePatternPumpEvent.swift in Sources */, @@ -2885,6 +2883,7 @@ C1842C011C8FA45100DB42AC /* JournalEntryPumpLowBatteryPumpEvent.swift in Sources */, 54BC447E1DB4753A00340EED /* GlucoseSensorDataGlucoseEvent.swift in Sources */, C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */, + C1F8B1DF223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift in Sources */, 54DA4E851DFDC0A70007F489 /* SensorValueGlucoseEvent.swift in Sources */, 4352A71C20DEC8C100CAC200 /* TimeZone.swift in Sources */, C1F6EB8B1F89C41200CFE393 /* MinimedPacket.swift in Sources */, @@ -3573,6 +3572,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3611,6 +3614,10 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/RileyLinkBLEKit/CommandSession.swift b/RileyLinkBLEKit/CommandSession.swift index 29cfca190..0ddd7b338 100644 --- a/RileyLinkBLEKit/CommandSession.swift +++ b/RileyLinkBLEKit/CommandSession.swift @@ -100,7 +100,7 @@ public struct CommandSession { case .zeroData: throw RileyLinkDeviceError.invalidResponse(Data()) case .invalidParam, .unknownCommand: - throw RileyLinkDeviceError.invalidInput(String(describing: command.data)) + throw RileyLinkDeviceError.invalidInput(command.data.hexadecimalString) case .success: return response } diff --git a/RileyLinkBLEKit/RFPacket.swift b/RileyLinkBLEKit/RFPacket.swift index d5dc904be..d0faf5875 100644 --- a/RileyLinkBLEKit/RFPacket.swift +++ b/RileyLinkBLEKit/RFPacket.swift @@ -8,7 +8,7 @@ import Foundation -public struct RFPacket { +public struct RFPacket : CustomStringConvertible { public let data: Data let packetCounter: Int public let rssi: Int @@ -32,5 +32,10 @@ public struct RFPacket { self.data = rfspyResponse.subdata(in: startIndex.advanced(by: 2).. Date: Wed, 27 Mar 2019 14:28:26 -0400 Subject: [PATCH 12/71] Add Cartfile as dependency for RileyLinkBLEKit --- RileyLink.xcodeproj/project.pbxproj | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 689760434..20692dcfe 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -630,6 +630,13 @@ remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; + C1C0BE28224BF75600C03B4D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43FB610120DDEF26002B996B; + remoteInfo = Cartfile; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -2148,6 +2155,7 @@ buildRules = ( ); dependencies = ( + C1C0BE29224BF75600C03B4D /* PBXTargetDependency */, ); name = RileyLinkBLEKit; productName = RileyLinkBLEKit; @@ -3235,6 +3243,11 @@ target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; + C1C0BE29224BF75600C03B4D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43FB610120DDEF26002B996B /* Cartfile */; + targetProxy = C1C0BE28224BF75600C03B4D /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ From 4f7576028f5a94a4bb6976f3a8837a6456938fc5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 28 Mar 2019 04:08:56 -0400 Subject: [PATCH 13/71] bump version for dev --- Crypto/Info.plist | 2 +- MinimedKit/Info.plist | 2 +- MinimedKitTests/Info.plist | 2 +- MinimedKitUI/Info.plist | 2 +- NightscoutUploadKit/Info.plist | 2 +- NightscoutUploadKitTests/Info.plist | 2 +- RileyLink.xcodeproj/project.pbxproj | 276 ++--------------------- RileyLink/RileyLink-Info.plist | 2 +- RileyLinkBLEKit/Info.plist | 2 +- RileyLinkBLEKitTests/Info.plist | 2 +- RileyLinkKit/Info.plist | 2 +- RileyLinkKitTests/Info.plist | 2 +- RileyLinkKitUI/Info.plist | 2 +- RileyLinkTests/RileyLinkTests-Info.plist | 2 +- 14 files changed, 37 insertions(+), 265 deletions(-) diff --git a/Crypto/Info.plist b/Crypto/Info.plist index a8e75ccfd..fb45ece9d 100644 --- a/Crypto/Info.plist +++ b/Crypto/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/MinimedKit/Info.plist b/MinimedKit/Info.plist index 449c7b6af..22c234462 100644 --- a/MinimedKit/Info.plist +++ b/MinimedKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKitTests/Info.plist b/MinimedKitTests/Info.plist index de2b7324d..4eb1eb63b 100644 --- a/MinimedKitTests/Info.plist +++ b/MinimedKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKitUI/Info.plist b/MinimedKitUI/Info.plist index 011e22e48..29bd83f27 100644 --- a/MinimedKitUI/Info.plist +++ b/MinimedKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/NightscoutUploadKit/Info.plist b/NightscoutUploadKit/Info.plist index 449c7b6af..22c234462 100644 --- a/NightscoutUploadKit/Info.plist +++ b/NightscoutUploadKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/NightscoutUploadKitTests/Info.plist b/NightscoutUploadKitTests/Info.plist index 01eb78a1c..09a1b67bb 100644 --- a/NightscoutUploadKitTests/Info.plist +++ b/NightscoutUploadKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 2a31426e7..43afa82de 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -3583,7 +3583,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3625,7 +3625,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3720,7 +3720,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3763,7 +3763,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -3794,11 +3794,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -3833,11 +3833,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -3923,12 +3923,12 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Crypto/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3955,12 +3955,12 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Crypto/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3991,7 +3991,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4032,7 +4032,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4078,12 +4078,12 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -4117,11 +4117,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -4230,7 +4230,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -4297,7 +4297,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -4427,11 +4427,11 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -4459,11 +4459,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 46; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -4530,234 +4530,6 @@ }; name = Release; }; -<<<<<<< HEAD -======= - C1FFAF8F213323CC00C50C1D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 45; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - C1FFAF90213323CC00C50C1D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKit/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKit; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - C1FFAF91213323CC00C50C1D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Debug; - }; - C1FFAF92213323CC00C50C1D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.4; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Release; - }; - C1FFAFF1213323FA00C50C1D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 45; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitUI/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitUI; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - C1FFAFF2213323FA00C50C1D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 45; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = OmniKitUI/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitUI; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; ->>>>>>> master /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ diff --git a/RileyLink/RileyLink-Info.plist b/RileyLink/RileyLink-Info.plist index f4fde960f..325e29244 100644 --- a/RileyLink/RileyLink-Info.plist +++ b/RileyLink/RileyLink-Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKit/Info.plist b/RileyLinkBLEKit/Info.plist index 011e22e48..29bd83f27 100644 --- a/RileyLinkBLEKit/Info.plist +++ b/RileyLinkBLEKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLinkBLEKitTests/Info.plist b/RileyLinkBLEKitTests/Info.plist index 477c24867..766804127 100644 --- a/RileyLinkBLEKitTests/Info.plist +++ b/RileyLinkBLEKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleVersion 1 diff --git a/RileyLinkKit/Info.plist b/RileyLinkKit/Info.plist index 449c7b6af..22c234462 100644 --- a/RileyLinkKit/Info.plist +++ b/RileyLinkKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKitTests/Info.plist b/RileyLinkKitTests/Info.plist index de2b7324d..4eb1eb63b 100644 --- a/RileyLinkKitTests/Info.plist +++ b/RileyLinkKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKitUI/Info.plist b/RileyLinkKitUI/Info.plist index 011e22e48..29bd83f27 100644 --- a/RileyLinkKitUI/Info.plist +++ b/RileyLinkKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLinkTests/RileyLinkTests-Info.plist b/RileyLinkTests/RileyLinkTests-Info.plist index 4809fa317..bae54fa16 100644 --- a/RileyLinkTests/RileyLinkTests-Info.plist +++ b/RileyLinkTests/RileyLinkTests-Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0dev CFBundleSignature ???? CFBundleVersion From e0dc72050510fb8bf7fc85f655b8d24dc4de0097 Mon Sep 17 00:00:00 2001 From: Darin Krauss Date: Fri, 29 Mar 2019 11:52:30 -0700 Subject: [PATCH 14/71] Fix framework search path for RileyLinkBLEKit target (#505) --- RileyLink.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 43afa82de..4b9253648 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -3587,7 +3587,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; @@ -3629,7 +3629,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", + "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; From be5bb7f1ef37a5c22bfa4b5493ae698786157271 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 6 Apr 2019 08:34:27 -0500 Subject: [PATCH 15/71] Use xcpretty to reduce build log size on travis (#509) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 565484ad1..0df04f338 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,4 @@ osx_image: xcode10 xcode_project: RileyLink.xcodeproj xcode_scheme: RileyLink script: - - xcodebuild -project RileyLink.xcodeproj -scheme RileyLink build -destination 'name=iPhone SE' test + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme RileyLink build -destination 'name=iPhone SE' test | xcpretty From e4ef2ee5fbd17e863f52c081a4493d8280149621 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 6 Apr 2019 09:46:36 -0500 Subject: [PATCH 16/71] Use xcode 10.2 for travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 565484ad1..2981ed206 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10 +osx_image: xcode10.2 xcode_project: RileyLink.xcodeproj xcode_scheme: RileyLink script: From 796d5dff2534d54f4c5cd0639e5bb6e6beb5fd08 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 25 Apr 2019 16:06:50 -0500 Subject: [PATCH 17/71] MinimedPumpManager thread-safety audit (#521) * Use xcode 10.2 for travis * MinimedPumpManager thread-safety audit * LoopKit updates * Update LoopKit rev and implement DeviceManagerDelegate methods --- .travis.yml | 2 +- Cartfile.resolved | 2 +- .../GetBatteryCarelinkMessageBody.swift | 2 +- .../PumpManager/EnliteSensorDisplayable.swift | 10 +- .../PumpManager/MinimedPumpManager.swift | 808 ++++++++++-------- .../MinimedPumpManagerRecents.swift | 43 + .../PumpManager/MinimedPumpManagerState.swift | 49 +- .../PumpManager/PumpMessageSender.swift | 1 + MinimedKitUI/MinimedHUDProvider.swift | 11 +- .../MinimedPumpSettingsViewController.swift | 32 +- ...nimedPumpSettingsSetupViewController.swift | 2 +- RileyLink.xcodeproj/project.pbxproj | 12 +- .../xcshareddata/xcschemes/Crypto.xcscheme | 2 +- .../xcschemes/MinimedKit.xcscheme | 2 +- .../xcschemes/MinimedKitUI.xcscheme | 2 +- .../xcschemes/NightscoutUploadKit.xcscheme | 2 +- .../xcshareddata/xcschemes/RileyLink.xcscheme | 2 +- .../xcschemes/RileyLinkBLEKit.xcscheme | 2 +- .../xcschemes/RileyLinkKit.xcscheme | 2 +- .../xcschemes/RileyLinkKitUI.xcscheme | 2 +- RileyLink/DeviceDataManager.swift | 33 +- RileyLinkBLEKit/RileyLinkDevice.swift | 14 + RileyLinkKit/PumpOpsSession.swift | 16 +- RileyLinkKit/RileyLinkPumpManager.swift | 19 +- 24 files changed, 606 insertions(+), 466 deletions(-) create mode 100644 MinimedKit/PumpManager/MinimedPumpManagerRecents.swift diff --git a/.travis.yml b/.travis.yml index 0df04f338..03be05f3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10 +osx_image: xcode10.2 xcode_project: RileyLink.xcodeproj xcode_scheme: RileyLink script: diff --git a/Cartfile.resolved b/Cartfile.resolved index 267678eda..02840443d 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "LoopKit/LoopKit" "e386a24577244ee6b2add52e8b2aff0385c2200b" +github "LoopKit/LoopKit" "7333408f054e269669c91146b0f29bffbdcc150a" diff --git a/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift b/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift index 549280ab5..550180f1f 100644 --- a/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift +++ b/MinimedKit/Messages/GetBatteryCarelinkMessageBody.swift @@ -8,7 +8,7 @@ import Foundation -public enum BatteryStatus { +public enum BatteryStatus: Equatable { case low case normal case unknown(rawVal: UInt8) diff --git a/MinimedKit/PumpManager/EnliteSensorDisplayable.swift b/MinimedKit/PumpManager/EnliteSensorDisplayable.swift index 25cbc6444..0f6637c83 100644 --- a/MinimedKit/PumpManager/EnliteSensorDisplayable.swift +++ b/MinimedKit/PumpManager/EnliteSensorDisplayable.swift @@ -10,16 +10,22 @@ import Foundation import LoopKit -struct EnliteSensorDisplayable: SensorDisplayable { +struct EnliteSensorDisplayable: Equatable, SensorDisplayable { public let isStateValid: Bool public let trendType: LoopKit.GlucoseTrend? public let isLocal: Bool - public init?(_ event: MinimedKit.RelativeTimestampedGlucoseEvent) { + public init(_ event: MinimedKit.RelativeTimestampedGlucoseEvent) { isStateValid = event.isStateValid trendType = event.trendType isLocal = event.isLocal } + + public init(_ status: MySentryPumpStatusMessageBody) { + isStateValid = status.isStateValid + trendType = status.trendType + isLocal = status.isLocal + } } extension MinimedKit.RelativeTimestampedGlucoseEvent { diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 6ba3eddcc..ec2f5cfba 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -15,47 +15,11 @@ public protocol MinimedPumpManagerStateObserver: class { func didUpdatePumpManagerState(_ state: MinimedPumpManagerState) } -public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { - public func roundToSupportedBasalRate(unitsPerHour: Double) -> Double { - return supportedBasalRates.filter({$0 <= unitsPerHour}).max() ?? 0 - } - - public func roundToSupportedBolusVolume(units: Double) -> Double { - return supportedBolusVolumes.filter({$0 <= units}).max() ?? 0 - } - - public var supportedBasalRates: [Double] { - return state.pumpModel.supportedBasalRates - } - - public var supportedBolusVolumes: [Double] { - return state.pumpModel.supportedBolusVolumes - } - - public var maximumBasalScheduleEntryCount: Int { - return state.pumpModel.maximumBasalScheduleEntryCount - } - - public var minimumBasalScheduleEntryDuration: TimeInterval { - return state.pumpModel.minimumBasalScheduleEntryDuration - } - - public static let managerIdentifier: String = "Minimed500" - - - public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? { - if case .inProgress(let dose) = bolusState { - return MinimedDoseProgressEstimator(dose: dose, pumpModel: state.pumpModel, reportingQueue: dispatchQueue) - } - return nil - } - - +public class MinimedPumpManager: RileyLinkPumpManager { public init(state: MinimedPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil, pumpOps: PumpOps? = nil) { self.lockedState = Locked(state) - self.bolusState = .none - self.device = HKDevice( + self.hkDevice = HKDevice( name: type(of: self).managerIdentifier, manufacturer: "Medtronic", model: state.pumpModel.rawValue, @@ -89,12 +53,11 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { rileyLinkConnectionManager.delegate = self } - public var rawState: PumpManager.RawStateValue { - return state.rawValue - } + public private(set) var pumpOps: PumpOps! + + // MARK: - PumpManager - // TODO: Accessed (various queues) and set (main) on different threads - public weak var stateObserver: MinimedPumpManagerStateObserver? + public let stateObservers = WeakSynchronizedSet() private(set) public var state: MinimedPumpManagerState { get { @@ -102,213 +65,102 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } set { let oldValue = lockedState.value + let oldStatus = status lockedState.value = newValue + // PumpManagerStatus may have changed if oldValue.timeZone != newValue.timeZone || - oldValue.batteryPercentage != newValue.batteryPercentage { - self.notifyStatusObservers() + oldValue.isPumpSuspended != newValue.isPumpSuspended + { + notifyStatusObservers(oldStatus: oldStatus) } - pumpManagerDelegate?.pumpManagerDidUpdateState(self) - stateObserver?.didUpdatePumpManagerState(newValue) + if oldValue != newValue { + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerDidUpdateState(self) + } + stateObservers.forEach { (observer) in + observer.didUpdatePumpManagerState(newValue) + } + } } } private let lockedState: Locked - override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { + /// Temporal state of the manager + private var recents: MinimedPumpManagerRecents { get { - return state.rileyLinkConnectionManagerState + return lockedRecents.value } set { - state.rileyLinkConnectionManagerState = newValue - } - } - - // Isolated to queue - private var statusObservers = WeakSet() - - public func addStatusObserver(_ observer: PumpManagerStatusObserver) { - queue.async { - self.statusObservers.insert(observer) - } - } - - public func removeStatusObserver(_ observer: PumpManagerStatusObserver) { - queue.async { - self.statusObservers.remove(observer) - } - } - - private func notifyStatusObservers() { - let status = self.status - pumpManagerDelegate?.pumpManager(self, didUpdate: status) - // TODO: Not thread-safe - for observer in statusObservers { - observer.pumpManager(self, didUpdate: status) - } - } - - public weak var cgmManagerDelegate: CGMManagerDelegate? - - public weak var pumpManagerDelegate: PumpManagerDelegate? - - public let log = OSLog(category: "MinimedPumpManager") - - // MARK: - CGMManager - - public private(set) var sensorState: SensorDisplayable? - - public var device: HKDevice? - - // MARK: - Pump data + let oldValue = recents + let oldStatus = status + lockedRecents.value = newValue + + // Battery percentage may have changed + if oldValue.latestPumpStatusFromMySentry != newValue.latestPumpStatusFromMySentry || + oldValue.latestPumpStatus != newValue.latestPumpStatus + { + let oldBatteryPercentage = state.batteryPercentage + let newBatteryPercentage: Double? + + // Persist the updated battery level + if let status = newValue.latestPumpStatusFromMySentry { + newBatteryPercentage = Double(status.batteryRemainingPercent) / 100 + } else if let status = newValue.latestPumpStatus { + newBatteryPercentage = batteryChemistry.chargeRemaining(at: status.batteryVolts) + } else { + newBatteryPercentage = nil + } - /// TODO: Isolate to queue - fileprivate var latestPumpStatusFromMySentry: MySentryPumpStatusMessageBody? { - didSet { - if let sensorState = latestPumpStatusFromMySentry { - self.sensorState = sensorState + if oldBatteryPercentage != newBatteryPercentage { + state.batteryPercentage = newBatteryPercentage + } } - - notifyStatusObservers() - storeBatteryPercentage() - } - } - - // TODO: Isolate to queue - private var latestPumpStatus: PumpStatus? { - didSet { - notifyStatusObservers() - storeBatteryPercentage() - } - } - - // TODO: Isolate to queue - private var lastAddedPumpEvents: Date = .distantPast - - // TODO: Isolate to queue - private func storeBatteryPercentage() { - if let status = latestPumpStatusFromMySentry { - state.batteryPercentage = Double(status.batteryRemainingPercent) / 100 - } else if let status = latestPumpStatus { - state.batteryPercentage = batteryChemistry.chargeRemaining(at: status.batteryVolts) - } else { - state.batteryPercentage = nil - } - } - - // MARK: - PumpManager - private enum SuspendTransition { - case suspending - case resuming - } - // TODO: Accessed and set on different threads - private var suspendTransition: SuspendTransition? { - didSet { - notifyStatusObservers() + // PumpManagerStatus may have changed + if oldStatus != status { + notifyStatusObservers(oldStatus: oldStatus) + } } } + private let lockedRecents = Locked(MinimedPumpManagerRecents()) - private var basalDeliveryState: PumpManagerStatus.BasalDeliveryState { - switch suspendTransition { - case .suspending?: - return .suspending - case .resuming?: - return .resuming - case .none: - return state.isPumpSuspended ? .suspended : .active - } - } + private let statusObservers = WeakSynchronizedSet() - private var bolusState: PumpManagerStatus.BolusState { - didSet { - notifyStatusObservers() + private func notifyStatusObservers(oldStatus: PumpManagerStatus) { + let status = self.status + pumpDelegate.notify { (delegate) in + delegate?.pumpManager(self, didUpdate: status, oldStatus: oldStatus) } - } - - public var status: PumpManagerStatus { - return PumpManagerStatus( - timeZone: state.timeZone, - device: device!, - pumpBatteryChargeRemaining: state.batteryPercentage, - basalDeliveryState: basalDeliveryState, - bolusState: bolusState) - } - - public func updateBLEHeartbeatPreference() { - queue.async { - /// Controls the management of the RileyLink timer tick, which is a reliably-changing BLE - /// characteristic which can cause the app to wake. For most users, the G5 Transmitter and - /// G4 Receiver are reliable as hearbeats, but users who find their resources extremely constrained - /// due to greedy apps or older devices may choose to always enable the timer by always setting `true` - self.rileyLinkDeviceProvider.timerTickEnabled = self.isPumpDataStale || (self.pumpManagerDelegate?.pumpManagerShouldProvideBLEHeartbeat(self) == true) + statusObservers.forEach { (observer) in + observer.pumpManager(self, didUpdate: status, oldStatus: oldStatus) } } - public var pumpRecordsBasalProfileStartEvents: Bool { - return state.pumpModel.recordsBasalProfileStartEvents - } - - public var pumpReservoirCapacity: Double { - return Double(state.pumpModel.reservoirCapacity) - } - - public var pumpTimeZone: TimeZone { - return state.timeZone - } + private let cgmDelegate = WeakSynchronizedDelegate() + private let pumpDelegate = WeakSynchronizedDelegate() - public static let localizedTitle = LocalizedString("Minimed 500/700 Series", comment: "Generic title of the minimed pump manager") + public let log = OSLog(category: "MinimedPumpManager") - public var localizedTitle: String { - return String(format: LocalizedString("Minimed %@", comment: "Pump title (1: model number)"), state.pumpModel.rawValue) - } - - public func suspendDelivery(completion: @escaping (Error?) -> Void) { - setSuspendResumeState(state: .suspend, completion: completion) - } - - public func resumeDelivery(completion: @escaping (Error?) -> Void) { - setSuspendResumeState(state: .resume, completion: completion) - } - - private func setSuspendResumeState(state: SuspendResumeMessageBody.SuspendResumeState, completion: @escaping (Error?) -> Void) { - rileyLinkDeviceProvider.getDevices { (devices) in - guard let device = devices.firstConnected else { - completion(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)) - return - } + // MARK: - CGMManager - let sessionName: String = { - switch state { - case .suspend: - return "Suspend Delivery" - case .resume: - return "Resume Delivery" - } - }() - - self.pumpOps.runSession(withName: sessionName, using: device) { (session) in - do { + private let hkDevice: HKDevice - defer { self.suspendTransition = nil } - self.suspendTransition = state == .suspend ? .suspending : .resuming + // MARK: - RileyLink Updates - try session.setSuspendResumeState(state) - self.state.isPumpSuspended = state == .suspend - completion(nil) - } catch let error { - self.troubleshootPumpComms(using: device) - completion(PumpManagerError.communication(error as? LocalizedError)) - } - } + override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { + get { + return state.rileyLinkConnectionManagerState + } + set { + state.rileyLinkConnectionManagerState = newValue } } - - - - // MARK: - RileyLink Updates override public func device(_ device: RileyLinkDevice, didReceivePacket packet: RFPacket) { + device.assertOnSessionQueue() + guard let data = MinimedPacket(encodedData: packet.data)?.data, let message = PumpMessage(rxData: data), message.address.hexadecimalString == state.pumpID, @@ -317,21 +169,20 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return } - queue.async { - switch message.messageBody { - case let body as MySentryPumpStatusMessageBody: - self.updatePumpStatus(body, from: device) - case is MySentryAlertMessageBody, is MySentryAlertClearedMessageBody: - break - case let body: - // TODO: I think we've learned everything we're going to learn here. - self.log.error("Unknown MySentry Message: %d: %{public}@", message.messageType.rawValue, body.txData.hexadecimalString) - } + switch message.messageBody { + case let body as MySentryPumpStatusMessageBody: + self.updatePumpStatus(body, from: device) + case is MySentryAlertMessageBody, is MySentryAlertClearedMessageBody: + break + case let body: + self.log.error("Unknown MySentry Message: %d: %{public}@", message.messageType.rawValue, body.txData.hexadecimalString) } } override public func deviceTimerDidTick(_ device: RileyLinkDevice) { - self.pumpManagerDelegate?.pumpManagerBLEHeartbeatDidFire(self) + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerBLEHeartbeatDidFire(self) + } } // MARK: - CustomDebugStringConvertible @@ -340,25 +191,26 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return [ "## MinimedPumpManager", "isPumpDataStale: \(isPumpDataStale)", - "status: \(String(describing: status))", - "latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry))", - "lastAddedPumpEvents: \(lastAddedPumpEvents)", - "state: \(String(reflecting: state))", - "sensorState: \(String(describing: sensorState))", - "", "pumpOps: \(String(reflecting: pumpOps))", - "", + "recents: \(String(reflecting: recents))", + "state: \(String(reflecting: state))", + "status: \(String(describing: status))", + "stateObservers.count: \(stateObservers.cleanupDeallocatedElements().count)", + "statusObservers.count: \(statusObservers.cleanupDeallocatedElements().count)", super.debugDescription, ].joined(separator: "\n") } +} +extension MinimedPumpManager { /** Attempts to fix an extended communication failure between a RileyLink device and the pump - parameter device: The RileyLink device */ private func troubleshootPumpComms(using device: RileyLinkDevice) { - /// TODO: Isolate to queue? + device.assertOnSessionQueue() + // Ensuring timer tick is enabled will allow more tries to bring the pump data up-to-date. updateBLEHeartbeatPreference() @@ -376,7 +228,9 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { self.log.error("Device %{public}@ auto-tune failed with error: %{public}@", device.name ?? "", String(describing: error)) self.rileyLinkDeviceProvider.deprioritize(device, completion: nil) if let error = error as? LocalizedError { - self.pumpManagerDelegate?.pumpManager(self, didError: PumpManagerError.communication(MinimedPumpManagerError.tuneFailed(error))) + self.pumpDelegate.notify { (delegate) in + delegate?.pumpManager(self, didError: PumpManagerError.communication(MinimedPumpManagerError.tuneFailed(error))) + } } } } @@ -385,6 +239,39 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } + private func setSuspendResumeState(state: SuspendResumeMessageBody.SuspendResumeState, completion: @escaping (Error?) -> Void) { + rileyLinkDeviceProvider.getDevices { (devices) in + guard let device = devices.firstConnected else { + completion(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)) + return + } + + let sessionName: String = { + switch state { + case .suspend: + return "Suspend Delivery" + case .resume: + return "Resume Delivery" + } + }() + + self.pumpOps.runSession(withName: sessionName, using: device) { (session) in + do { + + defer { self.recents.basalDeliveryStateTransitioning = false } + self.recents.basalDeliveryStateTransitioning = true + + try session.setSuspendResumeState(state) + self.state.isPumpSuspended = state == .suspend + completion(nil) + } catch let error { + self.troubleshootPumpComms(using: device) + completion(PumpManagerError.communication(error as? LocalizedError)) + } + } + } + } + /** Handles receiving a MySentry status message, which are only posted by MM x23 pumps. @@ -396,7 +283,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { - parameter device: The RileyLink that received the message */ private func updatePumpStatus(_ status: MySentryPumpStatusMessageBody, from device: RileyLinkDevice) { - dispatchPrecondition(condition: .onQueue(queue)) + device.assertOnSessionQueue() log.default("MySentry message received") @@ -408,7 +295,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { glucoseDateComponents?.timeZone = timeZone // The pump sends the same message 3x, so ignore it if we've already seen it. - guard status != latestPumpStatusFromMySentry, let pumpDate = pumpDateComponents.date else { + guard status != recents.latestPumpStatusFromMySentry, let pumpDate = pumpDateComponents.date else { return } @@ -418,7 +305,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return } - latestPumpStatusFromMySentry = status + recents.latestPumpStatusFromMySentry = status switch status.glucose { case .active(glucose: let glucose): @@ -432,20 +319,26 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { device: self.device ) - self.cgmManagerDelegate?.cgmManager(self, didUpdateWith: .newData([sample])) + cgmDelegate.notify { (delegate) in + delegate?.cgmManager(self, didUpdateWith: .newData([sample])) + } } case .off: // Enlite is disabled, so assert glucose from another source - self.pumpManagerDelegate?.pumpManagerBLEHeartbeatDidFire(self) + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerBLEHeartbeatDidFire(self) + } default: // Anything else is an Enlite error // TODO: Provide info about status.glucose - self.cgmManagerDelegate?.cgmManager(self, didUpdateWith: .error(PumpManagerError.deviceState(nil))) + cgmDelegate.notify { (delegate) in + delegate?.cgmManager(self, didUpdateWith: .error(PumpManagerError.deviceState(nil))) + } } // Sentry packets are sent in groups of 3, 5s apart. Wait 11s before allowing the loop data to continue to avoid conflicting comms. - queue.asyncAfter(deadline: .now() + .seconds(11)) { - self.updateReservoirVolume(status.reservoirRemainingUnits, at: pumpDate, withTimeLeft: TimeInterval(minutes: Double(status.reservoirRemainingMinutes))) + device.sessionQueueAsyncAfter(deadline: .now() + .seconds(11)) { [weak self] in + self?.updateReservoirVolume(status.reservoirRemainingUnits, at: pumpDate, withTimeLeft: TimeInterval(minutes: Double(status.reservoirRemainingMinutes))) } } @@ -457,38 +350,47 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { - parameter timeLeft: The approximate time before the reservoir is empty */ private func updateReservoirVolume(_ units: Double, at date: Date, withTimeLeft timeLeft: TimeInterval?) { - - self.state.lastReservoirReading = ReservoirReading(units: units, validAt: date) + // Must be called from the sessionQueue - pumpManagerDelegate?.pumpManager(self, didReadReservoirValue: units, at: date) { (result) in - /// TODO: Isolate to queue + state.lastReservoirReading = ReservoirReading(units: units, validAt: date) - switch result { - case .failure: - break - case .success(let (_, _, areStoredValuesContinuous)): - // Run a loop as long as we have fresh, reliable pump data. - if self.state.preferredInsulinDataSource == .pumpHistory || !areStoredValuesContinuous { - self.fetchPumpHistory { (error) in + pumpDelegate.notify { (delegate) in + delegate?.pumpManager(self, didReadReservoirValue: units, at: date) { (result) in + self.pumpManagerDelegateDidProcessReservoirValue(result) + } + } + + // New reservoir data means we may want to adjust our timer tick requirements + updateBLEHeartbeatPreference() + } + + /// Called on an unknown queue by the delegate + private func pumpManagerDelegateDidProcessReservoirValue(_ result: PumpManagerResult<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool)>) { + switch result { + case .failure: + break + case .success(let (_, _, areStoredValuesContinuous)): + // Run a loop as long as we have fresh, reliable pump data. + if state.preferredInsulinDataSource == .pumpHistory || !areStoredValuesContinuous { + fetchPumpHistory { (error) in // Can be centralQueue or sessionQueue + self.pumpDelegate.notify { (delegate) in if let error = error as? PumpManagerError { - self.pumpManagerDelegate?.pumpManager(self, didError: error) + delegate?.pumpManager(self, didError: error) } if error == nil || areStoredValuesContinuous { - self.pumpManagerDelegate?.pumpManagerRecommendsLoop(self) + delegate?.pumpManagerRecommendsLoop(self) } } - } else { - self.pumpManagerDelegate?.pumpManagerRecommendsLoop(self) + } + } else { + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerRecommendsLoop(self) } } - - // New reservoir data means we may want to adjust our timer tick requirements - self.updateBLEHeartbeatPreference() } } - /// TODO: Isolate to queue /// Polls the pump for new history events and passes them to the loop manager /// /// - Parameters: @@ -503,18 +405,27 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { self.pumpOps.runSession(withName: "Fetch Pump History", using: device) { (session) in do { - guard let startDate = self.pumpManagerDelegate?.startDateToFilterNewPumpEvents(for: self) else { + guard let startDate = self.pumpDelegate.call({ (delegate) in + return delegate?.startDateToFilterNewPumpEvents(for: self) + }) else { preconditionFailure("pumpManagerDelegate cannot be nil") } let (events, model) = try session.getHistoryEvents(since: startDate) - self.pumpManagerDelegate?.pumpManager(self, didReadPumpEvents: events.pumpEvents(from: model), completion: { (error) in - if error == nil { - self.lastAddedPumpEvents = Date() + self.pumpDelegate.notify({ (delegate) in + guard let delegate = delegate else { + preconditionFailure("pumpManagerDelegate cannot be nil") } - completion(error) + delegate.pumpManager(self, didReadPumpEvents: events.pumpEvents(from: model), completion: { (error) in + // Called on an unknown queue by the delegate + if error == nil { + self.recents.lastAddedPumpEvents = Date() + } + + completion(error) + }) }) } catch let error { self.troubleshootPumpComms(using: device) @@ -525,7 +436,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } - /// TODO: Isolate to queue + // Safe to call from any thread private var isPumpDataStale: Bool { // How long should we wait before we poll for new pump data? let pumpStatusAgeTolerance = rileyLinkDeviceProvider.idleListeningEnabled ? TimeInterval(minutes: 6) : TimeInterval(minutes: 4) @@ -533,11 +444,13 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return isReservoirDataOlderThan(timeIntervalSinceNow: -pumpStatusAgeTolerance) } + // Safe to call from any thread private func isReservoirDataOlderThan(timeIntervalSinceNow: TimeInterval) -> Bool { - var lastReservoirDate = pumpManagerDelegate?.startDateToFilterNewReservoirEvents(for: self) ?? .distantPast + let state = self.state + var lastReservoirDate = state.lastReservoirReading?.validAt ?? .distantPast // Look for reservoir data from MySentry that hasn't yet been written (due to 11-second imposed delay) - if let sentryStatus = latestPumpStatusFromMySentry { + if let sentryStatus = recents.latestPumpStatusFromMySentry { var components = sentryStatus.pumpDateComponents components.timeZone = state.timeZone @@ -547,74 +460,211 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return lastReservoirDate.timeIntervalSinceNow <= timeIntervalSinceNow } + private func updateBLEHeartbeatPreference() { + // Must not be called on the delegate's queue + rileyLinkDeviceProvider.timerTickEnabled = isPumpDataStale || pumpDelegate.call({ (delegate) -> Bool in + return delegate?.pumpManagerMustProvideBLEHeartbeat(self) == true + }) + } + + // MARK: - Configuration + + // MARK: Pump + + /// The user's preferred method of fetching insulin data from the pump + public var preferredInsulinDataSource: InsulinDataSource { + get { + return state.preferredInsulinDataSource + } + set { + state.preferredInsulinDataSource = newValue + } + } + + /// The pump battery chemistry, for voltage -> percentage calculation + public var batteryChemistry: BatteryChemistryType { + get { + return state.batteryChemistry + } + set { + state.batteryChemistry = newValue + } + } + +} + + +// MARK: - PumpManager +extension MinimedPumpManager: PumpManager { + public static let managerIdentifier: String = "Minimed500" + + public static let localizedTitle = LocalizedString("Minimed 500/700 Series", comment: "Generic title of the minimed pump manager") + + public var localizedTitle: String { + return String(format: LocalizedString("Minimed %@", comment: "Pump title (1: model number)"), state.pumpModel.rawValue) + } + + /* + It takes a MM pump about 40s to deliver 1 Unit while bolusing + See: http://www.healthline.com/diabetesmine/ask-dmine-speed-insulin-pumps#3 + */ + private static let deliveryUnitsPerMinute = 1.5 + + public var supportedBasalRates: [Double] { + return state.pumpModel.supportedBasalRates + } + + public var supportedBolusVolumes: [Double] { + return state.pumpModel.supportedBolusVolumes + } + + public var maximumBasalScheduleEntryCount: Int { + return state.pumpModel.maximumBasalScheduleEntryCount + } + + public var minimumBasalScheduleEntryDuration: TimeInterval { + return state.pumpModel.minimumBasalScheduleEntryDuration + } + + public var pumpRecordsBasalProfileStartEvents: Bool { + return state.pumpModel.recordsBasalProfileStartEvents + } + + public var pumpReservoirCapacity: Double { + return Double(state.pumpModel.reservoirCapacity) + } + + public var status: PumpManagerStatus { + let state = self.state + let recents = self.recents + let basalDeliveryState: PumpManagerStatus.BasalDeliveryState + + if recents.basalDeliveryStateTransitioning { + basalDeliveryState = state.isPumpSuspended ? .resuming : .suspending + } else { + basalDeliveryState = state.isPumpSuspended ? .suspended : .active + } + + return PumpManagerStatus( + timeZone: state.timeZone, + device: hkDevice, + pumpBatteryChargeRemaining: state.batteryPercentage, + basalDeliveryState: basalDeliveryState, + bolusState: recents.bolusState + ) + } + + public var rawState: PumpManager.RawStateValue { + return state.rawValue + } + + public var pumpManagerDelegate: PumpManagerDelegate? { + get { + return pumpDelegate.delegate + } + set { + pumpDelegate.delegate = newValue + } + } + + public var delegateQueue: DispatchQueue! { + get { + return pumpDelegate.queue + } + set { + pumpDelegate.queue = newValue + cgmDelegate.queue = newValue + } + } + + // MARK: Methods + + public func suspendDelivery(completion: @escaping (Error?) -> Void) { + setSuspendResumeState(state: .suspend, completion: completion) + } + + public func resumeDelivery(completion: @escaping (Error?) -> Void) { + setSuspendResumeState(state: .resume, completion: completion) + } + + public func addStatusObserver(_ observer: PumpManagerStatusObserver, queue: DispatchQueue) { + statusObservers.insert(observer, queue: queue) + } + + public func removeStatusObserver(_ observer: PumpManagerStatusObserver) { + statusObservers.removeElement(observer) + } + + public func setMustProvideBLEHeartbeat(_ mustProvideBLEHeartbeat: Bool) { + rileyLinkDeviceProvider.timerTickEnabled = isPumpDataStale || mustProvideBLEHeartbeat + } + /** Ensures pump data is current by either waking and polling, or ensuring we're listening to sentry packets. */ public func assertCurrentPumpData() { - queue.async { - self.rileyLinkDeviceProvider.assertIdleListening(forcingRestart: true) - - guard self.isPumpDataStale else { + rileyLinkDeviceProvider.assertIdleListening(forcingRestart: true) + + guard isPumpDataStale else { + return + } + + log.default("Pump data is stale, fetching.") + + rileyLinkDeviceProvider.getDevices { (devices) in + guard let device = devices.firstConnected else { + let error = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink) + self.log.error("No devices found while fetching pump data") + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didError: error) + }) return } - - self.log.default("Pump data is stale, fetching.") - - self.rileyLinkDeviceProvider.getDevices { (devices) in - guard let device = devices.firstConnected else { - let error = PumpManagerError.connection(MinimedPumpManagerError.noRileyLink) - self.log.error("No devices found while fetching pump data") - self.pumpManagerDelegate?.pumpManager(self, didError: error) - return - } - - self.pumpOps.runSession(withName: "Get Pump Status", using: device) { (session) in - do { - let status = try session.getCurrentPumpStatus() - guard var date = status.clock.date else { - assertionFailure("Could not interpret a valid date from \(status.clock) in the system calendar") + + self.pumpOps.runSession(withName: "Get Pump Status", using: device) { (session) in + do { + let status = try session.getCurrentPumpStatus() + guard var date = status.clock.date else { + assertionFailure("Could not interpret a valid date from \(status.clock) in the system calendar") + throw PumpManagerError.configuration(MinimedPumpManagerError.noDate) + } + + // Check if the clock should be reset + if abs(date.timeIntervalSinceNow) > .seconds(20) { + self.log.error("Pump clock is more than 20 seconds off. Resetting.") + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) + }) + try session.setTimeToNow() + + guard let newDate = try session.getTime().date else { throw PumpManagerError.configuration(MinimedPumpManagerError.noDate) } - - // Check if the clock should be reset - if abs(date.timeIntervalSinceNow) > .seconds(20) { - self.log.error("Pump clock is more than 20 seconds off. Resetting.") - self.pumpManagerDelegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) - try session.setTimeToNow() - - guard let newDate = try session.getTime().date else { - throw PumpManagerError.configuration(MinimedPumpManagerError.noDate) - } - - date = newDate - } - - self.state.isPumpSuspended = status.suspended - - self.latestPumpStatus = status - - self.updateReservoirVolume(status.reservoir, at: date, withTimeLeft: nil) - - } catch let error { - self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) - self.pumpManagerDelegate?.pumpManager(self, didError: PumpManagerError.communication(error as? LocalizedError)) - self.troubleshootPumpComms(using: device) + + date = newDate } + + self.state.isPumpSuspended = status.suspended + self.recents.latestPumpStatus = status + + self.updateReservoirVolume(status.reservoir, at: date, withTimeLeft: nil) + } catch let error { + self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didError: PumpManagerError.communication(error as? LocalizedError)) + }) + self.troubleshootPumpComms(using: device) } } } } - // TODO: Isolate to queue public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (_ dose: DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { guard units > 0 else { assertionFailure("Invalid zero unit bolus") return } - // If we don't have recent pump data, or the pump was recently rewound, read new pump data before bolusing. - let shouldReadReservoir = isReservoirDataOlderThan(timeIntervalSinceNow: .minutes(-6)) - pumpOps.runSession(withName: "Bolus", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in guard let session = session else { @@ -622,15 +672,18 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return } - self.bolusState = .initiating + self.recents.bolusState = .initiating - if shouldReadReservoir { + // If we don't have recent pump data, or the pump was recently rewound, read new pump data before bolusing. + if self.isReservoirDataOlderThan(timeIntervalSinceNow: .minutes(-6)) { do { let reservoir = try session.getRemainingInsulin() - self.pumpManagerDelegate?.pumpManager(self, didReadReservoirValue: reservoir.units, at: reservoir.clock.date!) { _ in - // Ignore result - } + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didReadReservoirValue: reservoir.units, at: reservoir.clock.date!) { _ in + // Ignore result + } + }) } catch let error as PumpOpsError { self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) completion(.failure(SetBolusError.certain(error))) @@ -645,7 +698,7 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } return } catch let error { - self.bolusState = .none + self.recents.bolusState = .none completion(.failure(error)) return } @@ -669,12 +722,12 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } return } catch let error { - self.bolusState = .none + self.recents.bolusState = .none completion(.failure(error)) return } } - + let date = Date() let deliveryTime = self.state.pumpModel.bolusDeliveryTime(units: units) let endDate = date.addingTimeInterval(deliveryTime) @@ -688,25 +741,25 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { let acknowledgedDate = Date().addingTimeInterval(commsOffset) let acknowledgedDose = DoseEntry(type: .bolus, startDate: acknowledgedDate, endDate: acknowledgedDate.addingTimeInterval(deliveryTime), value: units, unit: .units) - self.bolusState = .inProgress(acknowledgedDose) + self.recents.bolusState = .inProgress(acknowledgedDose) completion(.success(acknowledgedDose)) } catch let error { self.log.error("Failed to bolus: %{public}@", String(describing: error)) - self.bolusState = .none + self.recents.bolusState = .none completion(.failure(error)) } } } public func cancelBolus(completion: @escaping (PumpManagerResult) -> Void) { - let oldState = self.bolusState - self.bolusState = .canceling + let oldState = self.recents.bolusState + self.recents.bolusState = .canceling setSuspendResumeState(state: .suspend) { (error) in if let error = error { - self.bolusState = oldState + self.recents.bolusState = oldState completion(.failure(error)) } else { - self.bolusState = .none + self.recents.bolusState = .none completion(.success(nil)) } } @@ -726,6 +779,10 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { let now = Date() let endDate = now.addingTimeInterval(response.timeRemaining) let startDate = endDate.addingTimeInterval(-duration) + + // If we were successful, then we know we aren't suspended + self.state.isPumpSuspended = false + completion(.success(DoseEntry( type: .tempBasal, startDate: startDate, @@ -735,7 +792,21 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { ))) // Continue below - } catch let error { + } catch let error as PumpCommandError { + completion(.failure(error)) + + // If we got a command-refused error, we might be suspended or bolusing, so update the state accordingly + if case .arguments(.pumpError(.commandRefused)) = error { + do { + let status = try session.getCurrentPumpStatus() + self.state.isPumpSuspended = status.suspended + self.recents.latestPumpStatus = status + } catch { + self.log.error("Post-basal suspend state fetch failed: %{public}@", String(describing: error)) + } + } + return + } catch { completion(.failure(error)) return } @@ -743,12 +814,14 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { do { // If we haven't fetched history in a while, our preferredInsulinDataSource is probably .reservoir, so // let's take advantage of the pump radio being on. - if self.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) { + if self.recents.lastAddedPumpEvents.timeIntervalSinceNow < .minutes(-4) { let clock = try session.getTime() // Check if the clock should be reset if let date = clock.date, abs(date.timeIntervalSinceNow) > .seconds(20) { self.log.error("Pump clock is more than 20 seconds off. Resetting.") - self.pumpManagerDelegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) + }) try session.setTimeToNow() } @@ -758,55 +831,40 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } } - } catch let error { + } catch { self.log.error("Post-basal time sync failed: %{public}@", String(describing: error)) } } } - // MARK: - Configuration - - // MARK: Pump - - // TODO - public func getOpsForDevice(_ device: RileyLinkDevice, completion: @escaping (_ pumpOps: PumpOps) -> Void) { - queue.async { - completion(self.pumpOps) + public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? { + if case .inProgress(let dose) = recents.bolusState { + return MinimedDoseProgressEstimator(dose: dose, pumpModel: state.pumpModel, reportingQueue: dispatchQueue) } + return nil } +} +extension MinimedPumpManager: PumpOpsDelegate { + public func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) { + self.state.pumpState = state + } +} - public private(set) var pumpOps: PumpOps! - - /// The user's preferred method of fetching insulin data from the pump - public var preferredInsulinDataSource: InsulinDataSource { - get { - return state.preferredInsulinDataSource - } - set { - state.preferredInsulinDataSource = newValue - } +extension MinimedPumpManager: CGMManager { + public var device: HKDevice? { + return hkDevice } - /// The pump battery chemistry, for voltage -> percentage calculation - public var batteryChemistry: BatteryChemistryType { + public var cgmManagerDelegate: CGMManagerDelegate? { get { - return state.batteryChemistry + return cgmDelegate.delegate } set { - state.batteryChemistry = newValue + cgmDelegate.delegate = newValue } } - -} - -extension MinimedPumpManager: PumpOpsDelegate { - public func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) { - self.state.pumpState = state - } -} -extension MinimedPumpManager: CGMManager { public var shouldSyncToRemoteService: Bool { return true } @@ -819,6 +877,10 @@ extension MinimedPumpManager: CGMManager { return nil } + public var sensorState: SensorDisplayable? { + return recents.sensorState + } + public func fetchNewDataIfNeeded(_ completion: @escaping (CGMResult) -> Void) { rileyLinkDeviceProvider.getDevices { (devices) in guard let device = devices.firstConnected else { @@ -826,7 +888,9 @@ extension MinimedPumpManager: CGMManager { return } - let latestGlucoseDate = self.cgmManagerDelegate?.startDateToFilterNewData(for: self) ?? Date(timeIntervalSinceNow: TimeInterval(hours: -24)) + let latestGlucoseDate = self.cgmDelegate.call({ (delegate) -> Date in + return delegate?.startDateToFilterNewData(for: self) ?? Date(timeIntervalSinceNow: TimeInterval(hours: -24)) + }) guard latestGlucoseDate.timeIntervalSinceNow <= TimeInterval(minutes: -4.5) else { completion(.noData) @@ -838,7 +902,7 @@ extension MinimedPumpManager: CGMManager { let events = try session.getGlucoseHistoryEvents(since: latestGlucoseDate.addingTimeInterval(.minutes(1))) if let latestSensorEvent = events.compactMap({ $0.glucoseEvent as? RelativeTimestampedGlucoseEvent }).last { - self.sensorState = EnliteSensorDisplayable(latestSensorEvent) + self.recents.sensorState = EnliteSensorDisplayable(latestSensorEvent) } let unit = HKUnit.milligramsPerDeciliter diff --git a/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift b/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift new file mode 100644 index 000000000..165e772b5 --- /dev/null +++ b/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift @@ -0,0 +1,43 @@ +// +// MinimedPumpManagerRecents.swift +// MinimedKit +// +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +struct MinimedPumpManagerRecents: Equatable { + var bolusState: PumpManagerStatus.BolusState = .none + + var basalDeliveryStateTransitioning = false + + var lastAddedPumpEvents: Date = .distantPast + + var latestPumpStatus: PumpStatus? = nil + + var latestPumpStatusFromMySentry: MySentryPumpStatusMessageBody? = nil { + didSet { + if let sensorState = latestPumpStatusFromMySentry { + self.sensorState = EnliteSensorDisplayable(sensorState) + } + } + } + + var sensorState: EnliteSensorDisplayable? = nil +} + +extension MinimedPumpManagerRecents: CustomDebugStringConvertible { + var debugDescription: String { + return """ + ### MinimedPumpManagerRecents + bolusState: \(bolusState) + basalDeliveryStateTransitioning: \(basalDeliveryStateTransitioning) + lastAddedPumpEvents: \(lastAddedPumpEvents) + latestPumpStatus: \(String(describing: latestPumpStatus)) + latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry)) + sensorState: \(String(describing: sensorState)) + """ + } +} diff --git a/MinimedKit/PumpManager/MinimedPumpManagerState.swift b/MinimedKit/PumpManager/MinimedPumpManagerState.swift index 84434393c..319e83df3 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerState.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerState.swift @@ -16,6 +16,16 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public var batteryChemistry: BatteryChemistryType + public var batteryPercentage: Double? + + public var isPumpSuspended: Bool + + public var lastReservoirReading: ReservoirReading? + + public var lastTuned: Date? // In-memory only + + public var lastValidFrequency: Measurement? + public var preferredInsulinDataSource: InsulinDataSource public let pumpColor: PumpColor @@ -27,17 +37,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public let pumpID: String public let pumpRegion: PumpRegion - - public var isPumpSuspended: Bool - - public var lastValidFrequency: Measurement? - public var lastTuned: Date? - - public var batteryPercentage: Double? - - public var lastReservoirReading: ReservoirReading? - public var pumpSettings: PumpSettings { get { return PumpSettings(pumpID: pumpID, pumpRegion: pumpRegion) @@ -165,23 +165,12 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { "timeZone": timeZone.secondsFromGMT(), "isPumpSuspended": isPumpSuspended, "version": MinimedPumpManagerState.version, - ] - - if let rileyLinkConnectionManagerState = rileyLinkConnectionManagerState { - value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState.rawValue - } - - if let frequency = lastValidFrequency?.converted(to: .megahertz) { - value["lastValidFrequency"] = frequency.value - } - - if let batteryPercentage = batteryPercentage { - value["batteryPercentage"] = batteryPercentage - } + ] - if let lastReservoirReading = lastReservoirReading { - value["lastReservoirReading"] = lastReservoirReading.rawValue - } + value["batteryPercentage"] = batteryPercentage + value["lastReservoirReading"] = lastReservoirReading?.rawValue + value["lastValidFrequency"] = lastValidFrequency?.converted(to: .megahertz).value + value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState?.rawValue return value } @@ -198,18 +187,18 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible { return [ "## MinimedPumpManagerState", "batteryChemistry: \(batteryChemistry)", + "batteryPercentage: \(String(describing: batteryPercentage))", + "isPumpSuspended: \(isPumpSuspended)", + "lastValidFrequency: \(String(describing: lastValidFrequency))", "preferredInsulinDataSource: \(preferredInsulinDataSource)", "pumpColor: \(pumpColor)", "pumpID: ✔︎", "pumpModel: \(pumpModel.rawValue)", "pumpFirmwareVersion: \(pumpFirmwareVersion)", "pumpRegion: \(pumpRegion)", - "lastValidFrequency: \(String(describing: lastValidFrequency))", - "timeZone: \(timeZone)", - "isPumpSuspended: \(isPumpSuspended)", - "batteryPercentage: \(String(describing: batteryPercentage))", "reservoirUnits: \(String(describing: lastReservoirReading?.units))", "reservoirValidAt: \(String(describing: lastReservoirReading?.validAt))", + "timeZone: \(timeZone)", String(reflecting: rileyLinkConnectionManagerState), ].joined(separator: "\n") } diff --git a/MinimedKit/PumpManager/PumpMessageSender.swift b/MinimedKit/PumpManager/PumpMessageSender.swift index eee3f344d..ff673f7af 100644 --- a/MinimedKit/PumpManager/PumpMessageSender.swift +++ b/MinimedKit/PumpManager/PumpMessageSender.swift @@ -73,6 +73,7 @@ extension PumpMessageSender { /// - PumpOpsError.crosstalk /// - PumpOpsError.deviceError /// - PumpOpsError.noResponse + /// - PumpOpsError.pumpError /// - PumpOpsError.unexpectedResponse /// - PumpOpsError.unknownResponse func getResponse(to message: PumpMessage, responseType: MessageType = .pumpAck, repeatCount: Int = 0, timeout: TimeInterval = standardPumpResponseWindow, retryCount: Int = 3) throws -> T { diff --git a/MinimedKitUI/MinimedHUDProvider.swift b/MinimedKitUI/MinimedHUDProvider.swift index 135fc0a0f..80c33527a 100644 --- a/MinimedKitUI/MinimedHUDProvider.swift +++ b/MinimedKitUI/MinimedHUDProvider.swift @@ -11,7 +11,7 @@ import LoopKit import LoopKitUI import MinimedKit -class MinimedHUDProvider: HUDProvider, MinimedPumpManagerStateObserver { +class MinimedHUDProvider: HUDProvider { var managerIdentifier: String { return MinimedPumpManager.managerIdentifier @@ -38,7 +38,7 @@ class MinimedHUDProvider: HUDProvider, MinimedPumpManagerStateObserver { public init(pumpManager: MinimedPumpManager) { self.pumpManager = pumpManager self.state = pumpManager.state - pumpManager.stateObserver = self + pumpManager.stateObservers.insert(self, queue: .main) } var visible: Bool = false { @@ -122,10 +122,11 @@ class MinimedHUDProvider: HUDProvider, MinimedPumpManagerStateObserver { return [reservoirVolumeHUDView, batteryLevelHUDView] } +} +extension MinimedHUDProvider: MinimedPumpManagerStateObserver { func didUpdatePumpManagerState(_ state: MinimedPumpManagerState) { - DispatchQueue.main.async { - self.state = state - } + dispatchPrecondition(condition: .onQueue(.main)) + self.state = state } } diff --git a/MinimedKitUI/MinimedPumpSettingsViewController.swift b/MinimedKitUI/MinimedPumpSettingsViewController.swift index af47cbad4..47a0e4214 100644 --- a/MinimedKitUI/MinimedPumpSettingsViewController.swift +++ b/MinimedKitUI/MinimedPumpSettingsViewController.swift @@ -44,7 +44,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { imageView.frame.size.height += 18 // feels right tableView.tableHeaderView = imageView - pumpManager.addStatusObserver(self) + pumpManager.addStatusObserver(self, queue: .main) let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:))) self.navigationItem.setRightBarButton(button, animated: false) @@ -260,20 +260,19 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { case .rileyLinks: let device = devicesDataSource.devices[indexPath.row] - pumpManager.getOpsForDevice(device) { (pumpOps) in - DispatchQueue.main.async { - let vc = RileyLinkMinimedDeviceTableViewController( - device: device, - pumpOps: pumpOps - ) + let vc = RileyLinkMinimedDeviceTableViewController( + device: device, + pumpOps: pumpManager.pumpOps + ) - self.show(vc, sender: sender) - } - } + self.show(vc, sender: sender) case .delete: let confirmVC = UIAlertController(pumpDeletionHandler: { - self.pumpManager.pumpManagerDelegate?.pumpManagerWillDeactivate(self.pumpManager) - self.done() + self.pumpManager.notifyDelegateOfDeactivation { + DispatchQueue.main.async { + self.done() + } + } }) present(confirmVC, animated: true) { @@ -358,11 +357,10 @@ extension MinimedPumpSettingsViewController: RadioSelectionTableViewControllerDe } extension MinimedPumpSettingsViewController: PumpManagerStatusObserver { - public func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { - DispatchQueue.main.async { - let suspendResumeTableViewCell = self.tableView?.cellForRow(at: IndexPath(row: ActionsRow.suspendResume.rawValue, section: Section.actions.rawValue)) as! SuspendResumeTableViewCell - suspendResumeTableViewCell.basalDeliveryState = status.basalDeliveryState - } + public func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) { + dispatchPrecondition(condition: .onQueue(.main)) + let suspendResumeTableViewCell = self.tableView?.cellForRow(at: IndexPath(row: ActionsRow.suspendResume.rawValue, section: Section.actions.rawValue)) as! SuspendResumeTableViewCell + suspendResumeTableViewCell.basalDeliveryState = status.basalDeliveryState } } diff --git a/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift index 38e71a1dd..154cd9310 100644 --- a/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift @@ -131,7 +131,7 @@ class MinimedPumpSettingsSetupViewController: SetupTableViewController { vc.scheduleItems = profile.items vc.timeZone = profile.timeZone } else { - vc.timeZone = pumpManager.pumpTimeZone + vc.timeZone = pumpManager.state.timeZone } vc.title = sender?.textLabel?.text diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 4b9253648..256fb5c73 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -162,6 +162,7 @@ 43CA93311CB97191000026B5 /* ReadTempBasalCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA93301CB97191000026B5 /* ReadTempBasalCarelinkMessageBodyTests.swift */; }; 43CA93331CB9726A000026B5 /* ChangeTimeCarelinMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA93321CB9726A000026B5 /* ChangeTimeCarelinMessageBodyTests.swift */; }; 43CA93351CB9727F000026B5 /* ChangeTempBasalCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA93341CB9727F000026B5 /* ChangeTempBasalCarelinkMessageBodyTests.swift */; }; + 43CACE1A224844E200F90AF5 /* MinimedPumpManagerRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CACE19224844E200F90AF5 /* MinimedPumpManagerRecents.swift */; }; 43CEC07420D0949B00F1BC19 /* ReadRemoteControlIDsMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CEC07320D0949B00F1BC19 /* ReadRemoteControlIDsMessageBody.swift */; }; 43CEC07620D099B400F1BC19 /* SetRemoteControlEnabledMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CEC07520D099B400F1BC19 /* SetRemoteControlEnabledMessageBody.swift */; }; 43CEC07820D0CF7200F1BC19 /* ReadRemoteControlIDsMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CEC07720D0CF7200F1BC19 /* ReadRemoteControlIDsMessageBodyTests.swift */; }; @@ -773,6 +774,7 @@ 43CA93301CB97191000026B5 /* ReadTempBasalCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadTempBasalCarelinkMessageBodyTests.swift; sourceTree = ""; }; 43CA93321CB9726A000026B5 /* ChangeTimeCarelinMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeTimeCarelinMessageBodyTests.swift; sourceTree = ""; }; 43CA93341CB9727F000026B5 /* ChangeTempBasalCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeTempBasalCarelinkMessageBodyTests.swift; sourceTree = ""; }; + 43CACE19224844E200F90AF5 /* MinimedPumpManagerRecents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedPumpManagerRecents.swift; sourceTree = ""; }; 43CEC07320D0949B00F1BC19 /* ReadRemoteControlIDsMessageBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadRemoteControlIDsMessageBody.swift; sourceTree = ""; }; 43CEC07520D099B400F1BC19 /* SetRemoteControlEnabledMessageBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetRemoteControlEnabledMessageBody.swift; sourceTree = ""; }; 43CEC07720D0CF7200F1BC19 /* ReadRemoteControlIDsMessageBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadRemoteControlIDsMessageBodyTests.swift; sourceTree = ""; }; @@ -1557,16 +1559,17 @@ 43D8709820DE1CB6006B549E /* MinimedPumpManager.swift */, 43D8709920DE1CB6006B549E /* MinimedPumpManagerError.swift */, 43D8709A20DE1CB6006B549E /* MinimedPumpManagerState.swift */, - 2F962EC21E6873A10070EFBD /* PumpMessageSender.swift */, + 43CACE19224844E200F90AF5 /* MinimedPumpManagerRecents.swift */, 435535DB1FB8B37E00CE5A23 /* PumpMessage+PumpOpsSession.swift */, + 2F962EC21E6873A10070EFBD /* PumpMessageSender.swift */, 434AB0921CBA0DF600422F4A /* PumpOps.swift */, C1A7215F1EC29C0B0080FAD7 /* PumpOpsError.swift */, 434AB0931CBA0DF600422F4A /* PumpOpsSession.swift */, 43D8709220DE1C80006B549E /* PumpOpsSession+LoopKit.swift */, 4384C8C71FB937E500D916E6 /* PumpSettings.swift */, 434AB0941CBA0DF600422F4A /* PumpState.swift */, - 43D8709420DE1C91006B549E /* RileyLinkDevice.swift */, C16E61212208C7A80069F357 /* ReservoirReading.swift */, + 43D8709420DE1C91006B549E /* RileyLinkDevice.swift */, C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */, ); path = PumpManager; @@ -2404,7 +2407,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 1010; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Pete Schwamb"; TargetAttributes = { 431CE76E1F98564100255374 = { @@ -2475,7 +2478,7 @@ }; buildConfigurationList = C12EA232198B436800309FA4 /* Build configuration list for PBXProject "RileyLink" */; compatibilityVersion = "Xcode 6.3"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( English, @@ -2908,6 +2911,7 @@ C1EAD6CE1C826B92006DBA60 /* ReadSettingsCarelinkMessageBody.swift in Sources */, C1F0004C1EBE68A600F65163 /* DataFrameMessageBody.swift in Sources */, C12198AD1C8F332500BC374C /* TimestampedPumpEvent.swift in Sources */, + 43CACE1A224844E200F90AF5 /* MinimedPumpManagerRecents.swift in Sources */, C1F6EB891F89C3E200CFE393 /* FourByteSixByteEncoding.swift in Sources */, 54BC44751DB46B0A00340EED /* GlucosePage.swift in Sources */, C1EAD6C51C826B92006DBA60 /* Int.swift in Sources */, diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme index cd8f3e11a..2228c2fc4 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme @@ -1,6 +1,6 @@ Bool { + func pumpManagerMustProvideBLEHeartbeat(_ pumpManager: PumpManager) -> Bool { return true } - func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { + func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) { } func pumpManagerWillDeactivate(_ pumpManager: PumpManager) { @@ -97,8 +98,28 @@ extension DeviceDataManager: PumpManagerDelegate { func startDateToFilterNewPumpEvents(for manager: PumpManager) -> Date { return Date().addingTimeInterval(.hours(-2)) } - - func startDateToFilterNewReservoirEvents(for manager: PumpManager) -> Date { - return Date().addingTimeInterval(.minutes(-15)) - } +} + +// MARK: - DeviceManagerDelegate +extension DeviceDataManager: DeviceManagerDelegate { + func scheduleNotification(for manager: DeviceManager, + identifier: String, + content: UNNotificationContent, + trigger: UNNotificationTrigger?) { + let request = UNNotificationRequest( + identifier: identifier, + content: content, + trigger: trigger + ) + + DispatchQueue.main.async { + UNUserNotificationCenter.current().add(request) + } + } + + func clearNotification(for manager: DeviceManager, identifier: String) { + DispatchQueue.main.async { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) + } + } } diff --git a/RileyLinkBLEKit/RileyLinkDevice.swift b/RileyLinkBLEKit/RileyLinkDevice.swift index 687f4bd9b..aff3f47cb 100644 --- a/RileyLinkBLEKit/RileyLinkDevice.swift +++ b/RileyLinkBLEKit/RileyLinkDevice.swift @@ -96,6 +96,20 @@ extension RileyLinkDevice { public func enableBLELEDs() { manager.setLEDMode(mode: .on) } + + /// Asserts that the caller is currently on the session queue + public func assertOnSessionQueue() { + dispatchPrecondition(condition: .onQueue(manager.queue)) + } + + /// Schedules a closure to execute on the session queue after a specified time + /// + /// - Parameters: + /// - deadline: The time after which to execute + /// - execute: The closure to execute + public func sessionQueueAsyncAfter(deadline: DispatchTime, execute: @escaping () -> Void) { + manager.queue.asyncAfter(deadline: deadline, execute: execute) + } } diff --git a/RileyLinkKit/PumpOpsSession.swift b/RileyLinkKit/PumpOpsSession.swift index 340d989bb..f71e39f75 100644 --- a/RileyLinkKit/PumpOpsSession.swift +++ b/RileyLinkKit/PumpOpsSession.swift @@ -318,7 +318,7 @@ extension PumpOpsSession { // MARK: - Aggregate reads -public struct PumpStatus { +public struct PumpStatus: Equatable { // Date components read from the pump, along with PumpState.timeZone public let clock: DateComponents public let batteryVolts: Measurement @@ -458,7 +458,7 @@ extension PumpOpsSession { /// - Returns: The pump message body describing the new basal rate /// - Throws: PumpCommandError public func setTempBasal(_ unitsPerHour: Double, duration: TimeInterval) throws -> ReadTempBasalCarelinkMessageBody { - var lastError: Error? + var lastError: PumpCommandError? let message = PumpMessage(settings: settings, type: .changeTempBasal, body: ChangeTempBasalCarelinkMessageBody(unitsPerHour: unitsPerHour, duration: duration)) @@ -476,10 +476,10 @@ extension PumpOpsSession { do { let _: PumpAckMessageBody = try session.getResponse(to: message, retryCount: 0) } catch PumpOpsError.pumpError(let errorCode) { - lastError = PumpCommandError.arguments(.pumpError(errorCode)) + lastError = .arguments(.pumpError(errorCode)) break // Stop because we have a pump error response } catch PumpOpsError.unknownPumpErrorCode(let errorCode) { - lastError = PumpCommandError.arguments(.unknownPumpErrorCode(errorCode)) + lastError = .arguments(.unknownPumpErrorCode(errorCode)) break // Stop because we have a pump error response } catch { // The pump does not ACK a successful temp basal. We'll check manually below if it was successful. @@ -492,12 +492,16 @@ extension PumpOpsSession { } else { throw PumpCommandError.arguments(PumpOpsError.rfCommsFailure("Could not verify TempBasal on attempt \(attempt). ")) } - } catch let error { + } catch let error as PumpCommandError { lastError = error + } catch let error as PumpOpsError { + lastError = .command(error) + } catch { + lastError = .command(.noResponse(during: "Set temp basal")) } } - throw lastError ?? PumpOpsError.noResponse(during: "Set temp basal") + throw lastError! } public func readTempBasal() throws -> Double { diff --git a/RileyLinkKit/RileyLinkPumpManager.swift b/RileyLinkKit/RileyLinkPumpManager.swift index 6a6a1eedc..8a3b397a2 100644 --- a/RileyLinkKit/RileyLinkPumpManager.swift +++ b/RileyLinkKit/RileyLinkPumpManager.swift @@ -5,6 +5,7 @@ // Copyright © 2018 LoopKit Authors. All rights reserved. // +import LoopKit import RileyLinkBLEKit open class RileyLinkPumpManager { @@ -29,13 +30,9 @@ open class RileyLinkPumpManager { /// Access to rileylink devices public let rileyLinkDeviceProvider: RileyLinkDeviceProvider - - // TODO: Evaluate if this is necessary - public let queue = DispatchQueue(label: "com.loopkit.RileyLinkPumpManager", qos: .unspecified) - /// Isolated to queue // TODO: Put this on each RileyLinkDevice? - private var lastTimerTick: Date = .distantPast + private var lastTimerTick = Locked(Date.distantPast) /// Called when one of the connected devices receives a packet outside of a session /// @@ -52,7 +49,7 @@ open class RileyLinkPumpManager { return [ "## RileyLinkPumpManager", "rileyLinkConnectionManager: \(String(reflecting: rileyLinkConnectionManager))", - "lastTimerTick: \(String(describing: lastTimerTick))", + "lastTimerTick: \(String(describing: lastTimerTick.value))", "", String(reflecting: rileyLinkDeviceProvider), ].joined(separator: "\n") @@ -76,6 +73,8 @@ extension RileyLinkPumpManager { return } + device.assertOnSessionQueue() + self.device(device, didReceivePacket: packet) } @@ -84,12 +83,8 @@ extension RileyLinkPumpManager { return } - // TODO: Do we need a queue? - queue.async { - self.lastTimerTick = Date() - - self.deviceTimerDidTick(device) - } + self.lastTimerTick.value = Date() + self.deviceTimerDidTick(device) } open func connectToRileyLink(_ device: RileyLinkDevice) { From 23776aaf5ca54832aab275e148cb6f6079ccaaab Mon Sep 17 00:00:00 2001 From: jatneff Date: Thu, 25 Apr 2019 23:10:29 +0200 Subject: [PATCH 18/71] Fixed German translations (#515) * Update Localizable.strings Updated translations for wrong and missing texts * Update Localizable.strings Corrected wrong German texts, which seemed erroneously Spanish; harmonized German texts * Update Localizable.strings Finetuning translations to German --- MinimedKit/de.lproj/Localizable.strings | 4 ++-- MinimedKitUI/de.lproj/Localizable.strings | 26 +++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/MinimedKit/de.lproj/Localizable.strings b/MinimedKit/de.lproj/Localizable.strings index f04adfcd6..057d09dc9 100644 --- a/MinimedKit/de.lproj/Localizable.strings +++ b/MinimedKit/de.lproj/Localizable.strings @@ -68,13 +68,13 @@ "Pump responded unexpectedly" = "Pumpe hat unerwartet geantwortet"; /* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ -"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "MensageMicroinfusadora(%1$@, %2$@, %3$@, %4$@)"; +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "Pumpennachricht(%1$@, %2$@, %3$@, %4$@)"; /* Describing the reservoir insulin data source */ "Reservoir" = "Reservoir"; /* Error description */ -"RileyLink radio tune failed" = "RileyLink Radiosignal fehlerhaft"; +"RileyLink radio tune failed" = "Einstellen des RileyLink Funksignals fehlgeschlagen"; /* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ "Temporary Basal: %1$.3f U/hour" = "Temporäre Basalrate: %1$.3f E/St"; diff --git a/MinimedKitUI/de.lproj/Localizable.strings b/MinimedKitUI/de.lproj/Localizable.strings index 2f50eec3d..314abef9d 100644 --- a/MinimedKitUI/de.lproj/Localizable.strings +++ b/MinimedKitUI/de.lproj/Localizable.strings @@ -41,7 +41,7 @@ "Change Time" = "Zeit ändern"; /* The title of the command to change pump time zone */ -"Change Time Zone" = "Zeitzone Aendern"; +"Change Time Zone" = "Zeitzone ändern"; /* Progress message for changing pump time. */ "Changing time…" = "Zeit ändern…"; @@ -60,7 +60,7 @@ /* Button title to delete pump Title text for the button to remove a pump from Loop */ -"Delete Pump" = "Pumpe loeschen"; +"Delete Pump" = "Pumpe löschen"; /* Title text for delivery limits */ "Delivery Limits" = "Insulin Abgabelimits"; @@ -75,25 +75,25 @@ "Discovering commands…" = "Befehle werden entdeckt…"; /* The title of the command to enable diagnostic LEDs */ -"Enable Diagnostic LEDs" = "Diagnostische LEDs Aktivieren"; +"Enable Diagnostic LEDs" = "Diagnostische LEDs aktivieren"; /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Diagnostische LEDs aktiviert"; /* The title of the command to fetch recent glucose */ -"Fetch Enlite Glucose" = "Fetch Enlite Glucose"; +"Fetch Enlite Glucose" = "Enlite-Glukosewert einlesen"; /* The title of the command to fetch recent history */ -"Fetch Recent History" = "Recuperar Historia Reciente"; +"Fetch Recent History" = "Aktueller Verlauf abrufen"; /* Progress message for fetching pump glucose. */ -"Fetching glucose…" = "Recuperar glucosa…"; +"Fetching glucose…" = "Glukosewerte abrufen…"; /* Progress message for fetching pump history. */ -"Fetching history…" = "Recuperar historial…"; +"Fetching history…" = "Verlauf abrufen…"; /* Progress message for fetching pump model. */ -"Fetching pump model…" = "Recuperar modelo de microinfusora…"; +"Fetching pump model…" = "Pumpenmodell abrufen…"; /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; @@ -167,10 +167,10 @@ "Save to Pump…" = "In der Pumpe abspeichern…"; /* The title of the command to send a button press */ -"Send Button Press" = "Sende Knopf drücken"; +"Send Button Press" = "Sende-Taste drücken"; /* Progress message for sending button press to pump. */ -"Sending button press…" = "Senden der Taste drücken…"; +"Sending button press…" = "Sende Knopfdruck…"; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Signalstärke"; @@ -179,16 +179,16 @@ "Succeeded" = "Erfolgreich"; /* The format string describing pump suspended state: (1: suspended) */ -"Suspended: %1$@\n" = "Suspendiert: %1$@\n"; +"Suspended: %1$@\n" = "Unterbrochen: %1$@\n"; /* The label indicating the results of each frequency trial */ "Trials" = "Versuche"; /* The title of the command to re-tune the radio */ -"Tune Radio Frequency" = "Stellen Sie die Radiofrequenz ein"; +"Tune Radio Frequency" = "Stellen Sie die Sendefrequenz ein"; /* Progress message for tuning radio */ -"Tuning radio…" = "Radio abstimmen…"; +"Tuning radio…" = "Frequenz abstimmen…"; /* The detail text for an unknown pump model */ "Unknown" = "Unbekannt"; From bb260e3ddc44248dc71759d9ace5f4f9f408814a Mon Sep 17 00:00:00 2001 From: Darin Krauss Date: Mon, 6 May 2019 09:49:18 -0700 Subject: [PATCH 19/71] Do not automatically create schemes --- .gitignore | 2 -- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ .../xcshareddata/WorkspaceSettings.xcsettings | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 RileyLink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 RileyLink.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/.gitignore b/.gitignore index a144810b8..72c7458b7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,8 @@ DerivedData *.ipa *.xcuserstate *.xcscmblueprint -project.xcworkspace .DS_Store *.log -project.xcworkspace # CocoaPods # diff --git a/RileyLink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/RileyLink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/RileyLink.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/RileyLink.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/RileyLink.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..08de0be8d --- /dev/null +++ b/RileyLink.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + From 38dde011eedf83bc7d15e14c249ab180df23d0ab Mon Sep 17 00:00:00 2001 From: Darin Krauss Date: Mon, 6 May 2019 09:56:33 -0700 Subject: [PATCH 20/71] Explicitly specify project dependencies --- RileyLink.xcodeproj/project.pbxproj | 156 ++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 256fb5c73..88cd5e9d3 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -589,6 +589,90 @@ remoteGlobalIDString = 43FB610120DDEF26002B996B; remoteInfo = Cartfile; }; + A9B839C422809D82004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; + }; + A9B839C622809D8D004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; + }; + A9B839C822809D91004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; + }; + A9B839CA22809DB3004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; + }; + A9B839CC22809DC3004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C10D9BC01C8269D500378342; + remoteInfo = MinimedKit; + }; + A9B839CE22809DCF004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C10D9BC01C8269D500378342; + remoteInfo = MinimedKit; + }; + A9B839D022809DE7004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; + }; + A9B839D222809DF3004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C10D9BC01C8269D500378342; + remoteInfo = MinimedKit; + }; + A9B839D422809DF3004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; + }; + A9B839D622809E67004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43FB610120DDEF26002B996B; + remoteInfo = Cartfile; + }; + A9B839D822809E75004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43FB610120DDEF26002B996B; + remoteInfo = Cartfile; + }; + A9B839DA22809E83004E745E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43FB610120DDEF26002B996B; + remoteInfo = Cartfile; + }; C10D9BCC1C8269D500378342 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -2197,6 +2281,8 @@ ); dependencies = ( 4352A73620DECAE900CAC200 /* PBXTargetDependency */, + A9B839D322809DF3004E745E /* PBXTargetDependency */, + A9B839D522809DF3004E745E /* PBXTargetDependency */, ); name = MinimedKitUI; productName = MinimedKitUI; @@ -2216,6 +2302,7 @@ ); dependencies = ( 4352A71120DEC67300CAC200 /* PBXTargetDependency */, + A9B839CB22809DB3004E745E /* PBXTargetDependency */, ); name = RileyLinkKit; productName = RileyLinkKit; @@ -2233,6 +2320,7 @@ buildRules = ( ); dependencies = ( + A9B839D922809E75004E745E /* PBXTargetDependency */, 43722FBA1CB9F7640038B7F2 /* PBXTargetDependency */, 4327EEBB20E1E562002598CB /* PBXTargetDependency */, ); @@ -2272,6 +2360,7 @@ ); dependencies = ( 43FB610920DDF509002B996B /* PBXTargetDependency */, + A9B839D122809DE7004E745E /* PBXTargetDependency */, ); name = RileyLinkKitUI; productName = RileyLinkKitUI; @@ -2291,6 +2380,8 @@ ); dependencies = ( 4352A71420DEC68700CAC200 /* PBXTargetDependency */, + A9B839C722809D8D004E745E /* PBXTargetDependency */, + A9B839C922809D91004E745E /* PBXTargetDependency */, ); name = MinimedKit; productName = MinimedKit; @@ -2308,6 +2399,7 @@ buildRules = ( ); dependencies = ( + A9B839DB22809E83004E745E /* PBXTargetDependency */, C10D9BCD1C8269D500378342 /* PBXTargetDependency */, 4327EEB920E1E55D002598CB /* PBXTargetDependency */, ); @@ -2354,6 +2446,7 @@ buildRules = ( ); dependencies = ( + A9B839C522809D82004E745E /* PBXTargetDependency */, C12EA258198B436900309FA4 /* PBXTargetDependency */, ); name = RileyLinkTests; @@ -2374,6 +2467,7 @@ ); dependencies = ( 43C246A51D891DB80031F8D1 /* PBXTargetDependency */, + A9B839CD22809DC3004E745E /* PBXTargetDependency */, ); name = NightscoutUploadKit; productName = NightscoutUploadKit; @@ -2392,6 +2486,8 @@ buildRules = ( ); dependencies = ( + A9B839D722809E67004E745E /* PBXTargetDependency */, + A9B839CF22809DCF004E745E /* PBXTargetDependency */, C1B383171CD0665D00CE7782 /* PBXTargetDependency */, C1B383191CD0665D00CE7782 /* PBXTargetDependency */, ); @@ -3217,6 +3313,66 @@ target = 43FB610120DDEF26002B996B /* Cartfile */; targetProxy = 43FB610820DDF509002B996B /* PBXContainerItemProxy */; }; + A9B839C522809D82004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = A9B839C422809D82004E745E /* PBXContainerItemProxy */; + }; + A9B839C722809D8D004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = A9B839C622809D8D004E745E /* PBXContainerItemProxy */; + }; + A9B839C922809D91004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; + targetProxy = A9B839C822809D91004E745E /* PBXContainerItemProxy */; + }; + A9B839CB22809DB3004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; + targetProxy = A9B839CA22809DB3004E745E /* PBXContainerItemProxy */; + }; + A9B839CD22809DC3004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C10D9BC01C8269D500378342 /* MinimedKit */; + targetProxy = A9B839CC22809DC3004E745E /* PBXContainerItemProxy */; + }; + A9B839CF22809DCF004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C10D9BC01C8269D500378342 /* MinimedKit */; + targetProxy = A9B839CE22809DCF004E745E /* PBXContainerItemProxy */; + }; + A9B839D122809DE7004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = A9B839D022809DE7004E745E /* PBXContainerItemProxy */; + }; + A9B839D322809DF3004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C10D9BC01C8269D500378342 /* MinimedKit */; + targetProxy = A9B839D222809DF3004E745E /* PBXContainerItemProxy */; + }; + A9B839D522809DF3004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; + targetProxy = A9B839D422809DF3004E745E /* PBXContainerItemProxy */; + }; + A9B839D722809E67004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43FB610120DDEF26002B996B /* Cartfile */; + targetProxy = A9B839D622809E67004E745E /* PBXContainerItemProxy */; + }; + A9B839D922809E75004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43FB610120DDEF26002B996B /* Cartfile */; + targetProxy = A9B839D822809E75004E745E /* PBXContainerItemProxy */; + }; + A9B839DB22809E83004E745E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43FB610120DDEF26002B996B /* Cartfile */; + targetProxy = A9B839DA22809E83004E745E /* PBXContainerItemProxy */; + }; C10D9BCD1C8269D500378342 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C10D9BC01C8269D500378342 /* MinimedKit */; From 794e16d9b36765c7425843ec5cd7609e19b1fd6d Mon Sep 17 00:00:00 2001 From: Darin Krauss Date: Mon, 6 May 2019 10:27:41 -0700 Subject: [PATCH 21/71] Create one shared scheme to improve Carthage build times; remove Cartfile target; project updates --- RileyLink.xcodeproj/project.pbxproj | 240 +++--------------- .../xcshareddata/xcschemes/Crypto.xcscheme | 80 ------ .../xcschemes/MinimedKit.xcscheme | 99 -------- .../xcschemes/MinimedKitUI.xcscheme | 80 ------ .../xcschemes/NightscoutUploadKit.xcscheme | 99 -------- .../xcschemes/RileyLinkBLEKit.xcscheme | 99 -------- .../xcschemes/RileyLinkKit.xcscheme | 99 -------- .../xcschemes/RileyLinkKitUI.xcscheme | 80 ------ ...RileyLink.xcscheme => Shared iOS.xcscheme} | 163 ++++++------ 9 files changed, 109 insertions(+), 930 deletions(-) delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKitUI.xcscheme delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/NightscoutUploadKit.xcscheme delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkBLEKit.xcscheme delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKit.xcscheme delete mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKitUI.xcscheme rename RileyLink.xcodeproj/xcshareddata/xcschemes/{RileyLink.xcscheme => Shared iOS.xcscheme} (53%) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 88cd5e9d3..27c7c8bb9 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -6,20 +6,6 @@ objectVersion = 47; objects = { -/* Begin PBXAggregateTarget section */ - 43FB610120DDEF26002B996B /* Cartfile */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 43FB610420DDEF26002B996B /* Build configuration list for PBXAggregateTarget "Cartfile" */; - buildPhases = ( - 43FB610520DDEF32002B996B /* Build Carthage Dependencies */, - ); - dependencies = ( - ); - name = Cartfile; - productName = Cartfile; - }; -/* End PBXAggregateTarget section */ - /* Begin PBXBuildFile section */ 2B19B9881DF3EF68006AB65F /* NewTimePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B19B9871DF3EF68006AB65F /* NewTimePumpEvent.swift */; }; 2F962EC11E6872170070EFBD /* TimestampedHistoryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F962EC01E6872170070EFBD /* TimestampedHistoryEventTests.swift */; }; @@ -89,7 +75,6 @@ 4352A74720DED4AF00CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; 4352A74820DED80300CAC200 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; 4352A74920DED81D00CAC200 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; - 4352A74A20DED87500CAC200 /* LoopKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4352A74B20DED87F00CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; 4352A74C20DED8C200CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; 4352A74D20DED8FC00CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; @@ -154,7 +139,6 @@ 43C246A91D8A31540031F8D1 /* Crypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246931D8918AE0031F8D1 /* Crypto.framework */; }; 43C246AA1D8A31540031F8D1 /* Crypto.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246931D8918AE0031F8D1 /* Crypto.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 43C9071B1D863772002BAD29 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; - 43C9071C1D863782002BAD29 /* MinimedKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 43CA93291CB8CF22000026B5 /* ChangeTempBasalCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA93281CB8CF22000026B5 /* ChangeTempBasalCarelinkMessageBody.swift */; }; 43CA932B1CB8CF76000026B5 /* ChangeTimeCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA932A1CB8CF76000026B5 /* ChangeTimeCarelinkMessageBody.swift */; }; 43CA932E1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA932C1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift */; }; @@ -512,20 +496,6 @@ remoteGlobalIDString = C12EA236198B436800309FA4; remoteInfo = RileyLink; }; - 4352A71020DEC67300CAC200 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; - 4352A71320DEC68700CAC200 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; 4352A72A20DEC9B700CAC200 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -533,20 +503,6 @@ remoteGlobalIDString = 4352A72420DEC9B700CAC200; remoteInfo = MinimedKitUI; }; - 4352A73520DECAE900CAC200 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; - 4352A74420DED49C00CAC200 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; 43722FB91CB9F7640038B7F2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -582,13 +538,6 @@ remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; remoteInfo = RileyLinkKitUI; }; - 43FB610820DDF509002B996B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; A9B839C422809D82004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -652,27 +601,6 @@ remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; remoteInfo = RileyLinkKitUI; }; - A9B839D622809E67004E745E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; - A9B839D822809E75004E745E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; - A9B839DA22809E83004E745E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; C10D9BCC1C8269D500378342 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -715,27 +643,9 @@ remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; - C1C0BE28224BF75600C03B4D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 43B6E0151D24E4610022E6D7 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 43C9071C1D863782002BAD29 /* MinimedKit.framework in CopyFiles */, - 4352A74A20DED87500CAC200 /* LoopKit.framework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C10D9BB81C82614F00378342 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -825,7 +735,6 @@ 43709AEB20E0056F00F941B3 /* RileyLinkKitUI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = RileyLinkKitUI.xcassets; sourceTree = ""; }; 43709AED20E008F300F941B3 /* SetupImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupImageTableViewCell.swift; sourceTree = ""; }; 43709AEF20E0120F00F941B3 /* SetupImageTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SetupImageTableViewCell.xib; sourceTree = ""; }; - 4370A3791FAF8A7400EC666A /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS4.1.sdk/System/Library/Frameworks/CoreBluetooth.framework; sourceTree = DEVELOPER_DIR; }; 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RileyLinkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43722FB01CB9F7640038B7F2 /* RileyLinkKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RileyLinkKit.h; sourceTree = ""; }; 43722FB21CB9F7640038B7F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -888,8 +797,8 @@ 43DFB61420D3791A008A7BAE /* ChangeMaxBasalRateMessageBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeMaxBasalRateMessageBody.swift; sourceTree = ""; }; 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; 43F348051D596270009933DC /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; - 43FB610A20DDF55E002B996B /* LoopKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LoopKitUI.framework; path = Carthage/Build/iOS/LoopKitUI.framework; sourceTree = ""; }; - 43FB610B20DDF55F002B996B /* LoopKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LoopKit.framework; path = Carthage/Build/iOS/LoopKit.framework; sourceTree = ""; }; + 43FB610A20DDF55E002B996B /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43FB610B20DDF55F002B996B /* LoopKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateComponents.swift; sourceTree = ""; }; 492526701E4521FB00ACBA5F /* NoteNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteNightscoutTreatment.swift; sourceTree = ""; }; 541688DA1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadCurrentGlucosePageMessageBodyTests.swift; sourceTree = ""; }; @@ -1871,7 +1780,6 @@ 43FB610B20DDF55F002B996B /* LoopKit.framework */, 43FB610A20DDF55E002B996B /* LoopKitUI.framework */, 43CA93241CB8BB33000026B5 /* CoreBluetooth.framework */, - 4370A3791FAF8A7400EC666A /* CoreBluetooth.framework */, C12616431B685F0A001FAD87 /* CoreData.framework */, C12EA23A198B436800309FA4 /* Foundation.framework */, C12EA23C198B436800309FA4 /* CoreGraphics.framework */, @@ -2242,7 +2150,6 @@ buildRules = ( ); dependencies = ( - C1C0BE29224BF75600C03B4D /* PBXTargetDependency */, ); name = RileyLinkBLEKit; productName = RileyLinkBLEKit; @@ -2280,7 +2187,6 @@ buildRules = ( ); dependencies = ( - 4352A73620DECAE900CAC200 /* PBXTargetDependency */, A9B839D322809DF3004E745E /* PBXTargetDependency */, A9B839D522809DF3004E745E /* PBXTargetDependency */, ); @@ -2301,7 +2207,6 @@ buildRules = ( ); dependencies = ( - 4352A71120DEC67300CAC200 /* PBXTargetDependency */, A9B839CB22809DB3004E745E /* PBXTargetDependency */, ); name = RileyLinkKit; @@ -2320,7 +2225,6 @@ buildRules = ( ); dependencies = ( - A9B839D922809E75004E745E /* PBXTargetDependency */, 43722FBA1CB9F7640038B7F2 /* PBXTargetDependency */, 4327EEBB20E1E562002598CB /* PBXTargetDependency */, ); @@ -2359,7 +2263,6 @@ buildRules = ( ); dependencies = ( - 43FB610920DDF509002B996B /* PBXTargetDependency */, A9B839D122809DE7004E745E /* PBXTargetDependency */, ); name = RileyLinkKitUI; @@ -2379,7 +2282,6 @@ buildRules = ( ); dependencies = ( - 4352A71420DEC68700CAC200 /* PBXTargetDependency */, A9B839C722809D8D004E745E /* PBXTargetDependency */, A9B839C922809D91004E745E /* PBXTargetDependency */, ); @@ -2399,7 +2301,6 @@ buildRules = ( ); dependencies = ( - A9B839DB22809E83004E745E /* PBXTargetDependency */, C10D9BCD1C8269D500378342 /* PBXTargetDependency */, 4327EEB920E1E55D002598CB /* PBXTargetDependency */, ); @@ -2421,7 +2322,6 @@ buildRules = ( ); dependencies = ( - 4352A74520DED49C00CAC200 /* PBXTargetDependency */, 43C246A31D891D6C0031F8D1 /* PBXTargetDependency */, C10D9BD51C8269D500378342 /* PBXTargetDependency */, 43722FC21CB9F7640038B7F2 /* PBXTargetDependency */, @@ -2481,12 +2381,11 @@ C1B383101CD0665D00CE7782 /* Sources */, C1B383111CD0665D00CE7782 /* Frameworks */, C1B383121CD0665D00CE7782 /* Resources */, - 43B6E0151D24E4610022E6D7 /* CopyFiles */, + A9B839DC2280A178004E745E /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( - A9B839D722809E67004E745E /* PBXTargetDependency */, A9B839CF22809DCF004E745E /* PBXTargetDependency */, C1B383171CD0665D00CE7782 /* PBXTargetDependency */, C1B383191CD0665D00CE7782 /* PBXTargetDependency */, @@ -2541,10 +2440,6 @@ LastSwiftMigration = 1000; ProvisioningStyle = Manual; }; - 43FB610120DDEF26002B996B = { - CreatedOnToolsVersion = 9.4.1; - ProvisioningStyle = Automatic; - }; C10D9BC01C8269D500378342 = { CreatedOnToolsVersion = 7.2.1; LastSwiftMigration = 1000; @@ -2608,7 +2503,6 @@ 431CE7761F98564200255374 /* RileyLinkBLEKitTests */, 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */, 4352A72420DEC9B700CAC200 /* MinimedKitUI */, - 43FB610120DDEF26002B996B /* Cartfile */, ); }; /* End PBXProject section */ @@ -2738,8 +2632,8 @@ files = ( ); inputPaths = ( - "$(SRCROOT)/Carthage/Build/iOS/LoopKit.framework", - "$(SRCROOT)/Carthage/Build/iOS/LoopKitUI.framework", + "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", + "$(BUILT_PRODUCTS_DIR)/LoopKitUI.framework", ); name = "Copy Frameworks with Carthage"; outputPaths = ( @@ -2748,21 +2642,27 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks"; + shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n\n"; }; - 43FB610520DDEF32002B996B /* Build Carthage Dependencies */ = { + A9B839DC2280A178004E745E /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( ); - name = "Build Carthage Dependencies"; outputPaths = ( + "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"$CARTHAGE\" = \"YES\" ]; then\n echo \"Skipping carthage build because we're already in one\"\nelif [ -d $PROJECT_DIR/../../../Loop.xcworkspace ]; then\n echo \"Skipping carthage build because we're in a workspace\"\nelif [ -f $PROJECT_DIR/.gitmodules ]; then\n echo \"Skipping checkout due to presence of .gitmodules file\"\nif [ $ACTION = \"install\" ]; then\n echo \"You're installing: Make sure to keep all submodules up-to-date and run carthage build after changes.\"\nfi\nelse\n echo \"Bootstrapping carthage dependencies\"\n unset LLVM_TARGET_TRIPLE_SUFFIX\n /usr/local/bin/carthage bootstrap --project-directory \"$SRCROOT\" --cache-builds\nfi\n"; + shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -3258,31 +3158,11 @@ target = C12EA236198B436800309FA4 /* RileyLink */; targetProxy = 4327EEBA20E1E562002598CB /* PBXContainerItemProxy */; }; - 4352A71120DEC67300CAC200 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 4352A71020DEC67300CAC200 /* PBXContainerItemProxy */; - }; - 4352A71420DEC68700CAC200 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 4352A71320DEC68700CAC200 /* PBXContainerItemProxy */; - }; 4352A72B20DEC9B700CAC200 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4352A72420DEC9B700CAC200 /* MinimedKitUI */; targetProxy = 4352A72A20DEC9B700CAC200 /* PBXContainerItemProxy */; }; - 4352A73620DECAE900CAC200 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 4352A73520DECAE900CAC200 /* PBXContainerItemProxy */; - }; - 4352A74520DED49C00CAC200 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 4352A74420DED49C00CAC200 /* PBXContainerItemProxy */; - }; 43722FBA1CB9F7640038B7F2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; @@ -3308,11 +3188,6 @@ target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; targetProxy = 43D5E7931FAF7BFB004ACDB7 /* PBXContainerItemProxy */; }; - 43FB610920DDF509002B996B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 43FB610820DDF509002B996B /* PBXContainerItemProxy */; - }; A9B839C522809D82004E745E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; @@ -3358,21 +3233,6 @@ target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; targetProxy = A9B839D422809DF3004E745E /* PBXContainerItemProxy */; }; - A9B839D722809E67004E745E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = A9B839D622809E67004E745E /* PBXContainerItemProxy */; - }; - A9B839D922809E75004E745E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = A9B839D822809E75004E745E /* PBXContainerItemProxy */; - }; - A9B839DB22809E83004E745E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = A9B839DA22809E83004E745E /* PBXContainerItemProxy */; - }; C10D9BCD1C8269D500378342 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C10D9BC01C8269D500378342 /* MinimedKit */; @@ -3403,11 +3263,6 @@ target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; - C1C0BE29224BF75600C03B4D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = C1C0BE28224BF75600C03B4D /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -3747,7 +3602,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", + "$(PROJECT_DIR)/Carthage/Build/iOS", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; @@ -3757,12 +3612,10 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,4"; VERSION_INFO_PREFIX = ""; }; name = Debug; @@ -3789,7 +3642,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", + "$(PROJECT_DIR)/Carthage/Build/iOS", ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; @@ -3799,10 +3652,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,4"; VERSION_INFO_PREFIX = ""; }; name = Release; @@ -3963,7 +3814,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", + "$(PROJECT_DIR)/Carthage/Build/iOS", ); GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = RileyLinkKit/Info.plist; @@ -3973,11 +3824,9 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,4"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4002,7 +3851,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", + "$(PROJECT_DIR)/Carthage/Build/iOS", ); GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = RileyLinkKit/Info.plist; @@ -4012,10 +3861,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,4"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4214,22 +4061,6 @@ }; name = Release; }; - 43FB610220DDEF26002B996B /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 43FB610320DDEF26002B996B /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; C10D9BD91C8269D600378342 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4248,7 +4079,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", + "$(PROJECT_DIR)/Carthage/Build/iOS", ); GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = MinimedKit/Info.plist; @@ -4258,11 +4089,9 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,4"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4286,7 +4115,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/$(CARTHAGE_PLATFORM_PATH_$(PLATFORM_NAME))", + "$(PROJECT_DIR)/Carthage/Build/iOS", ); GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = MinimedKit/Info.plist; @@ -4296,10 +4125,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos watchos watchsimulator"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = "1,2,4"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4361,10 +4188,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CARTHAGE_PLATFORM_PATH_iphoneos = iOS; - CARTHAGE_PLATFORM_PATH_iphonesimulator = iOS; - CARTHAGE_PLATFORM_PATH_watchos = watchOS; - CARTHAGE_PLATFORM_PATH_watchsimulator = watchOS; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4393,6 +4216,10 @@ CURRENT_PROJECT_VERSION = 46; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -4428,10 +4255,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CARTHAGE_PLATFORM_PATH_iphoneos = iOS; - CARTHAGE_PLATFORM_PATH_iphonesimulator = iOS; - CARTHAGE_PLATFORM_PATH_watchos = watchOS; - CARTHAGE_PLATFORM_PATH_watchsimulator = watchOS; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -4460,6 +4283,10 @@ CURRENT_PROJECT_VERSION = 46; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -4756,15 +4583,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 43FB610420DDEF26002B996B /* Build configuration list for PBXAggregateTarget "Cartfile" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 43FB610220DDEF26002B996B /* Debug */, - 43FB610320DDEF26002B996B /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; C10D9BD81C8269D600378342 /* Build configuration list for PBXNativeTarget "MinimedKit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme deleted file mode 100644 index 2228c2fc4..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme deleted file mode 100644 index 338e0cc9e..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKitUI.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKitUI.xcscheme deleted file mode 100644 index ecc0259a3..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKitUI.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/NightscoutUploadKit.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/NightscoutUploadKit.xcscheme deleted file mode 100644 index ad6453b86..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/NightscoutUploadKit.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkBLEKit.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkBLEKit.xcscheme deleted file mode 100644 index 56d2c46f9..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkBLEKit.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKit.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKit.xcscheme deleted file mode 100644 index 053fb7426..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKit.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKitUI.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKitUI.xcscheme deleted file mode 100644 index 49806138c..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKitUI.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared iOS.xcscheme similarity index 53% rename from RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme rename to RileyLink.xcodeproj/xcshareddata/xcschemes/Shared iOS.xcscheme index f58f53190..63a5e62a3 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared iOS.xcscheme @@ -4,7 +4,7 @@ version = "1.3"> + buildImplicitDependencies = "NO"> - - - - - - - - - - - - - + - - + + - - + + - - + + - - + + - + + + + + @@ -136,16 +135,15 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + @@ -155,16 +153,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - + - + From b8df2fd0e74746822e32d5d74378705e7a264561 Mon Sep 17 00:00:00 2001 From: Darin Krauss Date: Wed, 8 May 2019 18:58:46 -0700 Subject: [PATCH 22/71] Use consistent iOS and watchOS scheme and target name convention --- .../xcschemes/{Shared iOS.xcscheme => Shared.xcscheme} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename RileyLink.xcodeproj/xcshareddata/xcschemes/{Shared iOS.xcscheme => Shared.xcscheme} (100%) diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared iOS.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme similarity index 100% rename from RileyLink.xcodeproj/xcshareddata/xcschemes/Shared iOS.xcscheme rename to RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme From 20b513fd92514c8cb491c47fc1cf6a1306d89c40 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 19 May 2019 13:21:15 -0500 Subject: [PATCH 23/71] Add tests back, and app scheme --- .../xcshareddata/xcschemes/RileyLink.xcscheme | 91 +++++++++++++++++++ .../xcshareddata/xcschemes/Shared.xcscheme | 50 ++++++++++ 2 files changed, 141 insertions(+) create mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme new file mode 100644 index 000000000..872ce4951 --- /dev/null +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme index 63a5e62a3..e51b1727e 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme @@ -112,6 +112,56 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + + + + + + + + + + + + + Date: Sun, 19 May 2019 13:37:50 -0500 Subject: [PATCH 24/71] Build Shared scheme --- .travis.yml | 8 +++++--- Cartfile.resolved | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03be05f3f..8090dcef2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: objective-c osx_image: xcode10.2 -xcode_project: RileyLink.xcodeproj -xcode_scheme: RileyLink + +before_script: + - carthage bootstrap + script: - - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme RileyLink build -destination 'name=iPhone SE' test | xcpretty + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone SE' test | xcpretty diff --git a/Cartfile.resolved b/Cartfile.resolved index 02840443d..56ca222e7 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "LoopKit/LoopKit" "7333408f054e269669c91146b0f29bffbdcc150a" +github "LoopKit/LoopKit" "346f5e85940fb56211035ae2e9f522512f17d0c0" From 7297016d4fc063c75c40f9e8402e303df78816aa Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 08:03:55 -0500 Subject: [PATCH 25/71] Omnipod testing integrate (#528) * Update to loopkit pumpmanager-updates * Avoid setting the alternateSegmentPulse flag for only one segment * Remove no-op store * Fix argument order * Fix rounding error when encoding half pulses into temp basal extra * Adding hex prints for full messages. * Finish BolusExtra command, and fix rounding error in Bolus command * Add test for large bolus * Fix another rounding issue with bolus schedule * Fix another likely rounding error for extended boluses * NNNN field in basal extra needs to be even multiple of 10 * Pulse rounding... - Allow rounding to 0.5 pulses for half hour segments of temp basals - Split up longer temp basals based on a limit of 6400 pulses max in an entry, and ensure that the rate for each entry rounds with 0.5 pulse accuracy * Cleanup * Persist faults in PodState, and display in issue report * Round request rate to nearest supported pod rate. * Round boluses to nearest pulse, and skip suspend during deactivation if pod is faulting * LoopKit PumpManagerStatus updates * Update LoopKit rev * Update set insulin command trigger delivery at half period intervals * PumpManagerUI vended HUD views * XXXXXXXX field is time remaining until next tenth of a pulse * Use LoopKit updates * LoopKit renaming * Many decoding fixes and added string formatting on PodInfoFaultEvent (#473) * Removed periods + created prefix * Moved prefix function into var description. Removed all hardcoded FaultEvent Type 0xXX from descriptions. * Added string variable for switch result to return together with generated fault code. * Return Unknown Error as a faultDescription variable * Code cleanup and removed duplicate FaultError string on Unknown Error * Renamed recieverLowGain->receiverLowGain * Renamed recieverLowGain->receiverLowGain Removed unused length enum * - Fixed proper encoding of bits for radioRSSI and receiverLowGain - Renamed receiverLowGain in IssueReport string * Be able to show negative RSSI and gain values by using an signed Integer. * Renamed Unknown Error Code -> Unknown Fault * Add missing rawType to testPumpAlarms assertion. * Add missing rawType to pass testPumpAlarm * Decode RSSI and Gain to Int8 values * index on omnipod-testing-podinfo-updates: 63086ef Add missing rawType to testPumpAlarms assertion. * Renamed swift file ReseroirStatus->PodProgressStatus * Decoding fixed for timeActive. * Fix failing Travis test: Renamed firstIndex -> index * Removed 'rawType' in assertion, to pass Travis test. * - Removed missed periods from faultevents - Added >50 U to reservoirLevel debugDescription - Removed unused function to parse time - Added switch statement for day/days * Updated PodInfotest to show String value for reservoirLevel * - Added FaultEvent description - Removed duplicate Status fields * Added new Faulteventcodes. * Updated FaultEvents with latest message.md values * Updated Loopkit in Cartfile.resolved to pass Travis build * Added description variable for showing names of DeliveryTypes in debugger instead of DeliveryType(rawValue:0) * - Removed whitespaces - Added enum for logEventErrorType to switch the different ErrorTypes instead of separate true/false evaluations. Still need to test if multiple ErrorTypes can be returned, no it assumes there can only be one. - Fixed missing last to bytes (YYYY) in encodedData PodInfoResponse - Added missing YYYY unknown Value - Renamed previousPodProgressStatus -> logEventErrorPodProgressStatus to let the PodProgressStatus be a part of the information about logged fault event - Renamed previousPodProgressStatusCheck -> previousPodProgressStatus * - Fixed seconds to minutes baseline for TimeInterval parsing - Parse minutes correctly to day/hour/minute integers - Renamed 'time since activation' strings to: 1 day plus 18:39 - Added tests to check proper string formatting for time debugger strings * Added missing 'plus' on time sting test * - Return nil for ReservoirLevel if it is not <50 U - Ensure new unknown logEventError values are printed properly. Setup logEventErrorType the same as faultEventType as a Model. This way it will return a value as a description or as an unknown code value. * Renamed index -> firstIndex back as it is setup in Data. Travis build error solved by updating from v9 to v10. * Unset all Development_team values in project. * Added Unit Test PodInfoFaultEvent: InsulinNotDelivered * Simplified the reservoirValue to read the decimal * podPulseSize And showing the value 50 as well. * - Fixed decoding only first 10 bits to count reservoirValue - Added StatusResponse test with 46U left. (real data capture) * Fixed typo unKnownValue->unknownValue * - Added hexadecimalString formatting to unknownvalue - Updated filename title of swift file to PodProgressStatus.swfit - Removed last development team value from project * Updated Cartfile.resolved to latest LoopKit to pass Travis * Add omnipod reservoir view * Update LoopKit rev * Adding pod life HUD view * Remove testing code * Bump loopkit rev * Adding missing file * Use actual basal schedule during time sync and pairing * Change alert name. belowFiftyUnits -> lowReservoir * Configurealarms beeprepeat fix and added back missing debugDescription values (#477) * - Fixed switched BeepRepeat <->BeepType encoding/decoding fields - Created enum for BeepRepeat to read more clearly what setting you will get - Fixed Tests to pass the enum values of BeepRepeat * Fixed failing podStateTests: added expiresAt value * Use the same Date instance on activatedAt and expiresAt parameters * Removed double entries in PodInfoFaultEvent description caused by rebasing * Added missing description values back to debugDescription * Replaced DeliveryType with DeliveryStatus to decode values like 0x06 * Updated to deliveryStatus types to pass tests * - Moved DeliveryStatus to Pod.swift because of it being a shared type. - Moved DeliveryType to cancelCommand because it is only applied there. - Removed DeliveryType description function because the options can be combined. - Rerouted DeliveryType in to cancelDeliveryCommand in PodCommsSession * Pod alarm handling * Update LoopKit reference * Add description for pod alarms * Comment out testing command for now * Omnipod testing pairing screen layout fixes (#478) * - Fixed cutoff text on 4s and 6 screen - Aligned footer text on all pages - Resized content boxes with constraint on margins * Aligned Filling Pod text to top left * Added 0x prefix to Fault en Log Code * Formatting for issue report * More issue report formatting updates * HUDProvider updates * Update loopkit rev * Update to Swift 4.2 * Fix reservoir update regression, and show hours floored for pod life remaining * Revert Storyboard changes from #478 * Use self sizing cells for omnipod pump setup * Updated FaultEventCodes from events.md commit be06014 back to e4d305c (#481) * Updated FaultEventCodes from events.md commit be06014 back to e4d305c * Upated cartfile.resolved * Use insulin remaining field of status resposne when canceling boluses. * Return DoseEntry for requested bolus, including duration * Add jumpstart pod method * Fix rounding issue with temp basals * Implement roundToDeliveryIncrement() for omnipod and minimed * Update loopkit rev * Add tests for delivery increments * Do not allow temp basal setting while bolus is in progress for omnipod. * Support multiple pumpmanager status observers * Use UIAlertController extension for error alerts * bump LoopKit rev * Basal schedule tests passing. * Basal schedule tests passing. * Fix BasalScheduleExtraCommand rate merging * Round current time to nearest second for calculation of fields in $13 command * Record message logs * Move OmnipodHUDProvider into its own module * Update LoopKit rev * Make OmnipodPumpManager persist between pod changes * Update LoopKit rev * Do not show PumpManager delete until pod is deactivated. * Update tests to use new PodState initializer * Setup mod podstate on simulator * Retry pod deactivation * Allow pairing on simulator * Track pairing state; only configure RL once during pairing * Handle pod pairing interruptions more atomically, pairing progress updates * Tweak retry params to closer match PDM * Handle faults during pairing * Fix tests * Forget pod immediately after deactivation, cancel doses on suspending fault * Experiments: * Set the A bit of B9 for the set basal schedule command during resume from suspend and during pairing * Advance nonce for each message send, instead of just successful message sends * Increment packet number for each packet exchange, instead of just successful exchanges. * $31 badMType -> insulinDeliveryCommandError * Shorter syncIdentifier for pumpevents * Do not make a status request before issuing a new temp basal, unless a bolus might be active. * $08 bit in the AX cancel byte is not cancel for extended boluses, and do not set it when cancelling all delivery * Handle RL responses with 0 length packets as just a bad packet instead of a more serious error * New understanding of flagA: expectFollowOnMessage. * Fix nonce resync. New packet parser * Fix nonce resync. New packet parser * Time formatting change for tests * Add omnipod packet parsing for loop issue report files * Forget podstate only after success or second failure. * Make PodCommsSession run synchronously; only used from OmnipodPumpManager's queue * Fix tests * Fix issue with basal sync when no pod is paired * Handle QQQQ of 0xFFFF for faultEventTimeSinceActivation * Make disabling x faults the default * Updates for LoopKit name changes * Fault Event and Issue Report updates (#488) * - Updated/Added new fault events up to commit 8bab297 of events.md - Updated IssueReport Faultevents with hex and total insulin delivered values - Updated PodInfoTest for new values * - Only prepend 0x hex for still unknown values, if decimal render without any. - Format reservoirLevel as String type only at display time - Single hex: type as Integer - Force 2 decimals on all insulin values - Updated Podinfo tests * Add additional setup state, after cannula insertion, before verification. * Alerts unify (#489) * AlertType + PodAlarm -> Alert * Add abstraction for assigning alerts to numbered slots on the pod * Register alerts with podstate * Fix alert slot serialization * Flip alert mask from msb first to lsb first * Revert alert bit order change, and fix expiration advisory alarm setup * Compute alarm timing based on fixed intervals since activation time * Test fixes * Fix totalInsulinDelivered decoding in faults * Updates for recent LoopKit pumpmanager-changes branch * Fix retain cycle * Persist message log * Bump loopkit rev * Handle LoopKit changes to setup indicator * Handle settings view controllers being presented modally * CompletionNotifying protocol updates * More changes for switch to modal presentation * MinimedPumpManager changes for PumpManager protocol updates * devicesSectionIndex is not changed after initialization * Bump LoopKit rev * Fix handling settings completion * Don't push root viewcontroller * Don't push root viewcontroller * Wrap unsuspend for bolus errors in SetBolusError.certain * Allow PumpState mock creation * Create suspend resume cell via cell reuse * Remove unused argument * Use higher res delivery for x23 pumps for roundToDeliveryIncrement * Guard access to MinimedPumpManagerState * Code organization * dequeue SuspendResumeTableViewCell * Remove unneeded conditional let * Fix typo * Make some properties immutable on MinimedPumpManagerState * Add notes about thread issues * Include dose in enactBolus completion * Dismiss settings controller on minimed pumpmanager delete * Bump LoopKit rev * Bump LoopKit rev * Setup controller is presented modally * Setup controller is shown modally * Update LoopKit rev * Add missing schemes * Show query date on pod reservoir * Fix insulin formatting * Disable timerTick when appropriate, loop based on bg arrival, and reduce number of status requests. * Avoid temp basal cancel and any status requests while bolusing. * Parse xcode logs for RL packets * ackUntilQuiet() should listen for packets with current pod address * Fix delay in alarm showing on reservoir view on startup. * Show pod settings when tapping on reservoir view * Fix alarm clearing UI updates * x23 pumps speed up bolus delivery to fit in 5 minute window * present instead of show settings vc * Remove zero bolus error; use assertionFailure instead * Fix vc present() call * ReservoirVolumeHUDView interface changed * Use active flag instead of appear/disapper methods * Update hud views only when active * Merge in latest from pumpmanager-changes * HUDProvider active -> visible * Update reservoir view on becoming active. * Fix travis build * Define allowed rates for x23 and x22 pumps, and integrate new basal picker * Define allowed rates for omnipod, and integrate new basal picker * Fix bug with tracking last temp basal * Add new pump precision methods required by PumpManager protocol * Add new pump precision methods required by PumpManager protocol * Report ongoing temp basal * Fix incorrect suspend state after changing time and basal schedule * Error when changing time zone or basal schedule while bolusing * Nonce resync is a certain failure while bolusing. * Refresh status when hud is made visible * Trackiong suspend PumpEvents * Track resume separately for InsulinMath reconciliation * omnipod pair/deactivate message edits (#502) * Show unfinalizedResume in issue report * Use previous, not current nonce, in nonce resync calculation * Record resume event for programming basal schedule * Nonce seed is 16 bit counter, and advance nonce after resync * Bolus progress integration * Add time offset for average comms delay * Fix suspend transitioning race * Update to latest LoopKit rev * Pre-build carthage * Replace pod command should only be red it is a destructive action * Update bolus state when bolus cancellation finishes * Subtract prime units from total insulin delivered * Add acknowledgeBeep Bool option for Pod insulin commands (#510) * Use only 32-bit math for nonce table as per firmware (#511) * Update fault codes and messages from firmware (#512) * Create suspend and resume events during pod replacement (#513) * Update wording on bolus in progress error * User configurable pod expiration warning * set initial expirationReminderDate after priming * Update the expiration reminder alert when settings is shown * On pod fault display Pod expired/Empty reservoir/Occlusion detected/fault code (#514) * On pod fault say Pod expired/Empty reservoir/Occlusion detected/hex code * Reworked to have PodReplacementReason.fault case contain the fault type * Have .fault case now store FaultEventCode, print miscellaneous fault values in decimal * Reworked to make rawType on FaultEventCode public so all pod faults can be simply printed, fix long-standing typo (setup -> set up) * Use faultCode.localizedDescription to provide the fault code description for the user * Deactive button should be red when deactivating functioning pod * If, during retry of cannula insertion, we detect that it is finished, continue without async wait * Update existing pod state with default expirationReminderDate * Beep Options confidenceReminder -> completionBeep (#519) * Fix tests * If pod acks in responsed to configure command, it is already configured. * Finalize doses when removing old pod state. * Set pumpManager for finish vc at end of first time pod setup * Use omnipod-testing LoopKit * Fix incorrectly reported error during deactivation of faulting pod. * Bump rev for LoopKit * include pump manufacturer and model in the pump status (#522) * Add BeepConfig command support, miscellaneous beep related cleanup (#524) * Add BeepConfig command support, miscelleous beep related cleanup * Changed byteType from UInt8 to BeepType, remove unneeded length, improved commenting * Fix command count check, commenting cleanup (#523) * Fix command count check, commenting cleanup * Fix another bad length check, miscellaneous comment and code fixes, remove wiki commands and duplicated type comments * Fixed a number of PodInfo length issues in commenting, checking and testing * Threading updates * Update carthage revisions * Pause progress circle when not visible * Add OmniKitTests to shared scheme * Update to Swift 5 --- Cartfile | 1 + Cartfile.resolved | 3 +- Common/Data.swift | 22 +- .../ChangeMaxBasalRateMessageBody.swift | 2 +- .../Messages/DataFrameMessageBody.swift | 2 +- .../Messages/MySentryAckMessageBody.swift | 2 +- .../MySentryPumpStatusMessageBody.swift | 4 +- MinimedKit/Messages/PumpMessage.swift | 2 +- .../ReadCurrentGlucosePageMessageBody.swift | 2 +- .../SelectBasalProfileMessageBody.swift | 2 +- .../SetRemoteControlEnabledMessageBody.swift | 2 +- MinimedKit/PumpManager/PumpOps.swift | 6 +- ...hangeRemoteControlIDMessageBodyTests.swift | 2 +- ...ReadRemoteControlIDsMessageBodyTests.swift | 8 +- MinimedKitTests/PumpOpsSynchronousTests.swift | 2 +- .../TimestampedHistoryEventTests.swift | 4 +- .../Base.lproj/MinimedPumpManager.storyboard | 6 +- .../CommandResponseViewController.swift | 2 +- .../MinimedPumpSettingsViewController.swift | 2 - ...LinkMinimedDeviceTableViewController.swift | 7 +- .../MinimedPumpIDSetupViewController.swift | 2 +- ...MinimedPumpSentrySetupViewController.swift | 2 +- .../DeviceStatus/PumpStatus.swift | 14 +- OmniKit/Extensions/Notification.swift | 13 + OmniKit/Info.plist | 24 + OmniKit/MessageTransport/CRC16.swift | 56 + OmniKit/MessageTransport/CRC8.swift | 40 + OmniKit/MessageTransport/Message.swift | 108 ++ .../AcknowledgeAlertCommand.swift | 42 + .../MessageBlocks/AssignAddressCommand.swift | 38 + .../BasalScheduleExtraCommand.swift | 97 ++ .../MessageBlocks/BeepConfigCommand.swift | 62 + .../MessageBlocks/BolusExtraCommand.swift | 77 + .../MessageBlocks/CancelDeliveryCommand.swift | 82 + .../ConfigureAlertsCommand.swift | 128 ++ .../MessageBlocks/ConfigurePodCommand.swift | 86 ++ .../MessageBlocks/DeactivatePodCommand.swift | 39 + .../MessageBlocks/ErrorResponse.swift | 35 + .../MessageBlocks/FaultConfigCommand.swift | 48 + .../MessageBlocks/GetStatusCommand.swift | 47 + .../MessageBlocks/MessageBlock.swift | 86 ++ .../PlaceholderMessageBlock.swift | 29 + .../MessageBlocks/PodInfo.swift | 51 + .../PodInfoConfiguredAlerts.swift | 55 + .../MessageBlocks/PodInfoDataLog.swift | 46 + .../MessageBlocks/PodInfoFault.swift | 47 + .../MessageBlocks/PodInfoFaultEvent.swift | 166 ++ .../MessageBlocks/PodInfoFlashLogRecent.swift | 31 + .../MessageBlocks/PodInfoResetStatus.swift | 40 + .../MessageBlocks/PodInfoResponse.swift | 35 + .../MessageBlocks/PodInfoTester.swift | 34 + .../SetInsulinScheduleCommand.swift | 194 +++ .../MessageBlocks/StatusResponse.swift | 107 ++ .../MessageBlocks/TempBasalExtraCommand.swift | 88 ++ .../MessageBlocks/VersionResponse.swift | 110 ++ .../MessageTransport/MessageTransport.swift | 285 ++++ .../MessageTransport/Packet+RFPacket.swift | 17 + OmniKit/MessageTransport/Packet.swift | 93 ++ OmniKit/Model/AlertSlot.swift | 271 ++++ OmniKit/Model/BasalDeliveryTable.swift | 236 +++ OmniKit/Model/BasalSchedule.swift | 123 ++ OmniKit/Model/BeepType.swift | 28 + OmniKit/Model/FaultEventCode.swift | 413 +++++ OmniKit/Model/LogEventErrorCode.swift | 54 + OmniKit/Model/Pod.swift | 98 ++ OmniKit/Model/PodProgressStatus.swift | 74 + OmniKit/Model/UnfinalizedDose.swift | 237 +++ OmniKit/OmniKit.h | 19 + OmniKit/PumpManager/MessageLog.swift | 92 ++ OmniKit/PumpManager/OmnipodPumpManager.swift | 1355 +++++++++++++++++ .../PumpManager/OmnipodPumpManagerState.swift | 189 +++ OmniKit/PumpManager/PodComms.swift | 329 ++++ .../PumpManager/PodCommsSession+LoopKit.swift | 16 + OmniKit/PumpManager/PodCommsSession.swift | 610 ++++++++ .../PodDoseProgressEstimator.swift | 47 + .../PumpManager/PodInsulinMeasurements.swift | 51 + OmniKit/PumpManager/PodState.swift | 494 ++++++ OmniKitPacketParser/main.swift | 260 ++++ OmniKitTests/AcknowledgeAlertsTests.swift | 30 + OmniKitTests/BasalScheduleTests.swift | 405 +++++ OmniKitTests/BolusTests.swift | 149 ++ OmniKitTests/CRC16Tests.swift | 21 + OmniKitTests/CRC8Tests.swift | 19 + OmniKitTests/Info.plist | 22 + OmniKitTests/MessageTests.swift | 240 +++ OmniKitTests/OmniKitTests-Bridging-Header.h | 4 + OmniKitTests/PacketTests.swift | 53 + OmniKitTests/PodCommsSessionTests.swift | 109 ++ OmniKitTests/PodInfoTests.swift | 389 +++++ OmniKitTests/PodStateTests.swift | 55 + OmniKitTests/StatusTests.swift | 100 ++ OmniKitTests/TempBasalTests.swift | 335 ++++ OmniKitUI/Info.plist | 24 + OmniKitUI/OmniKitUI.h | 19 + OmniKitUI/OmniKitUI.xcassets/Contents.json | 6 + .../Pod.imageset/Contents.json | 23 + .../OmniKitUI.xcassets/Pod.imageset/pod1x.png | Bin 0 -> 8892 bytes .../OmniKitUI.xcassets/Pod.imageset/pod2x.png | Bin 0 -> 21306 bytes .../OmniKitUI.xcassets/Pod.imageset/pod3x.png | Bin 0 -> 42936 bytes .../PodBottom.imageset/Contents.json | 23 + .../PodBottom.imageset/PodBottom1x.png | Bin 0 -> 11922 bytes .../PodBottom.imageset/PodBottom2x.png | Bin 0 -> 32936 bytes .../PodBottom.imageset/PodBottom3x.png | Bin 0 -> 55231 bytes .../PodLarge.imageset/Contents.json | 23 + .../PodLarge.imageset/pod_1x-1.png | Bin 0 -> 67130 bytes .../PodLarge.imageset/pod_1x-2.png | Bin 0 -> 19537 bytes .../PodLarge.imageset/pod_1x.png | Bin 0 -> 138788 bytes OmniKitUI/OmnipodPumpManager.storyboard | 658 ++++++++ .../PumpManager/OmniPodPumpManager+UI.swift | 96 ++ .../PumpManager/OmnipodHUDProvider.swift | 214 +++ .../CommandResponseViewController.swift | 64 + .../InsertCannulaSetupViewController.swift | 203 +++ ...mnipodPumpManagerSetupViewController.swift | 113 ++ .../OmnipodSettingsViewController.swift | 805 ++++++++++ .../PairPodSetupViewController.swift | 239 +++ .../PodReplacementNavigationController.swift | 73 + .../PodSettingsSetupViewController.swift | 204 +++ .../PodSetupCompleteViewController.swift | 67 + .../ReplacePodViewController.swift | 221 +++ .../ExpirationReminderDateTableViewCell.swift | 58 + .../ExpirationReminderDateTableViewCell.xib | 80 + .../Views/HUDAssets.xcassets/Contents.json | 6 + .../HUDAssets.xcassets/pod_life/Contents.json | 6 + .../pod_life/pod_life.imageset/Contents.json | 16 + .../pod_life/pod_life.imageset/PodLifeBG.pdf | Bin 0 -> 3551 bytes .../reservoir/Contents.json | 6 + .../pod_reservoir.imageset/Contents.json | 15 + .../pod_reservoir.imageset/reservoir.pdf | Bin 0 -> 4978 bytes .../pod_reservoir_mask.imageset/Contents.json | 12 + .../reservoir_mask.pdf | Bin 0 -> 5537 bytes OmniKitUI/Views/OmnipodReservoirView.swift | 147 ++ OmniKitUI/Views/OmnipodReservoirView.xib | 95 ++ OmniKitUI/Views/PodLifeHUDView.swift | 159 ++ OmniKitUI/Views/PodLifeHUDView.xib | 117 ++ RileyLink.xcodeproj/project.pbxproj | 1269 ++++++++++++++- .../xcschemes/OmniKitPacketParser.xcscheme | 97 ++ .../xcshareddata/xcschemes/Shared.xcscheme | 84 +- RileyLink/DeviceDataManager.swift | 1 + RileyLink/PumpManagerState.swift | 7 + .../View Controllers/MainViewController.swift | 10 + RileyLinkBLEKit/CBCentralManager.swift | 6 + RileyLinkBLEKit/Command.swift | 24 +- RileyLinkBLEKit/CommandSession.swift | 4 + RileyLinkBLEKit/PeripheralManager.swift | 10 +- RileyLinkBLEKit/RileyLinkDeviceManager.swift | 2 +- RileyLinkKitUI/CBPeripheralState.swift | 2 + .../RileyLinkDeviceTableViewCell.swift | 2 + .../RileyLinkDevicesTableViewDataSource.swift | 6 +- Scripts/copy-frameworks.sh | 39 + 149 files changed, 14768 insertions(+), 151 deletions(-) create mode 100644 OmniKit/Extensions/Notification.swift create mode 100644 OmniKit/Info.plist create mode 100644 OmniKit/MessageTransport/CRC16.swift create mode 100644 OmniKit/MessageTransport/CRC8.swift create mode 100644 OmniKit/MessageTransport/Message.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/AcknowledgeAlertCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/BeepConfigCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/ErrorResponse.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/FaultConfigCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PlaceholderMessageBlock.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfo.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoConfiguredAlerts.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoDataLog.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoFault.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoFaultEvent.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoResetStatus.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoResponse.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/PodInfoTester.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/SetInsulinScheduleCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift create mode 100644 OmniKit/MessageTransport/MessageBlocks/VersionResponse.swift create mode 100644 OmniKit/MessageTransport/MessageTransport.swift create mode 100644 OmniKit/MessageTransport/Packet+RFPacket.swift create mode 100644 OmniKit/MessageTransport/Packet.swift create mode 100644 OmniKit/Model/AlertSlot.swift create mode 100644 OmniKit/Model/BasalDeliveryTable.swift create mode 100644 OmniKit/Model/BasalSchedule.swift create mode 100644 OmniKit/Model/BeepType.swift create mode 100644 OmniKit/Model/FaultEventCode.swift create mode 100644 OmniKit/Model/LogEventErrorCode.swift create mode 100644 OmniKit/Model/Pod.swift create mode 100644 OmniKit/Model/PodProgressStatus.swift create mode 100644 OmniKit/Model/UnfinalizedDose.swift create mode 100644 OmniKit/OmniKit.h create mode 100644 OmniKit/PumpManager/MessageLog.swift create mode 100644 OmniKit/PumpManager/OmnipodPumpManager.swift create mode 100644 OmniKit/PumpManager/OmnipodPumpManagerState.swift create mode 100644 OmniKit/PumpManager/PodComms.swift create mode 100644 OmniKit/PumpManager/PodCommsSession+LoopKit.swift create mode 100644 OmniKit/PumpManager/PodCommsSession.swift create mode 100644 OmniKit/PumpManager/PodDoseProgressEstimator.swift create mode 100644 OmniKit/PumpManager/PodInsulinMeasurements.swift create mode 100644 OmniKit/PumpManager/PodState.swift create mode 100644 OmniKitPacketParser/main.swift create mode 100644 OmniKitTests/AcknowledgeAlertsTests.swift create mode 100644 OmniKitTests/BasalScheduleTests.swift create mode 100644 OmniKitTests/BolusTests.swift create mode 100644 OmniKitTests/CRC16Tests.swift create mode 100644 OmniKitTests/CRC8Tests.swift create mode 100644 OmniKitTests/Info.plist create mode 100644 OmniKitTests/MessageTests.swift create mode 100644 OmniKitTests/OmniKitTests-Bridging-Header.h create mode 100644 OmniKitTests/PacketTests.swift create mode 100644 OmniKitTests/PodCommsSessionTests.swift create mode 100644 OmniKitTests/PodInfoTests.swift create mode 100644 OmniKitTests/PodStateTests.swift create mode 100644 OmniKitTests/StatusTests.swift create mode 100644 OmniKitTests/TempBasalTests.swift create mode 100644 OmniKitUI/Info.plist create mode 100644 OmniKitUI/OmniKitUI.h create mode 100644 OmniKitUI/OmniKitUI.xcassets/Contents.json create mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json create mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod1x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod2x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod3x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom3x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-2.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png create mode 100644 OmniKitUI/OmnipodPumpManager.storyboard create mode 100644 OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift create mode 100644 OmniKitUI/PumpManager/OmnipodHUDProvider.swift create mode 100644 OmniKitUI/ViewControllers/CommandResponseViewController.swift create mode 100644 OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift create mode 100644 OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift create mode 100644 OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift create mode 100644 OmniKitUI/ViewControllers/PairPodSetupViewController.swift create mode 100644 OmniKitUI/ViewControllers/PodReplacementNavigationController.swift create mode 100644 OmniKitUI/ViewControllers/PodSettingsSetupViewController.swift create mode 100644 OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift create mode 100644 OmniKitUI/ViewControllers/ReplacePodViewController.swift create mode 100644 OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift create mode 100644 OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/Contents.json create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/pod_life/Contents.json create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/PodLifeBG.pdf create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/reservoir/Contents.json create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/Contents.json create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/reservoir.pdf create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/Contents.json create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/reservoir_mask.pdf create mode 100644 OmniKitUI/Views/OmnipodReservoirView.swift create mode 100644 OmniKitUI/Views/OmnipodReservoirView.xib create mode 100644 OmniKitUI/Views/PodLifeHUDView.swift create mode 100644 OmniKitUI/Views/PodLifeHUDView.xib create mode 100644 RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme create mode 100755 Scripts/copy-frameworks.sh diff --git a/Cartfile b/Cartfile index 05195c51b..e2488b425 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1,2 @@ github "LoopKit/LoopKit" "dev" +github "maxkonovalov/MKRingProgressView" ~> 2.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index 56ca222e7..237e068c9 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1,2 @@ -github "LoopKit/LoopKit" "346f5e85940fb56211035ae2e9f522512f17d0c0" +github "LoopKit/LoopKit" "4eca69b9f2e3f1fc74e5b62484db00777c002cb7" +github "maxkonovalov/MKRingProgressView" "2.2.2" diff --git a/Common/Data.swift b/Common/Data.swift index 6fb689bfe..7ed02fa6d 100644 --- a/Common/Data.swift +++ b/Common/Data.swift @@ -10,16 +10,22 @@ import Foundation extension Data { - func to(_: T.Type) -> T { - return self.withUnsafeBytes { (bytes: UnsafePointer) in - return T(littleEndian: bytes.pointee) - } + private func toDefaultEndian(_: T.Type) -> T { + return self.withUnsafeBytes({ (rawBufferPointer: UnsafeRawBufferPointer) -> T in + let bufferPointer = rawBufferPointer.bindMemory(to: T.self) + guard let pointer = bufferPointer.baseAddress else { + return 0 + } + return T(pointer.pointee) + }) } - func toBigEndian(_: T.Type) -> T { - return self.withUnsafeBytes { - return T(bigEndian: $0.pointee) - } + func to(_ type: T.Type) -> T { + return T(littleEndian: toDefaultEndian(type)) + } + + func toBigEndian(_ type: T.Type) -> T { + return T(bigEndian: toDefaultEndian(type)) } mutating func append(_ newElement: T) { diff --git a/MinimedKit/Messages/ChangeMaxBasalRateMessageBody.swift b/MinimedKit/Messages/ChangeMaxBasalRateMessageBody.swift index 46aa8d4f2..cfbcbc49a 100644 --- a/MinimedKit/Messages/ChangeMaxBasalRateMessageBody.swift +++ b/MinimedKit/Messages/ChangeMaxBasalRateMessageBody.swift @@ -19,7 +19,7 @@ public class ChangeMaxBasalRateMessageBody: CarelinkLongMessageBody { let ticks = UInt16(maxBasalUnitsPerHour * type(of: self).multiplier) let length = UInt8(clamping: ticks.bitWidth / 8) - var data = Data(bytes: [length]) + var data = Data([length]) data.appendBigEndian(ticks) diff --git a/MinimedKit/Messages/DataFrameMessageBody.swift b/MinimedKit/Messages/DataFrameMessageBody.swift index 98472652e..b1eb1deff 100644 --- a/MinimedKit/Messages/DataFrameMessageBody.swift +++ b/MinimedKit/Messages/DataFrameMessageBody.swift @@ -39,7 +39,7 @@ public class DataFrameMessageBody: CarelinkLongMessageBody { byte0 |= 0b1000_0000 } - var data = Data(bytes: [byte0]) + var data = Data([byte0]) data.append(contents) return data diff --git a/MinimedKit/Messages/MySentryAckMessageBody.swift b/MinimedKit/Messages/MySentryAckMessageBody.swift index 970d44354..8dd816994 100644 --- a/MinimedKit/Messages/MySentryAckMessageBody.swift +++ b/MinimedKit/Messages/MySentryAckMessageBody.swift @@ -46,6 +46,6 @@ public struct MySentryAckMessageBody: MessageBody { buffer.replaceSubrange(5..<5 + responseMessageTypes.count, with: responseMessageTypes.map({ $0.rawValue })) - return Data(bytes: buffer) + return Data(buffer) } } diff --git a/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift b/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift index 686ae8687..63b45d8d7 100644 --- a/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift +++ b/MinimedKit/Messages/MySentryPumpStatusMessageBody.swift @@ -158,8 +158,8 @@ public struct MySentryPumpStatusMessageBody: MessageBody, DictionaryRepresentabl let batteryRemainingPercent: UInt8 = rxData[14] self.batteryRemainingPercent = Int(round(Double(batteryRemainingPercent) / 4.0 * 100)) - let glucoseValue = Int(bigEndianBytes: Data(bytes: [rxData[9], rxData[24] << 7])) >> 7 - let previousGlucoseValue = Int(bigEndianBytes: Data(bytes: [rxData[10], rxData[24] << 6])) >> 7 + let glucoseValue = Int(bigEndianBytes: Data([rxData[9], rxData[24] << 7])) >> 7 + let previousGlucoseValue = Int(bigEndianBytes: Data([rxData[10], rxData[24] << 6])) >> 7 glucose = SensorReading(glucose: glucoseValue) previousGlucose = SensorReading(glucose: previousGlucoseValue) diff --git a/MinimedKit/Messages/PumpMessage.swift b/MinimedKit/Messages/PumpMessage.swift index 700c5b5cc..8d13ac324 100644 --- a/MinimedKit/Messages/PumpMessage.swift +++ b/MinimedKit/Messages/PumpMessage.swift @@ -44,7 +44,7 @@ public struct PumpMessage : CustomStringConvertible { buffer.append(messageType.rawValue) buffer.append(contentsOf: messageBody.txData) - return Data(bytes: buffer) + return Data(buffer) } public var description: String { diff --git a/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift b/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift index b35b34ede..44c98f5ff 100644 --- a/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift +++ b/MinimedKit/Messages/ReadCurrentGlucosePageMessageBody.swift @@ -20,7 +20,7 @@ public class ReadCurrentGlucosePageMessageBody: CarelinkLongMessageBody { } self.pageNum = rxData[1..<5 - ].withUnsafeBytes { UInt32(bigEndian: $0.pointee) } + ].toBigEndian(UInt32.self) self.glucose = Int(rxData[6]) self.isig = Int(rxData[8]) diff --git a/MinimedKit/Messages/SelectBasalProfileMessageBody.swift b/MinimedKit/Messages/SelectBasalProfileMessageBody.swift index 6cf3dbd1d..d5e2a5d95 100644 --- a/MinimedKit/Messages/SelectBasalProfileMessageBody.swift +++ b/MinimedKit/Messages/SelectBasalProfileMessageBody.swift @@ -9,6 +9,6 @@ import Foundation public class SelectBasalProfileMessageBody: CarelinkLongMessageBody { public convenience init(newProfile: BasalProfile) { - self.init(rxData: Data(bytes: [1, newProfile.rawValue]))! + self.init(rxData: Data([1, newProfile.rawValue]))! } } diff --git a/MinimedKit/Messages/SetRemoteControlEnabledMessageBody.swift b/MinimedKit/Messages/SetRemoteControlEnabledMessageBody.swift index 475fefa7f..f9f48da81 100644 --- a/MinimedKit/Messages/SetRemoteControlEnabledMessageBody.swift +++ b/MinimedKit/Messages/SetRemoteControlEnabledMessageBody.swift @@ -10,6 +10,6 @@ import Foundation public class SetRemoteControlEnabledMessageBody: CarelinkLongMessageBody { public convenience init(enabled: Bool) { - self.init(rxData: Data(bytes: [1, enabled ? 1 : 0]))! + self.init(rxData: Data([1, enabled ? 1 : 0]))! } } diff --git a/MinimedKit/PumpManager/PumpOps.swift b/MinimedKit/PumpManager/PumpOps.swift index f6c7845f7..8b8d6a65f 100644 --- a/MinimedKit/PumpManager/PumpOps.swift +++ b/MinimedKit/PumpManager/PumpOps.swift @@ -24,7 +24,7 @@ public class PumpOps { public let pumpSettings: PumpSettings - private let pumpState: Locked + public let pumpState: Locked private let configuredDevices: Locked> = Locked(Set()) @@ -108,10 +108,6 @@ public class PumpOps { value.remove(device) } } - - public func getPumpState(_ completion: @escaping (_ state: PumpState) -> Void) { - completion(self.pumpState.value) - } } // Delivered on RileyLinkDeviceManager.sessionQueue diff --git a/MinimedKitTests/Messages/ChangeRemoteControlIDMessageBodyTests.swift b/MinimedKitTests/Messages/ChangeRemoteControlIDMessageBodyTests.swift index e40c955e2..95d3cb463 100644 --- a/MinimedKitTests/Messages/ChangeRemoteControlIDMessageBodyTests.swift +++ b/MinimedKitTests/Messages/ChangeRemoteControlIDMessageBodyTests.swift @@ -12,7 +12,7 @@ class ChangeRemoteControlIDMessageBodyTests: XCTestCase { func testEncodeOneRemote() { let expected = Data(hexadecimalString: "0700313233343536000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")! - let body = ChangeRemoteControlIDMessageBody(id: Data(bytes: [1, 2, 3, 4, 5, 6]), index: 0)! + let body = ChangeRemoteControlIDMessageBody(id: Data([1, 2, 3, 4, 5, 6]), index: 0)! XCTAssertEqual(expected, body.txData, body.txData.hexadecimalString) } diff --git a/MinimedKitTests/Messages/ReadRemoteControlIDsMessageBodyTests.swift b/MinimedKitTests/Messages/ReadRemoteControlIDsMessageBodyTests.swift index e31e58606..63609d7ab 100644 --- a/MinimedKitTests/Messages/ReadRemoteControlIDsMessageBodyTests.swift +++ b/MinimedKitTests/Messages/ReadRemoteControlIDsMessageBodyTests.swift @@ -16,7 +16,7 @@ class ReadRemoteControlIDsMessageBodyTests: XCTestCase { let body = message.messageBody as! ReadRemoteControlIDsMessageBody XCTAssertEqual(1, body.ids.count) - XCTAssertEqual(Data(bytes: [1, 2, 3, 4, 5, 6]), body.ids[0]) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), body.ids[0]) } @@ -34,9 +34,9 @@ class ReadRemoteControlIDsMessageBodyTests: XCTestCase { let body = message.messageBody as! ReadRemoteControlIDsMessageBody XCTAssertEqual(3, body.ids.count) - XCTAssertEqual(Data(bytes: [0, 0, 0, 0, 0, 0]), body.ids[0]) - XCTAssertEqual(Data(bytes: [1, 0, 0, 1, 0, 0]), body.ids[1]) - XCTAssertEqual(Data(bytes: [9, 9, 9, 9, 9, 9]), body.ids[2]) + XCTAssertEqual(Data([0, 0, 0, 0, 0, 0]), body.ids[0]) + XCTAssertEqual(Data([1, 0, 0, 1, 0, 0]), body.ids[1]) + XCTAssertEqual(Data([9, 9, 9, 9, 9, 9]), body.ids[2]) } } diff --git a/MinimedKitTests/PumpOpsSynchronousTests.swift b/MinimedKitTests/PumpOpsSynchronousTests.swift index 36adc1079..9c978ecfa 100644 --- a/MinimedKitTests/PumpOpsSynchronousTests.swift +++ b/MinimedKitTests/PumpOpsSynchronousTests.swift @@ -253,7 +253,7 @@ class PumpOpsSynchronousTests: XCTestCase { let minuteMonthByte = minuteByte | monthLowerComponent let yearByte = UInt8(year) & 0b01111111 - let batteryData = Data(bytes: [0,0, secondMonthByte, minuteMonthByte, hourByte, dayByte, yearByte]) + let batteryData = Data([0,0, secondMonthByte, minuteMonthByte, hourByte, dayByte, yearByte]) let batteryPumpEvent = BatteryPumpEvent(availableData: batteryData, pumpModel: PumpModel.model523)! return batteryPumpEvent } diff --git a/MinimedKitTests/TimestampedHistoryEventTests.swift b/MinimedKitTests/TimestampedHistoryEventTests.swift index b3012f173..f118af488 100644 --- a/MinimedKitTests/TimestampedHistoryEventTests.swift +++ b/MinimedKitTests/TimestampedHistoryEventTests.swift @@ -27,7 +27,7 @@ class TimestampedHistoryEventTests: XCTestCase { } func testEventIsntMutable() { - let data = Data(bytes: Array([7,6,5,4,3,2,1,0])) + let data = Data(Array([7,6,5,4,3,2,1,0])) let event = BatteryPumpEvent(availableData: data, pumpModel: PumpModel.model523)! let sut = TimestampedHistoryEvent(pumpEvent:event, date:Date()) @@ -36,7 +36,7 @@ class TimestampedHistoryEventTests: XCTestCase { } func testEventIsNotMutableFor522() { - let data = Data(bytes: Array([7,6,5,4,3,2,1,0])) + let data = Data(Array([7,6,5,4,3,2,1,0])) let event = BatteryPumpEvent(availableData: data, pumpModel: PumpModel.model522)! let sut = TimestampedHistoryEvent(pumpEvent:event, date:Date()) diff --git a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard index 474f4db6d..497d769ce 100644 --- a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard +++ b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard @@ -1,11 +1,11 @@ - + - + @@ -596,6 +596,6 @@ - + diff --git a/MinimedKitUI/CommandResponseViewController.swift b/MinimedKitUI/CommandResponseViewController.swift index f530501c2..06aa540f3 100644 --- a/MinimedKitUI/CommandResponseViewController.swift +++ b/MinimedKitUI/CommandResponseViewController.swift @@ -177,7 +177,7 @@ extension CommandResponseViewController { return T { (completionHandler) -> String in var byteArray = [UInt8](repeating: 0, count: 16) (device.peripheralIdentifier as NSUUID).getBytes(&byteArray) - let watchdogID = Data(bytes: byteArray[0..<3]) + let watchdogID = Data(byteArray[0..<3]) ops?.runSession(withName: "Change watchdog marriage profile", using: device) { (session) in let response: String diff --git a/MinimedKitUI/MinimedPumpSettingsViewController.swift b/MinimedKitUI/MinimedPumpSettingsViewController.swift index 47a0e4214..d7bbf008e 100644 --- a/MinimedKitUI/MinimedPumpSettingsViewController.swift +++ b/MinimedKitUI/MinimedPumpSettingsViewController.swift @@ -364,8 +364,6 @@ extension MinimedPumpSettingsViewController: PumpManagerStatusObserver { } } - - private extension UIAlertController { convenience init(pumpDeletionHandler handler: @escaping () -> Void) { self.init( diff --git a/MinimedKitUI/RileyLinkMinimedDeviceTableViewController.swift b/MinimedKitUI/RileyLinkMinimedDeviceTableViewController.swift index 2f87ea5a2..682b7ee26 100644 --- a/MinimedKitUI/RileyLinkMinimedDeviceTableViewController.swift +++ b/MinimedKitUI/RileyLinkMinimedDeviceTableViewController.swift @@ -84,16 +84,11 @@ public class RileyLinkMinimedDeviceTableViewController: UITableViewController { public init(device: RileyLinkDevice, pumpOps: PumpOps) { self.device = device self.ops = pumpOps + self.pumpState = pumpOps.pumpState.value super.init(style: .grouped) updateDeviceStatus() - - pumpOps.getPumpState { (state) in - DispatchQueue.main.async { - self.pumpState = state - } - } } required public init?(coder aDecoder: NSCoder) { diff --git a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift index 9db9a918b..09131254b 100644 --- a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift @@ -269,7 +269,7 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { let remoteIDCount = try session.getRemoteControlIDs().ids.count if remoteIDCount == 0 { - try session.setRemoteControlID(Data(bytes: [9, 9, 9, 9, 9, 9]), atIndex: 2) + try session.setRemoteControlID(Data([9, 9, 9, 9, 9, 9]), atIndex: 2) } try session.setRemoteControlEnabled(true) diff --git a/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift index c485f69ad..5058fedfd 100644 --- a/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpSentrySetupViewController.swift @@ -93,7 +93,7 @@ class MinimedPumpSentrySetupViewController: SetupTableViewController { return } - let watchdogID = Data(bytes: [0xd0, 0x00, 0x07]) + let watchdogID = Data([0xd0, 0x00, 0x07]) do { try session.changeWatchdogMarriageProfile(watchdogID) DispatchQueue.main.async { diff --git a/NightscoutUploadKit/DeviceStatus/PumpStatus.swift b/NightscoutUploadKit/DeviceStatus/PumpStatus.swift index a69092f2c..27c937b2f 100644 --- a/NightscoutUploadKit/DeviceStatus/PumpStatus.swift +++ b/NightscoutUploadKit/DeviceStatus/PumpStatus.swift @@ -11,6 +11,8 @@ import Foundation public struct PumpStatus { let clock: Date let pumpID: String + let manufacturer: String? + let model: String? let iob: IOBStatus? let battery: BatteryStatus? let suspended: Bool? @@ -18,9 +20,11 @@ public struct PumpStatus { let reservoir: Double? let secondsFromGMT: Int? - public init(clock: Date, pumpID: String, iob: IOBStatus? = nil, battery: BatteryStatus? = nil, suspended: Bool? = nil, bolusing: Bool? = nil, reservoir: Double? = nil, secondsFromGMT: Int? = nil) { + public init(clock: Date, pumpID: String, manufacturer: String? = nil, model: String? = nil, iob: IOBStatus? = nil, battery: BatteryStatus? = nil, suspended: Bool? = nil, bolusing: Bool? = nil, reservoir: Double? = nil, secondsFromGMT: Int? = nil) { self.clock = clock self.pumpID = pumpID + self.manufacturer = manufacturer + self.model = model self.iob = iob self.battery = battery self.suspended = suspended @@ -35,6 +39,14 @@ public struct PumpStatus { rval["clock"] = TimeFormat.timestampStrFromDate(clock) rval["pumpID"] = pumpID + if let manufacturer = manufacturer { + rval["manufacturer"] = manufacturer + } + + if let model = model { + rval["model"] = model + } + if let iob = iob { rval["iob"] = iob.dictionaryRepresentation } diff --git a/OmniKit/Extensions/Notification.swift b/OmniKit/Extensions/Notification.swift new file mode 100644 index 000000000..bc0fdf190 --- /dev/null +++ b/OmniKit/Extensions/Notification.swift @@ -0,0 +1,13 @@ +// +// Notification.swift +// OmniKit +// +// Created by Pete Schwamb on 2/22/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +extension Notification.Name { + static let DeviceRadioConfigDidChange = Notification.Name(rawValue: "com.rileylink.RileyLinkKit.DeviceRadioConfigDidChange") +} diff --git a/OmniKit/Info.plist b/OmniKit/Info.plist new file mode 100644 index 000000000..3d809ffe3 --- /dev/null +++ b/OmniKit/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 2.1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/OmniKit/MessageTransport/CRC16.swift b/OmniKit/MessageTransport/CRC16.swift new file mode 100644 index 000000000..70a339e06 --- /dev/null +++ b/OmniKit/MessageTransport/CRC16.swift @@ -0,0 +1,56 @@ + // +// CRC16.swift +// OmniKit +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +let crc16Table: [UInt16] = [ + 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, + 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, + 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, + 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, + 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, + 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, + 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, + 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, + 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, + 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, + 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, + 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, + 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, + 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, + 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, + 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, + 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202] + +public extension Sequence where Element == UInt8 { + + func crc16() -> UInt16 { + + var acc: UInt16 = 0 + for byte in self { + let idx = (acc ^ UInt16(byte)) & 0xff + acc = (acc >> 8) ^ crc16Table[Int(idx)] + } + return acc + } +} diff --git a/OmniKit/MessageTransport/CRC8.swift b/OmniKit/MessageTransport/CRC8.swift new file mode 100644 index 000000000..46889130d --- /dev/null +++ b/OmniKit/MessageTransport/CRC8.swift @@ -0,0 +1,40 @@ +// +// CRC8.swift +// OmniKit +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +fileprivate let crcTable: [UInt8] = [ + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 +] + +public extension Sequence where Element == UInt8 { + + func crc8() -> UInt8 { + + var crc: UInt8 = 0 + for byte in self { + crc = crcTable[Int((crc ^ byte) & 0xff)] + } + return crc + } +} diff --git a/OmniKit/MessageTransport/Message.swift b/OmniKit/MessageTransport/Message.swift new file mode 100644 index 000000000..87f806323 --- /dev/null +++ b/OmniKit/MessageTransport/Message.swift @@ -0,0 +1,108 @@ +// +// Message.swift +// OmniKit +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum MessageError: Error { + case notEnoughData + case invalidCrc + case parsingError(offset: Int, data: Data, error: Error) + case unknownValue(value: UInt8, typeDescription: String) + case validationFailed(description: String) +} + +struct Message { + let address: UInt32 + let messageBlocks: [MessageBlock] + let sequenceNum: Int + let expectFollowOnMessage: Bool + + init(address: UInt32, messageBlocks: [MessageBlock], sequenceNum: Int, expectFollowOnMessage: Bool = false) { + self.address = address + self.messageBlocks = messageBlocks + self.sequenceNum = sequenceNum + self.expectFollowOnMessage = expectFollowOnMessage + } + + init(encodedData: Data) throws { + guard encodedData.count >= 10 else { + throw MessageError.notEnoughData + } + self.address = encodedData[0...].toBigEndian(UInt32.self) + let b9 = encodedData[4] + let bodyLen = encodedData[5] + + if bodyLen > encodedData.count - 8 { + throw MessageError.notEnoughData + } + + self.expectFollowOnMessage = (b9 & 0b10000000) != 0 + self.sequenceNum = Int((b9 >> 2) & 0b11111) + let crc = (UInt16(encodedData[encodedData.count-2]) << 8) + UInt16(encodedData[encodedData.count-1]) + let msgWithoutCrc = encodedData.prefix(encodedData.count - 2) + guard msgWithoutCrc.crc16() == crc else { + throw MessageError.invalidCrc + } + self.messageBlocks = try Message.decodeBlocks(data: Data(msgWithoutCrc.suffix(from: 6))) + } + + static private func decodeBlocks(data: Data) throws -> [MessageBlock] { + var blocks = [MessageBlock]() + var idx = 0 + repeat { + guard let blockType = MessageBlockType(rawValue: data[idx]) else { + throw MessageBlockError.unknownBlockType(rawVal: data[idx]) + } + do { + let block = try blockType.blockType.init(encodedData: Data(data.suffix(from: idx))) + blocks.append(block) + idx += Int(block.data.count) + } catch (let error) { + throw MessageError.parsingError(offset: idx, data: data.suffix(from: idx), error: error) + } + } while idx < data.count + return blocks + } + + func encoded() -> Data { + var bytes = Data(bigEndian: address) + + var cmdData = Data() + for cmd in messageBlocks { + cmdData.append(cmd.data) + } + + let b9: UInt8 = ((expectFollowOnMessage ? 1 : 0) << 7) + (UInt8(sequenceNum & 0b11111) << 2) + UInt8((cmdData.count >> 8) & 0b11) + bytes.append(b9) + bytes.append(UInt8(cmdData.count & 0xff)) + + var data = Data(bytes) + cmdData + let crc = data.crc16() + data.appendBigEndian(crc) + return data + } + + var fault: PodInfoFaultEvent? { + if messageBlocks.count > 0 && messageBlocks[0].blockType == .podInfoResponse, + let infoResponse = messageBlocks[0] as? PodInfoResponse, + infoResponse.podInfoResponseSubType == .faultEvents, + let fault = infoResponse.podInfo as? PodInfoFaultEvent + { + return fault + } else { + return nil + } + } +} + +extension Message: CustomDebugStringConvertible { + var debugDescription: String { + let sequenceNumStr = String(format: "%02d", sequenceNum) + return "Message(\(Data(bigEndian: address).hexadecimalString) seq:\(sequenceNumStr) \(messageBlocks))" + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/AcknowledgeAlertCommand.swift b/OmniKit/MessageTransport/MessageBlocks/AcknowledgeAlertCommand.swift new file mode 100644 index 000000000..b2709b74e --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/AcknowledgeAlertCommand.swift @@ -0,0 +1,42 @@ +// +// AcknowledgeAlertCommand.swift +// OmniKit +// +// Created by Eelke Jager on 18/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct AcknowledgeAlertCommand : NonceResyncableMessageBlock { + // OFF 1 2 3 4 5 6 + // 11 05 NNNNNNNN MM + + public let blockType: MessageBlockType = .acknowledgeAlert + public let length: UInt8 = 5 + public var nonce: UInt32 + public let alerts: AlertSet + + public init(nonce: UInt32, alerts: AlertSet) { + self.nonce = nonce + self.alerts = alerts + } + + public init(encodedData: Data) throws { + if encodedData.count < 7 { + throw MessageBlockError.notEnoughData + } + self.nonce = encodedData[2...].toBigEndian(UInt32.self) + self.alerts = AlertSet(rawValue: encodedData[6]) + } + + public var data: Data { + var data = Data([ + blockType.rawValue, + length + ]) + data.appendBigEndian(nonce) + data.append(alerts.rawValue) + return data + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift b/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift new file mode 100644 index 000000000..42404bcc4 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift @@ -0,0 +1,38 @@ +// +// AssignAddressCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 2/12/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct AssignAddressCommand : MessageBlock { + + public let blockType: MessageBlockType = .assignAddress + public let length: Int = 6 + + let address: UInt32 + + public var data: Data { + var data = Data([ + blockType.rawValue, + 4 + ]) + data.appendBigEndian(self.address) + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < length { + throw MessageBlockError.notEnoughData + } + + self.address = encodedData[2...].toBigEndian(UInt32.self) + } + + public init(address: UInt32) { + self.address = address + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift new file mode 100644 index 000000000..bf467ca9d --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift @@ -0,0 +1,97 @@ +// +// BasalScheduleExtraCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 3/30/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct BasalScheduleExtraCommand : MessageBlock { + + public let blockType: MessageBlockType = .basalScheduleExtra + + public let acknowledgementBeep: Bool + public let completionBeep: Bool + public let programReminderInterval: TimeInterval + public let currentEntryIndex: UInt8 + public let remainingPulses: Double + public let delayUntilNextTenthOfPulse: TimeInterval + public let rateEntries: [RateEntry] + + public var data: Data { + let beepOptions = (UInt8(programReminderInterval.minutes) & 0x3f) + (completionBeep ? (1<<6) : 0) + (acknowledgementBeep ? (1<<7) : 0) + var data = Data([ + blockType.rawValue, + UInt8(8 + rateEntries.count * 6), + beepOptions, + currentEntryIndex + ]) + data.appendBigEndian(UInt16(round(remainingPulses * 10))) + data.appendBigEndian(UInt32(round(delayUntilNextTenthOfPulse.milliseconds * 1000))) + for entry in rateEntries { + data.append(entry.data) + } + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 14 { + throw MessageBlockError.notEnoughData + } + let length = encodedData[1] + let numEntries = (length - 8) / 6 + + acknowledgementBeep = encodedData[2] & (1<<7) != 0 + completionBeep = encodedData[2] & (1<<6) != 0 + programReminderInterval = TimeInterval(minutes: Double(encodedData[2] & 0x3f)) + + currentEntryIndex = encodedData[3] + remainingPulses = Double(encodedData[4...].toBigEndian(UInt16.self)) / 10.0 + let timerCounter = encodedData[6...].toBigEndian(UInt32.self) + delayUntilNextTenthOfPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) + var entries = [RateEntry]() + for entryIndex in (0.. 0 ? squareWaveDuration / Double(pulseCountX10) : 0 + data.appendBigEndian(UInt32(timeBetweenExtendedPulses.hundredthsOfMilliseconds)) + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 15 { + throw MessageBlockError.notEnoughData + } + + acknowledgementBeep = encodedData[2] & (1<<7) != 0 + completionBeep = encodedData[2] & (1<<6) != 0 + programReminderInterval = TimeInterval(minutes: Double(encodedData[2] & 0x3f)) + + units = Double(encodedData[3...].toBigEndian(UInt16.self)) / 200 + + let delayCounts = encodedData[5...].toBigEndian(UInt32.self) + timeBetweenPulses = TimeInterval(hundredthsOfMilliseconds: Double(delayCounts)) + + let pulseCountX10 = encodedData[9...].toBigEndian(UInt16.self) + squareWaveUnits = Double(pulseCountX10) / 200 + + let intervalCounts = encodedData[5...].toBigEndian(UInt32.self) + let timeBetweenExtendedPulses = TimeInterval(hundredthsOfMilliseconds: Double(intervalCounts)) + squareWaveDuration = timeBetweenExtendedPulses * Double(pulseCountX10) / 10 + } + + public init(acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, units: Double, timeBetweenPulses: TimeInterval = 2, squareWaveUnits: Double = 0.0, squareWaveDuration: TimeInterval = 0) { + self.acknowledgementBeep = acknowledgementBeep + self.completionBeep = completionBeep + self.programReminderInterval = programReminderInterval + self.units = units + self.timeBetweenPulses = timeBetweenPulses + self.squareWaveUnits = squareWaveUnits + self.squareWaveDuration = squareWaveDuration + } +} + diff --git a/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift b/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift new file mode 100644 index 000000000..db6e880a3 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift @@ -0,0 +1,82 @@ +// +// CancelDeliveryCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 2/23/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + + + +public struct CancelDeliveryCommand : NonceResyncableMessageBlock { + + public let blockType: MessageBlockType = .cancelDelivery + + // ID1:1f00ee84 PTYPE:PDM SEQ:26 ID2:1f00ee84 B9:ac BLEN:7 MTYPE:1f05 BODY:e1f78752078196 CRC:03 + + // Cancel bolus + // 1f 05 be1b741a 64 - 1U + // 1f 05 a00a1a95 64 - 1U over 1hr + // 1f 05 ff52f6c8 64 - 1U immediate, 1U over 1hr + + // Cancel temp basal + // 1f 05 f76d34c4 62 - 30U/hr + // 1f 05 156b93e8 62 - ? + // 1f 05 62723698 62 - 0% + // 1f 05 2933db73 62 - 03ea + + // Suspend is a Cancel delivery, followed by a configure alerts command (0x19) + // 1f 05 50f02312 03 191050f02312580f000f06046800001e0302 + + // Deactivate pod: + // 1f 05 e1f78752 07 + + public struct DeliveryType: OptionSet, Equatable { + public let rawValue: UInt8 + + public static let none = DeliveryType(rawValue: 0) + public static let basal = DeliveryType(rawValue: 1 << 0) + public static let tempBasal = DeliveryType(rawValue: 1 << 1) + public static let bolus = DeliveryType(rawValue: 1 << 2) + + public static let all: DeliveryType = [.none, .basal, .tempBasal, .bolus] + + public init(rawValue: UInt8) { + self.rawValue = rawValue + } + + } + + public let deliveryType: DeliveryType + + public let beepType: BeepType + + public var nonce: UInt32 + + public var data: Data { + var data = Data([ + blockType.rawValue, + 5, + ]) + data.appendBigEndian(nonce) + data.append((beepType.rawValue << 4) + deliveryType.rawValue) + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 7 { + throw MessageBlockError.notEnoughData + } + self.nonce = encodedData[2...].toBigEndian(UInt32.self) + self.deliveryType = DeliveryType(rawValue: encodedData[6] & 0xf) + self.beepType = BeepType(rawValue: encodedData[6] >> 4)! + } + + public init(nonce: UInt32, deliveryType: DeliveryType, beepType: BeepType) { + self.nonce = nonce + self.deliveryType = deliveryType + self.beepType = beepType + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift b/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift new file mode 100644 index 000000000..8950a2337 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift @@ -0,0 +1,128 @@ +// +// ConfigureAlertsCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 2/22/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct ConfigureAlertsCommand : NonceResyncableMessageBlock { + + public let blockType: MessageBlockType = .configureAlerts + + public var nonce: UInt32 + let configurations: [AlertConfiguration] + + public var data: Data { + var data = Data([ + blockType.rawValue, + UInt8(4 + configurations.count * AlertConfiguration.length), + ]) + data.appendBigEndian(nonce) + for config in configurations { + data.append(contentsOf: config.data) + } + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 10 { + throw MessageBlockError.notEnoughData + } + self.nonce = encodedData[2...].toBigEndian(UInt32.self) + + let length = Int(encodedData[1]) + + let numConfigs = (length - 4) / AlertConfiguration.length + + var configs = [AlertConfiguration]() + + for i in 0..> 4 + guard let alertType = AlertSlot(rawValue: alertTypeBits) else { + throw MessageError.unknownValue(value: alertTypeBits, typeDescription: "AlertType") + } + self.slot = alertType + + self.active = encodedData[0] & 0b1000 != 0 + + self.autoOffModifier = encodedData[0] & 0b10 != 0 + + self.duration = TimeInterval(minutes: Double((Int(encodedData[0] & 0b1) << 8) + Int(encodedData[1]))) + + let yyyy = (Int(encodedData[2]) << 8) + (Int(encodedData[3])) & 0x3fff + + if encodedData[0] & 0b100 != 0 { + let volume = Double(yyyy * 2) * Pod.pulseSize + self.trigger = .unitsRemaining(volume) + } else { + self.trigger = .timeUntilAlert(TimeInterval(minutes: Double(yyyy))) + } + + let beepRepeatBits = encodedData[4] + guard let beepRepeat = BeepRepeat(rawValue: beepRepeatBits) else { + throw MessageError.unknownValue(value: beepRepeatBits, typeDescription: "BeepRepeat") + } + self.beepRepeat = beepRepeat + + let beepTypeBits = encodedData[5] + guard let beepType = BeepType(rawValue: beepTypeBits) else { + throw MessageError.unknownValue(value: beepTypeBits, typeDescription: "BeepType") + } + self.beepType = beepType + + } + + public var data: Data { + var firstByte = slot.rawValue << 4 + firstByte += active ? (1 << 3) : 0 + + if case .unitsRemaining = trigger { + firstByte += 1 << 2 + } + if autoOffModifier { + firstByte += 1 << 1 + } + // High bit of duration + firstByte += UInt8((Int(duration.minutes) >> 8) & 0x1) + + var data = Data([ + firstByte, + UInt8(Int(duration.minutes) & 0xff) + ]) + + switch trigger { + case .unitsRemaining(let volume): + let ticks = UInt16(volume / Pod.pulseSize / 2) + data.appendBigEndian(ticks) + case .timeUntilAlert(let duration): + let minutes = UInt16(duration.minutes) + data.appendBigEndian(minutes) + } + data.append(beepRepeat.rawValue) + data.append(beepType.rawValue) + + return data + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift b/OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift new file mode 100644 index 000000000..d6108d0bb --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift @@ -0,0 +1,86 @@ +// +// ConfigurePodCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 2/17/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct ConfigurePodCommand : MessageBlock { + + public let blockType: MessageBlockType = .setupPod + + let address: UInt32 + let lot: UInt32 + let tid: UInt32 + let dateComponents: DateComponents + let packetTimeoutLimit: UInt8 + + public static func dateComponents(date: Date, timeZone: TimeZone) -> DateComponents { + var cal = Calendar(identifier: .gregorian) + cal.timeZone = timeZone + return cal.dateComponents([.day, .month, .year, .hour, .minute], from: date) + } + + public static func date(from components: DateComponents, timeZone: TimeZone) -> Date? { + var cal = Calendar(identifier: .gregorian) + cal.timeZone = timeZone + return cal.date(from: components) + } + + // 03 13 1f08ced2 14 04 09 0b 11 0b 08 0000a640 00097c27 83e4 + public var data: Data { + var data = Data([ + blockType.rawValue, + 19, + ]) + data.appendBigEndian(self.address) + + let year = UInt8((dateComponents.year ?? 2000) - 2000) + let month = UInt8(dateComponents.month ?? 0) + let day = UInt8(dateComponents.day ?? 0) + let hour = UInt8(dateComponents.hour ?? 0) + let minute = UInt8(dateComponents.minute ?? 0) + + let data2: Data = Data([ + UInt8(0x14), // Unknown + packetTimeoutLimit, + month, + day, + year, + hour, + minute + ]) + data.append(data2) + data.appendBigEndian(self.lot) + data.appendBigEndian(self.tid) + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 21 { + throw MessageBlockError.notEnoughData + } + self.address = encodedData[2...].toBigEndian(UInt32.self) + packetTimeoutLimit = encodedData[7] + var components = DateComponents() + components.month = Int(encodedData[8]) + components.day = Int(encodedData[9]) + components.year = Int(encodedData[10]) + 2000 + components.hour = Int(encodedData[11]) + components.minute = Int(encodedData[12]) + self.dateComponents = components + self.lot = encodedData[13...].toBigEndian(UInt32.self) + self.tid = encodedData[17...].toBigEndian(UInt32.self) + } + + public init(address: UInt32, dateComponents: DateComponents, lot: UInt32, tid: UInt32, packetTimeoutLimit: UInt8 = 4) { + self.address = address + self.dateComponents = dateComponents + self.lot = lot + self.tid = tid + self.packetTimeoutLimit = packetTimeoutLimit + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift b/OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift new file mode 100644 index 000000000..184c62145 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift @@ -0,0 +1,39 @@ +// +// DeactivatePodCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 2/24/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct DeactivatePodCommand : NonceResyncableMessageBlock { + + // ID1:1f00ee84 PTYPE:PDM SEQ:09 ID2:1f00ee84 B9:34 BLEN:6 MTYPE:1c04 BODY:0f7dc4058344 CRC:f1 + + public let blockType: MessageBlockType = .deactivatePod + + public var nonce: UInt32 + + // e1f78752 07 8196 + public var data: Data { + var data = Data([ + blockType.rawValue, + 4, + ]) + data.appendBigEndian(nonce) + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 6 { + throw MessageBlockError.notEnoughData + } + self.nonce = encodedData[2...].toBigEndian(UInt32.self) + } + + public init(nonce: UInt32) { + self.nonce = nonce + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/ErrorResponse.swift b/OmniKit/MessageTransport/MessageBlocks/ErrorResponse.swift new file mode 100644 index 000000000..b984cc17d --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/ErrorResponse.swift @@ -0,0 +1,35 @@ +// +// ErrorResponse.swift +// OmniKit +// +// Created by Pete Schwamb on 2/25/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct ErrorResponse : MessageBlock { + + public let blockType: MessageBlockType = .errorResponse + + public enum ErrorReponseType: UInt8 { + case badNonce = 0x14 + } + + public let errorReponseType: ErrorReponseType + public let nonceSearchKey: UInt16 + + public let data: Data + + // 06 03 14 fa92 + + public init(encodedData: Data) throws { + self.data = encodedData + + guard let errorReponseType = ErrorReponseType(rawValue: encodedData[2]) else { + throw MessageError.unknownValue(value: encodedData[2], typeDescription: "ErrorReponseType") + } + self.errorReponseType = errorReponseType + nonceSearchKey = encodedData[3...].toBigEndian(UInt16.self) + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/FaultConfigCommand.swift b/OmniKit/MessageTransport/MessageBlocks/FaultConfigCommand.swift new file mode 100644 index 000000000..0acc91138 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/FaultConfigCommand.swift @@ -0,0 +1,48 @@ +// +// FaultConfigCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 12/18/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct FaultConfigCommand : NonceResyncableMessageBlock { + // OFF 1 2 3 4 5 6 7 + // 08 06 NNNNNNNN JJ KK + + public let blockType: MessageBlockType = .faultConfig + public let length: UInt8 = 6 + public var nonce: UInt32 + public let tab5Sub16: UInt8 + public let tab5Sub17: UInt8 + + public init(nonce: UInt32, tab5Sub16: UInt8, tab5Sub17: UInt8) { + self.nonce = nonce + self.tab5Sub16 = tab5Sub16 + self.tab5Sub17 = tab5Sub17 + } + + public init(encodedData: Data) throws { + if encodedData.count < 8 { + throw MessageBlockError.notEnoughData + } + + nonce = encodedData[2...].toBigEndian(UInt32.self) + + self.tab5Sub16 = encodedData[6] + self.tab5Sub17 = encodedData[7] + } + + public var data: Data { + var data = Data([ + blockType.rawValue, + length]) + + data.appendBigEndian(nonce) + data.append(tab5Sub16) + data.append(tab5Sub17) + return data + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift b/OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift new file mode 100644 index 000000000..d68ebdbbc --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift @@ -0,0 +1,47 @@ +// +// GetStatusCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct GetStatusCommand : MessageBlock { + // OFF 1 2 + // Oe 01 TT + + public let blockType: MessageBlockType = .getStatus + public let length: UInt8 = 1 + public let podInfoType: PodInfoResponseSubType + + public init(podInfoType: PodInfoResponseSubType = .normal) { + self.podInfoType = podInfoType + } + + public init(encodedData: Data) throws { + if encodedData.count < 3 { + throw MessageBlockError.notEnoughData + } + guard let podInfoType = PodInfoResponseSubType(rawValue: encodedData[2]) else { + throw MessageError.unknownValue(value: encodedData[2], typeDescription: "PodInfoResponseSubType") + } + self.podInfoType = podInfoType + } + + public var data: Data { + var data = Data([ + blockType.rawValue, + length + ]) + data.append(podInfoType.rawValue) + return data + } +} + +extension GetStatusCommand: CustomDebugStringConvertible { + public var debugDescription: String { + return "GetStatusCommand(\(podInfoType))" + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift b/OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift new file mode 100644 index 000000000..61d524627 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift @@ -0,0 +1,86 @@ +// +// MessageBlock.swift +// OmniKit +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum MessageBlockError: Error { + case notEnoughData + case unknownBlockType(rawVal: UInt8) + case parseError +} + +// See https://github.com/openaps/openomni/wiki/Message-Types +public enum MessageBlockType: UInt8 { + case versionResponse = 0x01 + case podInfoResponse = 0x02 + case setupPod = 0x03 + case errorResponse = 0x06 + case assignAddress = 0x07 + case faultConfig = 0x08 + case getStatus = 0x0e + case acknowledgeAlert = 0x11 + case basalScheduleExtra = 0x13 + case tempBasalExtra = 0x16 + case bolusExtra = 0x17 + case configureAlerts = 0x19 + case setInsulinSchedule = 0x1a + case deactivatePod = 0x1c + case statusResponse = 0x1d + case beepConfig = 0x1e + case cancelDelivery = 0x1f + + public var blockType: MessageBlock.Type { + switch self { + case .versionResponse: + return VersionResponse.self + case .acknowledgeAlert: + return AcknowledgeAlertCommand.self + case .podInfoResponse: + return PodInfoResponse.self + case .setupPod: + return ConfigurePodCommand.self + case .errorResponse: + return ErrorResponse.self + case .assignAddress: + return AssignAddressCommand.self + case .getStatus: + return GetStatusCommand.self + case .basalScheduleExtra: + return BasalScheduleExtraCommand.self + case .bolusExtra: + return BolusExtraCommand.self + case .configureAlerts: + return ConfigureAlertsCommand.self + case .setInsulinSchedule: + return SetInsulinScheduleCommand.self + case .deactivatePod: + return DeactivatePodCommand.self + case .statusResponse: + return StatusResponse.self + case .tempBasalExtra: + return TempBasalExtraCommand.self + case .beepConfig: + return BeepConfigCommand.self + case .cancelDelivery: + return CancelDeliveryCommand.self + case .faultConfig: + return FaultConfigCommand.self + } + } +} + +public protocol MessageBlock { + init(encodedData: Data) throws + + var blockType: MessageBlockType { get } + var data: Data { get } +} + +public protocol NonceResyncableMessageBlock : MessageBlock { + var nonce: UInt32 { get set } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/PlaceholderMessageBlock.swift b/OmniKit/MessageTransport/MessageBlocks/PlaceholderMessageBlock.swift new file mode 100644 index 000000000..21aaacfc0 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/PlaceholderMessageBlock.swift @@ -0,0 +1,29 @@ +// +// PlaceholderMessageBlock.swift +// OmniKit +// +// Created by Pete Schwamb on 10/24/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct PlaceholderMessageBlock: MessageBlock { + public let blockType: MessageBlockType + public let length: UInt8 + + public let data: Data + + public init(encodedData: Data) throws { + if encodedData.count < 2 { + throw MessageBlockError.notEnoughData + } + guard let blockType = MessageBlockType(rawValue: encodedData[0]) else { + throw MessageBlockError.unknownBlockType(rawVal: encodedData[0]) + } + self.blockType = blockType + length = encodedData[1] + data = encodedData.prefix(upTo: Int(length)) + } +} + diff --git a/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift b/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift new file mode 100644 index 000000000..99dc0f735 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift @@ -0,0 +1,51 @@ +// +// PodInfoResponseSubType.swift +// OmniKit +// +// Created by Eelke Jager on 15/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public protocol PodInfo { + init(encodedData: Data) throws + var podInfoType: PodInfoResponseSubType { get } + var data: Data { get } + +} + +public enum PodInfoResponseSubType: UInt8, Equatable { + case normal = 0x00 + case configuredAlerts = 0x01 + case faultEvents = 0x02 + case dataLog = 0x03 + case fault = 0x05 + case hardcodedTestValues = 0x06 + case resetStatus = 0x46 // including state, initialization time, any faults + case flashLogRecent = 0x50 // dumps up to 50 entries data from the flash log + case dumpOlderFlashlog = 0x51 // like 0x50, but dumps entries before the last 50 + + public var podInfoType: PodInfo.Type { + switch self { + case .normal: + return StatusResponse.self as! PodInfo.Type + case .configuredAlerts: + return PodInfoConfiguredAlerts.self + case .faultEvents: + return PodInfoFaultEvent.self + case .dataLog: + return PodInfoDataLog.self + case .fault: + return PodInfoFault.self + case .hardcodedTestValues: + return PodInfoTester.self + case .resetStatus: + return PodInfoResetStatus.self + case .flashLogRecent: + return PodInfoFlashLogRecent.self + case .dumpOlderFlashlog: + return PodInfoFlashLogRecent.self + } + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/PodInfoConfiguredAlerts.swift b/OmniKit/MessageTransport/MessageBlocks/PodInfoConfiguredAlerts.swift new file mode 100644 index 000000000..7f8e5004a --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/PodInfoConfiguredAlerts.swift @@ -0,0 +1,55 @@ +// +// PodInfoConfiguredAlerts.swift +// OmniKit +// +// Created by Eelke Jager on 16/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct PodInfoConfiguredAlerts : PodInfo { + // CMD 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 1920 + // DATA 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 + // 02 13 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + + public var podInfoType : PodInfoResponseSubType = .configuredAlerts + public let word_278 : Data + public let alertsActivations : [AlertActivation] + + public let data : Data + + public struct AlertActivation { + let beepType: BeepType + let unitsLeft: Double + let timeFromPodStart: UInt8 + + public init(beepType: BeepType, timeFromPodStart: UInt8, unitsLeft: Double) { + self.beepType = beepType + self.timeFromPodStart = timeFromPodStart + self.unitsLeft = unitsLeft + } + } + + public init(encodedData: Data) throws { + if encodedData.count < 11 { + throw MessageBlockError.notEnoughData + } + self.podInfoType = PodInfoResponseSubType.init(rawValue: encodedData[0])! + self.word_278 = encodedData[1...2] + + let numAlertTypes = 8 + let beepType = BeepType.self + + var activations = [AlertActivation]() + + for alarmType in (0..> 4) + + guard let logEventErrorPodProgressStatus = PodProgressStatus(rawValue: encodedData[17] & 0xF) else { + throw MessageError.unknownValue(value: encodedData[17] & 0xF, typeDescription: "PodProgressStatus") + } + self.logEventErrorPodProgressStatus = logEventErrorPodProgressStatus + + self.receiverLowGain = Int8(encodedData[18] >> 6) + + self.radioRSSI = Int8(encodedData[18] & 0x3F) + + guard let previousPodProgressStatus = PodProgressStatus(rawValue: encodedData[19] & 0xF) else { + throw MessageError.unknownValue(value: encodedData[19] & 0xF, typeDescription: "PodProgressStatus") + } + self.previousPodProgressStatus = previousPodProgressStatus + + self.unknownValue = encodedData[20...21] + + self.data = Data(encodedData) + } +} + +extension PodInfoFaultEvent: CustomDebugStringConvertible { + public typealias RawValue = Data + public var debugDescription: String { + return [ + "## PodInfoFaultEvent", + "* rawHex: \(data.hexadecimalString)", + "* podProgressStatus: \(podProgressStatus)", + "* deliveryStatus: \(deliveryStatus.description)", + "* insulinNotDelivered: \(insulinNotDelivered.twoDecimals) U", + "* podMessageCounter: \(podMessageCounter)", + "* totalInsulinDelivered: \(totalInsulinDelivered.twoDecimals) U", + "* currentStatus: \(currentStatus.description)", + "* faultEventTimeSinceActivation: \(faultEventTimeSinceActivation?.stringValue ?? "none")", + "* reservoirLevel: \(reservoirLevel?.twoDecimals ?? "50+") U", + "* timeActive: \(timeActive.stringValue)", + "* unacknowledgedAlerts: \(unacknowledgedAlerts)", + "* faultAccessingTables: \(faultAccessingTables)", + "* logEventErrorType: \(logEventErrorType.description)", + "* logEventErrorPodProgressStatus: \(logEventErrorPodProgressStatus)", + "* receiverLowGain: \(receiverLowGain)", + "* radioRSSI: \(radioRSSI)", + "* previousPodProgressStatus: \(previousPodProgressStatus)", + "* unknownValue: 0x\(unknownValue.hexadecimalString)", + "", + ].joined(separator: "\n") + } +} + +extension PodInfoFaultEvent: RawRepresentable { + public init?(rawValue: Data) { + do { + try self.init(encodedData: rawValue) + } catch { + return nil + } + } + + public var rawValue: Data { + return data + } +} + +extension TimeInterval { + var stringValue: String { + let totalSeconds = self + let minutes = Int(totalSeconds / 60) % 60 + let hours = Int(totalSeconds / 3600) - (Int(self / 3600)/24 * 24) + let days = Int((totalSeconds / 3600) / 24) + var pluralFormOfDays = "days" + if days == 1 { + pluralFormOfDays = "day" + } + let timeComponent = String(format: "%02d:%02d", hours, minutes) + if days > 0 { + return String(format: "%d \(pluralFormOfDays) plus %@", days, timeComponent) + } else { + return timeComponent + } + } +} + +extension Double { + var twoDecimals: String { + let reservoirLevel = self + return String(format: "%.2f", reservoirLevel) + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift b/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift new file mode 100644 index 000000000..af78d52e5 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift @@ -0,0 +1,31 @@ +// +// PodInfoFlashLogRecent.swift +// OmniKit +// +// Created by Eelke Jager on 26/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct PodInfoFlashLogRecent : PodInfo { + // CMD 1 2 3 4 5 6 7 8 + // DATA 0 1 2 3 4 5 6 + // 02 LL 50 IIII XXXXXXXX ... + // 02 LL 51 NNNN XXXXXXXX ... + + public var podInfoType : PodInfoResponseSubType = .flashLogRecent + public let indexLastEntry: UInt8 + public let hexWordLog : Data + public let data : Data + + public init(encodedData: Data) throws { + + if encodedData.count < 166 { + throw MessageBlockError.notEnoughData + } + self.indexLastEntry = encodedData[2] + self.hexWordLog = encodedData.subdata(in: 3.. ScheduleTypeCode { + switch self { + case .basalSchedule: + return .basalSchedule + case .tempBasal: + return .tempBasal + case .bolus: + return .bolus + } + } + + fileprivate var data: Data { + switch self { + case .basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table): + var data = Data([currentSegment]) + data.appendBigEndian(secondsRemaining << 3) + data.appendBigEndian(pulsesRemaining) + for entry in table.entries { + data.append(entry.data) + } + return data + case .bolus(let units, let timeBetweenPulses): + let pulseCount = UInt16(round(units / Pod.pulseSize)) + let multiplier = UInt16(round(timeBetweenPulses * 8)) + let fieldA = pulseCount * multiplier + let numHalfHourSegments: UInt8 = 1 + var data = Data([numHalfHourSegments]) + data.appendBigEndian(fieldA) + data.appendBigEndian(pulseCount) + data.appendBigEndian(pulseCount) + return data + case .tempBasal(let secondsRemaining, let firstSegmentPulses, let table): + var data = Data([UInt8(table.numSegments())]) + data.appendBigEndian(secondsRemaining << 3) + data.appendBigEndian(firstSegmentPulses) + for entry in table.entries { + data.append(entry.data) + } + return data + + } + } + + fileprivate func checksum() -> UInt16 { + switch self { + case .basalSchedule( _, _, _, let table): + return data[0..<5].reduce(0) { $0 + UInt16($1) } + + table.entries.reduce(0) { $0 + $1.checksum() } + case .bolus: + return data[0..<7].reduce(0) { $0 + UInt16($1) } + case .tempBasal(_, _, let table): + return data[0..<5].reduce(0) { $0 + UInt16($1) } + + table.entries.reduce(0) { $0 + $1.checksum() } + } + } + } + + public let blockType: MessageBlockType = .setInsulinSchedule + + public var nonce: UInt32 + public let deliverySchedule: DeliverySchedule + + public var data: Data { + var data = Data([ + blockType.rawValue, + UInt8(7 + deliverySchedule.data.count), + ]) + data.appendBigEndian(nonce) + data.append(deliverySchedule.typeCode().rawValue) + data.appendBigEndian(deliverySchedule.checksum()) + data.append(deliverySchedule.data) + return data + } + + public init(encodedData: Data) throws { + if encodedData.count < 6 { + throw MessageBlockError.notEnoughData + } + let length = encodedData[1] + + nonce = encodedData[2...].toBigEndian(UInt32.self) + + let checksum = encodedData[7...].toBigEndian(UInt16.self) + + guard let scheduleTypeCode = ScheduleTypeCode(rawValue: encodedData[6]) else { + throw MessageError.unknownValue(value: encodedData[6], typeDescription: "ScheduleTypeCode") + } + + switch scheduleTypeCode { + case .basalSchedule: + var entries = [BasalTableEntry]() + let numEntries = (length - 12) / 2 + for i in 0..> 3 + let pulsesRemaining = encodedData[12...].toBigEndian(UInt16.self) + let table = BasalDeliveryTable(entries: entries) + deliverySchedule = .basalSchedule(currentSegment: currentTableIndex, secondsRemaining: secondsRemaining, pulsesRemaining: pulsesRemaining, table: table) + case .tempBasal: + let secondsRemaining = encodedData[10...].toBigEndian(UInt16.self) >> 3 + let firstSegmentPulses = encodedData[12...].toBigEndian(UInt16.self) + var entries = [BasalTableEntry]() + let numEntries = (length - 12) / 2 + for i in 0.. UInt16 { + return data.reduce(0) { $0 + UInt16($1) } +} + +extension SetInsulinScheduleCommand: CustomDebugStringConvertible { + public var debugDescription: String { + return "SetInsulinScheduleCommand(nonce:\(nonce), \(deliverySchedule))" + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift b/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift new file mode 100644 index 000000000..28911d1d3 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift @@ -0,0 +1,107 @@ +// +// StatusResponse.swift +// OmniKit +// +// Created by Pete Schwamb on 10/23/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct StatusResponse : MessageBlock { + + public enum DeliveryStatus: UInt8, CustomStringConvertible { + case suspended = 0 + case normal = 1 + case tempBasalRunning = 2 + case priming = 4 + 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 .suspended: + return LocalizedString("Suspended", comment: "Delivery status when insulin delivery is suspended") + case .normal: + return LocalizedString("Scheduled Basal", comment: "Delivery status when basal is running") + case .tempBasalRunning: + return LocalizedString("Temp basal running", comment: "Delivery status when temp basal is running") + case .priming: + return LocalizedString("Priming", comment: "Delivery status when pod is priming") + case .bolusInProgress: + return LocalizedString("Bolusing", comment: "Delivery status when bolusing") + case .bolusAndTempBasal: + return LocalizedString("Bolusing with temp basal", comment: "Delivery status when bolusing and temp basal is running") + } + } + } + + public let blockType: MessageBlockType = .statusResponse + public let length: UInt8 = 10 + public let deliveryStatus: DeliveryStatus + public let podProgressStatus: PodProgressStatus + public let timeActive: TimeInterval + public let reservoirLevel: Double? + public let insulin: Double + public let insulinNotDelivered: Double + public let podMessageCounter: UInt8 + public let alerts: AlertSet + + + public let data: Data + + public init(encodedData: Data) throws { + if encodedData.count < length { + throw MessageBlockError.notEnoughData + } + + data = encodedData.prefix(upTo: Int(length)) + + guard let deliveryStatus = DeliveryStatus(rawValue: encodedData[1] >> 4) else { + throw MessageError.unknownValue(value: encodedData[1] >> 4, typeDescription: "DeliveryStatus") + } + self.deliveryStatus = deliveryStatus + + guard let podProgressStatus = PodProgressStatus(rawValue: encodedData[1] & 0xf) else { + throw MessageError.unknownValue(value: encodedData[1] & 0xf, typeDescription: "PodProgressStatus") + } + self.podProgressStatus = podProgressStatus + + let minutes = ((Int(encodedData[7]) & 0x7f) << 6) + (Int(encodedData[8]) >> 2) + self.timeActive = TimeInterval(minutes: Double(minutes)) + + let highInsulinBits = Int(encodedData[2] & 0xf) << 9 + let midInsulinBits = Int(encodedData[3]) << 1 + let lowInsulinBits = Int(encodedData[4] >> 7) + self.insulin = Double(highInsulinBits | midInsulinBits | lowInsulinBits) / Pod.pulsesPerUnit + + self.podMessageCounter = (encodedData[4] >> 3) & 0xf + + self.insulinNotDelivered = Double((Int(encodedData[4] & 0x3) << 8) | Int(encodedData[5])) / Pod.pulsesPerUnit + + self.alerts = AlertSet(rawValue: ((encodedData[6] & 0x7f) << 1) | (encodedData[7] >> 7)) + + let reservoirValue = Double((Int(encodedData[8] & 0x3) << 8) + Int(encodedData[9])) / Pod.pulsesPerUnit + if reservoirValue <= Pod.maximumReservoirReading { + self.reservoirLevel = reservoirValue + } else { + self.reservoirLevel = nil + } + } +} + +extension StatusResponse: CustomDebugStringConvertible { + public var debugDescription: String { + return "StatusResponse(deliveryStatus:\(deliveryStatus), progressStatus:\(podProgressStatus), timeActive:\(timeActive.stringValue), reservoirLevel:\(String(describing: reservoirLevel)), delivered:\(insulin), undelivered:\(insulinNotDelivered), seq:\(podMessageCounter), alerts:\(alerts))" + } +} + diff --git a/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift new file mode 100644 index 000000000..fa9b70be2 --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift @@ -0,0 +1,88 @@ +// +// TempBasalExtraCommand.swift +// OmniKit +// +// Created by Pete Schwamb on 6/6/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct TempBasalExtraCommand : MessageBlock { + + public let acknowledgementBeep: Bool + public let completionBeep: Bool + public let programReminderInterval: TimeInterval + public let remainingPulses: Double + public let delayUntilNextPulse: TimeInterval + public let rateEntries: [RateEntry] + + public let blockType: MessageBlockType = .tempBasalExtra + + public var data: Data { + let beepOptions = (UInt8(programReminderInterval.minutes) & 0x3f) + (completionBeep ? (1<<6) : 0) + (acknowledgementBeep ? (1<<7) : 0) + var data = Data([ + blockType.rawValue, + UInt8(8 + rateEntries.count * 6), + beepOptions, + 0 + ]) + data.appendBigEndian(UInt16(round(remainingPulses * 2) * 5)) + if remainingPulses == 0 { + data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds) * 10) + } else { + data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds)) + } + for entry in rateEntries { + data.append(entry.data) + } + return data + } + + public init(encodedData: Data) throws { + + let length = encodedData[1] + let numEntries = (length - 8) / 6 + + acknowledgementBeep = encodedData[2] & (1<<7) != 0 + completionBeep = encodedData[2] & (1<<6) != 0 + programReminderInterval = TimeInterval(minutes: Double(encodedData[2] & 0x3f)) + + remainingPulses = Double(encodedData[4...].toBigEndian(UInt16.self)) / 10.0 + let timerCounter = encodedData[6...].toBigEndian(UInt32.self) + if remainingPulses == 0 { + delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter) / 10) + } else { + delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) + } + var entries = [RateEntry]() + for entryIndex in (0.. Message + + /// Asserts that the caller is currently on the session's queue + func assertOnSessionQueue() +} + +class PodMessageTransport: MessageTransport { + + private let session: CommandSession + + private let log = OSLog(category: "PodMessageTransport") + + private var state: MessageTransportState { + didSet { + self.delegate?.messageTransport(self, didUpdate: state) + } + } + + private var packetNumber: Int { + get { + return state.packetNumber + } + set { + state.packetNumber = newValue + } + } + + private(set) var messageNumber: Int { + get { + return state.messageNumber + } + set { + state.messageNumber = newValue + } + } + + private let address: UInt32 + private var ackAddress: UInt32 // During pairing, PDM acks with address it is assigning to channel + + weak var messageLogger: MessageLogger? + weak var delegate: MessageTransportDelegate? + + init(session: CommandSession, address: UInt32 = 0xffffffff, ackAddress: UInt32? = nil, state: MessageTransportState) { + self.session = session + self.address = address + self.ackAddress = ackAddress ?? address + self.state = state + } + + private func incrementPacketNumber(_ count: Int = 1) { + packetNumber = (packetNumber + count) & 0b11111 + } + + private func incrementMessageNumber(_ count: Int = 1) { + messageNumber = (messageNumber + count) & 0b1111 + } + + func makeAckPacket() -> Packet { + return Packet(address: address, packetType: .ack, sequenceNum: packetNumber, data: Data(bigEndian: ackAddress)) + } + + func ackUntilQuiet() { + + let packetData = makeAckPacket().encoded() + + var lastHeardAt = Date() + let quietWindow = TimeInterval(milliseconds: 300) + while lastHeardAt.timeIntervalSinceNow > -quietWindow { + do { + let rfPacket = try session.sendAndListen(packetData, repeatCount: 1, timeout: quietWindow, retryCount: 0, preambleExtension: TimeInterval(milliseconds: 40)) + let packet = try Packet(rfPacket: rfPacket) + if packet.address == address { + lastHeardAt = Date() // Pod still sending + } + } catch RileyLinkDeviceError.responseTimeout { + // Haven't heard anything in 300ms. POD heard our ack. + break + } catch { + continue + } + } + incrementPacketNumber() + } + + + /// Encodes and sends a packet to the pod, and receives and decodes its response + /// + /// - Parameters: + /// - message: The packet to send + /// - repeatCount: Number of times to repeat packet before listening for a response. 0 = send once and do not repeat. + /// - packetResponseTimeout: The amount of time to wait before retrying + /// - exchangeTimeout: The amount of time to continue retrying before giving up + /// - preambleExtension: Duration of preamble. Default is 127ms + /// - Returns: The received response packet + /// - Throws: + /// - PodCommsError.noResponse + /// - RileyLinkDeviceError + func exchangePackets(packet: Packet, repeatCount: Int = 0, packetResponseTimeout: TimeInterval = .milliseconds(333), exchangeTimeout:TimeInterval = .seconds(9), preambleExtension: TimeInterval = .milliseconds(127)) throws -> Packet { + let packetData = packet.encoded() + let radioRetryCount = 9 + + let start = Date() + + incrementPacketNumber() + + while (-start.timeIntervalSinceNow < exchangeTimeout) { + do { + let rfPacket = try session.sendAndListen(packetData, repeatCount: repeatCount, timeout: packetResponseTimeout, retryCount: radioRetryCount, preambleExtension: preambleExtension) + + let candidatePacket: Packet + + do { + candidatePacket = try Packet(rfPacket: rfPacket) + } catch PacketError.insufficientData { + log.debug("Insufficient packet data: %@", rfPacket.data.hexadecimalString) + continue + } catch let error { + log.debug("Packet error: %@", String(describing: error)) + continue + } + + guard candidatePacket.address == packet.address else { + continue + } + + guard candidatePacket.sequenceNum == ((packet.sequenceNum + 1) & 0b11111) else { + continue + } + + // Once we have verification that the POD heard us, we can increment our counters + incrementPacketNumber() + + return candidatePacket + } catch RileyLinkDeviceError.responseTimeout { + continue + } + } + + throw PodCommsError.noResponse + } + + /// Packetizes a message, and performs a set of packet exchanges to send a message and receive the response + /// + /// - Parameters: + /// - message: The message to send + /// - Returns: The received message response + /// - Throws: + /// - PodCommsError.noResponse + /// - MessageError.invalidCrc + /// - RileyLinkDeviceError + func sendMessage(_ message: Message) throws -> Message { + + messageNumber = message.sequenceNum + incrementMessageNumber() + + do { + let responsePacket = try { () throws -> Packet in + var firstPacket = true + log.debug("Send: %@", String(describing: message)) + var dataRemaining = message.encoded() + log.debug("Send(Hex): %@", dataRemaining.hexadecimalString) + messageLogger?.didSend(dataRemaining) + while true { + let packetType: PacketType = firstPacket ? .pdm : .con + let sendPacket = Packet(address: address, packetType: packetType, sequenceNum: self.packetNumber, data: dataRemaining) + dataRemaining = dataRemaining.subdata(in: sendPacket.data.count.. Message in + var responseData = responsePacket.data + while true { + do { + let msg = try Message(encodedData: responseData) + log.debug("Recv(Hex): %@", responseData.hexadecimalString) + messageLogger?.didReceive(responseData) + return msg + } catch MessageError.notEnoughData { + log.debug("Sending ACK for CON") + let conPacket = try self.exchangePackets(packet: makeAckPacket(), repeatCount: 3, preambleExtension:TimeInterval(milliseconds: 40)) + + guard conPacket.packetType == .con else { + log.debug("Expected CON packet, received; %@", String(describing: conPacket)) + throw PodCommsError.unexpectedPacketType(packetType: conPacket.packetType) + } + responseData += conPacket.data + } + } + }() + + ackUntilQuiet() + + guard response.messageBlocks.count > 0 else { + log.debug("Empty response") + throw PodCommsError.emptyResponse + } + + if response.messageBlocks[0].blockType != .errorResponse { + incrementMessageNumber() + } + + log.debug("Recv: %@", String(describing: response)) + return response + } catch let error { + log.error("Error during communication with POD: %@", String(describing: error)) + throw error + } + } + + func assertOnSessionQueue() { + session.assertOnSessionQueue() + } +} diff --git a/OmniKit/MessageTransport/Packet+RFPacket.swift b/OmniKit/MessageTransport/Packet+RFPacket.swift new file mode 100644 index 000000000..e403f560c --- /dev/null +++ b/OmniKit/MessageTransport/Packet+RFPacket.swift @@ -0,0 +1,17 @@ +// +// Packet+RFPacket.swift +// OmniKit +// +// Created by Pete Schwamb on 12/19/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation +import RileyLinkBLEKit + +// Extensions for RFPacket support +extension Packet { + init(rfPacket: RFPacket) throws { + try self.init(encodedData: rfPacket.data) + } +} diff --git a/OmniKit/MessageTransport/Packet.swift b/OmniKit/MessageTransport/Packet.swift new file mode 100644 index 000000000..cb601a237 --- /dev/null +++ b/OmniKit/MessageTransport/Packet.swift @@ -0,0 +1,93 @@ +// +// Packet.swift +// OmniKit +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// +import Foundation + +public enum PacketError: Error { + case insufficientData + case crcMismatch + case unknownPacketType(rawType: UInt8) +} + + +public enum PacketType: UInt8 { + case pod = 0b111 + case pdm = 0b101 + case con = 0b100 + case ack = 0b010 + + func maxBodyLen() -> Int { + switch self { + case .ack: + return 4 + case .con, .pdm, .pod: + return 31 + } + } +} + +public struct Packet { + + let address: UInt32 + let packetType: PacketType + let sequenceNum: Int + let data: Data + + init(address: UInt32, packetType: PacketType, sequenceNum: Int, data: Data = Data()) { + self.address = address + self.packetType = packetType + self.sequenceNum = sequenceNum + + let bodyMaxLen = packetType.maxBodyLen() + if data.count > bodyMaxLen { + self.data = data.subdata(in: 0..= 7 else { + // Not enough data for packet + throw PacketError.insufficientData + } + + self.address = encodedData[0...].toBigEndian(UInt32.self) + + guard let packetType = PacketType(rawValue: encodedData[4] >> 5) else { + throw PacketError.unknownPacketType(rawType: encodedData[4]) + } + self.packetType = packetType + self.sequenceNum = Int(encodedData[4] & 0b11111) + + let len = encodedData.count + + // Check crc + guard encodedData[0.. Data { + var output = Data(bigEndian: address) + output.append(UInt8(packetType.rawValue << 5) + UInt8(sequenceNum & 0b11111)) + output.append(data) + output.append(output.crc8()) + return output + } +} + +extension Packet: CustomDebugStringConvertible { + public var debugDescription: String { + let sequenceNumStr = String(format: "%02d", sequenceNum) + return "Packet(\(Data(bigEndian: address).hexadecimalString) packetType:\(packetType) seq:\(sequenceNumStr) data:\(data.hexadecimalString))" + } +} + diff --git a/OmniKit/Model/AlertSlot.swift b/OmniKit/Model/AlertSlot.swift new file mode 100644 index 000000000..d5cf861d3 --- /dev/null +++ b/OmniKit/Model/AlertSlot.swift @@ -0,0 +1,271 @@ +// +// Alert.swift +// OmniKit +// +// Created by Pete Schwamb on 10/24/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum AlertTrigger { + case unitsRemaining(Double) + case timeUntilAlert(TimeInterval) +} + +public enum BeepRepeat: UInt8 { + case once = 0 + case every1MinuteFor3MinutesAndRepeatEvery60Minutes = 1 + case every1MinuteFor15Minutes = 2 + case every1MinuteFor3MinutesAndRepeatEvery15Minutes = 3 + case every3MinutesFor60minutesStartingAt2Minutes = 4 + case every60Minutes = 5 + case every15Minutes = 6 + case every15MinutesFor60minutesStartingAt14Minutes = 7 + case every5Minutes = 8 +} + + +public struct AlertConfiguration { + + let slot: AlertSlot + let trigger: AlertTrigger + let active: Bool + let duration: TimeInterval + let beepRepeat: BeepRepeat + let beepType: BeepType + let autoOffModifier: Bool + + static let length = 6 + + public init(alertType: AlertSlot, active: Bool = true, autoOffModifier: Bool = false, duration: TimeInterval, trigger: AlertTrigger, beepRepeat: BeepRepeat, beepType: BeepType) { + self.slot = alertType + self.active = active + self.autoOffModifier = autoOffModifier + self.duration = duration + self.trigger = trigger + self.beepRepeat = beepRepeat + self.beepType = beepType + } +} + +public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable { + public typealias RawValue = [String: Any] + + // 2 hours long, time for user to start pairing process + case waitingForPairingReminder + + // 1 hour long, time for user to finish priming, cannula insertion + case finishSetupReminder + + // User configurable with PDM (1-24 hours before 72 hour expiration) "Change Pod Soon" + case expirationAlert(TimeInterval) + + // 72 hour alarm + case expirationAdvisoryAlarm(alarmTime: TimeInterval, duration: TimeInterval) + + // 79 hour alarm (1 hour before shutdown) + case shutdownImminentAlarm(TimeInterval) + + // reservoir below configured value alarm + case lowReservoirAlarm(Double) + + // auto-off timer; requires user input every x minutes + case autoOffAlarm(active: Bool, countdownDuration: TimeInterval) + + public var description: String { + switch self { + case .waitingForPairingReminder: + return LocalizedString("Waiting for pairing reminder", comment: "Description waiting for pairing reminder") + case .finishSetupReminder: + return LocalizedString("Finish setup ", comment: "Description for expiration alert") + case .expirationAlert: + return LocalizedString("Expiration alert", comment: "Description for expiration alert") + case .expirationAdvisoryAlarm: + return LocalizedString("Pod expiration advisory alarm", comment: "Description for expiration advisory alarm") + case .shutdownImminentAlarm: + return LocalizedString("Shutdown imminent alarm", comment: "Description for shutdown imminent alarm") + case .lowReservoirAlarm: + return LocalizedString("Low reservoir advisory alarm", comment: "Description for low reservoir alarm") + case .autoOffAlarm: + return LocalizedString("Auto-off alarm", comment: "Description for auto-off alarm") + } + } + + public var configuration: AlertConfiguration { + switch self { + case .waitingForPairingReminder: + return AlertConfiguration(alertType: .slot7, duration: .minutes(110), trigger: .timeUntilAlert(.minutes(10)), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + case .finishSetupReminder: + return AlertConfiguration(alertType: .slot7, duration: .minutes(55), trigger: .timeUntilAlert(.minutes(5)), beepRepeat: .every5Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + case .expirationAlert(let alertTime): + return AlertConfiguration(alertType: .slot3, duration: 0, trigger: .timeUntilAlert(alertTime), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + case .expirationAdvisoryAlarm(let alarmTime, let duration): + return AlertConfiguration(alertType: .slot7, duration: duration, trigger: .timeUntilAlert(alarmTime), beepRepeat: .every60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + case .shutdownImminentAlarm(let alarmTime): + return AlertConfiguration(alertType: .slot2, duration: 0, trigger: .timeUntilAlert(alarmTime), beepRepeat: .every15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + case .lowReservoirAlarm(let units): + return AlertConfiguration(alertType: .slot4, duration: 0, trigger: .unitsRemaining(units), beepRepeat: .every1MinuteFor3MinutesAndRepeatEvery60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + case .autoOffAlarm(let active, let countdownDuration): + return AlertConfiguration(alertType: .slot0, active: active, autoOffModifier: true, duration: .minutes(15), trigger: .timeUntilAlert(countdownDuration), beepRepeat: .every1MinuteFor15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + } + } + + + // MARK: - RawRepresentable + public init?(rawValue: RawValue) { + + guard let name = rawValue["name"] as? String else { + return nil + } + + switch name { + case "waitingForPairingReminder": + self = .waitingForPairingReminder + case "finishSetupReminder": + self = .finishSetupReminder + case "expirationAlert": + guard let alertTime = rawValue["alertTime"] as? Double else { + return nil + } + self = .expirationAlert(TimeInterval(alertTime)) + case "expirationAdvisoryAlarm": + guard let alarmTime = rawValue["alarmTime"] as? Double, + let duration = rawValue["duration"] as? Double else + { + return nil + } + self = .expirationAdvisoryAlarm(alarmTime: TimeInterval(alarmTime), duration: TimeInterval(duration)) + case "shutdownImminentAlarm": + guard let alarmTime = rawValue["alarmTime"] as? Double else { + return nil + } + self = .shutdownImminentAlarm(alarmTime) + case "lowReservoirAlarm": + guard let units = rawValue["units"] as? Double else { + return nil + } + self = .lowReservoirAlarm(units) + case "autoOffAlarm": + guard let active = rawValue["active"] as? Bool, + let countdownDuration = rawValue["countdownDuration"] as? Double else + { + return nil + } + self = .autoOffAlarm(active: active, countdownDuration: TimeInterval(countdownDuration)) + default: + return nil + } + } + + public var rawValue: RawValue { + + let name: String = { + switch self { + case .waitingForPairingReminder: + return "waitingForPairingReminder" + case .finishSetupReminder: + return "finishSetupReminder" + case .expirationAlert: + return "expirationAlert" + case .expirationAdvisoryAlarm: + return "expirationAdvisoryAlarm" + case .shutdownImminentAlarm: + return "shutdownImminentAlarm" + case .lowReservoirAlarm: + return "lowReservoirAlarm" + case .autoOffAlarm: + return "autoOffAlarm" + } + }() + + + var rawValue: RawValue = [ + "name": name, + ] + + switch self { + case .expirationAlert(let alertTime): + rawValue["alertTime"] = alertTime + case .expirationAdvisoryAlarm(let alarmTime, let duration): + rawValue["alarmTime"] = alarmTime + rawValue["duration"] = duration + case .shutdownImminentAlarm(let alarmTime): + rawValue["alarmTime"] = alarmTime + case .lowReservoirAlarm(let units): + rawValue["units"] = units + case .autoOffAlarm(let active, let countdownDuration): + rawValue["active"] = active + rawValue["countdownDuration"] = countdownDuration + default: + break + } + + return rawValue + } +} + +public enum AlertSlot: UInt8 { + case slot0 = 0x00 + case slot1 = 0x01 + case slot2 = 0x02 + case slot3 = 0x03 + case slot4 = 0x04 + case slot5 = 0x05 + case slot6 = 0x06 + case slot7 = 0x07 + + public var bitMaskValue: UInt8 { + return 1< AlertSlot { + return elements[index] + } + + public func index(after i: Int) -> Int { + return i+1 + } + + public var description: String { + if elements.count == 0 { + return LocalizedString("No alerts", comment: "Pod alert state when no alerts are active") + } else { + let alarmDescriptions = elements.map { String(describing: $0) } + return alarmDescriptions.joined(separator: ", ") + } + } +} diff --git a/OmniKit/Model/BasalDeliveryTable.swift b/OmniKit/Model/BasalDeliveryTable.swift new file mode 100644 index 000000000..b1dacc348 --- /dev/null +++ b/OmniKit/Model/BasalDeliveryTable.swift @@ -0,0 +1,236 @@ +// +// BasalDeliveryTable.swift +// OmniKit +// +// Created by Pete Schwamb on 4/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct BasalTableEntry { + let segments: Int + let pulses: Int + let alternateSegmentPulse: Bool + + public init(encodedData: Data) { + segments = Int(encodedData[0] >> 4) + 1 + pulses = (Int(encodedData[0] & 0b11) << 8) + Int(encodedData[1]) + alternateSegmentPulse = (encodedData[0] >> 3) & 0x1 == 1 + } + + public init(segments: Int, pulses: Int, alternateSegmentPulse: Bool) { + self.segments = segments + self.pulses = pulses + self.alternateSegmentPulse = alternateSegmentPulse + } + + public var data: Data { + let pulsesHighBits = UInt8((pulses >> 8) & 0b11) + let pulsesLowBits = UInt8(pulses & 0xff) + return Data([ + UInt8((segments - 1) << 4) + UInt8((alternateSegmentPulse ? 1 : 0) << 3) + pulsesHighBits, + UInt8(pulsesLowBits) + ]) + } + + public func checksum() -> UInt16 { + let checksumPerSegment = (pulses & 0xff) + (pulses >> 8) + return UInt16(checksumPerSegment * segments + (alternateSegmentPulse ? segments / 2 : 0)) + } +} + +extension BasalTableEntry: CustomDebugStringConvertible { + public var debugDescription: String { + return "BasalTableEntry(segments:\(segments), pulses:\(pulses), alternateSegmentPulse:\(alternateSegmentPulse))" + } +} + + +public struct BasalDeliveryTable { + static let segmentDuration: TimeInterval = .minutes(30) + + let entries: [BasalTableEntry] + + public init(entries: [BasalTableEntry]) { + self.entries = entries + } + + + public init(schedule: BasalSchedule) { + + struct TempSegment { + let pulses: Int + } + + let numSegments = 48 + let maxSegmentsPerEntry = 16 + + var halfPulseRemainder = false + + let expandedSegments = stride(from: 0, to: numSegments, by: 1).map { (index) -> TempSegment in + let rate = schedule.rateAt(offset: Double(index) * .minutes(30)) + let pulsesPerHour = Int(round(rate / Pod.pulseSize)) + let pulsesPerSegment = pulsesPerHour >> 1 + let halfPulse = pulsesPerHour & 0b1 != 0 + + let segment = TempSegment(pulses: pulsesPerSegment + ((halfPulseRemainder && halfPulse) ? 1 : 0)) + halfPulseRemainder = halfPulseRemainder != halfPulse + + return segment + } + + var tableEntries = [BasalTableEntry]() + + let addEntry = { (segments: [TempSegment], alternateSegmentPulse: Bool) in + tableEntries.append(BasalTableEntry( + segments: segments.count, + pulses: segments.first!.pulses, + alternateSegmentPulse: alternateSegmentPulse + )) + } + + var altSegmentPulse = false + var segmentsToMerge = [TempSegment]() + + for segment in expandedSegments { + guard let firstSegment = segmentsToMerge.first else { + segmentsToMerge.append(segment) + continue + } + + let delta = segment.pulses - firstSegment.pulses + + if segmentsToMerge.count == 1 { + altSegmentPulse = delta == 1 + } + + let expectedDelta: Int + + if !altSegmentPulse { + expectedDelta = 0 + } else { + expectedDelta = segmentsToMerge.count % 2 + } + + if expectedDelta != delta || segmentsToMerge.count == maxSegmentsPerEntry { + addEntry(segmentsToMerge, altSegmentPulse) + segmentsToMerge.removeAll() + } + + segmentsToMerge.append(segment) + } + addEntry(segmentsToMerge, altSegmentPulse) + + self.entries = tableEntries + } + + public init(tempBasalRate: Double, duration: TimeInterval) { + self.entries = BasalDeliveryTable.rateToTableEntries(rate: tempBasalRate, duration: duration) + } + + private static func rateToTableEntries(rate: Double, duration: TimeInterval) -> [BasalTableEntry] { + var tableEntries = [BasalTableEntry]() + + let pulsesPerHour = Int(round(rate / Pod.pulseSize)) + let pulsesPerSegment = pulsesPerHour >> 1 + let alternateSegmentPulse = pulsesPerHour & 0b1 != 0 + + var remaining = Int(round(duration / BasalDeliveryTable.segmentDuration)) + + while remaining > 0 { + let segments = min(remaining, 16) + let tableEntry = BasalTableEntry(segments: segments, pulses: Int(pulsesPerSegment), alternateSegmentPulse: segments > 1 ? alternateSegmentPulse : false) + tableEntries.append(tableEntry) + remaining -= segments + } + return tableEntries + } + + public func numSegments() -> Int { + return entries.reduce(0) { $0 + $1.segments } + } +} + +extension BasalDeliveryTable: CustomDebugStringConvertible { + public var debugDescription: String { + return "BasalDeliveryTable(\(entries))" + } +} + +public struct RateEntry { + let totalPulses: Double + let delayBetweenPulses: TimeInterval + + public init(totalPulses: Double, delayBetweenPulses: TimeInterval) { + self.totalPulses = totalPulses + self.delayBetweenPulses = delayBetweenPulses + } + + public var rate: Double { + if totalPulses == 0 { + return 0 + } else { + return round(TimeInterval(hours: 1) / delayBetweenPulses) / Pod.pulsesPerUnit + } + } + + public var duration: TimeInterval { + if totalPulses == 0 { + return delayBetweenPulses + } else { + return round(delayBetweenPulses * Double(totalPulses)) + } + } + + public var data: Data { + var data = Data() + data.appendBigEndian(UInt16(round(totalPulses * 10))) + if totalPulses == 0 { + data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds) * 10) + } else { + data.appendBigEndian(UInt32(delayBetweenPulses.hundredthsOfMilliseconds)) + } + return data + } + + public static func makeEntries(rate: Double, duration: TimeInterval) -> [RateEntry] { + let maxPulsesPerEntry: Double = 6400 + var entries = [RateEntry]() + + var remainingSegments = Int(round(duration.minutes / 30)) + + let pulsesPerSegment = round(rate / Pod.pulseSize) / 2 + let maxSegmentsPerEntry = pulsesPerSegment > 0 ? Int(maxPulsesPerEntry / pulsesPerSegment) : 1 + + var remainingPulses = rate * duration.hours / Pod.pulseSize + let delayBetweenPulses = TimeInterval(hours: 1) / rate * Pod.pulseSize + + while (remainingSegments > 0) { + if rate == 0 { + entries.append(RateEntry(totalPulses: 0, delayBetweenPulses: .minutes(30))) + remainingSegments -= 1 + } else { + let numSegments = min(maxSegmentsPerEntry, Int(round(remainingPulses / pulsesPerSegment))) + remainingSegments -= numSegments + let pulseCount = pulsesPerSegment * Double(numSegments) + let entry = RateEntry(totalPulses: pulseCount, delayBetweenPulses: delayBetweenPulses) + entries.append(entry) + remainingPulses -= pulseCount + } + } + return entries + } +} + +extension RateEntry: CustomDebugStringConvertible { + public var debugDescription: String { + return "RateEntry(rate:\(rate) duration:\(duration.stringValue))" + } +} + + + + + + diff --git a/OmniKit/Model/BasalSchedule.swift b/OmniKit/Model/BasalSchedule.swift new file mode 100644 index 000000000..0ae6f680f --- /dev/null +++ b/OmniKit/Model/BasalSchedule.swift @@ -0,0 +1,123 @@ +// +// BasalSchedule.swift +// OmniKit +// +// Created by Pete Schwamb on 4/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct BasalScheduleEntry: RawRepresentable, Equatable { + + public typealias RawValue = [String: Any] + + let rate: Double + let startTime: TimeInterval + + public init(rate: Double, startTime: TimeInterval) { + self.rate = rate + self.startTime = startTime + } + + // MARK: - RawRepresentable + public init?(rawValue: RawValue) { + + guard + let rate = rawValue["rate"] as? Double, + let startTime = rawValue["startTime"] as? Double + else { + return nil + } + + self.rate = rate + self.startTime = startTime + } + + public var rawValue: RawValue { + let rawValue: RawValue = [ + "rate": rate, + "startTime": startTime + ] + + return rawValue + } +} + +// A basal schedule starts at midnight and should contain 24 hours worth of entries +public struct BasalSchedule: RawRepresentable, Equatable { + + public typealias RawValue = [String: Any] + + let entries: [BasalScheduleEntry] + + public func rateAt(offset: TimeInterval) -> Double { + let (_, entry, _) = lookup(offset: offset) + return entry.rate + } + + // Returns index, entry, and time remaining + func lookup(offset: TimeInterval) -> (Int, BasalScheduleEntry, TimeInterval) { + guard offset >= 0 && offset < .hours(24) else { + fatalError("Schedule offset out of bounds") + } + + var last: TimeInterval = .hours(24) + for (index, entry) in entries.reversed().enumerated() { + if entry.startTime <= offset { + return (entries.count - (index + 1), entry, last - entry.startTime) + } + last = entry.startTime + } + fatalError("Schedule incomplete") + } + + public init(entries: [BasalScheduleEntry]) { + self.entries = entries + } + + public func durations() -> [(rate: Double, duration: TimeInterval, startTime: TimeInterval)] { + var last: TimeInterval = .hours(24) + let durations = entries.reversed().map { (entry) -> (rate: Double, duration: TimeInterval, startTime: TimeInterval) in + let duration = (rate: entry.rate, duration: last - entry.startTime, startTime: entry.startTime) + last = entry.startTime + return duration + } + return durations.reversed() + } + + // MARK: - RawRepresentable + public init?(rawValue: RawValue) { + + guard + let entries = rawValue["entries"] as? [BasalScheduleEntry.RawValue] + else { + return nil + } + + self.entries = entries.compactMap { BasalScheduleEntry(rawValue: $0) } + } + + public var rawValue: RawValue { + let rawValue: RawValue = [ + "entries": entries.map { $0.rawValue } + ] + + return rawValue + } +} + +public extension Sequence where Element == BasalScheduleEntry { + func adjacentEqualRatesMerged() -> [BasalScheduleEntry] { + var output = [BasalScheduleEntry]() + let _ = self.reduce(nil) { (lastRate, entry) -> TimeInterval? in + if entry.rate != lastRate { + output.append(entry) + } + return entry.rate + } + return output + } +} + + diff --git a/OmniKit/Model/BeepType.swift b/OmniKit/Model/BeepType.swift new file mode 100644 index 000000000..b2dba4078 --- /dev/null +++ b/OmniKit/Model/BeepType.swift @@ -0,0 +1,28 @@ +// +// BeepType.swift +// OmniKit +// +// Created by Joseph Moran on 5/12/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation + +// BeepType is used for the $11 Acknowledge Alerts, $19 Configure Alerts, $1E Beep Configure, and $1F Cancel Commands +public enum BeepType: UInt8 { + case noBeep = 0x0 + case beepBeepBeepBeep = 0x1 + case bipBeepBipBeepBipBeepBipBeep = 0x2 + case bipBip = 0x3 + case beep = 0x4 + case beepBeepBeep = 0x5 + case beeeeeep = 0x6 + case bipBipBipbipBipBip = 0x7 + case beeepBeeep = 0x8 + // 0x9 and 0xA always return an error + case beepBeep = 0xB + case beeep = 0xC + case bipBeeeeep = 0xD + case fiveSecondBeep = 0xE // 5 second alarm beep *if* Pod is currently suspended + case beepConfig_NoBeep = 0xF // For Beep Config Command no beep, else fatal Pod fault $37 +} diff --git a/OmniKit/Model/FaultEventCode.swift b/OmniKit/Model/FaultEventCode.swift new file mode 100644 index 000000000..edda00946 --- /dev/null +++ b/OmniKit/Model/FaultEventCode.swift @@ -0,0 +1,413 @@ +// +// FaultEventCode.swift +// OmniKit +// +// Created by Pete Schwamb on 9/28/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + + +public struct FaultEventCode: CustomStringConvertible, Equatable { + public let rawValue: UInt8 + + public enum FaultEventType: UInt8 { + case noFaults = 0x00 + case failedFlashErase = 0x01 + case failedFlashStore = 0x02 + case tableCorruptionBasalSubcommand = 0x03 + case corruptionByte720 = 0x05 + case dataCorruptionInTestRTCInterrupt = 0x06 + case rtcInterruptHandlerInconsistentState = 0x07 + case valueGreaterThan8 = 0x08 + case bf0notEqualToBF1 = 0x0A + case tableCorruptionTempBasalSubcommand = 0x0B + case resetDueToCOP = 0x0D + case resetDueToIllegalOpcode = 0x0E + case resetDueToIllegalAddress = 0x0F + case resetDueToSAWCOP = 0x10 + case corruptionInByte_866 = 0x11 + case resetDueToLVD = 0x12 + case messageLengthTooLong = 0x13 + case occluded = 0x14 + case corruptionInWord129 = 0x15 + case corruptionInByte868 = 0x16 + case corruptionInAValidatedTable = 0x17 + case reservoirEmpty = 0x18 + case badPowerSwitchArrayValue1 = 0x19 + case badPowerSwitchArrayValue2 = 0x1A + case badLoadCnthValue = 0x1B + case exceededMaximumPodLife80Hrs = 0x1C + case badStateCommand1AScheduleParse = 0x1D + case unexpectedStateInRegisterUponReset = 0x1E + case wrongSummaryForTable129 = 0x1F + case validateCountErrorWhenBolusing = 0x20 + case badTimerVariableState = 0x21 + case unexpectedRTCModuleValueDuringReset = 0x22 + case problemCalibrateTimer = 0x23 + case rtcInterruptHandlerUnexpectedCall = 0x26 + case missing2hourAlertToFillTank = 0x27 + case faultEventSetupPod = 0x28 + case errorMainLoopHelper0 = 0x29 + case errorMainLoopHelper1 = 0x2A + case errorMainLoopHelper2 = 0x2B + case errorMainLoopHelper3 = 0x2C + case errorMainLoopHelper4 = 0x2D + case errorMainLoopHelper5 = 0x2E + case errorMainLoopHelper6 = 0x2F + case errorMainLoopHelper7 = 0x30 + case insulinDeliveryCommandError = 0x31 + case badValueStartupTest = 0x32 + case connectedPodCommandTimeout = 0x33 + case resetFromUnknownCause = 0x34 + case errorFlashInitialization = 0x36 + case badPiezoValue = 0x37 + case unexpectedValueByte358 = 0x38 + case problemWithLoad1and2 = 0x39 + case aGreaterThan7inMessage = 0x3A + case failedTestSawReset = 0x3B + case testInProgress = 0x3C + case problemWithPumpAnchor = 0x3D + case errorFlashWrite = 0x3E + case encoderCountTooHigh = 0x40 + case encoderCountExcessiveVariance = 0x41 + case encoderCountTooLow = 0x42 + case encoderCountProblem = 0x43 + case checkVoltageOpenWire1 = 0x44 + case checkVoltageOpenWire2 = 0x45 + case problemWithLoad1and2type46 = 0x46 + case problemWithLoad1and2type47 = 0x47 + case badTimerCalibration = 0x48 + case badTimerRatios = 0x49 + case badTimerValues = 0x4A + case trimICSTooCloseTo0x1FF = 0x4B + case problemFindingBestTrimValue = 0x4C + case badSetTPM1MultiCasesValue = 0x4D + case unexpectedRFErrorFlagDuringReset = 0x4F + case badCheckSdrhAndByte11FState = 0x51 + case issueTXOKprocessInputBuffer = 0x52 + case wrongValueWord_107 = 0x53 + case packetFrameLengthTooLong = 0x54 + case unexpectedIRQHighinTimerTick = 0x55 + case unexpectedIRQLowinTimerTick = 0x56 + case badArgToGetEntry = 0x57 + case badArgToUpdate37ATable = 0x58 + case errorUpdating37ATable = 0x59 + case occlusionCheckValueTooHigh = 0x5A + case loadTableCorruption = 0x5B + case primeOpenCountTooLow = 0x5C + case badValueByte109 = 0x5D + case disableFlashSecurityFailed = 0x5E + case checkVoltageFailure = 0x5F + case occlusionCheckStartup1 = 0x60 + case occlusionCheckStartup2 = 0x61 + case occlusionCheckTimeouts1 = 0x62 + case occlusionCheckTimeouts2 = 0x66 + case occlusionCheckTimeouts3 = 0x67 + case occlusionCheckPulseIssue = 0x68 + case occlusionCheckBolusProblem = 0x69 + case occlusionCheckAboveThreshold = 0x6A + case basalUnderInfusion = 0x80 + case basalOverInfusion = 0x81 + case tempBasalUnderInfusion = 0x82 + case tempBasalOverInfusion = 0x83 + case bolusUnderInfusion = 0x84 + case bolusOverInfusion = 0x85 + case basalOverInfusionPulse = 0x86 + case tempBasalOverInfusionPulse = 0x87 + case bolusOverInfusionPulse = 0x88 + case immediateBolusOverInfusionPulse = 0x89 + case extendedBolusOverInfusionPulse = 0x8A + case corruptionOfTables = 0x8B + case badInputToVerifyAndStartPump = 0x8D + case badPumpReq5State = 0x8E + case command1AParseUnexpectedFailed = 0x8F + case badValueForTables = 0x90 + case badPumpReq1State = 0x91 + case badPumpReq2State = 0x92 + case badPumpReq3State = 0x93 + case badValueField6in0x1A = 0x95 + case badStateInClearBolusIST2AndVars = 0x96 + case badStateInMaybeInc33D = 0x97 + case valuesDoNotMatchOrAreGreaterThan0x97 = 0x98 + } + + public var faultType: FaultEventType? { + return FaultEventType(rawValue: rawValue) + } + + init(rawValue: UInt8) { + self.rawValue = rawValue + } + + public var description: String { + let faultDescription: String + + if let faultType = faultType { + faultDescription = { + switch faultType { + case .noFaults: + return "No fault" + case .failedFlashErase: + return "Flash erase failed" + case .failedFlashStore: + return "Flash store failed" + case .tableCorruptionBasalSubcommand: + return "Basal subcommand table corruption" + case .corruptionByte720: + return "Corruption in byte_720" + case .dataCorruptionInTestRTCInterrupt: + return "Data corruption error in test_RTC_interrupt" + case .rtcInterruptHandlerInconsistentState: + return "RTC interrupt handler called with inconstent state" + case .valueGreaterThan8: + return "Value > 8" + case .bf0notEqualToBF1: + return "Corruption in byte_BF0" + case .tableCorruptionTempBasalSubcommand: + return "Temp basal subcommand table corruption" + case .resetDueToCOP: + return "Reset due to COP" + case .resetDueToIllegalOpcode: + return "Reset due to illegal opcode" + case .resetDueToIllegalAddress: + return "Reset due to illegal address" + case .resetDueToSAWCOP: + return "Reset due to SAWCOP" + case .corruptionInByte_866: + return "Corruption in byte_866" + case .resetDueToLVD: + return "Reset due to LVD" + case .messageLengthTooLong: + return "Message length too long" + case .occluded: + return "Occluded" + case .corruptionInWord129: + return "Corruption in word_129 table/word_86A/dword_86E" + case .corruptionInByte868: + return "Corruption in byte_868" + case .corruptionInAValidatedTable: + return "Corruption in a validated table" + case .reservoirEmpty: + return "Reservoir empty or exceeded maximum pulse delivery" + case .badPowerSwitchArrayValue1: + return "Bad Power Switch Array Status and Control Register value 1 before starting pump" + case .badPowerSwitchArrayValue2: + return "Bad Power Switch Array Status and Control Register value 2 before starting pump" + case .badLoadCnthValue: + return "Bad LOADCNTH value when running pump" + case .exceededMaximumPodLife80Hrs: + return "Exceeded maximum Pod life of 80 hours" + case .badStateCommand1AScheduleParse: + return "Unexpected internal state in command_1A_schedule_parse_routine_wrapper" + case .unexpectedStateInRegisterUponReset: + return "Unexpected commissioned state in status and control register upon reset" + case .wrongSummaryForTable129: + return "Sum mismatch for word_129 table" + case .validateCountErrorWhenBolusing: + return "Validate encoder count error when bolusing" + case .badTimerVariableState: + return "Bad timer variable state" + case .unexpectedRTCModuleValueDuringReset: + return "Unexpected RTC Modulo Register value during reset" + case .problemCalibrateTimer: + return "Problem in calibrate_timer_case_3" + case .rtcInterruptHandlerUnexpectedCall: + return "RTC interrupt handler unexpectedly called" + case .missing2hourAlertToFillTank: + return "Failed to set up 2 hour alert for tank fill operation" + case .faultEventSetupPod: + return "Bad arg or state in update_insulin_variables, verify_and_start_pump or main_loop_control_pump" + case .errorMainLoopHelper0: + return "Alert #0 auto-off timeout" + case .errorMainLoopHelper1: + return "Alert #1 auto-off timeout" + case .errorMainLoopHelper2: + return "Alert #2 auto-off timeout" + case .errorMainLoopHelper3: + return "Alert #3 auto-off timeout" + case .errorMainLoopHelper4: + return "Alert #4 auto-off timeout" + case .errorMainLoopHelper5: + return "Alert #5 auto-off timeout" + case .errorMainLoopHelper6: + return "Alert #6 auto-off timeout" + case .errorMainLoopHelper7: + return "Alert #7 auto-off timeout" + case .insulinDeliveryCommandError: + return "Incorrect pod state for command or error during insulin command setup" + case .badValueStartupTest: + return "Bad value during startup testing" + case .connectedPodCommandTimeout: + return "Connected Pod command timeout" + case .resetFromUnknownCause: + return "Reset from unknown cause" + case .errorFlashInitialization: + return "Flash initialization error" + case .badPiezoValue: + return "Bad piezo value" + case .unexpectedValueByte358: + return "Unexpected byte_358 value" + case .problemWithLoad1and2: + return "Problem with LOAD1/LOAD2" + case .aGreaterThan7inMessage: + return "A > 7 in message processing" + case .failedTestSawReset: + return "SAW reset testing fail" + case .testInProgress: + return "402D is 'Z' - test in progress" + case .problemWithPumpAnchor: + return "Problem with pump anchor" + case .errorFlashWrite: + return "Flash initialization or write error" + case .encoderCountTooHigh: + return "Encoder count too high" + case .encoderCountExcessiveVariance: + return "Encoder count excessive variance" + case .encoderCountTooLow: + return "Encoder count too low" + case .encoderCountProblem: + return "Encoder count problem" + case .checkVoltageOpenWire1: + return "Check voltage open wire 1 problem" + case .checkVoltageOpenWire2: + return "Check voltage open wire 2 problem" + case .problemWithLoad1and2type46: + return "Problem with LOAD1/LOAD2" + case .problemWithLoad1and2type47: + return "Problem with LOAD1/LOAD2" + case .badTimerCalibration: + return "Bad timer calibration" + case .badTimerRatios: + return "Bad timer values: COP timer ratio bad" + case .badTimerValues: + return "Bad timer values" + case .trimICSTooCloseTo0x1FF: + return "ICS trim too close to 0x1FF" + case .problemFindingBestTrimValue: + return "find_best_trim_value problem" + case .badSetTPM1MultiCasesValue: + return "Bad set_TPM1_multi_cases value" + case .unexpectedRFErrorFlagDuringReset: + return "Unexpected TXSCR2 RF Tranmission Error Flag set during reset" + case .badCheckSdrhAndByte11FState: + return "Bad check_SDIRH and byte_11F state before starting pump" + case .issueTXOKprocessInputBuffer: + return "TXOK issue in process_input_buffer" + case .wrongValueWord_107: + return "Wrong word_107 value during input message processing" + case .packetFrameLengthTooLong: + return "Packet frame length too long" + case .unexpectedIRQHighinTimerTick: + return "Unexpected IRQ high in timer_tick" + case .unexpectedIRQLowinTimerTick: + return "Unexpected IRQ low in timer_tick" + case .badArgToGetEntry: + return "Corrupt constants table at byte_37A[] or flash byte_4036[]" + case .badArgToUpdate37ATable: + return "Bad argument to update_37A_table" + case .errorUpdating37ATable: + return "Error updating constants byte_37A table" + case .occlusionCheckValueTooHigh: + return "Occlusion check value too high for detection" + case .loadTableCorruption: + return "Load table corruption" + case .primeOpenCountTooLow: + return "Prime open count too low" + case .badValueByte109: + return "Bad byte_109 value" + case .disableFlashSecurityFailed: + return "Write flash byte to disable flash security failed" + case .checkVoltageFailure: + return "Two check voltage failures before starting pump" + case .occlusionCheckStartup1: + return "Occlusion check startup problem 1" + case .occlusionCheckStartup2: + return "Occlusion check startup problem 2" + case .occlusionCheckTimeouts1: + return "Occlusion check excess timeouts 1" + case .occlusionCheckTimeouts2: + return "Occlusion check excess timeouts 2" + case .occlusionCheckTimeouts3: + return "Occlusion check excess timeouts 3" + case .occlusionCheckPulseIssue: + return "Occlusion check pulse issue" + case .occlusionCheckBolusProblem: + return "Occlusion check bolus problem" + case .occlusionCheckAboveThreshold: + return "Occlusion check above threshold" + case .basalUnderInfusion: + return "Basal under infusion" + case .basalOverInfusion: + return "Basal over infusion" + case .tempBasalUnderInfusion: + return "Temp basal under infusion" + case .tempBasalOverInfusion: + return "Temp basal over infusion" + case .bolusUnderInfusion: + return "Bolus under infusion" + case .bolusOverInfusion: + return "Bolus over infusion" + case .basalOverInfusionPulse: + return "Basal over infusion pulse" + case .tempBasalOverInfusionPulse: + return "Temp basal over infusion pulse" + case .bolusOverInfusionPulse: + return "Bolus over infusion pulse" + case .immediateBolusOverInfusionPulse: + return "Immediate bolus under infusion pulse" + case .extendedBolusOverInfusionPulse: + return "Extended bolus over infusion pulse" + case .corruptionOfTables: + return "Corruption of $283/$2E3/$315 tables" + case .badInputToVerifyAndStartPump: + return "Bad input value to verify_and_start_pump" + case .badPumpReq5State: + return "Pump req 5 with basal IST not set or temp basal IST set" + case .command1AParseUnexpectedFailed: + return "Command 1A parse routine unexpected failed" + case .badValueForTables: + return "Bad value for $283/$2E3/$315 table specification" + case .badPumpReq1State: + return "Pump request 1 with temp basal IST not set" + case .badPumpReq2State: + return "Pump request 2 with temp basal IST not set" + case .badPumpReq3State: + return "Pump request 3 and bolus IST not set when about to pulse" + case .badValueField6in0x1A: + return "Bad table specifier field6 in 1A command" + case .badStateInClearBolusIST2AndVars: + return "Bad variable state in clear_Bolus_IST2_and_vars" + case .badStateInMaybeInc33D: + return "Bad variable state in maybe_inc_33D" + case .valuesDoNotMatchOrAreGreaterThan0x97: + return "Unknown fault code" + } + }() + } else { + faultDescription = "Unknown Fault" + } + return String(format: "Fault Event Code 0x%02x: %@", rawValue, faultDescription) + } + + public var localizedDescription: String { + if let faultType = faultType { + switch faultType { + case .reservoirEmpty: + return LocalizedString("Empty reservoir", comment: "Description for Empty reservoir pod fault") + case .exceededMaximumPodLife80Hrs: + return LocalizedString("Pod expired", comment: "Description for Pod expired pod fault") + case .occluded, + .occlusionCheckValueTooHigh, .occlusionCheckStartup1, .occlusionCheckStartup2, + .occlusionCheckTimeouts1, .occlusionCheckTimeouts2, .occlusionCheckTimeouts3, + .occlusionCheckPulseIssue, .occlusionCheckBolusProblem, .occlusionCheckAboveThreshold: + return LocalizedString("Occlusion detected", comment: "Description for Occlusion detected pod fault") + default: + return String(format: LocalizedString("Internal pod fault %1$03d", comment: "The format string for Internal pod fault (1: The fault code value)"), rawValue) + } + } else { + return String(format: LocalizedString("Unknown pod fault %1$03d", comment: "The format string for Unknown pod fault (1: The fault code value)"), rawValue) + } + } +} diff --git a/OmniKit/Model/LogEventErrorCode.swift b/OmniKit/Model/LogEventErrorCode.swift new file mode 100644 index 000000000..c6dc4197a --- /dev/null +++ b/OmniKit/Model/LogEventErrorCode.swift @@ -0,0 +1,54 @@ +// +// LogEventErrorCode.swift +// OmniKit +// +// Created by Eelke Jager on 22/10/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + + +public struct LogEventErrorCode: CustomStringConvertible, Equatable { + let rawValue: UInt8 + + public var eventErrorType: EventErrorType? { + return EventErrorType(rawValue: rawValue) + } + + public enum EventErrorType: UInt8 { + case none = 0 + case immediateBolusInProgress = 1 + case internal2BitVariableSetAndManipulatedInMainLoopRoutines2 = 2 + case internal2BitVariableSetAndManipulatedInMainLoopRoutines3 = 3 + case insulinStateTableCorruption = 4 + } + + public var description: String { + let eventErrorDescription: String + + if let eventErrorType = eventErrorType { + eventErrorDescription = { + switch eventErrorType { + case .none: + return "None" + case .immediateBolusInProgress: + return "Immediate Bolus In Progress" + case .internal2BitVariableSetAndManipulatedInMainLoopRoutines2: + return "Internal 2-Bit Variable Set And Manipulated In Main Loop Routines 0x02" + case .internal2BitVariableSetAndManipulatedInMainLoopRoutines3: + return "Internal 2-Bit Variable Set And Manipulated In Main Loop Routines 0x03" + case .insulinStateTableCorruption: + return "Insulin State Table Corruption" + } + }() + } else { + eventErrorDescription = "Unknown Log Error State" + } + return String(format: "Log Event Error Code 0x%02x: %@", rawValue, eventErrorDescription) + } + + init(rawValue: UInt8) { + self.rawValue = rawValue + } +} diff --git a/OmniKit/Model/Pod.swift b/OmniKit/Model/Pod.swift new file mode 100644 index 000000000..8c430e84f --- /dev/null +++ b/OmniKit/Model/Pod.swift @@ -0,0 +1,98 @@ +// +// Pod.swift +// OmniKit +// +// Created by Pete Schwamb on 4/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct Pod { + // Volume of insulin in one motor pulse + public static let pulseSize: Double = 0.05 + + // Number of pulses required to deliver one unit of insulin + public static let pulsesPerUnit: Double = 20 + + // Units per second + public static let bolusDeliveryRate: Double = 0.025 + + // User configured time before expiration advisory (PDM allows 1-24 hours) + public static let expirationAlertWindow = TimeInterval(hours: 2) + + // Expiration advisory window: time after expiration alert, and end of service imminent alarm + public static let expirationAdvisoryWindow = TimeInterval(hours: 7) + + // End of service imminent window, relative to pod end of service + public static let endOfServiceImminentWindow = TimeInterval(hours: 1) + + // Total pod service time. A fault is triggered if this time is reached before pod deactivation. + public static let serviceDuration = TimeInterval(hours: 80) + + // Maximum reservoir level reading + public static let maximumReservoirReading: Double = 50 + + // Reservoir Capacity + public static let reservoirCapacity: Double = 200 + + // Supported basal rates + public static let supportedBasalRates: [Double] = (1...600).map { Double($0) / Double(pulsesPerUnit) } + + // Maximum number of basal schedule entries supported + public static let maximumBasalScheduleEntryCount: Int = 24 + + // Minimum duration of a single basal schedule entry + public static let minimumBasalScheduleEntryDuration = TimeInterval.minutes(30) + + // Amount of insulin delivered for priming + public static let primeUnits = 2.6 + + // Default and limits for expiration reminder alerts + public static let expirationReminderAlertDefaultTimeBeforeExpiration = TimeInterval.hours(2) + public static let expirationReminderAlertMinTimeBeforeExpiration = TimeInterval.hours(1) + public static let expirationReminderAlertMaxTimeBeforeExpiration = TimeInterval.hours(24) +} + +public enum SetupState: UInt8 { + case sleeping = 0 + case readyToPair = 1 + case addressAssigned = 2 + case paired = 3 + case pairingExpired = 14 +} + +// DeliveryStatus used in StatusResponse and PodInfoFaults +public enum DeliveryStatus: UInt8, CustomStringConvertible { + case suspended = 0 + case normal = 1 + case tempBasalRunning = 2 + case priming = 4 + 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 .suspended: + return LocalizedString("Suspended", comment: "Delivery status when insulin delivery is suspended") + case .normal: + return LocalizedString("Normal", comment: "Delivery status when basal is running") + case .tempBasalRunning: + return LocalizedString("Temp basal running", comment: "Delivery status when temp basal is running") + case .priming: + return LocalizedString("Priming", comment: "Delivery status when pod is priming") + case .bolusInProgress: + return LocalizedString("Bolusing", comment: "Delivery status when bolusing") + case .bolusAndTempBasal: + return LocalizedString("Bolusing with temp basal", comment: "Delivery status when bolusing and temp basal is running") + } + } +} diff --git a/OmniKit/Model/PodProgressStatus.swift b/OmniKit/Model/PodProgressStatus.swift new file mode 100644 index 000000000..a513e7505 --- /dev/null +++ b/OmniKit/Model/PodProgressStatus.swift @@ -0,0 +1,74 @@ +// +// PodProgressStatus.swift +// OmniKit +// +// Created by Pete Schwamb on 9/28/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum PodProgressStatus: UInt8, CustomStringConvertible, Equatable { + case initialized = 0 + case tankPowerActivated = 1 + case tankFillCompleted = 2 + case pairingSuccess = 3 + case priming = 4 + case readyForBasalSchedule = 5 + case readyForCannulaInsertion = 6 + case cannulaInserting = 7 + case aboveFiftyUnits = 8 + case belowFiftyUnits = 9 + case oneNotUsedButin33 = 10 + case twoNotUsedButin33 = 11 + case threeNotUsedButin33 = 12 + case errorEventLoggedShuttingDown = 13 + case delayedPrime = 14 // Saw this after delaying prime for a day + case inactive = 15 // ($1C Deactivate Pod or packet header mismatch) + + public var readyForDelivery: Bool { + return self == .belowFiftyUnits || self == .aboveFiftyUnits + } + + public var unfinishedPairing: Bool { + return self.rawValue < PodProgressStatus.aboveFiftyUnits.rawValue + } + + public var description: String { + switch self { + case .initialized: + return LocalizedString("Initialized", comment: "Pod inititialized") + case .tankPowerActivated: + return LocalizedString("Tank power activated", comment: "Pod power to motor activated") + case .tankFillCompleted: + return LocalizedString("Tank fill completed", comment: "Pod tank fill completed") + case .pairingSuccess: + return LocalizedString("Paired", comment: "Pod status after pairing") + case .priming: + return LocalizedString("Priming", comment: "Pod status when priming") + case .readyForBasalSchedule: + return LocalizedString("Ready for basal programming", comment: "Pod state when ready for basal programming") + case .readyForCannulaInsertion: + return LocalizedString("Ready to insert cannula", comment: "Pod state when ready for cannula insertion") + case .cannulaInserting: + return LocalizedString("Cannula inserting", comment: "Pod state when inserting cannula") + case .aboveFiftyUnits: + return LocalizedString("Normal", comment: "Pod state when running above fifty units") + case .belowFiftyUnits: + return LocalizedString("Below 50 units", comment: "Pod state when running below fifty units") + case .oneNotUsedButin33: + return LocalizedString("oneNotUsedButin33", comment: "Pod state oneNotUsedButin33") + case .twoNotUsedButin33: + return LocalizedString("twoNotUsedButin33", comment: "Pod state twoNotUsedButin33") + case .threeNotUsedButin33: + return LocalizedString("threeNotUsedButin33", comment: "Pod state threeNotUsedButin33") + case .errorEventLoggedShuttingDown: + return LocalizedString("Error event logged, shutting down", comment: "Pod state error event logged shutting down") + case .delayedPrime: + return LocalizedString("Pod setup window expired", comment: "Pod state when prime or cannula insertion has not completed in the time allotted") + case .inactive: + return LocalizedString("Deactivated", comment: "Pod state when pod has been deactivated") + } + } +} + diff --git a/OmniKit/Model/UnfinalizedDose.swift b/OmniKit/Model/UnfinalizedDose.swift new file mode 100644 index 000000000..7a5b93d39 --- /dev/null +++ b/OmniKit/Model/UnfinalizedDose.swift @@ -0,0 +1,237 @@ +// +// UnfinalizedDose.swift +// OmniKit +// +// Created by Pete Schwamb on 9/5/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConvertible { + public typealias RawValue = [String: Any] + + enum DoseType: Int { + case bolus = 0 + case tempBasal + case suspend + case resume + } + + enum ScheduledCertainty: Int { + case certain = 0 + case uncertain + + public var localizedDescription: String { + switch self { + case .certain: + return LocalizedString("Certain", comment: "String describing a dose that was certainly scheduled") + case .uncertain: + return LocalizedString("Uncertain", comment: "String describing a dose that was possibly scheduled") + } + } + } + + private let insulinFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 3 + return formatter + }() + + private let shortDateFormatter: DateFormatter = { + let timeFormatter = DateFormatter() + timeFormatter.dateStyle = .short + timeFormatter.timeStyle = .medium + return timeFormatter + }() + + private let dateFormatter = ISO8601DateFormatter() + + fileprivate var uniqueKey: Data { + return "\(doseType) \(scheduledUnits ?? units) \(dateFormatter.string(from: startTime))".data(using: .utf8)! + } + + let doseType: DoseType + public var units: Double + var scheduledUnits: Double? + let startTime: Date + var duration: TimeInterval? + var scheduledCertainty: ScheduledCertainty + + var finishTime: Date? { + get { + return duration != nil ? startTime.addingTimeInterval(duration!) : nil + } + set { + duration = newValue?.timeIntervalSince(startTime) + } + } + + public var progress: Double { + guard let duration = duration else { + return 0 + } + let elapsed = -startTime.timeIntervalSinceNow + return min(elapsed / duration, 1) + } + + public var finished: Bool { + return progress >= 1 + } + + // Units per hour + public var rate: Double { + guard let duration = duration else { + return 0 + } + return units / duration.hours + } + + init(bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty) { + self.doseType = .bolus + self.units = bolusAmount + self.startTime = startTime + self.duration = TimeInterval(bolusAmount / Pod.bolusDeliveryRate) + self.scheduledCertainty = scheduledCertainty + self.scheduledUnits = nil + } + + 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 + self.scheduledUnits = nil + } + + init(suspendStartTime: Date, scheduledCertainty: ScheduledCertainty) { + self.doseType = .suspend + self.units = 0 + self.startTime = suspendStartTime + self.scheduledCertainty = scheduledCertainty + } + + init(resumeStartTime: Date, scheduledCertainty: ScheduledCertainty) { + self.doseType = .resume + self.units = 0 + self.startTime = resumeStartTime + self.scheduledCertainty = scheduledCertainty + } + + public mutating func cancel(at date: Date, withRemaining remaining: Double? = nil) { + scheduledUnits = units + let oldRate = rate + duration = date.timeIntervalSince(startTime) + if let remaining = remaining { + units = units - remaining + } else if let duration = duration { + units = oldRate * duration.hours + } + } + + public var description: String { + let unitsStr = insulinFormatter.string(from: units) ?? "" + let startTimeStr = shortDateFormatter.string(from: startTime) + let durationStr = duration?.format(using: [.minute, .second]) ?? "" + switch doseType { + case .bolus: + if let scheduledUnits = scheduledUnits { + let scheduledUnitsStr = insulinFormatter.string(from: scheduledUnits) ?? "?" + return String(format: LocalizedString("InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@", comment: "The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty)"), unitsStr, scheduledUnitsStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) + } else { + return String(format: LocalizedString("Bolus: %1$@U %2$@ %3$@ %4$@", comment: "The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty)"), unitsStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) + } + case .tempBasal: + let rateStr = NumberFormatter.localizedString(from: NSNumber(value: rate), number: .decimal) + return String(format: LocalizedString("TempBasal: %1$@ U/hour %2$@ %3$@ %4$@", comment: "The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: scheduled certainty"), rateStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) + case .suspend: + return String(format: LocalizedString("Suspend: %1$@ %2$@", comment: "The format string describing a suspend. (1: Time)(2: Scheduled certainty"), startTimeStr, scheduledCertainty.localizedDescription) + case .resume: + return String(format: LocalizedString("Resume: %1$@ %2$@", comment: "The format string describing a resume. (1: Time)(2: Scheduled certainty"), startTimeStr, scheduledCertainty.localizedDescription) + } + } + + // 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 rawScheduledCertainty = rawValue["scheduledCertainty"] as? Int, + let scheduledCertainty = ScheduledCertainty(rawValue: rawScheduledCertainty) + else { + return nil + } + + self.doseType = doseType + self.units = units + self.startTime = startTime + self.scheduledCertainty = scheduledCertainty + + if let scheduledUnits = rawValue["scheduledUnits"] as? Double { + self.scheduledUnits = scheduledUnits + } + + if let duration = rawValue["duration"] as? Double { + self.duration = duration + } + } + + public var rawValue: RawValue { + var rawValue: RawValue = [ + "doseType": doseType.rawValue, + "units": units, + "startTime": startTime, + "scheduledCertainty": scheduledCertainty.rawValue + ] + + if let scheduledUnits = scheduledUnits { + rawValue["scheduledUnits"] = scheduledUnits + } + + if let duration = duration { + rawValue["duration"] = duration + } + + return rawValue + } +} + +private extension TimeInterval { + func format(using units: NSCalendar.Unit) -> String? { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = units + formatter.unitsStyle = .full + formatter.zeroFormattingBehavior = .dropLeading + formatter.maximumUnitCount = 2 + + return formatter.string(from: self) + } +} + +extension NewPumpEvent { + init(_ dose: UnfinalizedDose) { + let title = String(describing: dose) + let entry = DoseEntry(dose) + self.init(date: dose.startTime, dose: entry, isMutable: false, raw: dose.uniqueKey, title: title) + } +} + +extension DoseEntry { + init (_ dose: UnfinalizedDose) { + switch dose.doseType { + case .bolus: + self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.units, unit: .units) + case .tempBasal: + self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.rate, unit: .unitsPerHour) + case .suspend: + self = DoseEntry(suspendDate: dose.startTime) + case .resume: + self = DoseEntry(resumeDate: dose.startTime) + } + } +} diff --git a/OmniKit/OmniKit.h b/OmniKit/OmniKit.h new file mode 100644 index 000000000..b353125fd --- /dev/null +++ b/OmniKit/OmniKit.h @@ -0,0 +1,19 @@ +// +// OmniKit.h +// OmniKit +// +// Created by Pete Schwamb on 8/26/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +#import + +//! Project version number for OmniKit. +FOUNDATION_EXPORT double OmniKitVersionNumber; + +//! Project version string for OmniKit. +FOUNDATION_EXPORT const unsigned char OmniKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/OmniKit/PumpManager/MessageLog.swift b/OmniKit/PumpManager/MessageLog.swift new file mode 100644 index 000000000..1b3feb841 --- /dev/null +++ b/OmniKit/PumpManager/MessageLog.swift @@ -0,0 +1,92 @@ +// +// MessageLog.swift +// OmniKit +// +// Created by Pete Schwamb on 1/28/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct MessageLogEntry: CustomStringConvertible, Equatable { + + public var description: String { + return "\(timestamp) \(messageDirection) \(data.hexadecimalString)" + } + + enum MessageDirection: Int { + case send + case receive + } + + let messageDirection: MessageDirection + let timestamp: Date + let data: Data +} + +extension MessageLogEntry: RawRepresentable { + public typealias RawValue = [String: Any] + + public init?(rawValue: RawValue) { + guard + let rawMessageDirection = rawValue["messageDirection"] as? Int, + let messageDirection = MessageDirection(rawValue: rawMessageDirection), + let timestamp = rawValue["timestamp"] as? Date, + let data = rawValue["data"] as? Data + else { + return nil + } + + self.messageDirection = messageDirection + self.timestamp = timestamp + self.data = data + } + + public var rawValue: RawValue { + return [ + "messageDirection": messageDirection.rawValue, + "timestamp": timestamp, + "data": data, + ] + } + +} + +public struct MessageLog: CustomStringConvertible, Equatable { + + var entries = [MessageLogEntry]() + + public var description: String { + var lines = ["### MessageLog"] + for entry in entries { + lines.append("* " + entry.description) + } + return lines.joined(separator: "\n") + } + + mutating func erase() { + entries.removeAll() + } + + mutating func record(_ entry: MessageLogEntry) { + entries.append(entry) + } +} + +extension MessageLog: RawRepresentable { + public typealias RawValue = [String: Any] + + public init?(rawValue: RawValue) { + guard let rawEntries = rawValue["entries"] as? [MessageLogEntry.RawValue] else { + return nil + } + + self.entries = rawEntries.compactMap { MessageLogEntry(rawValue: $0) } + } + + public var rawValue: RawValue { + return [ + "entries": entries.map { $0.rawValue } + ] + } +} diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift new file mode 100644 index 000000000..b09314778 --- /dev/null +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -0,0 +1,1355 @@ +// +// OmnipodPumpManager.swift +// OmniKit +// +// Created by Pete Schwamb on 8/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import HealthKit +import LoopKit +import RileyLinkKit +import RileyLinkBLEKit +import UserNotifications +import os.log + +public enum ReservoirAlertState { + case ok + case lowReservoir + case empty +} + +public protocol PodStateObserver: class { + func podStateDidUpdate(_ state: PodState?) +} + +public enum OmnipodPumpManagerError: Error { + case noPodPaired + case podAlreadyPaired + case podAlreadyPrimed + case notReadyForPrime + case notReadyForCannulaInsertion +} + +extension OmnipodPumpManagerError: LocalizedError { + public var errorDescription: String? { + switch self { + case .noPodPaired: + return LocalizedString("No pod paired", comment: "Error message shown when no pod is paired") + case .podAlreadyPrimed: + return LocalizedString("Pod already primed", comment: "Error message shown when prime is attempted, but pod is already primed") + case .podAlreadyPaired: + return LocalizedString("Pod already paired", comment: "Error message shown when user cannot pair because pod is already paired") + case .notReadyForPrime: + return LocalizedString("Pod is not in a state ready for priming.", comment: "Error message when prime fails because the pod is in an unexpected state") + case .notReadyForCannulaInsertion: + return LocalizedString("Pod is not in a state ready for cannula insertion.", comment: "Error message when cannula insertion fails because the pod is in an unexpected state") + } + } + + public var failureReason: String? { + switch self { + case .noPodPaired: + return nil + case .podAlreadyPrimed: + return nil + case .podAlreadyPaired: + return nil + case .notReadyForPrime: + return nil + case .notReadyForCannulaInsertion: + return nil + } + } + + public var recoverySuggestion: String? { + switch self { + case .noPodPaired: + return LocalizedString("Please pair a new pod", comment: "Recover suggestion shown when no pod is paired") + case .podAlreadyPrimed: + return nil + case .podAlreadyPaired: + return nil + case .notReadyForPrime: + return nil + case .notReadyForCannulaInsertion: + return nil + } + } +} + + +public class OmnipodPumpManager: RileyLinkPumpManager { + public init(state: OmnipodPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil) { + self.lockedState = Locked(state) + self.lockedPodComms = Locked(PodComms(podState: state.podState)) + super.init(rileyLinkDeviceProvider: rileyLinkDeviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager) + + self.podComms.delegate = self + self.podComms.messageLogger = self + } + + public required convenience init?(rawState: PumpManager.RawStateValue) { + guard let state = OmnipodPumpManagerState(rawValue: rawState), + let connectionManagerState = state.rileyLinkConnectionManagerState else + { + return nil + } + + let rileyLinkConnectionManager = RileyLinkConnectionManager(state: connectionManagerState) + + self.init(state: state, rileyLinkDeviceProvider: rileyLinkConnectionManager.deviceProvider, rileyLinkConnectionManager: rileyLinkConnectionManager) + + rileyLinkConnectionManager.delegate = self + } + + private var podComms: PodComms { + get { + return lockedPodComms.value + } + set { + lockedPodComms.value = newValue + } + } + private let lockedPodComms: Locked + + private let podStateObservers = WeakSynchronizedSet() + + public var state: OmnipodPumpManagerState { + return lockedState.value + } + + private func setState(_ changes: (_ state: inout OmnipodPumpManagerState) -> Void) -> Void { + return setStateWithResult(changes) + } + + private func mutateState(_ changes: (_ state: inout OmnipodPumpManagerState) -> Void) -> OmnipodPumpManagerState { + return setStateWithResult({ (state) -> OmnipodPumpManagerState in + changes(&state) + return state + }) + } + + private func setStateWithResult(_ changes: (_ state: inout OmnipodPumpManagerState) -> ReturnType) -> ReturnType { + var oldValue: OmnipodPumpManagerState! + var returnType: ReturnType! + let newValue = lockedState.mutate { (state) in + oldValue = state + returnType = changes(&state) + } + + guard oldValue != newValue else { + return returnType + } + + if oldValue.podState != newValue.podState { + podStateObservers.forEach { (observer) in + observer.podStateDidUpdate(newValue.podState) + } + } + + // Ideally we ensure that oldValue.rawValue != newValue.rawValue, but the types aren't + // defined as equatable + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerDidUpdateState(self) + } + + let oldStatus = status(for: oldValue) + let newStatus = status(for: newValue) + + if oldStatus != newStatus { + // TODO: remove comment + // if oldValue.podState?.suspended != newValue.podState?.suspended || + // oldValue.timeZone != newValue.timeZone + notifyStatusObservers(oldStatus: oldStatus) + } + + // Reschedule expiration notification if relevant values change + if oldValue.expirationReminderDate != newValue.expirationReminderDate || + oldValue.podState?.expiresAt != newValue.podState?.expiresAt + { + schedulePodExpirationNotification(for: newValue) + } + + return returnType + } + private let lockedState: Locked + + private let statusObservers = WeakSynchronizedSet() + + private func notifyStatusObservers(oldStatus: PumpManagerStatus) { + let status = self.status + pumpDelegate.notify { (delegate) in + delegate?.pumpManager(self, didUpdate: status, oldStatus: oldStatus) + } + statusObservers.forEach { (observer) in + observer.pumpManager(self, didUpdate: status, oldStatus: oldStatus) + } + } + + private let pumpDelegate = WeakSynchronizedDelegate() + + public let log = OSLog(category: "OmnipodPumpManager") + + // MARK: - RileyLink Updates + + override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { + get { + return state.rileyLinkConnectionManagerState + } + set { + setState { (state) in + state.rileyLinkConnectionManagerState = newValue + } + } + } + + override public func deviceTimerDidTick(_ device: RileyLinkDevice) { + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerBLEHeartbeatDidFire(self) + } + } + + // MARK: - CustomDebugStringConvertible + + override public var debugDescription: String { + let lines = [ + "## OmnipodPumpManager", + "podComms: \(String(reflecting: podComms))", + "state: \(String(reflecting: state))", + "status: \(String(describing: status))", + "podStateObservers.count: \(podStateObservers.cleanupDeallocatedElements().count)", + "statusObservers.count: \(statusObservers.cleanupDeallocatedElements().count)", + super.debugDescription, + ] + return lines.joined(separator: "\n") + } +} + +extension OmnipodPumpManager { + // MARK: - PodStateObserver + + public func addPodStateObserver(_ observer: PodStateObserver, queue: DispatchQueue) { + podStateObservers.insert(observer, queue: queue) + } + + public func removePodStateObserver(_ observer: PodStateObserver) { + podStateObservers.removeElement(observer) + } + + private func updateBLEHeartbeatPreference() { + dispatchPrecondition(condition: .notOnQueue(delegateQueue)) + + rileyLinkDeviceProvider.timerTickEnabled = self.state.isPumpDataStale || pumpDelegate.call({ (delegate) -> Bool in + return delegate?.pumpManagerMustProvideBLEHeartbeat(self) == true + }) + } + + private func status(for state: OmnipodPumpManagerState) -> PumpManagerStatus { + return PumpManagerStatus( + timeZone: state.timeZone, + device: device(for: state), + pumpBatteryChargeRemaining: nil, + basalDeliveryState: basalDeliveryState(for: state), + bolusState: bolusState(for: state) + ) + } + + private func device(for state: OmnipodPumpManagerState) -> HKDevice { + if let podState = state.podState { + return HKDevice( + name: type(of: self).managerIdentifier, + manufacturer: "Insulet", + model: "Eros", + hardwareVersion: nil, + firmwareVersion: podState.piVersion, + softwareVersion: String(OmniKitVersionNumber), + localIdentifier: String(format:"%04X", podState.address), + udiDeviceIdentifier: nil + ) + } else { + return HKDevice( + name: type(of: self).managerIdentifier, + manufacturer: "Insulet", + model: "Eros", + hardwareVersion: nil, + firmwareVersion: nil, + softwareVersion: String(OmniKitVersionNumber), + localIdentifier: nil, + udiDeviceIdentifier: nil + ) + } + } + + private func basalDeliveryState(for state: OmnipodPumpManagerState) -> PumpManagerStatus.BasalDeliveryState { + guard let podState = state.podState else { + return .suspended + } + + switch state.suspendTransition { + case .suspending?: + return .suspending + case .resuming?: + return .resuming + case .none: + return podState.suspended ? .suspended : .active + } + } + + private func bolusState(for state: OmnipodPumpManagerState) -> PumpManagerStatus.BolusState { + guard let podState = state.podState else { + return .none + } + + switch state.bolusTransition { + case .initiating?: + return .initiating + case .canceling?: + return .canceling + case .none: + if let bolus = podState.unfinalizedBolus, !bolus.finished { + return .inProgress(DoseEntry(bolus)) + } else { + return .none + } + } + } + + // Thread-safe + public var hasActivePod: Bool { + // TODO: Should this check be done automatically before each session? + return state.hasActivePod + } + + // Thread-safe + public var expirationReminderDate: Date? { + get { + return state.expirationReminderDate + } + set { + // Setting a new value reschedules notifications + setState { (state) in + state.expirationReminderDate = newValue + } + } + } + + // MARK: - Notifications + + static let podExpirationNotificationIdentifier = "Omnipod:\(LoopNotificationCategory.pumpExpired.rawValue)" + + func schedulePodExpirationNotification(for state: OmnipodPumpManagerState) { + guard let expirationReminderDate = state.expirationReminderDate, + expirationReminderDate.timeIntervalSinceNow > 0, + let expiresAt = state.podState?.expiresAt + else { + pumpDelegate.notify { (delegate) in + delegate?.clearNotification(for: self, identifier: OmnipodPumpManager.podExpirationNotificationIdentifier) + } + return + } + + let content = UNMutableNotificationContent() + + let timeBetweenNoticeAndExpiration = expiresAt.timeIntervalSince(expirationReminderDate) + + let formatter = DateComponentsFormatter() + formatter.maximumUnitCount = 1 + formatter.allowedUnits = [.hour, .minute] + formatter.unitsStyle = .full + + let timeUntilExpiration = formatter.string(from: timeBetweenNoticeAndExpiration) ?? "" + + content.title = NSLocalizedString("Pod Expiration Notice", comment: "The title for pod expiration notification") + + content.body = String(format: NSLocalizedString("Time to replace your pod! Your pod will expire in %1$@", comment: "The format string for pod expiration notification body (1: time until expiration)"), timeUntilExpiration) + content.sound = UNNotificationSound.default + content.categoryIdentifier = LoopNotificationCategory.pumpExpired.rawValue + content.threadIdentifier = LoopNotificationCategory.pumpExpired.rawValue + + let trigger = UNTimeIntervalNotificationTrigger( + timeInterval: expirationReminderDate.timeIntervalSinceNow, + repeats: false + ) + + pumpDelegate.notify { (delegate) in + delegate?.scheduleNotification(for: self, identifier: OmnipodPumpManager.podExpirationNotificationIdentifier, content: content, trigger: trigger) + } + } + + // MARK: - Pod comms + + // Does not support concurrent callers. Not thread-safe. + private func forgetPod(completion: @escaping () -> Void) { + let resetPodState = { (_ state: inout OmnipodPumpManagerState) in + self.podComms = PodComms(podState: nil) + self.podComms.delegate = self + self.podComms.messageLogger = self + + state.podState = nil + state.messageLog.erase() + state.expirationReminderDate = nil + } + + // TODO: PodState shouldn't be mutated outside of the session queue + // TODO: Consider serializing the entire forget-pod path instead of relying on the UI to do it + + let state = mutateState { (state) in + state.podState?.finalizeFinishedDoses() + } + + if let dosesToStore = state.podState?.dosesToStore { + store(doses: dosesToStore, completion: { error in + self.setState({ (state) in + if error != nil { + state.unstoredDoses.append(contentsOf: dosesToStore) + } + + resetPodState(&state) + }) + completion() + }) + } else { + setState { (state) in + resetPodState(&state) + } + + completion() + } + } + + // MARK: Testing + #if targetEnvironment(simulator) + private func jumpStartPod(address: UInt32, lot: UInt32, tid: UInt32, fault: PodInfoFaultEvent? = nil, startDate: Date? = nil, mockFault: Bool) { + let start = startDate ?? Date() + var podState = PodState(address: address, piVersion: "jumpstarted", pmVersion: "jumpstarted", lot: lot, tid: tid) + podState.setupProgress = .podConfigured + podState.activatedAt = start + + let fault = mockFault ? try? PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) : nil + podState.fault = fault + + self.podComms = PodComms(podState: podState) + + setState({ (state) in + state.podState = podState + state.expirationReminderDate = start + .hours(70) + }) + } + #endif + + // MARK: - Pairing + + // Called on the main thread + public func pairAndPrime(completion: @escaping (PumpManagerResult) -> Void) { + #if targetEnvironment(simulator) + // If we're in the simulator, create a mock PodState + let mockFaultDuringPairing = false + DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + .seconds(2)) { + self.jumpStartPod(address: 0x1f0b3557, lot: 40505, tid: 6439, mockFault: mockFaultDuringPairing) + let fault: PodInfoFaultEvent? = self.setStateWithResult({ (state) in + state.podState?.setupProgress = .priming + return state.podState?.fault + }) + if mockFaultDuringPairing { + completion(.failure(PodCommsError.podFault(fault: fault!))) + } else { + let mockPrimeDuration = TimeInterval(.seconds(3)) + completion(.success(mockPrimeDuration)) + } + } + #else + let deviceSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + let configureAndPrimeSession = { (result: PodComms.SessionRunResult) in + switch result { + case .success(let session): + // We're on the session queue + session.assertOnSessionQueue() + + self.log.default("Beginning pod configuration and prime") + + // Clean up any previously un-stored doses if needed + let unstoredDoses = self.state.unstoredDoses + if self.store(doses: unstoredDoses, in: session) { + self.setState({ (state) in + state.unstoredDoses.removeAll() + }) + } + + do { + let primeFinishedAt = try session.prime() + completion(.success(primeFinishedAt)) + } catch let error { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + + let needsPairing = setStateWithResult({ (state) -> PumpManagerResult in + guard let podState = state.podState else { + return .success(true) // Needs pairing + } + + guard podState.setupProgress.primingNeeded else { + return .failure(OmnipodPumpManagerError.podAlreadyPrimed) + } + + // If still need configuring, run pair() + return .success(podState.setupProgress == .addressAssigned) + }) + + switch needsPairing { + case .success(true): + self.log.default("Pairing pod before priming") + + self.podComms.pair(using: deviceSelector, timeZone: .currentFixed, messageLogger: self) { (session) in + // Calls completion + configureAndPrimeSession(session) + } + case .success(false): + self.log.default("Pod already paired. Continuing.") + + self.podComms.runSession(withName: "Configure and prime pod", using: deviceSelector) { (result) in + // Calls completion + configureAndPrimeSession(result) + } + case .failure(let error): + completion(.failure(error)) + } + #endif + } + + // Called on the main thread + public func insertCannula(completion: @escaping (PumpManagerResult) -> Void) { + #if targetEnvironment(simulator) + let mockDelay = TimeInterval(seconds: 3) + DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + mockDelay) { + let result = self.setStateWithResult({ (state) -> PumpManagerResult in + // Mock fault + // let fault = try! PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) + // self.state.podState?.fault = fault + // return .failure(PodCommsError.podFault(fault: fault)) + + // Mock success + state.podState?.setupProgress = .completed + return .success(mockDelay) + }) + + completion(result) + } + #else + let preError = setStateWithResult({ (state) -> OmnipodPumpManagerError? in + guard let podState = state.podState, let expiresAt = podState.expiresAt, podState.readyForCannulaInsertion else + { + return .notReadyForCannulaInsertion + } + + state.expirationReminderDate = expiresAt.addingTimeInterval(-Pod.expirationReminderAlertDefaultTimeBeforeExpiration) + + guard podState.setupProgress.needsCannulaInsertion else { + return .podAlreadyPaired + } + + return nil + }) + + if let error = preError { + completion(.failure(error)) + return + } + + let deviceSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + let timeZone = self.state.timeZone + + self.podComms.runSession(withName: "Insert cannula", using: deviceSelector) { (result) in + switch result { + case .success(let session): + do { + if self.state.podState?.setupProgress.needsInitialBasalSchedule == true { + let scheduleOffset = timeZone.scheduleOffset(forDate: Date()) + try session.programInitialBasalSchedule(self.state.basalSchedule, scheduleOffset: scheduleOffset) + + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + } + + let finishWait = try session.insertCannula() + + DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + finishWait) { + // Runs a new session + self.checkCannulaInsertionFinished() + } + completion(.success(finishWait)) + } catch let error { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + #endif + } + + private func checkCannulaInsertionFinished() { + let deviceSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Check cannula insertion finished", using: deviceSelector) { (result) in + switch result { + case .success(let session): + do { + try session.checkInsertionCompleted() + } catch let error { + self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + } + case .failure(let error): + self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + } + } + } + + public func refreshStatus(completion: ((_ result: PumpManagerResult) -> Void)? = nil) { + guard self.hasActivePod else { + completion?(.failure(OmnipodPumpManagerError.noPodPaired)) + return + } + + self.getPodStatus(completion) + } + + // MARK: - Pump Commands + + private func getPodStatus(_ completion: ((_ result: PumpManagerResult) -> Void)? = nil) { + guard state.podState?.unfinalizedBolus?.finished != false else { + self.log.info("Skipping status request due to unfinalized bolus in progress.") + completion?(.failure(PodCommsError.unfinalizedBolus)) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + podComms.runSession(withName: "Get pod status", using: rileyLinkSelector) { (result) in + do { + switch result { + case .success(let session): + let status = try session.getStatus() + + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + + if let reservoirLevel = status.reservoirLevel { + let reservoirDate = Date() + // We block the session until the data's confirmed stored by the delegate + let semaphore = DispatchSemaphore(value: 0) + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didReadReservoirValue: reservoirLevel, at: reservoirDate) { (_) in + semaphore.signal() + } + }) + semaphore.wait() + } + completion?(.success(status)) + case .failure(let error): + throw error + } + } catch let error { + completion?(.failure(error)) + self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + } + } + } + + public func acknowledgeAlerts(_ alertsToAcknowledge: AlertSet, completion: @escaping (_ alerts: [AlertSlot: PodAlert]?) -> Void) { + guard self.hasActivePod else { + completion(nil) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Acknowledge Alarms", using: rileyLinkSelector) { (result) in + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure: + completion(nil) + return + } + + do { + let alerts = try session.acknowledgeAlerts(alerts: alertsToAcknowledge) + completion(alerts) + } catch { + completion(nil) + } + } + } + + public func setTime(completion: @escaping (Error?) -> Void) { + + let timeZone = TimeZone.currentFixed + + let preError = setStateWithResult { (state) -> Error? in + guard state.hasActivePod else { + return OmnipodPumpManagerError.noPodPaired + } + + guard state.podState?.unfinalizedBolus?.finished != false else { + return PodCommsError.unfinalizedBolus + } + + return nil + } + + if let error = preError { + completion(error) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Set time zone", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + let _ = try session.setTime(timeZone: timeZone, basalSchedule: self.state.basalSchedule, date: Date()) + self.setState { (state) in + state.timeZone = timeZone + } + completion(nil) + } catch let error { + completion(error) + } + case .failure(let error): + completion(error) + } + } + } + + public func setBasalSchedule(_ schedule: BasalSchedule, completion: @escaping (Error?) -> Void) { + let shouldContinue = setStateWithResult({ (state) -> PumpManagerResult in + guard state.hasActivePod else { + // If there's no active pod yet, save the basal schedule anyway + state.basalSchedule = schedule + return .success(false) + } + + guard state.podState?.unfinalizedBolus?.finished != false else { + return .failure(PodCommsError.unfinalizedBolus) + } + + return .success(true) + }) + + switch shouldContinue { + case .success(true): + break + case .success(false): + completion(nil) + return + case .failure(let error): + completion(error) + return + } + + let timeZone = self.state.timeZone + + self.podComms.runSession(withName: "Save Basal Profile", using: self.rileyLinkDeviceProvider.firstConnectedDevice) { (result) in + do { + switch result { + case .success(let session): + let scheduleOffset = timeZone.scheduleOffset(forDate: Date()) + let result = session.cancelDelivery(deliveryType: .all, beepType: .noBeep) + switch result { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + case .success: + break + } + let _ = try session.setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: false, completionBeep: false, programReminderInterval: 0) + + self.setState { (state) in + state.basalSchedule = schedule + } + completion(nil) + case .failure(let error): + throw error + } + } catch let error { + self.log.error("Save basal profile failed: %{public}@", String(describing: error)) + completion(error) + } + } + } + + // Called on the main thread. + // The UI is responsible for serializing calls to this method; + // it does not handle concurrent calls. + public func deactivatePod(forgetPodOnFail: Bool, completion: @escaping (Error?) -> Void) { + #if targetEnvironment(simulator) + DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + .seconds(2)) { + + self.forgetPod(completion: { + completion(nil) + }) + } + #else + guard self.state.podState != nil else { + if forgetPodOnFail { + forgetPod(completion: { + completion(OmnipodPumpManagerError.noPodPaired) + }) + } else { + completion(OmnipodPumpManagerError.noPodPaired) + } + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Deactivate pod", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + try session.deactivatePod() + + self.forgetPod(completion: { + completion(nil) + }) + } catch let error { + if forgetPodOnFail { + self.forgetPod(completion: { + completion(error) + }) + } else { + completion(error) + } + } + case .failure(let error): + if forgetPodOnFail { + self.forgetPod(completion: { + completion(error) + }) + } else { + completion(error) + } + } + } + #endif + } + + public func testingCommands(completion: @escaping (Error?) -> Void) { + guard self.hasActivePod else { + completion(OmnipodPumpManagerError.noPodPaired) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Testing Commands", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + let _ = try session.testingCommands() + completion(nil) + } catch let error { + completion(error) + } + case .failure(let error): + completion(error) + } + } + } +} + +// MARK: - PumpManager +extension OmnipodPumpManager: PumpManager { + public static let managerIdentifier: String = "Omnipod" + + public static let localizedTitle = LocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager") + + public var supportedBolusVolumes: [Double] { + // 0.05 units for rates between 0.05-30U/hr + // 0 is not a supported bolus volume + return (1...600).map { Double($0) / Double(Pod.pulsesPerUnit) } + } + + public var supportedBasalRates: [Double] { + // 0.05 units for rates between 0.05-30U/hr + // 0 is not a supported scheduled basal rate + return (1...600).map { Double($0) / Double(Pod.pulsesPerUnit) } + } + + public func roundToSupportedBolusVolume(units: Double) -> Double { + // We do support rounding a 0 U volume to 0 + return supportedBolusVolumes.last(where: { $0 <= units }) ?? 0 + } + + public func roundToSupportedBasalRate(unitsPerHour: Double) -> Double { + // We do support rounding a 0 U/hr rate to 0 + return supportedBasalRates.last(where: { $0 <= unitsPerHour }) ?? 0 + } + + public var maximumBasalScheduleEntryCount: Int { + return Pod.maximumBasalScheduleEntryCount + } + + public var minimumBasalScheduleEntryDuration: TimeInterval { + return Pod.minimumBasalScheduleEntryDuration + } + + public var pumpRecordsBasalProfileStartEvents: Bool { + return false + } + + public var pumpReservoirCapacity: Double { + return Pod.reservoirCapacity + } + + public var status: PumpManagerStatus { + // Acquire the lock just once + let state = self.state + + return status(for: state) + } + + public var rawState: PumpManager.RawStateValue { + return state.rawValue + } + + public var pumpManagerDelegate: PumpManagerDelegate? { + get { + return pumpDelegate.delegate + } + set { + pumpDelegate.delegate = newValue + + // TODO: is there still a scenario where this is required? + // self.schedulePodExpirationNotification() + } + } + + public var delegateQueue: DispatchQueue! { + get { + return pumpDelegate.queue + } + set { + pumpDelegate.queue = newValue + } + } + + // MARK: Methods + + public func suspendDelivery(completion: @escaping (Error?) -> Void) { + guard self.hasActivePod else { + completion(OmnipodPumpManagerError.noPodPaired) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Suspend", using: rileyLinkSelector) { (result) in + + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure(let error): + completion(error) + return + } + + defer { + self.setState({ (state) in + state.suspendTransition = .none + }) + } + self.setState({ (state) in + state.suspendTransition = .suspending + }) + + let result = session.cancelDelivery(deliveryType: .all, beepType: .noBeep) + switch result { + case .certainFailure(let error): + completion(error) + case .uncertainFailure(let error): + completion(error) + case .success: + completion(nil) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + } + } + } + + public func resumeDelivery(completion: @escaping (Error?) -> Void) { + guard self.hasActivePod else { + completion(OmnipodPumpManagerError.noPodPaired) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Resume", using: rileyLinkSelector) { (result) in + + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure(let error): + completion(error) + return + } + + defer { + self.setState({ (state) in + state.suspendTransition = .none + }) + } + self.setState({ (state) in + state.suspendTransition = .resuming + }) + + do { + let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) + let _ = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset) + completion(nil) + } catch (let error) { + completion(error) + } + } + } + + public func addStatusObserver(_ observer: PumpManagerStatusObserver, queue: DispatchQueue) { + statusObservers.insert(observer, queue: queue) + } + + public func removeStatusObserver(_ observer: PumpManagerStatusObserver) { + statusObservers.removeElement(observer) + } + + public func setMustProvideBLEHeartbeat(_ mustProvideBLEHeartbeat: Bool) { + rileyLinkDeviceProvider.timerTickEnabled = self.state.isPumpDataStale || mustProvideBLEHeartbeat + } + + public func assertCurrentPumpData() { + let shouldFetchStatus = setStateWithResult { (state) -> Bool? in + guard state.hasActivePod else { + return nil // No active pod + } + + return state.isPumpDataStale + } + + switch shouldFetchStatus { + case .none: + return // No active pod + case true?: + log.default("Fetching status because pumpData is too old") + getPodStatus { (response) in + self.pumpDelegate.notify({ (delegate) in + switch response { + case .success: + self.log.default("Recommending Loop") + delegate?.pumpManagerRecommendsLoop(self) + case .failure(let error): + self.log.default("Not recommending Loop because pump data is stale: %@", String(describing: error)) + if let error = error as? PumpManagerError { + delegate?.pumpManager(self, didError: error) + } + } + }) + } + case false?: + log.default("Skipping status update because pumpData is fresh") + pumpDelegate.notify { (delegate) in + self.log.default("Recommending Loop") + delegate?.pumpManagerRecommendsLoop(self) + } + } + } + + public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { + guard self.hasActivePod else { + completion(.failure(SetBolusError.certain(OmnipodPumpManagerError.noPodPaired))) + return + } + + // Round to nearest supported volume + let enactUnits = roundToSupportedBolusVolume(units: units) + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Bolus", using: rileyLinkSelector) { (result) in + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure(let error): + completion(.failure(SetBolusError.certain(error))) + return + } + + var podStatus: StatusResponse + + do { + podStatus = try session.getStatus() + } catch let error { + completion(.failure(SetBolusError.certain(error as? PodCommsError ?? PodCommsError.commsError(error: error)))) + return + } + + // If pod suspended, resume basal before bolusing + if podStatus.deliveryStatus == .suspended { + do { + let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) + podStatus = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset) + } catch let error { + completion(.failure(SetBolusError.certain(error as? PodCommsError ?? PodCommsError.commsError(error: error)))) + return + } + } + + guard !podStatus.deliveryStatus.bolusing else { + completion(.failure(SetBolusError.certain(PodCommsError.unfinalizedBolus))) + return + } + + // TODO: Move this to the top, since Loop is expecting a status change to cancel its loading indicator? + defer { + self.setState({ (state) in + state.bolusTransition = nil + }) + } + self.setState({ (state) in + state.bolusTransition = .initiating + }) + + let date = Date() + let endDate = date.addingTimeInterval(enactUnits / Pod.bolusDeliveryRate) + let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: enactUnits, unit: .units) + willRequest(dose) + + let result = session.bolus(units: enactUnits) + + switch result { + case .success: + completion(.success(dose)) + case .certainFailure(let error): + completion(.failure(SetBolusError.certain(error))) + case .uncertainFailure(let error): + completion(.failure(SetBolusError.uncertain(error))) + } + } + } + + public func cancelBolus(completion: @escaping (PumpManagerResult) -> Void) { + guard self.hasActivePod else { + completion(.failure(OmnipodPumpManagerError.noPodPaired)) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Cancel Bolus", using: rileyLinkSelector) { (result) in + + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure(let error): + completion(.failure(error)) + return + } + + do { + defer { + self.setState({ (state) in + state.bolusTransition = nil + }) + } + self.setState({ (state) in + state.bolusTransition = .canceling + }) + + let result = session.cancelDelivery(deliveryType: .bolus, beepType: .noBeep) + switch result { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + case .success(_, let canceledBolus): + let canceledDoseEntry: DoseEntry? = canceledBolus != nil ? DoseEntry(canceledBolus!) : nil + completion(.success(canceledDoseEntry)) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + } + } catch { + completion(.failure(error)) + } + } + } + + public func enactTempBasal(unitsPerHour: Double, for duration: TimeInterval, completion: @escaping (PumpManagerResult) -> Void) { + guard self.hasActivePod else { + completion(.failure(OmnipodPumpManagerError.noPodPaired)) + return + } + + // Round to nearest supported rate + let rate = roundToSupportedBasalRate(unitsPerHour: unitsPerHour) + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Enact Temp Basal", using: rileyLinkSelector) { (result) in + self.log.info("Enact temp basal %.03fU/hr for %ds", rate, Int(duration)) + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure(let error): + completion(.failure(error)) + return + } + + do { + let preError = self.setStateWithResult({ (state) -> PodCommsError? in + guard state.podState?.suspended == false else { + self.log.info("Canceling temp basal because podState indicates pod is suspended.") + return .podSuspended + } + + guard state.podState?.unfinalizedBolus?.finished != false else { + self.log.info("Canceling temp basal because podState indicates unfinalized bolus in progress.") + return .unfinalizedBolus + } + + return nil + }) + + if let error = preError { + throw error + } + + let status: StatusResponse + let result = session.cancelDelivery(deliveryType: .tempBasal, beepType: .noBeep) + switch result { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + case .success(let cancelTempStatus,_): + status = cancelTempStatus + } + + guard !status.deliveryStatus.bolusing else { + throw PodCommsError.unfinalizedBolus + } + + guard status.deliveryStatus != .suspended else { + self.log.info("Canceling temp basal because status return indicates pod is suspended.") + throw PodCommsError.podSuspended + } + + if duration < .ulpOfOne { + // 0 duration temp basals are used to cancel any existing temp basal + let cancelTime = Date() + let dose = DoseEntry(type: .tempBasal, startDate: cancelTime, endDate: cancelTime, value: 0, unit: .unitsPerHour) + completion(.success(dose)) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + } else { + let result = session.setTempBasal(rate: rate, duration: duration, acknowledgementBeep: false, completionBeep: false, programReminderInterval: 0) + let basalStart = Date() + let dose = DoseEntry(type: .tempBasal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: rate, unit: .unitsPerHour) + switch result { + case .success: + completion(.success(dose)) + case .uncertainFailure(let error): + self.log.error("Temp basal uncertain error: %@", String(describing: error)) + completion(.success(dose)) + case .certainFailure(let error): + completion(.failure(error)) + } + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + } + } catch let error { + self.log.error("Error during temp basal: %@", String(describing: error)) + completion(.failure(error)) + } + } + } + + /// Returns a dose estimator for the current bolus, if one is in progress + public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? { + if case .inProgress(let dose) = bolusState(for: self.state) { + return PodDoseProgressEstimator(dose: dose, pumpManager: self, reportingQueue: dispatchQueue) + } + return nil + } +} + +extension OmnipodPumpManager: MessageLogger { + func didSend(_ message: Data) { + setState { (state) in + state.messageLog.record(MessageLogEntry(messageDirection: .send, timestamp: Date(), data: message)) + } + } + + func didReceive(_ message: Data) { + setState { (state) in + state.messageLog.record(MessageLogEntry(messageDirection: .receive, timestamp: Date(), data: message)) + } + } +} + +extension OmnipodPumpManager: PodCommsDelegate { + + // This cannot be called from within the lockedState lock! + func store(doses: [UnfinalizedDose], in session: PodCommsSession) -> Bool { + session.assertOnSessionQueue() + + // We block the session until the data's confirmed stored by the delegate + let semaphore = DispatchSemaphore(value: 0) + var success = false + + store(doses: doses) { (error) in + success = (error == nil) + semaphore.signal() + } + + semaphore.wait() + + if success { + setState { (state) in + state.lastPumpDataReportDate = Date() + } + } + return success + } + + func store(doses: [UnfinalizedDose], completion: @escaping (_ error: Error?) -> Void) { + pumpDelegate.notify { (delegate) in + guard let delegate = delegate else { + preconditionFailure("pumpManagerDelegate cannot be nil") + } + + delegate.pumpManager(self, didReadPumpEvents: doses.map { NewPumpEvent($0) }, completion: { (error) in + if let error = error { + self.log.error("Error storing pod events: %@", String(describing: error)) + } else { + self.log.error("Stored pod events: %@", String(describing: doses)) + } + + completion(error) + }) + } + } + + func podComms(_ podComms: PodComms, didChange podState: PodState) { + setState { (state) in + state.podState = podState + } + } +} + diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift new file mode 100644 index 000000000..eae34db16 --- /dev/null +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -0,0 +1,189 @@ +// +// OmnipodPumpManagerState.swift +// OmniKit +// +// Created by Pete Schwamb on 8/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import RileyLinkKit +import RileyLinkBLEKit +import LoopKit + + +public struct OmnipodPumpManagerState: RawRepresentable, Equatable { + public typealias RawValue = PumpManager.RawStateValue + + public static let version = 2 + + public var podState: PodState? + + public var timeZone: TimeZone + + public var basalSchedule: BasalSchedule + + public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? + + public var messageLog = MessageLog() + + public var unstoredDoses: [UnfinalizedDose] + + public var expirationReminderDate: Date? + + // Temporal state not persisted + + internal enum SuspendTransition { + case suspending + case resuming + } + + internal var suspendTransition: SuspendTransition? + + internal enum BolusTransition { + case initiating + case canceling + } + + internal var bolusTransition: BolusTransition? + + internal var lastPumpDataReportDate: Date? + + // MARK: - + + public init(podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?) { + self.podState = podState + self.timeZone = timeZone + self.basalSchedule = basalSchedule + self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState + self.unstoredDoses = [] + } + + public init?(rawValue: RawValue) { + + guard let version = rawValue["version"] as? Int else { + return nil + } + + let basalSchedule: BasalSchedule + + if version == 1 { + // migrate: basalSchedule moved from podState to oppm state + if let podStateRaw = rawValue["podState"] as? PodState.RawValue, + let rawBasalSchedule = podStateRaw["basalSchedule"] as? BasalSchedule.RawValue, + let migrateSchedule = BasalSchedule(rawValue: rawBasalSchedule) + { + basalSchedule = migrateSchedule + } else { + return nil + } + } else { + guard let rawBasalSchedule = rawValue["basalSchedule"] as? BasalSchedule.RawValue, + let schedule = BasalSchedule(rawValue: rawBasalSchedule) else + { + return nil + } + basalSchedule = schedule + } + + let podState: PodState? + if let podStateRaw = rawValue["podState"] as? PodState.RawValue { + podState = PodState(rawValue: podStateRaw) + } else { + podState = nil + } + + let timeZone: TimeZone + if let timeZoneSeconds = rawValue["timeZone"] as? Int, + let tz = TimeZone(secondsFromGMT: timeZoneSeconds) { + timeZone = tz + } else { + timeZone = TimeZone.currentFixed + } + + let rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? + if let rileyLinkConnectionManagerStateRaw = rawValue["rileyLinkConnectionManagerState"] as? RileyLinkConnectionManagerState.RawValue { + rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(rawValue: rileyLinkConnectionManagerStateRaw) + } else { + rileyLinkConnectionManagerState = nil + } + + self.init( + podState: podState, + timeZone: timeZone, + basalSchedule: basalSchedule, + rileyLinkConnectionManagerState: rileyLinkConnectionManagerState + ) + + if let rawMessageLog = rawValue["messageLog"] as? MessageLog.RawValue, let messageLog = MessageLog(rawValue: rawMessageLog) { + self.messageLog = messageLog + } + + if let expirationReminderDate = rawValue["expirationReminderDate"] as? Date { + self.expirationReminderDate = expirationReminderDate + } else if let expiresAt = podState?.expiresAt { + self.expirationReminderDate = expiresAt.addingTimeInterval(-Pod.expirationReminderAlertDefaultTimeBeforeExpiration) + } + + if let rawUnstoredDoses = rawValue["unstoredDoses"] as? [UnfinalizedDose.RawValue] { + self.unstoredDoses = rawUnstoredDoses.compactMap( { UnfinalizedDose(rawValue: $0) } ) + } else { + self.unstoredDoses = [] + } + } + + public var rawValue: RawValue { + var value: [String : Any] = [ + "version": OmnipodPumpManagerState.version, + "timeZone": timeZone.secondsFromGMT(), + "basalSchedule": basalSchedule.rawValue, + "messageLog": messageLog.rawValue, + "unstoredDoses": unstoredDoses.map { $0.rawValue }, + ] + + if let podState = podState { + value["podState"] = podState.rawValue + } + + if let expirationReminderDate = expirationReminderDate { + value["expirationReminderDate"] = expirationReminderDate + } + + if let rileyLinkConnectionManagerState = rileyLinkConnectionManagerState { + value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState.rawValue + } + + return value + } +} + +extension OmnipodPumpManagerState { + var hasActivePod: Bool { + return podState?.isActive == true + } + + var isPumpDataStale: Bool { + let pumpStatusAgeTolerance = TimeInterval(minutes: 6) + let pumpDataAge = -(self.lastPumpDataReportDate ?? .distantPast).timeIntervalSinceNow + return pumpDataAge > pumpStatusAgeTolerance + } +} + + +extension OmnipodPumpManagerState: CustomDebugStringConvertible { + public var debugDescription: String { + return [ + "## OmnipodPumpManagerState", + "* timeZone: \(timeZone)", + "* basalSchedule: \(String(describing: basalSchedule))", + "* expirationReminderDate: \(String(describing: expirationReminderDate))", + "* unstoredDoses: \(String(describing: unstoredDoses))", + "* suspendTransition: \(String(describing: suspendTransition))", + "* bolusTransition: \(String(describing: bolusTransition))", + "* lastPumpDataReportDate: \(String(describing: lastPumpDataReportDate))", + "* isPumpDataStale: \(String(describing: isPumpDataStale))", + String(reflecting: podState), + String(reflecting: rileyLinkConnectionManagerState), + String(reflecting: messageLog), + ].joined(separator: "\n") + } +} diff --git a/OmniKit/PumpManager/PodComms.swift b/OmniKit/PumpManager/PodComms.swift new file mode 100644 index 000000000..4f4668579 --- /dev/null +++ b/OmniKit/PumpManager/PodComms.swift @@ -0,0 +1,329 @@ +// +// PodComms.swift +// OmniKit +// +// Created by Pete Schwamb on 10/7/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation +import RileyLinkBLEKit +import LoopKit +import os.log + +protocol PodCommsDelegate: class { + func podComms(_ podComms: PodComms, didChange podState: PodState) +} + +class PodComms: CustomDebugStringConvertible { + + private let configuredDevices: Locked> = Locked(Set()) + + weak var delegate: PodCommsDelegate? + + weak var messageLogger: MessageLogger? + + public let log = OSLog(category: "PodComms") + + // Only valid to access on the session serial queue + private var podState: PodState? { + didSet { + if let newValue = podState, newValue != oldValue { + log.debug("Notifying delegate of new podState: %{public}@", String(reflecting: newValue)) + delegate?.podComms(self, didChange: newValue) + } + } + } + + init(podState: PodState?) { + self.podState = podState + self.delegate = nil + self.messageLogger = nil + } + + private func assignAddress(commandSession: CommandSession) throws -> PodState { + commandSession.assertOnSessionQueue() + + // Testing + //try sendPacket(session: commandSession) + + let messageTransportState = MessageTransportState(packetNumber: 0, messageNumber: 0) + + // Create random address with 20 bits. Can we use all 24 bits? + let address = 0x1f000000 | (arc4random() & 0x000fffff) + + let transport = PodMessageTransport(session: commandSession, address: 0xffffffff, ackAddress: address, state: messageTransportState) + transport.messageLogger = messageLogger + + // Assign Address + let assignAddress = AssignAddressCommand(address: address) + + let message = Message(address: 0xffffffff, messageBlocks: [assignAddress], sequenceNum: transport.messageNumber) + + let response = try transport.sendMessage(message) + + if let fault = response.fault { + self.log.error("Pod Fault: %@", String(describing: fault)) + throw PodCommsError.podFault(fault: fault) + } + + guard let config = response.messageBlocks[0] as? VersionResponse else { + let responseType = response.messageBlocks[0].blockType + throw PodCommsError.unexpectedResponse(response: responseType) + } + + // Pairing state should be addressAssigned + return PodState( + address: address, + piVersion: String(describing: config.piVersion), + pmVersion: String(describing: config.pmVersion), + lot: config.lot, + tid: config.tid + ) + } + + private func configurePod(podState: PodState, timeZone: TimeZone, commandSession: CommandSession) throws { + commandSession.assertOnSessionQueue() + + let transport = PodMessageTransport(session: commandSession, address: 0xffffffff, ackAddress: podState.address, state: podState.messageTransportState) + transport.messageLogger = messageLogger + + let dateComponents = ConfigurePodCommand.dateComponents(date: Date(), timeZone: timeZone) + let setupPod = ConfigurePodCommand(address: podState.address, dateComponents: dateComponents, lot: podState.lot, tid: podState.tid) + + let message = Message(address: 0xffffffff, messageBlocks: [setupPod], sequenceNum: transport.messageNumber) + + let response: Message + do { + response = try transport.sendMessage(message) + } catch let error { + if case PodCommsError.podAckedInsteadOfReturningResponse = error { + // Pod already configured... + self.podState?.setupProgress = .podConfigured + return + } + throw error + } + + if let fault = response.fault { + self.log.error("Pod Fault: %@", String(describing: fault)) + throw PodCommsError.podFault(fault: fault) + } + + guard let config = response.messageBlocks[0] as? VersionResponse else { + let responseType = response.messageBlocks[0].blockType + throw PodCommsError.unexpectedResponse(response: responseType) + } + + self.podState?.setupProgress = .podConfigured + + guard config.setupState == .paired else { + throw PodCommsError.invalidData + } + } + + func pair(using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, timeZone: TimeZone, messageLogger: MessageLogger?, _ block: @escaping (_ result: SessionRunResult) -> Void) + { + deviceSelector { (device) in + guard let device = device else { + block(.failure(PodCommsError.noRileyLinkAvailable)) + return + } + + device.runSession(withName: "Pair Pod") { (commandSession) in + do { + self.configureDevice(device, with: commandSession) + + if self.podState == nil { + self.podState = try self.assignAddress(commandSession: commandSession) + } + + guard self.podState != nil else { + block(.failure(PodCommsError.noPodPaired)) + return + } + + try self.configurePod(podState: self.podState!, timeZone: timeZone, commandSession: commandSession) + + // Run a session now for any post-pairing commands + let transport = PodMessageTransport(session: commandSession, address: self.podState!.address, state: self.podState!.messageTransportState) + transport.messageLogger = self.messageLogger + let podSession = PodCommsSession(podState: self.podState!, transport: transport, delegate: self) + + block(.success(session: podSession)) + } catch let error as PodCommsError { + block(.failure(error)) + } catch { + block(.failure(PodCommsError.commsError(error: error))) + } + } + } + } + + enum SessionRunResult { + case success(session: PodCommsSession) + case failure(PodCommsError) + } + + func runSession(withName name: String, using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, _ block: @escaping (_ result: SessionRunResult) -> Void) { + + deviceSelector { (device) in + guard let device = device else { + block(.failure(PodCommsError.noRileyLinkAvailable)) + return + } + + device.runSession(withName: name) { (commandSession) in + guard self.podState != nil else { + block(.failure(PodCommsError.noPodPaired)) + return + } + + self.configureDevice(device, with: commandSession) + let transport = PodMessageTransport(session: commandSession, address: self.podState!.address, state: self.podState!.messageTransportState) + transport.messageLogger = self.messageLogger + let podSession = PodCommsSession(podState: self.podState!, transport: transport, delegate: self) + block(.success(session: podSession)) + } + } + } + + // Must be called from within the RileyLinkDevice sessionQueue + private func configureDevice(_ device: RileyLinkDevice, with session: CommandSession) { + session.assertOnSessionQueue() + + guard !self.configuredDevices.value.contains(device) else { + return + } + + do { + log.debug("configureRadio (omnipod)") + _ = try session.configureRadio() + } catch let error { + log.error("configure Radio failed with error: %{public}@", String(describing: error)) + // Ignore the error and let the block run anyway + return + } + + NotificationCenter.default.post(name: .DeviceRadioConfigDidChange, object: device) + NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceRadioConfigDidChange, object: device) + NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceConnectionStateDidChange, object: device) + + log.debug("added device %{public}@ to configuredDevices", device.name ?? "unknown") + _ = configuredDevices.mutate { (value) in + value.insert(device) + } + } + + @objc private func deviceRadioConfigDidChange(_ note: Notification) { + guard let device = note.object as? RileyLinkDevice else { + return + } + log.debug("removing device %{public}@ from configuredDevices", device.name ?? "unknown") + + NotificationCenter.default.removeObserver(self, name: .DeviceRadioConfigDidChange, object: device) + NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device) + + _ = configuredDevices.mutate { (value) in + value.remove(device) + } + } + + // MARK: - CustomDebugStringConvertible + + var debugDescription: String { + return [ + "## PodComms", + "podState: \(String(reflecting: podState))", + "configuredDevices: \(configuredDevices.value.map { $0.peripheralIdentifier.uuidString })", + "delegate: \(String(describing: delegate != nil))", + "" + ].joined(separator: "\n") + } + +} + +extension PodComms: PodCommsSessionDelegate { + func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) { + podCommsSession.assertOnSessionQueue() + self.podState = state + } +} + + +private extension CommandSession { + + func configureRadio() throws { + + // SYNC1 |0xDF00|0x54|Sync Word, High Byte + // SYNC0 |0xDF01|0xC3|Sync Word, Low Byte + // PKTLEN |0xDF02|0x32|Packet Length + // PKTCTRL1 |0xDF03|0x24|Packet Automation Control + // PKTCTRL0 |0xDF04|0x00|Packet Automation Control + // FSCTRL1 |0xDF07|0x06|Frequency Synthesizer Control + // FREQ2 |0xDF09|0x12|Frequency Control Word, High Byte + // FREQ1 |0xDF0A|0x14|Frequency Control Word, Middle Byte + // FREQ0 |0xDF0B|0x5F|Frequency Control Word, Low Byte + // MDMCFG4 |0xDF0C|0xCA|Modem configuration + // MDMCFG3 |0xDF0D|0xBC|Modem Configuration + // MDMCFG2 |0xDF0E|0x0A|Modem Configuration + // MDMCFG1 |0xDF0F|0x13|Modem Configuration + // MDMCFG0 |0xDF10|0x11|Modem Configuration + // MCSM0 |0xDF14|0x18|Main Radio Control State Machine Configuration + // FOCCFG |0xDF15|0x17|Frequency Offset Compensation Configuration + // AGCCTRL1 |0xDF18|0x70|AGC Control + // FSCAL3 |0xDF1C|0xE9|Frequency Synthesizer Calibration + // FSCAL2 |0xDF1D|0x2A|Frequency Synthesizer Calibration + // FSCAL1 |0xDF1E|0x00|Frequency Synthesizer Calibration + // FSCAL0 |0xDF1F|0x1F|Frequency Synthesizer Calibration + // TEST1 |0xDF24|0x31|Various Test Settings + // TEST0 |0xDF25|0x09|Various Test Settings + // PA_TABLE0 |0xDF2E|0x60|PA Power Setting 0 + // VERSION |0xDF37|0x04|Chip ID[7:0] + + try setSoftwareEncoding(.manchester) + try setPreamble(0x6665) + try setBaseFrequency(Measurement(value: 433.91, unit: .megahertz)) + try updateRegister(.pktctrl1, value: 0x20) + try updateRegister(.pktctrl0, value: 0x00) + try updateRegister(.fsctrl1, value: 0x06) + try updateRegister(.mdmcfg4, value: 0xCA) + try updateRegister(.mdmcfg3, value: 0xBC) // 0xBB for next lower bitrate + try updateRegister(.mdmcfg2, value: 0x06) + try updateRegister(.mdmcfg1, value: 0x70) + try updateRegister(.mdmcfg0, value: 0x11) + try updateRegister(.deviatn, value: 0x44) + try updateRegister(.mcsm0, value: 0x18) + try updateRegister(.foccfg, value: 0x17) + try updateRegister(.fscal3, value: 0xE9) + try updateRegister(.fscal2, value: 0x2A) + try updateRegister(.fscal1, value: 0x00) + try updateRegister(.fscal0, value: 0x1F) + + try updateRegister(.test1, value: 0x31) + try updateRegister(.test0, value: 0x09) + try updateRegister(.paTable0, value: 0x84) + try updateRegister(.sync1, value: 0xA5) + try updateRegister(.sync0, value: 0x5A) + } + + // This is just a testing function for spoofing PDM packets, or other times when you need to generate a custom packet + private func sendPacket() throws { + let packetNumber = 19 + let messageNumber = 0x24 >> 2 + let address: UInt32 = 0x1f0b3554 + + let cmd = GetStatusCommand(podInfoType: .normal) + + let message = Message(address: address, messageBlocks: [cmd], sequenceNum: messageNumber) + + var dataRemaining = message.encoded() + + let sendPacket = Packet(address: address, packetType: .pdm, sequenceNum: packetNumber, data: dataRemaining) + dataRemaining = dataRemaining.subdata(in: sendPacket.data.count..]) { + self.init(entries: repeatingScheduleValues.map { BasalScheduleEntry(rate: $0.value, startTime: $0.startTime) }) + } +} diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift new file mode 100644 index 000000000..9f31619de --- /dev/null +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -0,0 +1,610 @@ +// +// PodCommsSession.swift +// OmniKit +// +// Created by Pete Schwamb on 10/13/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation +import RileyLinkBLEKit +import LoopKit +import os.log + +public enum PodCommsError: Error { + case noPodPaired + case invalidData + case noResponse + case emptyResponse + case podAckedInsteadOfReturningResponse + case unexpectedPacketType(packetType: PacketType) + case unexpectedResponse(response: MessageBlockType) + case unknownResponseType(rawType: UInt8) + case noRileyLinkAvailable + case unfinalizedBolus + case unfinalizedTempBasal + case nonceResyncFailed + case podSuspended + case podFault(fault: PodInfoFaultEvent) + case commsError(error: Error) +} + +extension PodCommsError: LocalizedError { + public var errorDescription: String? { + switch self { + case .noPodPaired: + return LocalizedString("No pod paired", comment: "Error message shown when no pod is paired") + case .invalidData: + return nil + case .noResponse: + return LocalizedString("No response from pod", comment: "Error message shown when no response from pod was received") + case .emptyResponse: + return LocalizedString("Empty response from pod", comment: "Error message shown when empty response from pod was received") + case .podAckedInsteadOfReturningResponse: + return nil + case .unexpectedPacketType: + return nil + case .unexpectedResponse: + return LocalizedString("Unexpected response from pod", comment: "Error message shown when empty response from pod was received") + case .unknownResponseType: + return nil + case .noRileyLinkAvailable: + return LocalizedString("No RileyLink available", comment: "Error message shown when no response from pod was received") + case .unfinalizedBolus: + return LocalizedString("Bolus in progress", comment: "Error message shown when operation could not be completed due to existing bolus in progress") + case .unfinalizedTempBasal: + return LocalizedString("Temp basal in progress", comment: "Error message shown when temp basal could not be set due to existing temp basal in progress") + case .nonceResyncFailed: + return nil + case .podSuspended: + return LocalizedString("Pod is suspended", comment: "Error message action could not be performed because pod is suspended") + case .podFault(let fault): + let faultDescription = String(describing: fault.currentStatus) + return String(format: LocalizedString("Pod Fault: %1$@", comment: "Format string for pod fault code"), faultDescription) + case .commsError: + return nil + } + } + + public var failureReason: String? { + switch self { + case .noPodPaired: + return nil + case .invalidData: + return nil + case .noResponse: + return nil + case .emptyResponse: + return nil + case .podAckedInsteadOfReturningResponse: + return nil + case .unexpectedPacketType: + return nil + case .unexpectedResponse: + return nil + case .unknownResponseType: + return nil + case .noRileyLinkAvailable: + return nil + case .unfinalizedBolus: + return nil + case .unfinalizedTempBasal: + return nil + case .nonceResyncFailed: + return nil + case .podSuspended: + return nil + case .podFault: + return nil + case .commsError: + return nil + } + } + + public var recoverySuggestion: String? { + switch self { + case .noPodPaired: + return nil + case .invalidData: + return nil + case .noResponse: + return LocalizedString("Please bring your pod closer to the RileyLink and try again", comment: "Recovery suggestion when no response is received from pod") + case .emptyResponse: + return nil + case .podAckedInsteadOfReturningResponse: + return nil + case .unexpectedPacketType: + return nil + case .unexpectedResponse: + return nil + case .unknownResponseType: + return nil + case .noRileyLinkAvailable: + return LocalizedString("Make sure your RileyLink is nearby and powered on", comment: "Recovery suggestion when no RileyLink is available") + case .unfinalizedBolus: + return LocalizedString("Wait for existing bolus to finish, or cancel bolus", comment: "Recovery suggestion when operation could not be completed due to existing bolus in progress") + case .unfinalizedTempBasal: + return LocalizedString("Wait for existing temp basal to finish, or suspend to cancel", comment: "Recovery suggestion when operation could not be completed due to existing temp basal in progress") + case .nonceResyncFailed: + return nil + case .podSuspended: + return nil + case .podFault: + return nil + case .commsError: + return nil + } + } +} + + + +public protocol PodCommsSessionDelegate: class { + func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) +} + +public class PodCommsSession { + + public let log = OSLog(category: "PodCommsSession") + + private var podState: PodState { + didSet { + assertOnSessionQueue() + delegate.podCommsSession(self, didChange: podState) + } + } + + private unowned let delegate: PodCommsSessionDelegate + private var transport: MessageTransport + + init(podState: PodState, transport: MessageTransport, delegate: PodCommsSessionDelegate) { + self.podState = podState + self.transport = transport + self.delegate = delegate + self.transport.delegate = self + } + + /// Performs a message exchange, handling nonce resync, pod faults + /// + /// - Parameters: + /// - messageBlocks: The message blocks to send + /// - expectFollowOnMessage: If true, the pod will expect another message within 4 minutes, or will alarm with an 0x33 (51) fault. + /// - Returns: The received message response + /// - Throws: + /// - PodCommsError.nonceResyncFailed + /// - PodCommsError.noResponse + /// - MessageError.invalidCrc + /// - RileyLinkDeviceError + func send(_ messageBlocks: [MessageBlock], expectFollowOnMessage: Bool = false) throws -> T { + + var triesRemaining = 2 // Retries only happen for nonce resync + + var blocksToSend = messageBlocks + + if blocksToSend.contains(where: { $0 as? NonceResyncableMessageBlock != nil }) { + podState.advanceToNextNonce() + } + + let messageNumber = transport.messageNumber + + var sentNonce: UInt32? + + + while (triesRemaining > 0) { + triesRemaining -= 1 + + if let nonceBlock = messageBlocks[0] as? NonceResyncableMessageBlock { + sentNonce = nonceBlock.nonce + } + + let message = Message(address: podState.address, messageBlocks: blocksToSend, sequenceNum: messageNumber, expectFollowOnMessage: expectFollowOnMessage) + + + let response = try transport.sendMessage(message) + + // Simulate fault + //let podInfoResponse = try PodInfoResponse(encodedData: Data(hexadecimalString: "0216020d0000000000ab6a038403ff03860000285708030d0000")!) + //let response = Message(address: podState.address, messageBlocks: [podInfoResponse], sequenceNum: message.sequenceNum) + + if let responseMessageBlock = response.messageBlocks[0] as? T { + log.info("POD Response: %@", String(describing: responseMessageBlock)) + return responseMessageBlock + } else { + let responseType = response.messageBlocks[0].blockType + + if responseType == .errorResponse, + let sentNonce = sentNonce, + let errorResponse = response.messageBlocks[0] as? ErrorResponse, + errorResponse.errorReponseType == .badNonce + { + podState.resyncNonce(syncWord: errorResponse.nonceSearchKey, sentNonce: sentNonce, messageSequenceNum: message.sequenceNum) + log.info("resyncNonce(syncWord: %02X, sentNonce: %04X, messageSequenceNum: %d) -> %04X", errorResponse.nonceSearchKey, sentNonce, message.sequenceNum, podState.currentNonce) + + blocksToSend = blocksToSend.map({ (block) -> MessageBlock in + if var resyncableBlock = block as? NonceResyncableMessageBlock { + resyncableBlock.nonce = podState.currentNonce + return resyncableBlock + } else { + return block + } + }) + podState.advanceToNextNonce() + } else if let fault = response.fault { + self.podState.fault = fault + log.error("Pod Fault: %@", String(describing: fault)) + let now = Date() + if fault.deliveryStatus == .suspended { + podState.unfinalizedTempBasal?.cancel(at: now) + podState.unfinalizedBolus?.cancel(at: now, withRemaining: fault.insulinNotDelivered) + } + + throw PodCommsError.podFault(fault: fault) + } + else { + log.error("Unexpected response: %@", String(describing: response.messageBlocks[0])) + throw PodCommsError.unexpectedResponse(response: responseType) + } + } + } + throw PodCommsError.nonceResyncFailed + } + + // Returns time at which prime is expected to finish. + public func prime() throws -> TimeInterval { + //4c00 00c8 0102 + + let primeDuration = TimeInterval(seconds: 55) + + // Skip following alerts if we've already done them before + if podState.setupProgress != .startingPrime { + + // The following will set Tab5[$16] to 0 during pairing, which disables $6x faults. + let _: StatusResponse = try send([FaultConfigCommand(nonce: podState.currentNonce, tab5Sub16: 0, tab5Sub17: 0)]) + + // Uncomment to get an audible pod alert for low reservoir +// let lowReservoirAlarm = PodAlert.lowReservoirAlarm(20) // Alarm at 20 units remaining +// let _ = try configureAlerts([lowReservoirAlarm]) + + let finishSetupReminder = PodAlert.finishSetupReminder + let _ = try configureAlerts([finishSetupReminder]) + } else { + // We started prime, but didn't get confirmation somehow, so check status + let status: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(status) + if status.podProgressStatus == .priming || status.podProgressStatus == .readyForBasalSchedule { + podState.setupProgress = .priming + return podState.primeFinishTime?.timeIntervalSinceNow ?? primeDuration + } + } + + // Mark 2.6U delivery for prime + + let primeFinishTime = Date() + primeDuration + podState.primeFinishTime = primeFinishTime + podState.setupProgress = .startingPrime + + let timeBetweenPulses = TimeInterval(seconds: 1) + let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: Pod.primeUnits, timeBetweenPulses: timeBetweenPulses) + let scheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) + let bolusExtraCommand = BolusExtraCommand(units: Pod.primeUnits, timeBetweenPulses: timeBetweenPulses) + let status: StatusResponse = try send([scheduleCommand, bolusExtraCommand]) + podState.updateFromStatusResponse(status) + podState.setupProgress = .priming + return primeFinishTime.timeIntervalSinceNow + } + + public func programInitialBasalSchedule(_ basalSchedule: BasalSchedule, scheduleOffset: TimeInterval) throws { + if podState.setupProgress == .settingInitialBasalSchedule { + // We started basal schedule programming, but didn't get confirmation somehow, so check status + let status: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(status) + if status.podProgressStatus == .readyForCannulaInsertion { + podState.setupProgress = .initialBasalScheduleSet + return + } + } else { + // Uncomment the following to get an audible expiration notice before the expiration advisory alert +// let timeUntilExpirationAlert = (podState.activatedAt + Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow - Pod.expirationAlertWindow).timeIntervalSinceNow +// let expirationAlert = PodAlert.expirationAlert(timeUntilExpirationAlert) +// let _ = try configureAlerts([expirationAlert]) + } + + podState.setupProgress = .settingInitialBasalSchedule + // Set basal schedule + let _ = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(0)) + podState.setupProgress = .initialBasalScheduleSet + podState.finalizedDoses.append(UnfinalizedDose(resumeStartTime: Date(), scheduledCertainty: .certain)) + } + + private func configureAlerts(_ alerts: [PodAlert]) throws -> StatusResponse { + let configurations = alerts.map { $0.configuration } + let configureAlerts = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations: configurations) + let status: StatusResponse = try send([configureAlerts]) + for alert in alerts { + podState.registerConfiguredAlert(slot: alert.configuration.slot, alert: alert) + } + podState.updateFromStatusResponse(status) + return status + } + + public func insertCannula() throws -> TimeInterval { + let insertionWait: TimeInterval = .seconds(10) + + guard let activatedAt = podState.activatedAt else { + throw PodCommsError.noPodPaired + } + + if podState.setupProgress == .startingInsertCannula || podState.setupProgress == .cannulaInserting { + // We started cannula insertion, but didn't get confirmation somehow, so check status + let status: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(status) + if status.podProgressStatus == .cannulaInserting { + podState.setupProgress = .cannulaInserting + return insertionWait// Not sure when it started, wait full time to be sure + } + if status.podProgressStatus.readyForDelivery { + podState.setupProgress = .completed + return TimeInterval(0) // Already done; no need to wait + } + } else { + // Configure Alerts + let endOfServiceTime = activatedAt + Pod.serviceDuration + let timeUntilExpirationAdvisory = (endOfServiceTime - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow).timeIntervalSinceNow + let expirationAdvisoryAlarm = PodAlert.expirationAdvisoryAlarm(alarmTime: timeUntilExpirationAdvisory, duration: Pod.expirationAdvisoryWindow) + let shutdownImminentAlarm = PodAlert.shutdownImminentAlarm((endOfServiceTime - Pod.endOfServiceImminentWindow).timeIntervalSinceNow) + let autoOffAlarm = PodAlert.autoOffAlarm(active: false, countdownDuration: 0) // Turn Auto-off feature off + let _ = try configureAlerts([expirationAdvisoryAlarm, shutdownImminentAlarm, autoOffAlarm]) + } + + // Insert Cannula + // 1a0e7e30bf16020065010050000a000a + let insertionBolusAmount = 0.5 + let timeBetweenPulses = TimeInterval(seconds: 1) + let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: insertionBolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) + + // 17 0d 00 0064 0001 86a0000000000000 + podState.setupProgress = .startingInsertCannula + let bolusExtraCommand = BolusExtraCommand(units: insertionBolusAmount, timeBetweenPulses: timeBetweenPulses) + let status2: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) + podState.updateFromStatusResponse(status2) + + podState.setupProgress = .cannulaInserting + return insertionWait + } + + public func checkInsertionCompleted() throws { + if podState.setupProgress == .cannulaInserting { + let response: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(response) + if response.podProgressStatus.readyForDelivery { + podState.setupProgress = .completed + } + } + } + + // Throws SetBolusError + public enum DeliveryCommandResult { + case success(statusResponse: StatusResponse) + case certainFailure(error: PodCommsError) + case uncertainFailure(error: PodCommsError) + } + + public enum CancelDeliveryResult { + case success(statusResponse: StatusResponse, canceledBolus: UnfinalizedDose?) + case certainFailure(error: PodCommsError) + case uncertainFailure(error: PodCommsError) + } + + + public func bolus(units: Double) -> DeliveryCommandResult { + + let timeBetweenPulses = TimeInterval(seconds: 2) + let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: units, timeBetweenPulses: timeBetweenPulses) + let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) + + guard podState.unfinalizedBolus == nil else { + return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) + } + + // 17 0d 00 0064 0001 86a0000000000000 + let bolusExtraCommand = BolusExtraCommand(units: units) + do { + // Between bluetooth and the radio and firmware, about 1.2s on average passes before we start tracking + let commsOffset = TimeInterval(seconds: -1.5) + let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) + podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date().addingTimeInterval(commsOffset), scheduledCertainty: .certain) + return DeliveryCommandResult.success(statusResponse: statusResponse) + } catch PodCommsError.nonceResyncFailed { + return DeliveryCommandResult.certainFailure(error: PodCommsError.nonceResyncFailed) + } catch let error { + podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), scheduledCertainty: .uncertain) + return DeliveryCommandResult.uncertainFailure(error: error as? PodCommsError ?? PodCommsError.commsError(error: error)) + } + } + + public func setTempBasal(rate: Double, duration: TimeInterval, acknowledgementBeep: Bool, completionBeep: Bool, programReminderInterval: TimeInterval) -> DeliveryCommandResult { + + let tempBasalCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, tempBasalRate: rate, duration: duration) + let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + + guard podState.unfinalizedBolus?.finished != false else { + return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) + } + + do { + let status: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) + podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: Date(), duration: duration, scheduledCertainty: .certain) + podState.updateFromStatusResponse(status) + return DeliveryCommandResult.success(statusResponse: status) + } catch PodCommsError.nonceResyncFailed { + return DeliveryCommandResult.certainFailure(error: PodCommsError.nonceResyncFailed) + } catch let error { + podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: Date(), duration: duration, scheduledCertainty: .uncertain) + return DeliveryCommandResult.uncertainFailure(error: error as? PodCommsError ?? PodCommsError.commsError(error: error)) + } + } + + public func cancelDelivery(deliveryType: CancelDeliveryCommand.DeliveryType, beepType: BeepType) -> CancelDeliveryResult { + + let cancelDelivery = CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: deliveryType, beepType: beepType) + + do { + let status: StatusResponse = try send([cancelDelivery]) + let now = Date() + if deliveryType.contains(.basal) { + podState.unfinalizedSuspend = UnfinalizedDose(suspendStartTime: now, scheduledCertainty: .certain) + } + + if let unfinalizedTempBasal = podState.unfinalizedTempBasal, + let finishTime = unfinalizedTempBasal.finishTime, + deliveryType.contains(.tempBasal), + finishTime.compare(now) == .orderedDescending + { + podState.unfinalizedTempBasal?.cancel(at: now) + log.info("Interrupted temp basal: %@", String(describing: unfinalizedTempBasal)) + } + + var canceledBolus: UnfinalizedDose? = nil + + if let unfinalizedBolus = podState.unfinalizedBolus, + let finishTime = unfinalizedBolus.finishTime, + deliveryType.contains(.bolus), + finishTime.compare(now) == .orderedDescending + { + podState.unfinalizedBolus?.cancel(at: now, withRemaining: status.insulinNotDelivered) + canceledBolus = podState.unfinalizedBolus + log.info("Interrupted bolus: %@", String(describing: canceledBolus)) + } + + podState.updateFromStatusResponse(status) + + return CancelDeliveryResult.success(statusResponse: status, canceledBolus: canceledBolus) + + } catch PodCommsError.nonceResyncFailed { + return CancelDeliveryResult.certainFailure(error: PodCommsError.nonceResyncFailed) + } catch let error { + podState.unfinalizedSuspend = UnfinalizedDose(suspendStartTime: Date(), scheduledCertainty: .uncertain) + return CancelDeliveryResult.uncertainFailure(error: error as? PodCommsError ?? PodCommsError.commsError(error: error)) + } + } + + public func testingCommands() throws { + let _ = try getStatus() + // uncomment the next line to enable pod check alarms + // let _ = try checkAlarms() + } + + public func setTime(timeZone: TimeZone, basalSchedule: BasalSchedule, date: Date) throws -> StatusResponse { + let result = cancelDelivery(deliveryType: .all, beepType: .noBeep) + switch result { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + case .success: + let scheduleOffset = timeZone.scheduleOffset(forDate: date) + let status = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(0)) + return status + } + } + + public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool, completionBeep: Bool, programReminderInterval: TimeInterval) throws -> StatusResponse { + + let basalScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, basalSchedule: schedule, scheduleOffset: scheduleOffset) + let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + + do { + let status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) + podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: Date(), scheduledCertainty: .certain) + podState.updateFromStatusResponse(status) + return status + } catch PodCommsError.nonceResyncFailed { + throw PodCommsError.nonceResyncFailed + } catch let error { + podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: Date(), scheduledCertainty: .uncertain) + throw error + } + } + + public func resumeBasal(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { + + let status = try setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + + return status + } + + public func getStatus() throws -> StatusResponse { + let response: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(response) + return response + } + + public func checkAlarms() throws -> StatusResponse { + var response: StatusResponse + + response = try send([BeepConfigCommand(beepType: .bipBeepBipBeepBipBeepBipBeep)]) + podState.updateFromStatusResponse(response) + // Could use .fiveSecondBeep for PDM style "Check alarms", but this can only be successfully used if pod is suspended + response = try send([BeepConfigCommand(beepType: .beeeeeep)]) + podState.updateFromStatusResponse(response) + return response + } + + public func deactivatePod() throws { + + if podState.fault == nil && !podState.suspended { + let result = cancelDelivery(deliveryType: .all, beepType: .noBeep) + switch result { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + default: + break + } + } + + let deactivatePod = DeactivatePodCommand(nonce: podState.currentNonce) + + do { + let _: StatusResponse = try send([deactivatePod]) + } catch let error as PodCommsError { + switch error { + case .podFault, .unexpectedResponse: + break + default: + throw error + } + } + } + + public func acknowledgeAlerts(alerts: AlertSet) throws -> [AlertSlot: PodAlert] { + let cmd = AcknowledgeAlertCommand(nonce: podState.currentNonce, alerts: alerts) + let status: StatusResponse = try send([cmd]) + podState.updateFromStatusResponse(status) + return podState.activeAlerts + } + + func dosesForStorage(_ storageHandler: ([UnfinalizedDose]) -> Bool) { + assertOnSessionQueue() + + let dosesToStore = podState.dosesToStore + + if storageHandler(dosesToStore) { + log.info("Stored %@", String(describing: dosesToStore)) + self.podState.finalizedDoses.removeAll() + } + } + + public func assertOnSessionQueue() { + transport.assertOnSessionQueue() + } +} + +extension PodCommsSession: MessageTransportDelegate { + func messageTransport(_ messageTransport: MessageTransport, didUpdate state: MessageTransportState) { + messageTransport.assertOnSessionQueue() + podState.messageTransportState = state + } +} diff --git a/OmniKit/PumpManager/PodDoseProgressEstimator.swift b/OmniKit/PumpManager/PodDoseProgressEstimator.swift new file mode 100644 index 000000000..c994a9b45 --- /dev/null +++ b/OmniKit/PumpManager/PodDoseProgressEstimator.swift @@ -0,0 +1,47 @@ +// +// PodDoseProgressEstimator.swift +// OmniKit +// +// Created by Pete Schwamb on 3/12/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +class PodDoseProgressEstimator: DoseProgressTimerEstimator { + + let dose: DoseEntry + + weak var pumpManager: PumpManager? + + override var progress: DoseProgress { + let elapsed = -dose.startDate.timeIntervalSinceNow + let duration = dose.endDate.timeIntervalSince(dose.startDate) + let percentComplete = min(elapsed / duration, 1) + let delivered = pumpManager?.roundToSupportedBolusVolume(units: percentComplete * dose.units) ?? dose.units + return DoseProgress(deliveredUnits: delivered, percentComplete: percentComplete) + } + + init(dose: DoseEntry, pumpManager: PumpManager, reportingQueue: DispatchQueue) { + self.dose = dose + self.pumpManager = pumpManager + super.init(reportingQueue: reportingQueue) + } + + override func timerParameters() -> (delay: TimeInterval, repeating: TimeInterval) { + let timeSinceStart = dose.startDate.timeIntervalSinceNow + let timeBetweenPulses: TimeInterval + switch dose.type { + case .bolus: + timeBetweenPulses = Pod.pulseSize / Pod.bolusDeliveryRate + case .basal, .tempBasal: + timeBetweenPulses = Pod.pulseSize / dose.unitsPerHour + default: + fatalError("Can only estimate progress on basal rates or boluses.") + } + let delayUntilNextPulse = timeBetweenPulses - timeSinceStart.remainder(dividingBy: timeBetweenPulses) + + return (delay: delayUntilNextPulse, repeating: timeBetweenPulses) + } +} diff --git a/OmniKit/PumpManager/PodInsulinMeasurements.swift b/OmniKit/PumpManager/PodInsulinMeasurements.swift new file mode 100644 index 000000000..5bc071904 --- /dev/null +++ b/OmniKit/PumpManager/PodInsulinMeasurements.swift @@ -0,0 +1,51 @@ +// +// PodInsulinMeasurements.swift +// OmniKit +// +// Created by Pete Schwamb on 9/5/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public struct PodInsulinMeasurements: RawRepresentable, Equatable { + public typealias RawValue = [String: Any] + + public let validTime: Date + public let delivered: Double + public let reservoirVolume: Double? + + public init(statusResponse: StatusResponse, validTime: Date) { + self.validTime = validTime + self.delivered = statusResponse.insulin - Pod.primeUnits + self.reservoirVolume = statusResponse.reservoirLevel + } + + // RawRepresentable + public init?(rawValue: RawValue) { + guard + let validTime = rawValue["validTime"] as? Date, + let delivered = rawValue["delivered"] as? Double + else { + return nil + } + self.validTime = validTime + self.delivered = delivered + self.reservoirVolume = rawValue["reservoirVolume"] as? Double + } + + public var rawValue: RawValue { + var rawValue: RawValue = [ + "validTime": validTime, + "delivered": delivered + ] + + if let reservoirVolume = reservoirVolume { + rawValue["reservoirVolume"] = reservoirVolume + } + + return rawValue + } + +} + diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift new file mode 100644 index 000000000..a12f72812 --- /dev/null +++ b/OmniKit/PumpManager/PodState.swift @@ -0,0 +1,494 @@ +// +// PodState.swift +// OmniKit +// +// Created by Pete Schwamb on 10/13/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import Foundation + +public enum SetupProgress: Int { + case addressAssigned = 0 + case podConfigured + case startingPrime + case priming + case settingInitialBasalSchedule + case initialBasalScheduleSet + case startingInsertCannula + case cannulaInserting + case completed + + public var primingNeeded: Bool { + return self.rawValue < SetupProgress.priming.rawValue + } + + public var needsInitialBasalSchedule: Bool { + return self.rawValue < SetupProgress.initialBasalScheduleSet.rawValue + } + + public var needsCannulaInsertion: Bool { + return self.rawValue < SetupProgress.completed.rawValue + } +} + +// TODO: Mutating functions aren't guaranteed to synchronize read/write calls. +// mutating funcs should be moved to something like this: +// extension Locked where T == PodState { +// } +public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertible { + + public typealias RawValue = [String: Any] + + public let address: UInt32 + fileprivate var nonceState: NonceState + public var activatedAt: Date? + + public var expiresAt: Date? { + return activatedAt?.addingTimeInterval(Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) + } + + public let piVersion: String + public let pmVersion: String + public let lot: UInt32 + public let tid: UInt32 + var activeAlertSlots: AlertSet + public var lastInsulinMeasurements: PodInsulinMeasurements? + + public var unfinalizedBolus: UnfinalizedDose? + public var unfinalizedTempBasal: UnfinalizedDose? + public var unfinalizedSuspend: UnfinalizedDose? + public var unfinalizedResume: UnfinalizedDose? + + var finalizedDoses: [UnfinalizedDose] + + public var dosesToStore: [UnfinalizedDose] { + var dosesToStore = finalizedDoses + if let unfinalizedTempBasal = unfinalizedTempBasal { + dosesToStore.append(unfinalizedTempBasal) + } + if let unfinalizedSuspend = unfinalizedSuspend { + dosesToStore.append(unfinalizedSuspend) + } + return dosesToStore + } + + public private(set) var suspended: Bool + public var fault: PodInfoFaultEvent? + public var messageTransportState: MessageTransportState + public var primeFinishTime: Date? + public var setupProgress: SetupProgress + var configuredAlerts: [AlertSlot: PodAlert] + + public var activeAlerts: [AlertSlot: PodAlert] { + var active = [AlertSlot: PodAlert]() + for slot in activeAlertSlots { + if let alert = configuredAlerts[slot] { + active[slot] = alert + } + } + return active + } + + public init(address: UInt32, piVersion: String, pmVersion: String, lot: UInt32, tid: UInt32) { + self.address = address + self.nonceState = NonceState(lot: lot, tid: tid) + self.piVersion = piVersion + self.pmVersion = pmVersion + self.lot = lot + self.tid = tid + self.lastInsulinMeasurements = nil + self.finalizedDoses = [] + self.suspended = false + self.fault = nil + self.activeAlertSlots = .none + self.messageTransportState = MessageTransportState(packetNumber: 0, messageNumber: 0) + self.primeFinishTime = nil + self.setupProgress = .addressAssigned + self.configuredAlerts = [.slot7: .waitingForPairingReminder] + } + + public var unfinishedPairing: Bool { + return setupProgress != .completed + } + + public var readyForCannulaInsertion: Bool { + guard let primeFinishTime = self.primeFinishTime else { + return false + } + return !setupProgress.primingNeeded && primeFinishTime.timeIntervalSinceNow < 0 + } + + public var isActive: Bool { + return setupProgress == .completed && fault == nil + } + + public mutating func advanceToNextNonce() { + nonceState.advanceToNextNonce() + } + + public var currentNonce: UInt32 { + return nonceState.currentNonce + } + + public mutating func resyncNonce(syncWord: UInt16, sentNonce: UInt32, messageSequenceNum: Int) { + let sum = (sentNonce & 0xffff) + UInt32(crc16Table[messageSequenceNum]) + (lot & 0xffff) + (tid & 0xffff) + let seed = UInt16(sum & 0xffff) ^ syncWord + nonceState = NonceState(lot: lot, tid: tid, seed: seed) + } + + public mutating func updateFromStatusResponse(_ response: StatusResponse) { + if activatedAt == nil { + self.activatedAt = Date() - response.timeActive + } + updateDeliveryStatus(deliveryStatus: response.deliveryStatus) + lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: response, validTime: Date()) + activeAlertSlots = response.alerts + } + + public mutating func registerConfiguredAlert(slot: AlertSlot, alert: PodAlert) { + configuredAlerts[slot] = alert + } + + public mutating func finalizeFinishedDoses() { + if let bolus = unfinalizedBolus, bolus.finished { + finalizedDoses.append(bolus) + unfinalizedBolus = nil + } + + if let tempBasal = unfinalizedTempBasal, tempBasal.finished { + finalizedDoses.append(tempBasal) + unfinalizedTempBasal = nil + } + } + + private mutating func updateDeliveryStatus(deliveryStatus: StatusResponse.DeliveryStatus) { + finalizeFinishedDoses() + + if let bolus = unfinalizedBolus, bolus.scheduledCertainty == .uncertain { + if deliveryStatus.bolusing { + // Bolus did schedule + unfinalizedBolus?.scheduledCertainty = .certain + } else { + // Bolus didn't happen + unfinalizedBolus = nil + } + } + + if let tempBasal = unfinalizedTempBasal, tempBasal.scheduledCertainty == .uncertain { + if deliveryStatus.tempBasalRunning { + // Temp basal did schedule + unfinalizedTempBasal?.scheduledCertainty = .certain + } else { + // Temp basal didn't happen + unfinalizedTempBasal = nil + } + } + + if let resume = unfinalizedResume, resume.scheduledCertainty == .uncertain { + if deliveryStatus != .suspended { + // Resume was enacted + unfinalizedResume?.scheduledCertainty = .certain + } else { + // Resume wasn't enacted + unfinalizedResume = nil + } + } + + if let suspend = unfinalizedSuspend { + if suspend.scheduledCertainty == .uncertain { + if deliveryStatus == .suspended { + // Suspend was enacted + unfinalizedSuspend?.scheduledCertainty = .certain + } else { + // Suspend wasn't enacted + unfinalizedSuspend = nil + } + } + + if let resume = unfinalizedResume, suspend.startTime < resume.startTime { + finalizedDoses.append(suspend) + finalizedDoses.append(resume) + unfinalizedSuspend = nil + unfinalizedResume = nil + } + } + + suspended = deliveryStatus == .suspended + } + + // MARK: - RawRepresentable + public init?(rawValue: RawValue) { + + guard + let address = rawValue["address"] as? UInt32, + let nonceStateRaw = rawValue["nonceState"] as? NonceState.RawValue, + let nonceState = NonceState(rawValue: nonceStateRaw), + let piVersion = rawValue["piVersion"] as? String, + let pmVersion = rawValue["pmVersion"] as? String, + let lot = rawValue["lot"] as? UInt32, + let tid = rawValue["tid"] as? UInt32 + else { + return nil + } + + self.address = address + self.nonceState = nonceState + self.piVersion = piVersion + self.pmVersion = pmVersion + self.lot = lot + self.tid = tid + + + if let activatedAt = rawValue["activatedAt"] as? Date { + self.activatedAt = activatedAt + } + + if let suspended = rawValue["suspended"] as? Bool { + self.suspended = suspended + } else { + self.suspended = false + } + + if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue, + let unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus) + { + self.unfinalizedBolus = unfinalizedBolus + } else { + self.unfinalizedBolus = nil + } + + if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue, + let unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) + { + self.unfinalizedTempBasal = unfinalizedTempBasal + } else { + self.unfinalizedTempBasal = nil + } + + if let rawUnfinalizedSuspend = rawValue["unfinalizedSuspend"] as? UnfinalizedDose.RawValue, + let unfinalizedSuspend = UnfinalizedDose(rawValue: rawUnfinalizedSuspend) + { + self.unfinalizedSuspend = unfinalizedSuspend + } else { + self.unfinalizedSuspend = nil + } + + if let rawUnfinalizedResume = rawValue["unfinalizedResume"] as? UnfinalizedDose.RawValue, + let unfinalizedResume = UnfinalizedDose(rawValue: rawUnfinalizedResume) + { + self.unfinalizedResume = unfinalizedResume + } else { + self.unfinalizedResume = nil + } + + if let rawLastInsulinMeasurements = rawValue["lastInsulinMeasurements"] as? PodInsulinMeasurements.RawValue { + self.lastInsulinMeasurements = PodInsulinMeasurements(rawValue: rawLastInsulinMeasurements) + } else { + self.lastInsulinMeasurements = nil + } + + if let rawFinalizedDoses = rawValue["finalizedDoses"] as? [UnfinalizedDose.RawValue] { + self.finalizedDoses = rawFinalizedDoses.compactMap( { UnfinalizedDose(rawValue: $0) } ) + } else { + self.finalizedDoses = [] + } + + if let rawFault = rawValue["fault"] as? PodInfoFaultEvent.RawValue { + self.fault = PodInfoFaultEvent(rawValue: rawFault) + } else { + self.fault = nil + } + + if let alarmsRawValue = rawValue["alerts"] as? UInt8 { + self.activeAlertSlots = AlertSet(rawValue: alarmsRawValue) + } else { + self.activeAlertSlots = .none + } + + if let setupProgressRaw = rawValue["setupProgress"] as? Int, + let setupProgress = SetupProgress(rawValue: setupProgressRaw) + { + self.setupProgress = setupProgress + } else { + // Migrate + self.setupProgress = .completed + } + + if let messageTransportStateRaw = rawValue["messageTransportState"] as? MessageTransportState.RawValue, + let messageTransportState = MessageTransportState(rawValue: messageTransportStateRaw) + { + self.messageTransportState = messageTransportState + } else { + self.messageTransportState = MessageTransportState(packetNumber: 0, messageNumber: 0) + } + + if let rawConfiguredAlerts = rawValue["configuredAlerts"] as? [String: PodAlert.RawValue] { + var configuredAlerts = [AlertSlot: PodAlert]() + for (rawSlot, rawAlert) in rawConfiguredAlerts { + if let slotNum = UInt8(rawSlot), let slot = AlertSlot(rawValue: slotNum), let alert = PodAlert(rawValue: rawAlert) { + configuredAlerts[slot] = alert + } + } + self.configuredAlerts = configuredAlerts + } else { + // Assume migration, and set up with alerts that are normally configured + self.configuredAlerts = [ + .slot2: .shutdownImminentAlarm(0), + .slot3: .expirationAlert(0), + .slot4: .lowReservoirAlarm(0), + .slot7: .expirationAdvisoryAlarm(alarmTime: 0, duration: 0) + ] + } + + self.primeFinishTime = rawValue["primeFinishTime"] as? Date + } + + public var rawValue: RawValue { + var rawValue: RawValue = [ + "address": address, + "nonceState": nonceState.rawValue, + "piVersion": piVersion, + "pmVersion": pmVersion, + "lot": lot, + "tid": tid, + "suspended": suspended, + "finalizedDoses": finalizedDoses.map( { $0.rawValue }), + "alerts": activeAlertSlots.rawValue, + "messageTransportState": messageTransportState.rawValue, + "setupProgress": setupProgress.rawValue + ] + + if let unfinalizedBolus = self.unfinalizedBolus { + rawValue["unfinalizedBolus"] = unfinalizedBolus.rawValue + } + + if let unfinalizedTempBasal = self.unfinalizedTempBasal { + rawValue["unfinalizedTempBasal"] = unfinalizedTempBasal.rawValue + } + + if let unfinalizedSuspend = self.unfinalizedSuspend { + rawValue["unfinalizedSuspend"] = unfinalizedSuspend.rawValue + } + + if let unfinalizedResume = self.unfinalizedResume { + rawValue["unfinalizedResume"] = unfinalizedResume.rawValue + } + + if let lastInsulinMeasurements = self.lastInsulinMeasurements { + rawValue["lastInsulinMeasurements"] = lastInsulinMeasurements.rawValue + } + + if let fault = self.fault { + rawValue["fault"] = fault.rawValue + } + + if let primeFinishTime = primeFinishTime { + rawValue["primeFinishTime"] = primeFinishTime + } + + if let activatedAt = activatedAt { + rawValue["activatedAt"] = activatedAt + } + + if configuredAlerts.count > 0 { + let rawConfiguredAlerts = Dictionary(uniqueKeysWithValues: + configuredAlerts.map { slot, alarm in (String(describing: slot.rawValue), alarm.rawValue) }) + rawValue["configuredAlerts"] = rawConfiguredAlerts + } + + return rawValue + } + + // MARK: - CustomDebugStringConvertible + + public var debugDescription: String { + return [ + "### PodState", + "* address: \(String(format: "%04X", address))", + "* activatedAt: \(String(reflecting: activatedAt))", + "* expiresAt: \(String(reflecting: expiresAt))", + "* piVersion: \(piVersion)", + "* pmVersion: \(pmVersion)", + "* lot: \(lot)", + "* tid: \(tid)", + "* suspended: \(suspended)", + "* unfinalizedBolus: \(String(describing: unfinalizedBolus))", + "* unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))", + "* unfinalizedSuspend: \(String(describing: unfinalizedSuspend))", + "* unfinalizedResume: \(String(describing: unfinalizedResume))", + "* finalizedDoses: \(String(describing: finalizedDoses))", + "* activeAlerts: \(String(describing: activeAlerts))", + "* messageTransportState: \(String(describing: messageTransportState))", + "* setupProgress: \(setupProgress)", + "* primeFinishTime: \(String(describing: primeFinishTime))", + "* configuredAlerts: \(String(describing: configuredAlerts))", + "", + fault != nil ? String(reflecting: fault!) : "fault: nil", + "", + ].joined(separator: "\n") + } +} + +fileprivate struct NonceState: RawRepresentable, Equatable { + public typealias RawValue = [String: Any] + + var table: [UInt32] + var idx: UInt8 + + public init(lot: UInt32 = 0, tid: UInt32 = 0, seed: UInt16 = 0) { + table = Array(repeating: UInt32(0), count: 2 + 16) + table[0] = (lot & 0xFFFF) &+ (lot >> 16) &+ 0x55543DC3 + table[1] = (tid & 0xFFFF) &+ (tid >> 16) &+ 0xAAAAE44E + + idx = 0 + + table[0] += UInt32((seed & 0x00ff)) + table[1] += UInt32((seed & 0xff00) >> 8) + + for i in 0..<16 { + table[2 + i] = generateEntry() + } + + idx = UInt8((table[0] + table[1]) & 0x0F) + } + + private mutating func generateEntry() -> UInt32 { + table[0] = (table[0] >> 16) &+ ((table[0] & 0xFFFF) &* 0x5D7F) + table[1] = (table[1] >> 16) &+ ((table[1] & 0xFFFF) &* 0x8CA0) + return table[1] &+ ((table[0] & 0xFFFF) << 16) + } + + public mutating func advanceToNextNonce() { + let nonce = currentNonce + table[Int(2 + idx)] = generateEntry() + idx = UInt8(nonce & 0x0F) + } + + public var currentNonce: UInt32 { + return table[Int(2 + idx)] + } + + // RawRepresentable + public init?(rawValue: RawValue) { + guard + let table = rawValue["table"] as? [UInt32], + let idx = rawValue["idx"] as? UInt8 + else { + return nil + } + self.table = table + self.idx = idx + } + + public var rawValue: RawValue { + let rawValue: RawValue = [ + "table": table, + "idx": idx, + ] + + return rawValue + } +} + + diff --git a/OmniKitPacketParser/main.swift b/OmniKitPacketParser/main.swift new file mode 100644 index 000000000..f5cc84dcf --- /dev/null +++ b/OmniKitPacketParser/main.swift @@ -0,0 +1,260 @@ +// +// main.swift +// OmniKitPacketParser +// +// Created by Pete Schwamb on 12/19/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +let printRepeats = true + +enum ParsingError: Error { + case invalidPacketType(str: String) +} + +extension PacketType { + init(rtlomniString: String) throws { + switch rtlomniString { + case "PTYPE:POD": + self = .pod + case "PTYPE:PDM": + self = .pdm + case "PTYPE:CON": + self = .con + case "PTYPE:ACK": + self = .ack + default: + throw ParsingError.invalidPacketType(str: rtlomniString) + } + } +} + +extension String { + func valPart() -> String { + return String(split(separator: ":")[1]) + } +} + +extension Int { + func nextPacketNumber(_ increment: Int) -> Int { + return (self + increment) & 0b11111 + } +} + +//from NSHipster - http://nshipster.com/swift-literal-convertible/ +struct Regex { + let pattern: String + let options: NSRegularExpression.Options! + + private var matcher: NSRegularExpression { + return try! NSRegularExpression(pattern: self.pattern, options: self.options) + } + + init(_ pattern: String, options: NSRegularExpression.Options = []) { + self.pattern = pattern + self.options = options + } + + func match(string: String, options: NSRegularExpression.MatchingOptions = []) -> Bool { + return self.matcher.numberOfMatches(in: string, options: options, range: NSMakeRange(0, string.count)) != 0 + } +} + +protocol RegularExpressionMatchable { + func match(regex: Regex) -> Bool +} + +extension String: RegularExpressionMatchable { + func match(regex: Regex) -> Bool { + return regex.match(string: self) + } +} + +func ~=(pattern: Regex, matchable: T) -> Bool { + return matchable.match(regex: pattern) +} + + +class LoopIssueReportParser { + // * 2018-12-27 01:46:56 +0000 send 1f0e41a6101f1a0e81ed50b102010a0101a000340034170d000208000186a00000000000000111 + func parseLine(_ line: String) { + let components = line.components(separatedBy: .whitespaces) + if components.count == 6, let data = Data(hexadecimalString: components[5]), let message = try? Message(encodedData: data) { + let direction = components[4].padding(toLength: 7, withPad: " ", startingAt: 0) + let date = components[1..<4].joined(separator: " ") + print("\(date) \(direction) \(message)") + } + } +} + +class RTLOmniLineParser { + private var lastPacket: ArraySlice? = nil + private var messageDate: String = "" + private var lastMessageData = Data() + private var messageData = Data() + private var messageSource: PacketType = .pdm + private var address: String = "" + private var packetNumber: Int = 0 + private var repeatCount: Int = 0 + + func parseLine(_ line: String) { + let components = line.components(separatedBy: .whitespaces) + if components.count > 3, let packetType = try? PacketType(rtlomniString: components[2]) { + if lastPacket == components[1...] { + return + } + lastPacket = components[1...] + switch packetType { + case .pod, .pdm: + if components.count != 9 { + print("Invalid line:\(line)") + return + } + // 2018-12-19T20:50:48.3d ID1:1f0b3557 PTYPE:POD SEQ:31 ID2:1f0b3557 B9:00 BLEN:205 BODY:02cb510032602138800120478004213c80092045800c203980 CRC:a8 + // 2018-05-25T13:03:51.765792 ID1:ffffffff PTYPE:POD SEQ:01 ID2:ffffffff B9:04 BLEN:23 BODY:011502070002070002020000aa6400088cb98f1f16b11e82a5 CRC:72 + messageDate = components[0] + messageSource = packetType + address = String(components[1].valPart()) + packetNumber = Int(components[3].valPart())! + let messageAddress = String(components[4].valPart()) + let b9 = String(components[5].valPart()) + if messageData.count > 0 { + print("Dropping incomplete message data: \(messageData.hexadecimalString)") + } + messageData = Data(hexadecimalString: messageAddress + b9)! + let messageLen = UInt8(components[6].valPart())! + messageData.append(messageLen) + let packetData = Data(hexadecimalString: components[7].valPart())! + messageData.append(packetData) + case .con: + // 2018-12-19T05:19:04.3d ID1:1f0b3557 PTYPE:CON SEQ:12 CON:0000000000000126 CRC:60 + let packetAddress = String(components[1].valPart()) + let nextPacketNumber = Int(components[3].valPart())! + if (packetAddress == address) && (nextPacketNumber == packetNumber.nextPacketNumber(2)) { + packetNumber = nextPacketNumber + let packetData = Data(hexadecimalString: components[4].valPart())! + messageData.append(packetData) + } else if packetAddress != address { + print("mismatched address: \(line)") + } else if nextPacketNumber != packetNumber.nextPacketNumber(2) { + print("mismatched packet number: \(nextPacketNumber) != \(packetNumber.nextPacketNumber(2)) \(line)") + } + default: + break + } + do { + let message = try Message(encodedData: messageData) + let messageStr = "\(messageDate) \(messageSource) \(message)" + if lastMessageData == messageData { + repeatCount += 1 + if printRepeats { + print(messageStr + " repeat:\(repeatCount)") + } + } else { + lastMessageData = messageData + repeatCount = 0 + print(messageStr) + } + messageData = Data() + } catch MessageError.notEnoughData { + return + } catch let error { + print("Error decoding message: \(error)") + } + } + } +} + +class XcodeLogParser { + private var lastPacket: ArraySlice? = nil + private var messageDate: String = "" + private var lastMessageData = Data() + private var messageData = Data() + private var messageSource: PacketType = .pdm + private var address: String = "" + private var packetNumber: Int = 0 + private var repeatCount: Int = 0 + + func parseLine(_ line: String) { + let components = line.components(separatedBy: .whitespaces) + if let rlCmd = components.last { + let direction = components[5].prefix(4) + let timeStamp = "\(components[0]) \(components[1])" + + switch direction { + case "Send": + let cmdCode = rlCmd.prefix(4).suffix(2) + switch(cmdCode) { + case "05": // SendAndListen + let packetData = Data(hexadecimalString: String(rlCmd.suffix(rlCmd.count - 28)))! + do { + let packet = try Packet(encodedData: packetData) + print("\(timeStamp) \(direction) \(packet)") + } catch let error { + print("Error parsing \(rlCmd): \(error)") + } + default: + print("Unhandled command: \(direction) \(cmdCode) \(rlCmd)") + } + case "Recv": + let status = rlCmd.prefix(2) + switch(status) { + case "dd": + if rlCmd.count > 6 { + let packetData = Data(hexadecimalString: String(rlCmd.suffix(rlCmd.count - 6)))! + do { + let packet = try Packet(encodedData: packetData) + print("\(timeStamp) \(direction) success \(packet)") + } catch let error { + print("Error parsing \(rlCmd): \(error)") + } + } else { + print("\(timeStamp) \(direction) \(rlCmd)") + } + default: + print("Unhandled response type: \(direction) \(rlCmd)") + } + default: + break + } + } + } +} + + +for filename in CommandLine.arguments[1...] { + let rtlOmniParser = RTLOmniLineParser() + let loopIssueReportParser = LoopIssueReportParser() + let xcodeLogParser = XcodeLogParser() + print("Parsing \(filename)") + + do { + let data = try String(contentsOfFile: filename, encoding: .utf8) + let lines = data.components(separatedBy: .newlines) + + for line in lines { + switch line { + case Regex("ID1:[0-9a-fA-F]+ PTYPE:"): + // 2018-12-24T10:58:41.3d ID1:1f0f407e PTYPE:POD SEQ:02 ID2:1f0f407e B9:3c BLEN:24 BODY:0216020d0000000000d23102b103ff02b1000008ab08016e83 CRC:c2 + // 2018-05-25T13:03:51.765792 ID1:ffffffff PTYPE:POD SEQ:01 ID2:ffffffff B9:04 BLEN:23 BODY:011502070002070002020000aa6400088cb98f1f16b11e82a5 CRC:72 + rtlOmniParser.parseLine(line) + case Regex("(send|receive) [0-9a-fA-F]+"): + // 2018-12-27 01:46:56 +0000 send 1f0e41a6101f1a0e81ed50b102010a0101a000340034170d000208000186a00000000000000111 + loopIssueReportParser.parseLine(line) + case Regex("RL (Send|Recv) ?\\(single\\): [0-9a-fA-F]+"): +// 2019-02-09 08:23:27.605518-0800 Loop[2978:2294033] [PeripheralManager+RileyLink] RL Send (single): 17050005000000000002580000281f0c27a4591f0c27a447 +// 2019-02-09 08:23:28.262888-0800 Loop[2978:2294816] [PeripheralManager+RileyLink] RL Recv(single): dd0c2f1f079e674b1f079e6769 + xcodeLogParser.parseLine(line) + default: + break + } + + + } + } catch let error { + print("Error: \(error)") + } +} + diff --git a/OmniKitTests/AcknowledgeAlertsTests.swift b/OmniKitTests/AcknowledgeAlertsTests.swift new file mode 100644 index 000000000..6c1ba859e --- /dev/null +++ b/OmniKitTests/AcknowledgeAlertsTests.swift @@ -0,0 +1,30 @@ +// +// AcknowledgeAlertsTests.swift +// OmniKitTests +// +// Created by Eelke Jager on 18/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// +import Foundation + +import XCTest +@testable import OmniKit + +class AcknowledgeAlertsTests: XCTestCase { + func testAcknowledgeLowReservoirAlert() { + // 11 05 2f9b5b2f 10 + do { + // Encode + let encoded = AcknowledgeAlertCommand(nonce: 0x2f9b5b2f, alerts: AlertSet(rawValue: 0x10)) + XCTAssertEqual("11052f9b5b2f10", encoded.data.hexadecimalString) + + // Decode + let cmd = try AcknowledgeAlertCommand(encodedData: Data(hexadecimalString: "11052f9b5b2f10")!) + XCTAssertEqual(.acknowledgeAlert,cmd.blockType) + XCTAssertEqual(0x2f9b5b2f, cmd.nonce) + XCTAssert(cmd.alerts.contains(.slot4)) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } +} diff --git a/OmniKitTests/BasalScheduleTests.swift b/OmniKitTests/BasalScheduleTests.swift new file mode 100644 index 000000000..d91f9b733 --- /dev/null +++ b/OmniKitTests/BasalScheduleTests.swift @@ -0,0 +1,405 @@ +// +// BasalScheduleTests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 4/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import OmniKit + +class BasalScheduleTests: XCTestCase { + + func testBasalTableEntry() { + let entry = BasalTableEntry(segments: 2, pulses: 300, alternateSegmentPulse: false) + // $01 $2c $01 $2c = 1 + 44 + 1 + 44 = 90 = $5a + XCTAssertEqual(0x5a, entry.checksum()) + + let entry2 = BasalTableEntry(segments: 2, pulses: 260, alternateSegmentPulse: true) + // $01 $04 $01 $04 = 1 + 4 + 1 + 5 = 1 = $0b + XCTAssertEqual(0x0b, entry2.checksum()) + } + + func testSetBasalScheduleCommand() { + do { + // Decode 1a 12 77a05551 00 0062 2b 1708 0000 f800 f800 f800 + let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a1277a055510000622b17080000f800f800f800")!) + + XCTAssertEqual(0x77a05551, cmd.nonce) + if case SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table) = cmd.deliverySchedule { + XCTAssertEqual(0x2b, currentSegment) + XCTAssertEqual(737, secondsRemaining) + XCTAssertEqual(0, pulsesRemaining) + XCTAssertEqual(3, table.entries.count) + } else { + XCTFail("Expected ScheduleEntry.basalSchedule type") + } + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let scheduleEntry = BasalTableEntry(segments: 16, pulses: 0, alternateSegmentPulse: true) + let table = BasalDeliveryTable(entries: [scheduleEntry, scheduleEntry, scheduleEntry]) + let deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(currentSegment: 0x2b, secondsRemaining: 737, pulsesRemaining: 0, table: table) + let cmd = SetInsulinScheduleCommand(nonce: 0x77a05551, deliverySchedule: deliverySchedule) + XCTAssertEqual("1a1277a055510000622b17080000f800f800f800", cmd.data.hexadecimalString) + } + + func testBasalScheduleCommandFromSchedule() { + // Encode from schedule + let entry = BasalScheduleEntry(rate: 0.05, startTime: 0) + let schedule = BasalSchedule(entries: [entry]) + + let cmd = SetInsulinScheduleCommand(nonce: 0x01020304, basalSchedule: schedule, scheduleOffset: .hours(8.25)) + + XCTAssertEqual(0x01020304, cmd.nonce) + if case SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table) = cmd.deliverySchedule { + XCTAssertEqual(16, currentSegment) + XCTAssertEqual(UInt16(TimeInterval(minutes: 15)), secondsRemaining) + XCTAssertEqual(0, pulsesRemaining) + XCTAssertEqual(3, table.entries.count) + let tableEntry = table.entries[0] + XCTAssertEqual(true, tableEntry.alternateSegmentPulse) + XCTAssertEqual(0, tableEntry.pulses) + XCTAssertEqual(16, tableEntry.segments) + } else { + XCTFail("Expected ScheduleEntry.basalSchedule type") + } + // 1a LL NNNNNNNN 00 CCCC HH SSSS PPPP napp napp napp napp + // 1a 12 01020304 00 0065 10 1c20 0001 f800 f800 f800 + XCTAssertEqual("1a1201020304000064101c200000f800f800f800", cmd.data.hexadecimalString) + } + + + func testBasalScheduleExtraCommand() { + do { + // Decode 130e40 00 1aea 001e8480 3840005b8d80 + + let cmd = try BasalScheduleExtraCommand(encodedData: Data(hexadecimalString: "130e40001aea001e84803840005b8d80")!) + + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) + XCTAssertEqual(0, cmd.programReminderInterval) + XCTAssertEqual(0, cmd.currentEntryIndex) + XCTAssertEqual(689, cmd.remainingPulses) + XCTAssertEqual(TimeInterval(seconds: 20), cmd.delayUntilNextTenthOfPulse) + XCTAssertEqual(1, cmd.rateEntries.count) + let entry = cmd.rateEntries[0] + XCTAssertEqual(TimeInterval(seconds: 60), entry.delayBetweenPulses) + XCTAssertEqual(1440, entry.totalPulses) + XCTAssertEqual(3.0, entry.rate) + XCTAssertEqual(TimeInterval(hours: 24), entry.duration) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let rateEntries = RateEntry.makeEntries(rate: 3.0, duration: TimeInterval(hours: 24)) + let cmd = BasalScheduleExtraCommand(acknowledgementBeep: false, completionBeep: true, programReminderInterval: 0, currentEntryIndex: 0, remainingPulses: 689, delayUntilNextTenthOfPulse: TimeInterval(seconds: 20), rateEntries: rateEntries) + + + XCTAssertEqual("130e40001aea01312d003840005b8d80", cmd.data.hexadecimalString) + } + + func testBasalScheduleExtraCommandFromSchedule() { + // Encode from schedule + let entry = BasalScheduleEntry(rate: 0.05, startTime: 0) + let schedule = BasalSchedule(entries: [entry]) + + let cmd = BasalScheduleExtraCommand(schedule: schedule, scheduleOffset: .hours(8.25), acknowledgementBeep: false, completionBeep: true, programReminderInterval: 60) + + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) + XCTAssertEqual(60, cmd.programReminderInterval) + XCTAssertEqual(0, cmd.currentEntryIndex) + XCTAssertEqual(15.8, cmd.remainingPulses, accuracy: 0.01) + XCTAssertEqual(TimeInterval(minutes: 3), cmd.delayUntilNextTenthOfPulse) + XCTAssertEqual(1, cmd.rateEntries.count) + let rateEntry = cmd.rateEntries[0] + XCTAssertEqual(TimeInterval(minutes: 60), rateEntry.delayBetweenPulses) + XCTAssertEqual(24, rateEntry.totalPulses, accuracy: 0.001) + XCTAssertEqual(0.05, rateEntry.rate) + XCTAssertEqual(TimeInterval(hours: 24), rateEntry.duration, accuracy: 0.001) + } + + func testBasalExtraEncoding() { + // Encode + + let schedule = BasalSchedule(entries: [ + BasalScheduleEntry(rate: 1.05, startTime: 0), + BasalScheduleEntry(rate: 0.9, startTime: .hours(10.5)), + BasalScheduleEntry(rate: 1, startTime: .hours(18.5)) + ]) + + let hh = 0x2e + let ssss = 0x1be8 + let offset = TimeInterval(minutes: Double((hh + 1) * 30)) - TimeInterval(seconds: Double(ssss / 8)) + + // 1a LL NNNNNNNN 00 CCCC HH SSSS PPPP napp napp napp napp + // 1a 14 0d6612db 00 0310 2e 1be8 0005 f80a 480a f009 a00a + + let cmd1 = SetInsulinScheduleCommand(nonce: 0x0d6612db, basalSchedule: schedule, scheduleOffset: offset) + XCTAssertEqual("1a140d6612db0003102e1be80005f80a480af009a00a", cmd1.data.hexadecimalString) + + // 13 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ YYYY ZZZZZZZZ YYYY ZZZZZZZZ + // 13 1a 40 02 0096 00a7d8c0 089d 01059449 05a0 01312d00 044c 0112a880 * PDM + // 13 1a 40 02 0095 00a7d8c0 089d 01059449 05a0 01312d00 044c 0112a880 + let cmd2 = BasalScheduleExtraCommand(schedule: schedule, scheduleOffset: offset, acknowledgementBeep: false, completionBeep: true, programReminderInterval: 0) + XCTAssertEqual("131a4002009600a7d8c0089d0105944905a001312d00044c0112a880", cmd2.data.hexadecimalString) // PDM + } + + func checkBasalScheduleExtraCommandDataWithLessPrecision(_ data: Data, _ expected: Data, line: UInt = #line) { + // The XXXXXXXX field is in thousands of a millisecond. Since we use TimeIntervals (floating point) for + // recreating the offset, we can have small errors in reproducing the the encoded output, which we really + // don't care about. + + func extractXXXXXXXX(_ data: Data) -> TimeInterval { + return TimeInterval(Double(data[6...].toBigEndian(UInt32.self)) / 1000000.0) + } + + let xxxxxxxx1 = extractXXXXXXXX(data) + let xxxxxxxx2 = extractXXXXXXXX(expected) + XCTAssertEqual(xxxxxxxx1, xxxxxxxx2, accuracy: 0.01, line: line) + + func blurXXXXXXXX(_ inStr: String) -> String { + let start = inStr.index(inStr.startIndex, offsetBy:12) + let end = inStr.index(start, offsetBy:8) + return inStr.replacingCharacters(in: start.. 2 pulses + // NNNN = $001e = 30 (dec) / 10 -> 3 pulses + + + // Found in PDM logs: 1a0e243085c802002501002000020002 170d00001400030d40000000000000 + func testBolusAndBolusExtraMatch() { + let bolusAmount = 0.1 + + // 1a 0e NNNNNNNN 02 CCCC HH SSSS 0ppp 0ppp + // 1a 0e 243085c8 02 0025 01 0020 0002 0002 + let timeBetweenPulses = TimeInterval(seconds: 2) + let scheduleEntry = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: bolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusCommand = SetInsulinScheduleCommand(nonce: 0x243085c8, deliverySchedule: scheduleEntry) + XCTAssertEqual("1a0e243085c802002501002000020002", bolusCommand.data.hexadecimalString) + + // 17 LL RR NNNN XXXXXXXX + // 17 0d 00 0014 00030d40 000000000000 + let bolusExtraCommand = BolusExtraCommand(units: bolusAmount) + XCTAssertEqual("170d00001400030d40000000000000", bolusExtraCommand.data.hexadecimalString) + } + + func testBolusAndBolusExtraMatch2() { + let bolusAmount = 0.15 + let timeBetweenPulses = TimeInterval(seconds: 2) + let scheduleEntry = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: bolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusCommand = SetInsulinScheduleCommand(nonce: 0x243085c8, deliverySchedule: scheduleEntry) + XCTAssertEqual("1a0e243085c802003701003000030003", bolusCommand.data.hexadecimalString) + + let bolusExtraCommand = BolusExtraCommand(units: bolusAmount) + XCTAssertEqual("170d00001e00030d40000000000000", bolusExtraCommand.data.hexadecimalString) + } + + func testLargeBolus() { + let bolusAmount = 29.95 + let timeBetweenPulses = TimeInterval(seconds: 2) + let scheduleEntry = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: bolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusCommand = SetInsulinScheduleCommand(nonce: 0x31204ba7, deliverySchedule: scheduleEntry) + XCTAssertEqual("1a0e31204ba702014801257002570257", bolusCommand.data.hexadecimalString) + + let bolusExtraCommand = BolusExtraCommand(acknowledgementBeep: false, completionBeep: true, programReminderInterval: .hours(1), units: bolusAmount) + XCTAssertEqual("170d7c176600030d40000000000000", bolusExtraCommand.data.hexadecimalString) + } + + func testOddBolus() { + // 1a 0e NNNNNNNN 02 CCCC HH SSSS 0ppp 0ppp + // 1a 0e cf9e81ac 02 00e5 01 0290 0029 0029 + + let bolusAmount = 2.05 + let timeBetweenPulses = TimeInterval(seconds: 2) + let scheduleEntry = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: bolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusCommand = SetInsulinScheduleCommand(nonce: 0xcf9e81ac, deliverySchedule: scheduleEntry) + XCTAssertEqual("1a0ecf9e81ac0200e501029000290029", bolusCommand.data.hexadecimalString) + + // 17 LL RR NNNN XXXXXXXX + // 17 0d 3c 019a 00030d40 0000 00000000 + let bolusExtraCommand = BolusExtraCommand(acknowledgementBeep: false, completionBeep: false, programReminderInterval: .hours(1), units: bolusAmount) + XCTAssertEqual("170d3c019a00030d40000000000000", bolusExtraCommand.data.hexadecimalString) + } + + + func testCancelBolusCommand() { + do { + // Decode 1f 05 4d91f8ff 64 + let cmd = try CancelDeliveryCommand(encodedData: Data(hexadecimalString: "1f054d91f8ff64")!) + XCTAssertEqual(0x4d91f8ff, cmd.nonce) + XCTAssertEqual(.beeeeeep, cmd.beepType) + XCTAssertEqual(.bolus, cmd.deliveryType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = CancelDeliveryCommand(nonce: 0x4d91f8ff, deliveryType: .bolus, beepType: .beeeeeep) + XCTAssertEqual("1f054d91f8ff64", cmd.data.hexadecimalString) + } +} diff --git a/OmniKitTests/CRC16Tests.swift b/OmniKitTests/CRC16Tests.swift new file mode 100644 index 000000000..491f959e2 --- /dev/null +++ b/OmniKitTests/CRC16Tests.swift @@ -0,0 +1,21 @@ +// +// CRC16Tests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import OmniKit + +class CRC16Tests: XCTestCase { + + func testComputeCRC16() { + let input = Data(hexadecimalString: "1f01482a10030e0100")! + XCTAssertEqual(0x802c, input.crc16()) + } +} + + + diff --git a/OmniKitTests/CRC8Tests.swift b/OmniKitTests/CRC8Tests.swift new file mode 100644 index 000000000..cc4c3e009 --- /dev/null +++ b/OmniKitTests/CRC8Tests.swift @@ -0,0 +1,19 @@ +// +// CRC8Tests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import OmniKit + +class CRC8Tests: XCTestCase { + + func testComputeCRC8() { + let input = Data(hexadecimalString: "1f07b1eeae1f07b1ee181f1a0eeb5701b202010a0101a000340034170d000208000186a0")! + XCTAssertEqual(0x19, input.crc8()) + } +} + diff --git a/OmniKitTests/Info.plist b/OmniKitTests/Info.plist new file mode 100644 index 000000000..9366bbbb5 --- /dev/null +++ b/OmniKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 2.1.0 + CFBundleVersion + 1 + + diff --git a/OmniKitTests/MessageTests.swift b/OmniKitTests/MessageTests.swift new file mode 100644 index 000000000..763d5d296 --- /dev/null +++ b/OmniKitTests/MessageTests.swift @@ -0,0 +1,240 @@ +// +// MessageTests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import OmniKit + +class MessageTests: XCTestCase { + + func testMessageData() { + // 2016-06-26T20:33:28.412197 ID1:1f01482a PTYPE:PDM SEQ:13 ID2:1f01482a B9:10 BLEN:3 BODY:0e0100802c CRC:88 + + let msg = Message(address: 0x1f01482a, messageBlocks: [GetStatusCommand()], sequenceNum: 4) + + XCTAssertEqual("1f01482a10030e0100802c", msg.encoded().hexadecimalString) + } + + func testMessageDecoding() { + do { + let msg = try Message(encodedData: Data(hexadecimalString: "1f00ee84300a1d18003f1800004297ff8128")!) + + XCTAssertEqual(0x1f00ee84, msg.address) + XCTAssertEqual(12, msg.sequenceNum) + + let messageBlocks = msg.messageBlocks + + XCTAssertEqual(1, messageBlocks.count) + + let statusResponse = messageBlocks[0] as! StatusResponse + + XCTAssertEqual(nil, statusResponse.reservoirLevel) + XCTAssertEqual(TimeInterval(minutes: 4261), statusResponse.timeActive) + + XCTAssertEqual(.normal, statusResponse.deliveryStatus) + XCTAssertEqual(.aboveFiftyUnits, statusResponse.podProgressStatus) + XCTAssertEqual(6.3, statusResponse.insulin, accuracy: 0.01) + XCTAssertEqual(0, statusResponse.insulinNotDelivered) + XCTAssertEqual(3, statusResponse.podMessageCounter) + XCTAssert(statusResponse.alerts.isEmpty) + + XCTAssertEqual("1f00ee84300a1d18003f1800004297ff8128", msg.encoded().hexadecimalString) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testAssemblingMultiPacketMessage() { + do { + let packet1 = try Packet(encodedData: Data(hexadecimalString: "ffffffffe4ffffffff041d011b13881008340a5002070002070002030000a62b0004479420")!) + XCTAssertEqual(packet1.data.hexadecimalString, "ffffffff041d011b13881008340a5002070002070002030000a62b00044794") + XCTAssertEqual(packet1.packetType, .pod) + + XCTAssertThrowsError(try Message(encodedData: packet1.data)) { error in + XCTAssertEqual(String(describing: error), "notEnoughData") + } + + let packet2 = try Packet(encodedData: Data(hexadecimalString: "ffffffff861f00ee878352ff")!) + XCTAssertEqual(packet2.address, 0xffffffff) + XCTAssertEqual(packet2.data.hexadecimalString, "1f00ee878352") + XCTAssertEqual(packet2.packetType, .con) + + let messageBody = packet1.data + packet2.data + XCTAssertEqual(messageBody.hexadecimalString, "ffffffff041d011b13881008340a5002070002070002030000a62b000447941f00ee878352") + + let message = try Message(encodedData: messageBody) + XCTAssertEqual(message.messageBlocks.count, 1) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testParsingVersionResponse() { + do { + let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a64000097c279c1f08ced2")!) + XCTAssertEqual(23, config.data.count) + XCTAssertEqual(0x1f08ced2, config.address) + XCTAssertEqual(42560, config.lot) + XCTAssertEqual(621607, config.tid) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testParsingLongVersionResponse() { + do { + let message = try Message(encodedData: Data(hexadecimalString: "ffffffff041d011b13881008340a5002070002070002030000a62b000447941f00ee878352")!) + let config = message.messageBlocks[0] as! VersionResponse + XCTAssertEqual(29, config.data.count) + XCTAssertEqual(0x1f00ee87, config.address) + XCTAssertEqual(42539, config.lot) + XCTAssertEqual(280468, config.tid) + XCTAssertEqual("2.7.0", String(describing: config.piVersion)) + XCTAssertEqual("2.7.0", String(describing: config.pmVersion)) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testParsingConfigWithPairingExpired() { + do { + let message = try Message(encodedData: Data(hexadecimalString: "ffffffff04170115020700020700020e0000a5ad00053030971f08686301fd")!) + let config = message.messageBlocks[0] as! VersionResponse + XCTAssertEqual(.pairingExpired, config.setupState) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testAssignAddressCommand() { + do { + // Encode + let encoded = AssignAddressCommand(address: 0x1f01482a) + XCTAssertEqual("07041f01482a", encoded.data.hexadecimalString) + + // Decode + let decoded = try AssignAddressCommand(encodedData: Data(hexadecimalString: "07041f01482a")!) + XCTAssertEqual(0x1f01482a, decoded.address) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testSetupPodCommand() { + do { + var components = DateComponents() + components.day = 12 + components.month = 6 + components.year = 2016 + components.hour = 13 + components.minute = 47 + + // Decode + let decoded = try ConfigurePodCommand(encodedData: Data(hexadecimalString: "03131f0218c31404060c100d2f0000a4be0004e4a1")!) + XCTAssertEqual(0x1f0218c3, decoded.address) + XCTAssertEqual(components, decoded.dateComponents) + XCTAssertEqual(0x0000a4be, decoded.lot) + XCTAssertEqual(0x0004e4a1, decoded.tid) + + // Encode + let encoded = ConfigurePodCommand(address: 0x1f0218c3, dateComponents: components, lot: 0x0000a4be, tid: 0x0004e4a1) + XCTAssertEqual("03131f0218c31404060c100d2f0000a4be0004e4a1", encoded.data.hexadecimalString) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testInsertCannula() { +// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:PDM SEQ:17 ID2:1f00ee85 B9:38 BLEN:31 BODY:1a0e7e30bf16020065010050000a000a170d000064000186a0 CRC:33 +// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:ACK SEQ:18 ID2:1f00ee85 CRC:89 +// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:CON SEQ:19 CON:000000000000808c CRC:6f +// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:POD SEQ:20 ID2:1f00ee85 B9:3c BLEN:10 BODY:1d570016f00a00000bff8099 CRC:86 +// 2018-04-03T19:23:14.3d ID1:1f00ee85 PTYPE:ACK SEQ:21 ID2:1f00ee85 CRC:a0 + + do { + // Decode + let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0ebed2e16b02010a0101a000340034")!) + XCTAssertEqual(0xbed2e16b, cmd.nonce) + + if case SetInsulinScheduleCommand.DeliverySchedule.bolus(let units, let timeBetweenPulses) = cmd.deliverySchedule { + XCTAssertEqual(2.6, units) + XCTAssertEqual(.seconds(1), timeBetweenPulses) + } else { + XCTFail("Expected ScheduleEntry.bolus type") + } + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testStatusResponseAlarmsParsing() { + // 1d 28 0082 00 0044 46eb ff + + do { + // Decode + let status = try StatusResponse(encodedData: Data(hexadecimalString: "1d28008200004446ebff")!) + XCTAssert(status.alerts.contains(.slot3)) + XCTAssert(status.alerts.contains(.slot7)) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testConfigureAlertsCommand() { + // 79a4 10df 0502 + // Pod expires 1 minute short of 3 days + let podSoftExpirationTime = TimeInterval(hours:72) - TimeInterval(minutes:1) + let alertConfig1 = AlertConfiguration(alertType: .slot7, active: true, autoOffModifier: false, duration: .hours(7), trigger: .timeUntilAlert(podSoftExpirationTime), beepRepeat: .every60Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + XCTAssertEqual("79a410df0502", alertConfig1.data.hexadecimalString) + + // 2800 1283 0602 + let podHardExpirationTime = TimeInterval(hours:79) - TimeInterval(minutes:1) + let alertConfig2 = AlertConfiguration(alertType: .slot2, active: true, autoOffModifier: false, duration: .minutes(0), trigger: .timeUntilAlert(podHardExpirationTime), beepRepeat: .every15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + XCTAssertEqual("280012830602", alertConfig2.data.hexadecimalString) + + // 020f 0000 0202 + let alertConfig3 = AlertConfiguration(alertType: .slot0, active: false, autoOffModifier: true, duration: .minutes(15), trigger: .timeUntilAlert(0), beepRepeat: .every1MinuteFor15Minutes, beepType: .bipBeepBipBeepBipBeepBipBeep) + XCTAssertEqual("020f00000202", alertConfig3.data.hexadecimalString) + + let configureAlerts = ConfigureAlertsCommand(nonce: 0xfeb6268b, configurations:[alertConfig1, alertConfig2, alertConfig3]) + XCTAssertEqual("1916feb6268b79a410df0502280012830602020f00000202", configureAlerts.data.hexadecimalString) + + do { + let decoded = try ConfigureAlertsCommand(encodedData: Data(hexadecimalString: "1916feb6268b79a410df0502280012830602020f00000202")!) + XCTAssertEqual(3, decoded.configurations.count) + + let config1 = decoded.configurations[0] + XCTAssertEqual(.slot7, config1.slot) + XCTAssertEqual(true, config1.active) + XCTAssertEqual(false, config1.autoOffModifier) + XCTAssertEqual(.hours(7), config1.duration) + if case AlertTrigger.timeUntilAlert(let duration) = config1.trigger { + XCTAssertEqual(podSoftExpirationTime, duration) + } + XCTAssertEqual(.every60Minutes, config1.beepRepeat) + XCTAssertEqual(.bipBeepBipBeepBipBeepBipBeep, config1.beepType) + + let cfg = try AlertConfiguration(encodedData: Data(hexadecimalString: "4c0000640102")!) + XCTAssertEqual(.slot4, cfg.slot) + XCTAssertEqual(true, cfg.active) + XCTAssertEqual(false, cfg.autoOffModifier) + XCTAssertEqual(0, cfg.duration) + if case AlertTrigger.unitsRemaining(let volume) = cfg.trigger { + XCTAssertEqual(10, volume) + } + XCTAssertEqual(.every1MinuteFor3MinutesAndRepeatEvery60Minutes, cfg.beepRepeat) + XCTAssertEqual(.bipBeepBipBeepBipBeepBipBeep, cfg.beepType) + + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } +} + diff --git a/OmniKitTests/OmniKitTests-Bridging-Header.h b/OmniKitTests/OmniKitTests-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/OmniKitTests/OmniKitTests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/OmniKitTests/PacketTests.swift b/OmniKitTests/PacketTests.swift new file mode 100644 index 000000000..3b97bf458 --- /dev/null +++ b/OmniKitTests/PacketTests.swift @@ -0,0 +1,53 @@ +// +// PacketTests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 10/14/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import OmniKit + +class PacketTests: XCTestCase { + + func testPacketData() { + // 2016-06-26T20:33:28.412197 ID1:1f01482a PTYPE:PDM SEQ:13 ID2:1f01482a B9:10 BLEN:3 BODY:0e0100802c CRC:88 + + let msg = Message(address: 0x1f01482a, messageBlocks: [GetStatusCommand()], sequenceNum: 4) + + let packet = Packet(address: 0x1f01482a, packetType: .pdm, sequenceNum: 13, data: msg.encoded()) + + XCTAssertEqual("1f01482aad1f01482a10030e0100802c88", packet.encoded().hexadecimalString) + + XCTAssertEqual("1f01482a10030e0100802c", packet.data.hexadecimalString) + + } + + func testPacketDecoding() { + do { + let packet = try Packet(encodedData: Data(hexadecimalString:"1f01482aad1f01482a10030e0100802c88")!) + XCTAssertEqual(0x1f01482a, packet.address) + XCTAssertEqual(13, packet.sequenceNum) + XCTAssertEqual(.pdm, packet.packetType) + XCTAssertEqual("1f01482a10030e0100802c", packet.data.hexadecimalString) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPacketFragmenting() { + let longMessageData = Data(hexadecimalString:"02cb5000c92162368024632d8029623f002c62320031623b003463320039633d003c63310041623e0044633200496340004c6333005163448101627c8104627c8109627c810c62198111627c811460198103fe")! + let packet = Packet(address: 0x1f01482a, packetType: .pdm, sequenceNum: 13, data: longMessageData) + XCTAssertEqual(31, packet.data.count) + XCTAssertEqual("02cb5000c92162368024632d8029623f002c62320031623b00346332003963", packet.data.hexadecimalString) + let con1 = Packet(address: 0x1f01482a, packetType: .con, sequenceNum: 14, data: longMessageData.subdata(in: 31.. Void)? + + init(address: UInt32, messageNumber: Int) { + self.address = address + self.messageNumber = messageNumber + } + + func sendMessage(_ message: Message) throws -> Message { + sentMessages.append(message) + if responseMessageBlocks.isEmpty { + throw PodCommsError.noResponse + } + return Message(address: address, messageBlocks: [responseMessageBlocks.removeFirst()], sequenceNum: messageNumber) + } + + func addResponse(_ messageBlock: MessageBlock) { + responseMessageBlocks.append(messageBlock) + } + + func assertOnSessionQueue() { + // Do nothing in tests + } +} + +class PodCommsSessionTests: XCTestCase, PodCommsSessionDelegate { + + var lastPodStateUpdate: PodState? + + func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) { + lastPodStateUpdate = state + } + + + func testNonceResync() { + + // From https://raw.githubusercontent.com/wiki/openaps/openomni/Full-life-of-a-pod-(omni-flo).md + + let now = Date() + + // 2018-05-25T13:03:51.765792 pod Message(ffffffff seq:01 [OmniKitPacketParser.VersionResponse(blockType: OmniKitPacketParser.MessageBlockType.versionResponse, lot: 43620, tid: 560313, address: Optional(521580830), setupState: OmniKitPacketParser.SetupState.addressAssigned, pmVersion: 2.7.0, piVersion: 2.7.0, data: 23 bytes)]) + + let podState = PodState(address: 521580830, piVersion: "2.7.0", pmVersion: "2.7.0", lot: 43620, tid: 560313) + + let messageTransport = MockMessageTransport(address: podState.address, messageNumber: 5) + + do { + // 2018-05-26T09:11:08.580347 pod Message(1f16b11e seq:06 [OmniKitPacketParser.ErrorResponse(blockType: OmniKitPacketParser.MessageBlockType.errorResponse, errorReponseType: OmniKitPacketParser.ErrorResponse.ErrorReponseType.badNonce, nonceSearchKey: 43492, data: 5 bytes)]) + messageTransport.addResponse(try ErrorResponse(encodedData: Data(hexadecimalString: "060314a9e403f5")!)) + messageTransport.addResponse(try StatusResponse(encodedData: Data(hexadecimalString: "1d5800d1a8140012e3ff8018")!)) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + return + } + + let session = PodCommsSession(podState: podState, transport: messageTransport, delegate: self) + + + // 2018-05-26T09:11:07.984983 pdm Message(1f16b11e seq:05 [SetInsulinScheduleCommand(nonce:2232447658, bolus(units: 1.0, timeBetweenPulses: 2.0)), OmniKitPacketParser.BolusExtraCommand(blockType: OmniKitPacketParser.MessageBlockType.bolusExtra, completionBeep: false, programReminderInterval: 0.0, units: 1.0, timeBetweenPulses: 2.0, squareWaveUnits: 0.0, squareWaveDuration: 0.0)]) + let bolusDelivery = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: 1.0, timeBetweenPulses: 2.0) + let sentCommand = SetInsulinScheduleCommand(nonce: 2232447658, deliverySchedule: bolusDelivery) + + do { + let status: StatusResponse = try session.send([sentCommand]) + + XCTAssertEqual(2, messageTransport.sentMessages.count) + + let bolusTry1 = messageTransport.sentMessages[0].messageBlocks[0] as! SetInsulinScheduleCommand + XCTAssertEqual(2232447658, bolusTry1.nonce) + + let bolusTry2 = messageTransport.sentMessages[1].messageBlocks[0] as! SetInsulinScheduleCommand + XCTAssertEqual(1521036535, bolusTry2.nonce) + + XCTAssert(status.deliveryStatus.bolusing) + } catch (let error) { + XCTFail("message sending error: \(error)") + } + + // Try sending another bolus command: nonce should be 676940027 + XCTAssertEqual(545302454, lastPodStateUpdate!.currentNonce) + + let _ = session.bolus(units: 2) + let bolusTry3 = messageTransport.sentMessages[2].messageBlocks[0] as! SetInsulinScheduleCommand + XCTAssertEqual(545302454, bolusTry3.nonce) + + } +} diff --git a/OmniKitTests/PodInfoTests.swift b/OmniKitTests/PodInfoTests.swift new file mode 100644 index 000000000..58369cb84 --- /dev/null +++ b/OmniKitTests/PodInfoTests.swift @@ -0,0 +1,389 @@ +// +// PodInfoTests.swift +// OmniKitTests +// +// Created by Eelke Jager on 18/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +import XCTest +@testable import OmniKit + +class PodInfoTests: XCTestCase { + func testFullMessage() { + do { + // Decode + let infoResponse = try PodInfoResponse(encodedData: Data(hexadecimalString: "0216020d0000000000ab6a038403ff03860000285708030d0000")!) + XCTAssertEqual(infoResponse.podInfoResponseSubType, .faultEvents) + let faultEvent = infoResponse.podInfo as! PodInfoFaultEvent + XCTAssertEqual(faultEvent.faultAccessingTables, false) + XCTAssertEqual(faultEvent.logEventErrorType, LogEventErrorCode(rawValue: 2)) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoConfiguredAlertsNoAlerts() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 + // 02 13 // 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + // 02 13 // 01 0000 0000 0000 0000 0000 0000 0000 0000 0000 + do { + // Decode + let decoded = try PodInfoConfiguredAlerts(encodedData: Data(hexadecimalString: "01000000000000000000000000000000000000")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoConfiguredAlertsSuspendStillActive() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 + // 02 13 // 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + // 02 13 // 01 0000 0000 0000 0000 0000 0000 0bd7 0c40 0000 // real alert value after 2 hour suspend + // 02 13 // 01 0000 0102 0304 0506 0708 090a 0bd7 0c40 0000 // used as a tester to find each alarm + do { + // Decode + let decoded = try PodInfoConfiguredAlerts(encodedData: Data(hexadecimalString: "010000000000000000000000000bd70c400000")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) + XCTAssertEqual(.beepBeepBeep, decoded.alertsActivations[5].beepType) + XCTAssertEqual(11, decoded.alertsActivations[5].timeFromPodStart) // in minutes + XCTAssertEqual(10.75, decoded.alertsActivations[5].unitsLeft) //, accuracy: 1) + XCTAssertEqual(.beeeeeep, decoded.alertsActivations[6].beepType) + XCTAssertEqual(12, decoded.alertsActivations[6].timeFromPodStart) // in minutes + XCTAssertEqual(3.2, decoded.alertsActivations[6].unitsLeft) //, accuracy: 1) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoConfiguredAlertsReplacePodAfter3DaysAnd8Hours() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 + // 02 13 // 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + // 02 13 // 01 0000 0000 0000 0000 0000 0000 0000 0000 10e1 + do { + let decoded = try PodInfoConfiguredAlerts(encodedData: Data(hexadecimalString: "010000000000000000000000000000000010e1")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) + XCTAssertEqual(.bipBipBipbipBipBip, decoded.alertsActivations[7].beepType) + XCTAssertEqual(16, decoded.alertsActivations[7].timeFromPodStart) // in 2 hours steps + XCTAssertEqual(11.25, decoded.alertsActivations[7].unitsLeft, accuracy: 1) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoConfiguredAlertsReplacePodAfterReservoirEmpty() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 + // 02 13 // 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + // 02 13 // 01 0000 0000 0000 1285 0000 11c7 0000 0000 119c + do { + let decoded = try PodInfoConfiguredAlerts(encodedData: Data(hexadecimalString: "010000000000001285000011c700000000119c")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) + XCTAssertEqual(.bipBeepBipBeepBipBeepBipBeep, decoded.alertsActivations[2].beepType) + XCTAssertEqual(18, decoded.alertsActivations[2].timeFromPodStart) // in 2 hours steps + XCTAssertEqual(6.6, decoded.alertsActivations[2].unitsLeft, accuracy: 1) + XCTAssertEqual(.beep, decoded.alertsActivations[4].beepType) + XCTAssertEqual(17, decoded.alertsActivations[4].timeFromPodStart) // in 2 hours steps + XCTAssertEqual(9.95, decoded.alertsActivations[4].unitsLeft, accuracy: 2) + XCTAssertEqual(.bipBipBipbipBipBip, decoded.alertsActivations[7].beepType) + XCTAssertEqual(17, decoded.alertsActivations[7].timeFromPodStart) // in 2 hours steps + XCTAssertEqual(7.8, decoded.alertsActivations[7].unitsLeft, accuracy: 1) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoConfiguredAlertsReplacePod() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 1516 1718 + // 02 13 // 01 XXXX VVVV VVVV VVVV VVVV VVVV VVVV VVVV VVVV + // 02 13 // 01 0000 0000 0000 1284 0000 0000 0000 0000 10e0 + do { + let decoded = try PodInfoConfiguredAlerts(encodedData: Data(hexadecimalString: "010000000000001284000000000000000010e0")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) + XCTAssertEqual(.bipBeepBipBeepBipBeepBipBeep, decoded.alertsActivations[2].beepType) + XCTAssertEqual(18, decoded.alertsActivations[2].timeFromPodStart) // in 2 hours steps + XCTAssertEqual(6.6, decoded.alertsActivations[2].unitsLeft, accuracy: 1) + XCTAssertEqual(.bipBipBipbipBipBip, decoded.alertsActivations[7].beepType) + XCTAssertEqual(16, decoded.alertsActivations[7].timeFromPodStart) // in 2 hours steps + XCTAssertEqual(11.2, decoded.alertsActivations[7].unitsLeft, accuracy: 1) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoNoFaultAlerts() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 08 01 0000 0a 0038 00 0000 03ff 0087 00 00 00 95 ff 0000 + do { + // Decode + let decoded = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "02080100000a003800000003ff008700000095ff0000")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + XCTAssertEqual(.aboveFiftyUnits, decoded.podProgressStatus) + XCTAssertEqual(.normal, decoded.deliveryStatus) + XCTAssertEqual(0000, decoded.insulinNotDelivered) + XCTAssertEqual(0x0a, decoded.podMessageCounter) + XCTAssertEqual(.noFaults, decoded.currentStatus.faultType) + XCTAssertEqual(0000, decoded.faultEventTimeSinceActivation) + XCTAssertEqual(nil, decoded.reservoirLevel) + XCTAssertEqual(8100, decoded.timeActive) + XCTAssertEqual("02:15", decoded.timeActive.stringValue) + XCTAssertEqual(0, decoded.unacknowledgedAlerts.rawValue) + XCTAssertEqual(false, decoded.faultAccessingTables) + XCTAssertEqual(LogEventErrorCode(rawValue: 0), decoded.logEventErrorType) + XCTAssertEqual(.inactive, decoded.previousPodProgressStatus) + XCTAssertEqual(2, decoded.receiverLowGain) + XCTAssertEqual(21, decoded.radioRSSI) + XCTAssertEqual(.inactive, decoded.previousPodProgressStatus) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoDeliveryErrorDuringPriming() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 0f 00 0000 09 0034 5c 0001 03ff 0001 00 00 05 ae 05 6029 + do { + // Decode + let decoded = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020f0000000900345c000103ff0001000005ae056029")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + XCTAssertEqual(.inactive, decoded.podProgressStatus) + XCTAssertEqual(.suspended, decoded.deliveryStatus) + XCTAssertEqual(0000, decoded.insulinNotDelivered) + XCTAssertEqual(9, decoded.podMessageCounter) + XCTAssertEqual(.primeOpenCountTooLow, decoded.currentStatus.faultType) + XCTAssertEqual(60, decoded.faultEventTimeSinceActivation) + XCTAssertEqual(nil, decoded.reservoirLevel) + XCTAssertEqual(TimeInterval(minutes: 1), decoded.timeActive) + XCTAssertEqual(60, decoded.timeActive) + XCTAssertEqual(00, decoded.unacknowledgedAlerts.rawValue) + XCTAssertEqual(false, decoded.faultAccessingTables) + XCTAssertEqual(LogEventErrorCode(rawValue: 0), decoded.logEventErrorType) + XCTAssertEqual(.readyForBasalSchedule, decoded.logEventErrorPodProgressStatus) + XCTAssertEqual(2, decoded.receiverLowGain) + XCTAssertEqual(46, decoded.radioRSSI) + XCTAssertEqual(.readyForBasalSchedule, decoded.previousPodProgressStatus) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoDuringPriming() { + // Needle cap accidentally removed before priming started leaking and gave error: + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 0d 00 0000 06 0000 8f 0000 03ff 0000 00 00 03 a2 03 86a0 + do { + // Decode + let decoded = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000600008f000003ff0000000003a20386a0")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + XCTAssertEqual(.errorEventLoggedShuttingDown, decoded.podProgressStatus) + XCTAssertEqual(.suspended, decoded.deliveryStatus) + XCTAssertEqual(0, decoded.insulinNotDelivered, accuracy: 0.01) + XCTAssertEqual(6, decoded.podMessageCounter) + XCTAssertEqual(.command1AParseUnexpectedFailed, decoded.currentStatus.faultType) + XCTAssertEqual(0000*60, decoded.faultEventTimeSinceActivation) + XCTAssertEqual(nil, decoded.reservoirLevel) + XCTAssertEqual(0, decoded.timeActive) // timeActive converts minutes to seconds + XCTAssertEqual(0, decoded.unacknowledgedAlerts.rawValue) + XCTAssertEqual(false, decoded.faultAccessingTables) + XCTAssertEqual(LogEventErrorCode(rawValue: 0), decoded.logEventErrorType) + XCTAssertEqual(.pairingSuccess, decoded.logEventErrorPodProgressStatus) + XCTAssertEqual(2, decoded.receiverLowGain) + XCTAssertEqual(34, decoded.radioRSSI) + XCTAssertEqual(.pairingSuccess, decoded.previousPodProgressStatus) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoFaultEventErrorShuttingDown() { + // Failed Pod after 1 day, 18+ hours of live use shortly after installing new omniloop. + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 0d 00 0000 04 07f2 86 09ff 03ff 0a02 00 00 08 23 08 0000 + do { + // Decode + let decoded = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000407f28609ff03ff0a0200000823080000")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + XCTAssertEqual(.errorEventLoggedShuttingDown, decoded.podProgressStatus) + XCTAssertEqual(.suspended, decoded.deliveryStatus) + XCTAssertEqual(0, decoded.insulinNotDelivered) + XCTAssertEqual(4, decoded.podMessageCounter) + XCTAssertEqual(101.7, decoded.totalInsulinDelivered, accuracy: 0.01) + XCTAssertEqual(.basalOverInfusionPulse, decoded.currentStatus.faultType) + XCTAssertEqual(0, decoded.unacknowledgedAlerts.rawValue) + XCTAssertEqual(2559 * 60, decoded.faultEventTimeSinceActivation) //09ff + XCTAssertEqual("1 day plus 18:39", decoded.faultEventTimeSinceActivation?.stringValue) + XCTAssertEqual(nil, decoded.reservoirLevel) + XCTAssertEqual(false, decoded.faultAccessingTables) + XCTAssertEqual(LogEventErrorCode(rawValue: 0), decoded.logEventErrorType) + XCTAssertEqual(.aboveFiftyUnits, decoded.logEventErrorPodProgressStatus) + XCTAssertEqual(0, decoded.receiverLowGain) + XCTAssertEqual(35, decoded.radioRSSI) + XCTAssertEqual(.aboveFiftyUnits, decoded.previousPodProgressStatus) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoFaultEventLogEventErrorCode2() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 0d 00 0000 04 07eb 6a 0e0c 03ff 0e14 00 00 28 17 08 0000 + do { + // Decode + let decoded = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000407eb6a0e0c03ff0e1400002817080000")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + XCTAssertEqual(.errorEventLoggedShuttingDown, decoded.podProgressStatus) + XCTAssertEqual(.suspended, decoded.deliveryStatus) + XCTAssertEqual(0, decoded.insulinNotDelivered) + XCTAssertEqual(4, decoded.podMessageCounter) + XCTAssertEqual(101.35, decoded.totalInsulinDelivered, accuracy: 0.01) + XCTAssertEqual(.occlusionCheckAboveThreshold, decoded.currentStatus.faultType) + XCTAssertEqual(0, decoded.unacknowledgedAlerts.rawValue) + XCTAssertEqual(3596 * 60, decoded.faultEventTimeSinceActivation) //09ff + XCTAssertEqual("2 days plus 11:56", decoded.faultEventTimeSinceActivation?.stringValue) + XCTAssertEqual(nil, decoded.reservoirLevel) + XCTAssertEqual(false, decoded.faultAccessingTables) + XCTAssertEqual(.internal2BitVariableSetAndManipulatedInMainLoopRoutines2, decoded.logEventErrorType.eventErrorType) + XCTAssertEqual(.aboveFiftyUnits, decoded.logEventErrorPodProgressStatus) + XCTAssertEqual(0, decoded.receiverLowGain) + XCTAssertEqual(23, decoded.radioRSSI) + XCTAssertEqual(.aboveFiftyUnits, decoded.previousPodProgressStatus) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoFaultEventIsulinNotDelivered() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 0f 00 0001 02 00ec 6a 0268 03ff 026b 00 00 28 a7 08 2023 + do { + // Decode + let decoded = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020f0000010200ec6a026803ff026b000028a7082023")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + XCTAssertEqual(.inactive, decoded.podProgressStatus) + XCTAssertEqual(.suspended, decoded.deliveryStatus) + XCTAssertEqual(0.05, decoded.insulinNotDelivered) + XCTAssertEqual(2, decoded.podMessageCounter) + XCTAssertEqual(11.8, decoded.totalInsulinDelivered, accuracy: 0.01) + XCTAssertEqual(.occlusionCheckAboveThreshold, decoded.currentStatus.faultType) + XCTAssertEqual(0, decoded.unacknowledgedAlerts.rawValue) + XCTAssertEqual(616 * 60, decoded.faultEventTimeSinceActivation) //09ff + XCTAssertEqual("10:16", decoded.faultEventTimeSinceActivation?.stringValue) + XCTAssertEqual(nil, decoded.reservoirLevel) + XCTAssertEqual(false, decoded.faultAccessingTables) + XCTAssertEqual(.internal2BitVariableSetAndManipulatedInMainLoopRoutines2, decoded.logEventErrorType.eventErrorType) + XCTAssertEqual(.aboveFiftyUnits, decoded.logEventErrorPodProgressStatus) + XCTAssertEqual(2, decoded.receiverLowGain) + XCTAssertEqual(39, decoded.radioRSSI) + XCTAssertEqual(.aboveFiftyUnits, decoded.previousPodProgressStatus) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoDataLog() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 + // 02 LL // 03 PP QQQQ SSSS 04 3c .... + // 02 7c // 03 01 0001 0001 04 3c .... + do { + let decoded = try PodInfoDataLog(encodedData: Data(hexadecimalString: "030100010001043c")!) + XCTAssertEqual(.dataLog, decoded.podInfoType) + XCTAssertEqual(.failedFlashErase, decoded.faultEventCode.faultType) + XCTAssertEqual(0001*60, decoded.timeFaultEvent) + XCTAssertEqual(0001*60, decoded.timeActivation) + XCTAssertEqual(04, decoded.dataChunkSize) + XCTAssertEqual(60, decoded.dataChunkWords) + // TODO adding a datadump variable based on length LL + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoFault() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 91011 1213141516 + // 02 11 // 05 PP QQQQ 00000000 00000000 MMDDYYHHMM + // 02 11 // 05 92 0001 00000000 00000000 091912170e + // 09-25-18 23:14 int values for datetime + do { + // Decode + let decoded = try PodInfoFault(encodedData: Data(hexadecimalString: "059200010000000000000000091912170e")!) + XCTAssertEqual(.fault, decoded.podInfoType) + XCTAssertEqual(.badPumpReq2State, decoded.faultEventCode.faultType) + XCTAssertEqual(0001*60, decoded.timeActivation) + let decodedDateTime = decoded.dateTime + XCTAssertEqual(2018, decodedDateTime.year) + XCTAssertEqual(09, decodedDateTime.month) + XCTAssertEqual(25, decodedDateTime.day) + XCTAssertEqual(23, decodedDateTime.hour) + XCTAssertEqual(14, decodedDateTime.minute) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoTester() { + // 02DATAOFF 0 1 2 3 4 + // 02 05 // 06 01 00 3F A8 + do { + // Decode + let decoded = try PodInfoTester(encodedData: Data(hexadecimalString: "0601003FA8")!) + XCTAssertEqual(.hardcodedTestValues, decoded.podInfoType) + XCTAssertEqual(0x01, decoded.byte1) + XCTAssertEqual(0x00, decoded.byte2) + XCTAssertEqual(0x3F, decoded.byte3) + XCTAssertEqual(0xA8, decoded.byte4) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoFlashLogRecent() { + //02 cb 50 0086 34212e00 39203100 3c212d00 41203000 44202c00 49212e00 4c212b00 51202f00 54212c00 59203080 5c202d80 61203080 00212e80 05213180 08202f80 0d203280 10202f80 15213180 18202f80 1d213180 20202e80 25213300 28203200 2d213500 30213100 35213400 38213100 3d203500 40203100 45213300 48203000 4d213200 50212f00 55203300 58203080 5d213280 60202f800 12030800 4202c800 92131800 c2130801 12132801 42031801 92133801 c2031802 12032802 42132002 92035002 c2131003 12134000 3c3801c2 03180212 03280242 13200292 035002c2 13100312 1340003c 3 + do { + // Decode + let decoded = try PodInfoFlashLogRecent(encodedData: Data(hexadecimalString: "50008634212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f0055203300582030805d21328060202f800120308004202c80092131800c2130801121328014203180192133801c2031802120328024213200292035002c2131003121340003c3801c2031802120328024213200292035002c2131003121340003c3")!) + XCTAssertEqual(.flashLogRecent, decoded.podInfoType) + XCTAssertEqual(134, decoded.indexLastEntry) + XCTAssertEqual(Data(hexadecimalString:"34212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f"), decoded.hexWordLog) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoResetStatus() { + // 02DATAOF 0 1 2 3 + // 02 LL // 46 00 NN XX ... + // 02 7c // 46 00 79 1f00ee841f00ee84ff00ff00ffffffffffff0000ffffffffffffffffffffffff04060d10070000a62b0004e3db0000ffffffffffffff32cd50af0ff014eb01fe01fe06f9ff00ff0002fd649b14eb14eb07f83cc332cd05fa02fd58a700ffffffffffffffffffffffffffffffffffffffffffffffffffffffff + do { + // Decode + let decoded = try PodInfoResetStatus(encodedData: Data(hexadecimalString: "4600791f00ee841f00ee84ff00ff00ffffffffffff0000ffffffffffffffffffffffff04060d10070000a62b0004e3db0000ffffffffffffff32cd50af0ff014eb01fe01fe06f9ff00ff0002fd649b14eb14eb07f83cc332cd05fa02fd58a700ffffffffffffffffffffffffffffffffffffffffffffffffffffffff")!) + XCTAssertEqual(.resetStatus, decoded.podInfoType) + XCTAssertEqual(0, decoded.zero) + XCTAssertEqual(121, decoded.numberOfBytes) + XCTAssertEqual(0x1f00ee84, decoded.podAddress) // Pod address is in the first 4 bytes of the flash + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodFault12() { + // 02DATAOFF 0 1 2 3 4 5 6 7 8 910 1112 1314 15 16 17 18 19 2021 + // 02 16 // 02 0J 0K LLLL MM NNNN PP QQQQ RRRR SSSS TT UU VV WW 0X YYYY + // 02 16 // 02 0d 00 0000 00 0000 12 ffff 03ff 0000 00 00 87 92 07 0000 + do { + // Decode + let faultEvent = try PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d00000000000012ffff03ff000000008792070000")!) + XCTAssertEqual(faultEvent.faultAccessingTables, false) + XCTAssertNil(faultEvent.faultEventTimeSinceActivation) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } +} diff --git a/OmniKitTests/PodStateTests.swift b/OmniKitTests/PodStateTests.swift new file mode 100644 index 000000000..4ec244422 --- /dev/null +++ b/OmniKitTests/PodStateTests.swift @@ -0,0 +1,55 @@ +// +// PodStateTests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 10/13/17. +// Copyright © 2017 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import OmniKit + +class PodStateTests: XCTestCase { + + func testNonceValues() { + var podState = PodState(address: 0x1f000000, piVersion: "1.1.0", pmVersion: "1.1.0", lot: 42560, tid: 661771) + + XCTAssertEqual(podState.currentNonce, 0x8c61ee59) + podState.advanceToNextNonce() + XCTAssertEqual(podState.currentNonce, 0xc0256620) + podState.advanceToNextNonce() + XCTAssertEqual(podState.currentNonce, 0x15022c8a) + podState.advanceToNextNonce() + XCTAssertEqual(podState.currentNonce, 0xacf076ca) + } + + func testResyncNonce() { + do { + let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a62b0002249da11f00ee860318")!) + var podState = PodState(address: 0x1f00ee86, piVersion: "1.1.0", pmVersion: "1.1.0", lot: config.lot, tid: config.tid) + + XCTAssertEqual(42539, config.lot) + XCTAssertEqual(140445, config.tid) + + XCTAssertEqual(0x8fd39264, podState.currentNonce) + + // ID1:1f00ee86 PTYPE:PDM SEQ:26 ID2:1f00ee86 B9:24 BLEN:6 BODY:1c042e07c7c703c1 CRC:f4 + let sentPacket = try Packet(encodedData: Data(hexadecimalString: "1f00ee86ba1f00ee8624061c042e07c7c703c1f4")!) + let sentMessage = try Message(encodedData: sentPacket.data) + let sentCommand = sentMessage.messageBlocks[0] as! DeactivatePodCommand + + let errorResponse = try ErrorResponse(encodedData: Data(hexadecimalString: "06031492c482f5")!) + + XCTAssertEqual(9, sentMessage.sequenceNum) + + podState.resyncNonce(syncWord: errorResponse.nonceSearchKey, sentNonce: sentCommand.nonce, messageSequenceNum: sentMessage.sequenceNum) + + XCTAssertEqual(0x40ccdacb, podState.currentNonce) + + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } +} + diff --git a/OmniKitTests/StatusTests.swift b/OmniKitTests/StatusTests.swift new file mode 100644 index 000000000..612e42053 --- /dev/null +++ b/OmniKitTests/StatusTests.swift @@ -0,0 +1,100 @@ +// +// StatusTests.swift +// OmniKitTests +// +// Created by Eelke Jager on 08/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// +import Foundation + +import XCTest +@testable import OmniKit + +class StatusTests: XCTestCase { + + func testStatusRequestCommand() { + // 0e 01 00 + do { + // Encode + let encoded = GetStatusCommand(podInfoType: .normal) + XCTAssertEqual("0e0100", encoded.data.hexadecimalString) + + // Decode + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0100")!) + XCTAssertEqual(.normal, decoded.podInfoType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + } + + func testStatusResponse46UnitsLeft() { + /// 1d19050ec82c08376f9801dc + do { + // Decode + let decoded = try StatusResponse(encodedData: Data(hexadecimalString: "1d19050ec82c08376f9801dc")!) + XCTAssertEqual(TimeInterval(minutes: 3547), decoded.timeActive) + XCTAssertEqual(.normal, decoded.deliveryStatus) + XCTAssertEqual(.belowFiftyUnits, decoded.podProgressStatus) + XCTAssertEqual(129.45, decoded.insulin, accuracy: 0.01) + XCTAssertEqual(46.00, decoded.reservoirLevel) + XCTAssertEqual(2.2, decoded.insulinNotDelivered) + XCTAssertEqual(9, decoded.podMessageCounter) + //XCTAssert(,decoded.alarms) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testStatusRequestCommandConfiguredAlerts() { + // 0e 01 01 + do { + // Encode + let encoded = GetStatusCommand(podInfoType: .configuredAlerts) + XCTAssertEqual("0e0101", encoded.data.hexadecimalString) + + // Decode + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0101")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + } + + func testStatusRequestCommandFaultEvents() { + // 0e 01 02 + do { + // Encode + let encoded = GetStatusCommand(podInfoType: .faultEvents) + XCTAssertEqual("0e0102", encoded.data.hexadecimalString) + + // Decode + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0102")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + } + + func testStatusRequestCommandResetStatus() { + // 0e 01 46 + do { + // Encode + let encoded = GetStatusCommand(podInfoType: .resetStatus) + XCTAssertEqual("0e0146", encoded.data.hexadecimalString) + + // Decode + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0146")!) + XCTAssertEqual(.resetStatus, decoded.podInfoType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + } + +} + + + diff --git a/OmniKitTests/TempBasalTests.swift b/OmniKitTests/TempBasalTests.swift new file mode 100644 index 000000000..320b422a1 --- /dev/null +++ b/OmniKitTests/TempBasalTests.swift @@ -0,0 +1,335 @@ +// +// TempBasalTests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 6/5/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +import XCTest +@testable import OmniKit + +class TempBasalTests: XCTestCase { + + func testRateQuantization() { +// // Test previously failing case +// XCTAssertEqual(0.15, OmnipodPumpManager.roundToDeliveryIncrement(units: 0.15)) +// +// XCTAssertEqual(0.15, OmnipodPumpManager.roundToDeliveryIncrement(units: 0.15000000000000002)) +// +// XCTAssertEqual(0.15, OmnipodPumpManager.roundToDeliveryIncrement(units: 0.145)) + } + + func testAlternatingSegmentFlag() { + // Encode 0.05U/hr 30mins + let cmd = SetInsulinScheduleCommand(nonce: 0x9746c65b, tempBasalRate: 0.05, duration: .hours(0.5)) + // 1a 0e 9746c65b 01 0079 01 3840 0000 0000 + XCTAssertEqual("1a0e9746c65b01007901384000000000", cmd.data.hexadecimalString) + + // Encode 0.05U/hr 8.5hours + let cmd2 = SetInsulinScheduleCommand(nonce: 0x9746c65b, tempBasalRate: 0.05, duration: .hours(8.5)) + // 1a 10 9746c65b 01 0091 11 3840 0000 f800 0000 + XCTAssertEqual("1a109746c65b0100911138400000f8000000", cmd2.data.hexadecimalString) + + // Encode 0.05U/hr 16.5hours + let cmd3 = SetInsulinScheduleCommand(nonce: 0x9746c65b, tempBasalRate: 0.05, duration: .hours(16.5)) + // 1a 12 9746c65b 01 00a9 21 3840 0000 f800 f800 0000 + XCTAssertEqual("1a129746c65b0100a92138400000f800f8000000", cmd3.data.hexadecimalString) + } + + func testTempBasalThreeTenthsUnitPerHour() { + let cmd = SetInsulinScheduleCommand(nonce: 0xeac79411, tempBasalRate: 0.3, duration: .hours(0.5)) + XCTAssertEqual("1a0eeac7941101007f01384000030003", cmd.data.hexadecimalString) + } + + func testSetTempBasalCommand() { + do { + // Decode 1a 0e ea2d0a3b 01 007d 01 3840 0002 0002 + // 1a 0e 9746c65b 01 0079 01 3840 0000 0000 160e7c00000515752a + let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0eea2d0a3b01007d01384000020002")!) + + XCTAssertEqual(0xea2d0a3b, cmd.nonce) + if case SetInsulinScheduleCommand.DeliverySchedule.tempBasal(let secondsRemaining, let firstSegmentPulses, let table) = cmd.deliverySchedule { + + XCTAssertEqual(1800, secondsRemaining) + XCTAssertEqual(2, firstSegmentPulses) + let entry = table.entries[0] + XCTAssertEqual(1, entry.segments) + XCTAssertEqual(2, entry.pulses) + } else { + XCTFail("Expected ScheduleEntry.tempBasal type") + } + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = SetInsulinScheduleCommand(nonce: 0xea2d0a3b, tempBasalRate: 0.20, duration: .hours(0.5)) + XCTAssertEqual("1a0eea2d0a3b01007d01384000020002", cmd.data.hexadecimalString) + } + + func testSetTempBasalWithAlternatingPulse() { + do { + // 0.05U/hr for 2.5 hours + // Decode 1a 0e 4e2c2717 01 007f 05 3840 0000 4800 + let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0e4e2c271701007f05384000004800")!) + + XCTAssertEqual(0x4e2c2717, cmd.nonce) + if case SetInsulinScheduleCommand.DeliverySchedule.tempBasal(let secondsRemaining, let firstSegmentPulses, let table) = cmd.deliverySchedule { + + XCTAssertEqual(1800, secondsRemaining) + XCTAssertEqual(0, firstSegmentPulses) + XCTAssertEqual(1, table.entries.count) + XCTAssertEqual(5, table.entries[0].segments) + XCTAssertEqual(0, table.entries[0].pulses) + XCTAssertEqual(true, table.entries[0].alternateSegmentPulse) + } else { + XCTFail("Expected ScheduleEntry.tempBasal type") + } + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = SetInsulinScheduleCommand(nonce: 0x4e2c2717, tempBasalRate: 0.05, duration: .hours(2.5)) + XCTAssertEqual("1a0e4e2c271701007f05384000004800", cmd.data.hexadecimalString) + } + + func testLargerTempBasalCommand() { + do { + // 2.00 U/h for 1.5h + // Decode 1a 0e 87e8d03a 01 00cb 03 3840 0014 2014 + let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0e87e8d03a0100cb03384000142014")!) + + XCTAssertEqual(0x87e8d03a, cmd.nonce) + if case SetInsulinScheduleCommand.DeliverySchedule.tempBasal(let secondsRemaining, let firstSegmentPulses, let table) = cmd.deliverySchedule { + + XCTAssertEqual(1800, secondsRemaining) + XCTAssertEqual(0x14, firstSegmentPulses) + let entry = table.entries[0] + XCTAssertEqual(3, entry.segments) + XCTAssertEqual(20, entry.pulses) + } else { + XCTFail("Expected ScheduleEntry.tempBasal type") + } + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = SetInsulinScheduleCommand(nonce: 0x87e8d03a, tempBasalRate: 2, duration: .hours(1.5)) + XCTAssertEqual("1a0e87e8d03a0100cb03384000142014", cmd.data.hexadecimalString) + } + + func testCancelTempBasalCommand() { + do { + // Decode 1f 05 f76d34c4 62 + let cmd = try CancelDeliveryCommand(encodedData: Data(hexadecimalString: "1f05f76d34c462")!) + XCTAssertEqual(0xf76d34c4, cmd.nonce) + XCTAssertEqual(.beeeeeep, cmd.beepType) + XCTAssertEqual(.tempBasal, cmd.deliveryType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = CancelDeliveryCommand(nonce: 0xf76d34c4, deliveryType: .tempBasal, beepType: .beeeeeep) + XCTAssertEqual("1f05f76d34c462", cmd.data.hexadecimalString) + } + + func testCancelTempBasalnoBeepCommand() { + do { + let cmd = try CancelDeliveryCommand(encodedData: Data(hexadecimalString: "1f05f76d34c402")!) + XCTAssertEqual(0xf76d34c4, cmd.nonce) + XCTAssertEqual(.noBeep, cmd.beepType) + XCTAssertEqual(.tempBasal, cmd.deliveryType) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = CancelDeliveryCommand(nonce: 0xf76d34c4, deliveryType: .tempBasal, beepType: .noBeep) + XCTAssertEqual("1f05f76d34c402", cmd.data.hexadecimalString) + } + + func testZeroTempExtraCommand() { + do { + // 0 U/h for 0.5 hours + // 16 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ + // Decode 16 0e 7c 00 0000 6b49d200 0000 6b49d200 + + let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "160e7c0000006b49d20000006b49d200")!) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) + XCTAssertEqual(.minutes(60), cmd.programReminderInterval) + XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilNextPulse) + XCTAssertEqual(0, cmd.remainingPulses) + XCTAssertEqual(1, cmd.rateEntries.count) + let entry = cmd.rateEntries[0] + XCTAssertEqual(TimeInterval(seconds: 1800), entry.delayBetweenPulses) + XCTAssertEqual(TimeInterval(minutes: 30), entry.duration) + XCTAssertEqual(0, entry.rate) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = TempBasalExtraCommand(rate: 0, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: true, programReminderInterval: .minutes(60)) + XCTAssertEqual("160e7c0000006b49d20000006b49d200", cmd.data.hexadecimalString) + } + + func testZeroTempThreeHoursExtraCommand() { + do { + // 0 U/h for 3 hours + let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "162c7c0000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d200")!) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) + XCTAssertEqual(.minutes(60), cmd.programReminderInterval) + XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilNextPulse) + XCTAssertEqual(0, cmd.remainingPulses) + XCTAssertEqual(6, cmd.rateEntries.count) + let entry = cmd.rateEntries[0] + XCTAssertEqual(TimeInterval(seconds: 1800), entry.delayBetweenPulses) + XCTAssertEqual(TimeInterval(minutes: 30), entry.duration) + XCTAssertEqual(0, entry.rate) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = TempBasalExtraCommand(rate: 0, duration: .hours(3), acknowledgementBeep: false, completionBeep: true, programReminderInterval: .minutes(60)) + XCTAssertEqual("162c7c0000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d200", cmd.data.hexadecimalString) + } + + + func testTempBasalExtremeValues() { + do { + // 30 U/h for 12 hours + // Decode 1a 10 a958c5ad 01 04f5 18 3840 012c f12c 712c + let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a10a958c5ad0104f5183840012cf12c712c")!) + + XCTAssertEqual(0xa958c5ad, cmd.nonce) + if case SetInsulinScheduleCommand.DeliverySchedule.tempBasal(let secondsRemaining, let firstSegmentPulses, let table) = cmd.deliverySchedule { + + XCTAssertEqual(1800, secondsRemaining) + XCTAssertEqual(300, firstSegmentPulses) + XCTAssertEqual(2, table.entries.count) + let entry1 = table.entries[0] + XCTAssertEqual(16, entry1.segments) + XCTAssertEqual(300, entry1.pulses) + let entry2 = table.entries[1] + XCTAssertEqual(8, entry2.segments) + XCTAssertEqual(300, entry2.pulses) + } else { + XCTFail("Expected ScheduleEntry.tempBasal type") + } + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = SetInsulinScheduleCommand(nonce: 0xa958c5ad, tempBasalRate: 30, duration: .hours(12)) + XCTAssertEqual("1a10a958c5ad0104f5183840012cf12c712c", cmd.data.hexadecimalString) + } + + func testTempBasalExtraCommand() { + do { + // 30 U/h for 0.5 hours + // Decode 16 0e 7c 00 0bb8 000927c0 0bb8 000927c0 + let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "160e7c000bb8000927c00bb8000927c0")!) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) + XCTAssertEqual(.minutes(60), cmd.programReminderInterval) + XCTAssertEqual(TimeInterval(seconds: 6), cmd.delayUntilNextPulse) + XCTAssertEqual(300, cmd.remainingPulses) + XCTAssertEqual(1, cmd.rateEntries.count) + let entry = cmd.rateEntries[0] + XCTAssertEqual(TimeInterval(seconds: 6), entry.delayBetweenPulses) + XCTAssertEqual(TimeInterval(minutes: 30), entry.duration) + XCTAssertEqual(30, entry.rate) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = TempBasalExtraCommand(rate: 30, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: true, programReminderInterval: .minutes(60)) + XCTAssertEqual("160e7c000bb8000927c00bb8000927c0", cmd.data.hexadecimalString) + } + + func testBasalExtraCommandForOddPulseCountRate() { + + let cmd1 = TempBasalExtraCommand(rate: 0.05, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: true, programReminderInterval: .minutes(60)) + XCTAssertEqual("160e7c00000515752a00000515752a00", cmd1.data.hexadecimalString) + + let cmd2 = TempBasalExtraCommand(rate: 2.05, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(60)) + XCTAssertEqual("160e3c0000cd0085fac700cd0085fac7", cmd2.data.hexadecimalString) + + let cmd3 = TempBasalExtraCommand(rate: 2.10, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(60)) + XCTAssertEqual("160e3c0000d20082ca2400d20082ca24", cmd3.data.hexadecimalString) + + let cmd4 = TempBasalExtraCommand(rate: 2.15, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(60)) + XCTAssertEqual("160e3c0000d7007fbf7d00d7007fbf7d", cmd4.data.hexadecimalString) + } + + func testBasalExtraCommandPulseCount() { + // 16 LL RR MM NNNN XXXXXXXX YYYY ZZZZZZZZ YYYY ZZZZZZZZ + // 16 14 00 00 f5b9 000a0ad7 f5b9 000a0ad7 0aaf 000a0ad7 + // 16 14 00 00 f618 000a0ad7 f618 000a0ad7 0a50 000a0ad7 + let cmd2 = TempBasalExtraCommand(rate: 27.35, duration: .hours(12), acknowledgementBeep: false, completionBeep: false, programReminderInterval: 0) + XCTAssertEqual("16140000f5b9000a0ad7f5b9000a0ad70aaf000a0ad7", cmd2.data.hexadecimalString) + } + + func testTempBasalExtraCommandExtremeValues() { + do { + // 30 U/h for 12 hours + // Decode 16 14 3c 00 f618 000927c0 f618 000927c0 2328 000927c0 + let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "16143c00f618000927c0f618000927c02328000927c0")!) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(false, cmd.completionBeep) + XCTAssertEqual(.minutes(60), cmd.programReminderInterval) + XCTAssertEqual(TimeInterval(seconds: 6), cmd.delayUntilNextPulse) + XCTAssertEqual(6300, cmd.remainingPulses) + XCTAssertEqual(2, cmd.rateEntries.count) + let entry = cmd.rateEntries[0] + XCTAssertEqual(TimeInterval(seconds: 6), entry.delayBetweenPulses) + XCTAssertEqual(TimeInterval(hours: 10.5), entry.duration) + XCTAssertEqual(30, entry.rate) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + // Encode + let cmd = TempBasalExtraCommand(rate: 30, duration: .hours(12), acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(60)) + XCTAssertEqual("16143c00f618000927c0f618000927c02328000927c0", cmd.data.hexadecimalString) + } + + func testTempBasalExtraCommandExtremeValues2() { + do { + // 29.95 U/h for 12 hours + let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "16143c00f5af00092ba9f5af00092ba9231900092ba9")!) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(false, cmd.completionBeep) + XCTAssertEqual(.minutes(60), cmd.programReminderInterval) + XCTAssertEqual(TimeInterval(seconds: 6.01001), cmd.delayUntilNextPulse) + XCTAssertEqual(6289.5, cmd.remainingPulses) + XCTAssertEqual(2, cmd.rateEntries.count) + let entry1 = cmd.rateEntries[0] + let entry2 = cmd.rateEntries[1] + XCTAssertEqual(TimeInterval(seconds: 6.01001), entry1.delayBetweenPulses, accuracy: .ulpOfOne) + XCTAssertEqual(TimeInterval(hours: 12), entry1.duration + entry2.duration, accuracy: 1) + XCTAssertEqual(29.95, entry1.rate, accuracy: 0.025) + + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + + let cmd = TempBasalExtraCommand(rate: 29.95, duration: .hours(12), acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(60)) + XCTAssertEqual("16143c00f5af00092ba9f5af00092ba9231900092ba9", cmd.data.hexadecimalString) + } +} diff --git a/OmniKitUI/Info.plist b/OmniKitUI/Info.plist new file mode 100644 index 000000000..3d809ffe3 --- /dev/null +++ b/OmniKitUI/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 2.1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/OmniKitUI/OmniKitUI.h b/OmniKitUI/OmniKitUI.h new file mode 100644 index 000000000..02da72a59 --- /dev/null +++ b/OmniKitUI/OmniKitUI.h @@ -0,0 +1,19 @@ +// +// OmniKitUI.h +// OmniKitUI +// +// Created by Pete Schwamb on 8/26/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +#import + +//! Project version number for OmniKitUI. +FOUNDATION_EXPORT double OmniKitUIVersionNumber; + +//! Project version string for OmniKitUI. +FOUNDATION_EXPORT const unsigned char OmniKitUIVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/OmniKitUI/OmniKitUI.xcassets/Contents.json b/OmniKitUI/OmniKitUI.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/OmniKitUI/OmniKitUI.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json new file mode 100644 index 000000000..27c1a878d --- /dev/null +++ b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pod1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pod2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pod3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod1x.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod1x.png new file mode 100644 index 0000000000000000000000000000000000000000..e15cf636c8174738ad6b17fcb46d9f83da6e476a GIT binary patch literal 8892 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 H_5J?>?oI;J literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod2x.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7656aef0a27e221d45f79e531eee8d093603c7a8 GIT binary patch literal 21306 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json new file mode 100644 index 000000000..00500888f --- /dev/null +++ b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "PodBottom1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "PodBottom2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "PodBottom3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.png b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.png new file mode 100644 index 0000000000000000000000000000000000000000..109d65d8a4d7b92bca9ef791bee623735974d938 GIT binary patch literal 11922 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 G7y4gHcJcxM literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.png b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.png new file mode 100644 index 0000000000000000000000000000000000000000..33024fcc87c9c2a6558d2abcc11cb4805fceaa13 GIT binary patch literal 32936 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom3x.png b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom3x.png new file mode 100644 index 0000000000000000000000000000000000000000..57d23825089e7c50d7cf656dac41a83fe902026f GIT binary patch literal 55231 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json new file mode 100644 index 000000000..c1722d577 --- /dev/null +++ b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pod_1x-2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pod_1x-1.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pod_1x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b89405d93442b07eb1116c7463790e9583af9470 GIT binary patch literal 67130 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 z

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 Qj{pDw07*qoM6N<$g7mTL7ytkO literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..086e4a4c9bb964e24d12d0380be767ae4e32c86c GIT binary patch literal 138788 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 z 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmnipodPumpManager.storyboard b/OmniKitUI/OmnipodPumpManager.storyboard new file mode 100644 index 000000000..a0c36d97b --- /dev/null +++ b/OmniKitUI/OmnipodPumpManager.storyboard @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift new file mode 100644 index 000000000..86b6a5f8a --- /dev/null +++ b/OmniKitUI/PumpManager/OmniPodPumpManager+UI.swift @@ -0,0 +1,96 @@ +// +// OmniPodPumpManager+UI.swift +// OmniKitUI +// +// Created by Pete Schwamb on 8/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +import UIKit +import LoopKit +import LoopKitUI +import OmniKit + +extension OmnipodPumpManager: PumpManagerUI { + + static public func setupViewController() -> (UIViewController & PumpManagerSetupViewController & CompletionNotifying) { + return OmnipodPumpManagerSetupViewController.instantiateFromStoryboard() + } + + public func settingsViewController() -> (UIViewController & CompletionNotifying) { + let settings = OmnipodSettingsViewController(pumpManager: self) + let nav = SettingsNavigationViewController(rootViewController: settings) + return nav + } + + public var smallImage: UIImage? { + return UIImage(named: "Pod", in: Bundle(for: OmnipodSettingsViewController.self), compatibleWith: nil)! + } + + public func hudProvider() -> HUDProvider? { + return OmnipodHUDProvider(pumpManager: self) + } + + public static func createHUDViews(rawValue: HUDProvider.HUDViewsRawState) -> [BaseHUDView] { + return OmnipodHUDProvider.createHUDViews(rawValue: rawValue) + } + +} + +// MARK: - DeliveryLimitSettingsTableViewControllerSyncSource +extension OmnipodPumpManager { + public func syncDeliveryLimitSettings(for viewController: DeliveryLimitSettingsTableViewController, completion: @escaping (DeliveryLimitSettingsResult) -> Void) { + guard let maxBasalRate = viewController.maximumBasalRatePerHour, + let maxBolus = viewController.maximumBolus else + { + completion(.failure(PodCommsError.invalidData)) + return + } + + completion(.success(maximumBasalRatePerHour: maxBasalRate, maximumBolus: maxBolus)) + } + + public func syncButtonTitle(for viewController: DeliveryLimitSettingsTableViewController) -> String { + return LocalizedString("Save", comment: "Title of button to save delivery limit settings") } + + public func syncButtonDetailText(for viewController: DeliveryLimitSettingsTableViewController) -> String? { + return nil + } + + public func deliveryLimitSettingsTableViewControllerIsReadOnly(_ viewController: DeliveryLimitSettingsTableViewController) -> Bool { + return false + } +} + +// MARK: - BasalScheduleTableViewControllerSyncSource +extension OmnipodPumpManager { + + public func syncScheduleValues(for viewController: BasalScheduleTableViewController, completion: @escaping (SyncBasalScheduleResult) -> Void) { + let newSchedule = BasalSchedule(repeatingScheduleValues: viewController.scheduleItems) + setBasalSchedule(newSchedule) { (error) in + if let error = error { + completion(.failure(error)) + } else { + completion(.success(scheduleItems: viewController.scheduleItems, timeZone: self.state.timeZone)) + } + } + } + + public func syncButtonTitle(for viewController: BasalScheduleTableViewController) -> String { + if self.hasActivePod { + return LocalizedString("Sync With Pod", comment: "Title of button to sync basal profile from pod") + } else { + return LocalizedString("Save", comment: "Title of button to sync basal profile when no pod paired") + } + } + + public func syncButtonDetailText(for viewController: BasalScheduleTableViewController) -> String? { + return nil + } + + public func basalScheduleTableViewControllerIsReadOnly(_ viewController: BasalScheduleTableViewController) -> Bool { + return false + } +} diff --git a/OmniKitUI/PumpManager/OmnipodHUDProvider.swift b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift new file mode 100644 index 000000000..41a6881ba --- /dev/null +++ b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift @@ -0,0 +1,214 @@ +// +// OmnipodHUDProvider.swift +// OmniKitUI +// +// Created by Pete Schwamb on 11/26/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKit +import LoopKitUI +import OmniKit + +internal class OmnipodHUDProvider: NSObject, HUDProvider, PodStateObserver { + var managerIdentifier: String { + return OmnipodPumpManager.managerIdentifier + } + + private var podState: PodState? { + didSet { + guard visible else { + return + } + + guard oldValue != podState else { + return + } + + if oldValue?.lastInsulinMeasurements != podState?.lastInsulinMeasurements { + updateReservoirView() + } + + if oldValue?.fault != podState?.fault { + updateFaultDisplay() + } + + if oldValue != nil && podState == nil { + updateReservoirView() + updateFaultDisplay() + } + + if (oldValue == nil || podState == nil) && (oldValue != nil || podState != nil) { + updatePodLifeView() + } + } + } + + private let pumpManager: OmnipodPumpManager + + private var reservoirView: OmnipodReservoirView? + + private var podLifeView: PodLifeHUDView? + + var visible: Bool = false { + didSet { + if oldValue != visible && visible { + hudDidAppear() + } + } + } + + public init(pumpManager: OmnipodPumpManager) { + self.pumpManager = pumpManager + self.podState = pumpManager.state.podState + super.init() + self.pumpManager.addPodStateObserver(self, queue: .main) + } + + private func updateReservoirView() { + if let lastInsulinMeasurements = podState?.lastInsulinMeasurements, + let reservoirView = reservoirView, + let podState = podState + { + let reservoirVolume = lastInsulinMeasurements.reservoirVolume + + let reservoirLevel = reservoirVolume?.asReservoirPercentage() + + var reservoirAlertState: ReservoirAlertState = .ok + for (_, alert) in podState.activeAlerts { + if case .lowReservoirAlarm = alert { + reservoirAlertState = .lowReservoir + break + } + } + + reservoirView.update(volume: reservoirVolume, at: lastInsulinMeasurements.validTime, level: reservoirLevel, reservoirAlertState: reservoirAlertState) + } + } + + private func updateFaultDisplay() { + if let podLifeView = podLifeView { + if podState?.fault != nil { + podLifeView.alertState = .fault + } else { + podLifeView.alertState = .none + } + } + } + + private func updatePodLifeView() { + guard let podLifeView = podLifeView else { + return + } + if let activatedAt = podState?.activatedAt, let expiresAt = podState?.expiresAt { + let lifetime = expiresAt.timeIntervalSince(activatedAt) + podLifeView.setPodLifeCycle(startTime: activatedAt, lifetime: lifetime) + } else { + podLifeView.setPodLifeCycle(startTime: Date(), lifetime: 0) + } + } + + public func createHUDViews() -> [BaseHUDView] { + self.reservoirView = OmnipodReservoirView.instantiate() + self.updateReservoirView() + + podLifeView = PodLifeHUDView.instantiate() + + if visible { + updatePodLifeView() + updateFaultDisplay() + } + + return [reservoirView, podLifeView].compactMap { $0 } + } + + public func didTapOnHUDView(_ view: BaseHUDView) -> HUDTapAction? { + if podState?.fault != nil { + return HUDTapAction.presentViewController(PodReplacementNavigationController.instantiatePodReplacementFlow(pumpManager)) + } else { + return HUDTapAction.presentViewController(pumpManager.settingsViewController()) + } + } + + func hudDidAppear() { + updatePodLifeView() + updateReservoirView() + updateFaultDisplay() + pumpManager.refreshStatus() + } + + func hudDidDisappear(_ animated: Bool) { + if let podLifeView = podLifeView { + podLifeView.pauseUpdates() + } + } + + public var hudViewsRawState: HUDProvider.HUDViewsRawState { + var rawValue: HUDProvider.HUDViewsRawState = [:] + + if let podState = podState { + rawValue["podActivatedAt"] = podState.activatedAt + let lifetime: TimeInterval + if let expiresAt = podState.expiresAt, let activatedAt = podState.activatedAt { + lifetime = expiresAt.timeIntervalSince(activatedAt) + } else { + lifetime = 0 + } + rawValue["lifetime"] = lifetime + rawValue["alerts"] = podState.activeAlerts.values.map { $0.rawValue } + } + + if let lastInsulinMeasurements = podState?.lastInsulinMeasurements, lastInsulinMeasurements.reservoirVolume != nil { + rawValue["reservoirVolume"] = lastInsulinMeasurements.reservoirVolume + rawValue["validTime"] = lastInsulinMeasurements.validTime + } + + return rawValue + } + + public static func createHUDViews(rawValue: HUDProvider.HUDViewsRawState) -> [BaseHUDView] { + guard let podActivatedAt = rawValue["podActivatedAt"] as? Date, + let lifetime = rawValue["lifetime"] as? Double, + let rawAlerts = rawValue["alerts"] as? [PodAlert.RawValue] else + { + return [] + } + + let reservoirView: OmnipodReservoirView? + + let alerts = rawAlerts.compactMap { PodAlert.init(rawValue: $0) } + let reservoirVolume = rawValue["reservoirVolume"] as? Double + let validTime = rawValue["validTime"] as? Date + + if let validTime = validTime + { + reservoirView = OmnipodReservoirView.instantiate() + let reservoirLevel = reservoirVolume?.asReservoirPercentage() + var reservoirAlertState: ReservoirAlertState = .ok + for alert in alerts { + if case .lowReservoirAlarm = alert { + reservoirAlertState = .lowReservoir + } + } + reservoirView!.update(volume: reservoirVolume, at: validTime, level: reservoirLevel, reservoirAlertState: reservoirAlertState) + } else { + reservoirView = nil + } + + let podLifeHUDView = PodLifeHUDView.instantiate() + podLifeHUDView.setPodLifeCycle(startTime: podActivatedAt, lifetime: lifetime) + + return [reservoirView, podLifeHUDView].compactMap({ $0 }) + } + + func podStateDidUpdate(_ podState: PodState?) { + self.podState = podState + } +} + +extension Double { + func asReservoirPercentage() -> Double { + return min(1, max(0, self / Pod.reservoirCapacity)) + } +} diff --git a/OmniKitUI/ViewControllers/CommandResponseViewController.swift b/OmniKitUI/ViewControllers/CommandResponseViewController.swift new file mode 100644 index 000000000..55a67635b --- /dev/null +++ b/OmniKitUI/ViewControllers/CommandResponseViewController.swift @@ -0,0 +1,64 @@ +// +// CommandResponseViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 8/28/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKitUI +import OmniKit +import RileyLinkBLEKit + +extension CommandResponseViewController { + typealias T = CommandResponseViewController + + private static let successText = LocalizedString("Succeeded", comment: "A message indicating a command succeeded") + + static func changeTime(pumpManager: OmnipodPumpManager) -> T { + return T { (completionHandler) -> String in + pumpManager.setTime() { (error) in + let response: String + if let error = error as? LocalizedError { + let sentenceFormat = LocalizedString("%@.", comment: "Appends a full-stop to a statement") + let messageWithRecovery = [error.failureReason, error.recoverySuggestion].compactMap({ $0 }).map({ + String(format: sentenceFormat, $0) + }).joined(separator: "\n") + + if messageWithRecovery.isEmpty { + response = String(describing: error) + } else { + response = messageWithRecovery + } + } else if let error = error { + response = String(describing: error) + } else { + response = self.successText + } + DispatchQueue.main.async { + completionHandler(response) + } + } + return LocalizedString("Changing time…", comment: "Progress message for changing pod time.") + } + } + + + static func testCommand(pumpManager: OmnipodPumpManager) -> T { + return T { (completionHandler) -> String in + pumpManager.testingCommands() { (error) in + let response: String + if let error = error { + response = String(describing: error) + } else { + response = self.successText + } + DispatchQueue.main.async { + completionHandler(response) + } + } + return LocalizedString("Testing Commands…", comment: "Progress message for testing commands.") + } + } +} diff --git a/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift b/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift new file mode 100644 index 000000000..e5aea5951 --- /dev/null +++ b/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift @@ -0,0 +1,203 @@ +// +// InsertCannulaSetupViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 9/18/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKit +import LoopKitUI +import RileyLinkKit +import OmniKit + +class InsertCannulaSetupViewController: SetupTableViewController { + + var pumpManager: OmnipodPumpManager! + + // MARK: - + + @IBOutlet weak var activityIndicator: SetupIndicatorView! + + @IBOutlet weak var loadingLabel: UILabel! + + private var loadingText: String? { + didSet { + tableView.beginUpdates() + loadingLabel.text = loadingText + + let isHidden = (loadingText == nil) + loadingLabel.isHidden = isHidden + tableView.endUpdates() + } + } + + private var cancelErrorCount = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + continueState = .initial + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if case .startingInsertion = continueState { + return + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + // MARK: - Navigation + + private enum State { + case initial + case startingInsertion + case inserting(finishTime: CFTimeInterval) + case fault + case ready + } + + private var continueState: State = .initial { + didSet { + switch continueState { + case .initial: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.setConnectTitle() + case .startingInsertion: + activityIndicator.state = .indeterminantProgress + footerView.primaryButton.isEnabled = false + lastError = nil + case .inserting(let finishTime): + activityIndicator.state = .timedProgress(finishTime: CACurrentMediaTime() + finishTime) + footerView.primaryButton.isEnabled = false + lastError = nil + case .fault: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.setDeactivateTitle() + case .ready: + activityIndicator.state = .completed + footerView.primaryButton.isEnabled = true + footerView.primaryButton.resetTitle() + lastError = nil + } + } + } + + private var lastError: Error? { + didSet { + guard oldValue != nil || lastError != nil else { + return + } + + var errorText = lastError?.localizedDescription + + if let error = lastError as? LocalizedError { + let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ") + "." + + if !localizedText.isEmpty { + errorText = localizedText + } + } + + loadingText = errorText + + // If we have an error, update the continue state + if let podCommsError = lastError as? PodCommsError, + case PodCommsError.podFault = podCommsError + { + continueState = .fault + } else if lastError != nil { + continueState = .initial + } + } + } + + private func navigateToReplacePod() { + performSegue(withIdentifier: "ReplacePod", sender: nil) + } + + override func continueButtonPressed(_ sender: Any) { + switch continueState { + case .initial: + continueState = .startingInsertion + insertCannula() + case .ready: + super.continueButtonPressed(sender) + case .fault: + navigateToReplacePod() + case .startingInsertion, + .inserting: + break + } + } + + override func cancelButtonPressed(_ sender: Any) { + let confirmVC = UIAlertController(pumpDeletionHandler: { + self.navigateToReplacePod() + }) + present(confirmVC, animated: true) {} + } + + func insertCannula() { + pumpManager.insertCannula() { (result) in + DispatchQueue.main.async { + switch(result) { + case .success(let finishTime): + self.continueState = .inserting(finishTime: finishTime) + let delay = finishTime + if delay > 0 { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + self.continueState = .ready + } + } else { + self.continueState = .ready + } + case .failure(let error): + self.lastError = error + } + } + } + } +} + +private extension SetupButton { + func setConnectTitle() { + setTitle(LocalizedString("Insert Cannula", comment: "Button title to insert cannula during setup"), for: .normal) + } + func setDeactivateTitle() { + setTitle(LocalizedString("Deactivate", comment: "Button title to deactivate pod because of fault during setup"), for: .normal) + } + +} + +private extension UIAlertController { + convenience init(pumpDeletionHandler handler: @escaping () -> Void) { + self.init( + title: nil, + message: LocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"), + preferredStyle: .actionSheet + ) + + addAction(UIAlertAction( + title: LocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"), + style: .destructive, + handler: { (_) in + handler() + } + )) + + let exit = LocalizedString("Continue", comment: "The title of the continue action in an action sheet") + addAction(UIAlertAction(title: exit, style: .default, handler: nil)) + } +} + diff --git a/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift b/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift new file mode 100644 index 000000000..4a6001172 --- /dev/null +++ b/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift @@ -0,0 +1,113 @@ +// +// OmnipodPumpManagerSetupViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 8/4/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +import UIKit +import LoopKit +import LoopKitUI +import OmniKit +import RileyLinkBLEKit +import RileyLinkKit +import RileyLinkKitUI + +// PumpManagerSetupViewController +public class OmnipodPumpManagerSetupViewController: RileyLinkManagerSetupViewController { + + class func instantiateFromStoryboard() -> OmnipodPumpManagerSetupViewController { + return UIStoryboard(name: "OmnipodPumpManager", bundle: Bundle(for: OmnipodPumpManagerSetupViewController.self)).instantiateInitialViewController() as! OmnipodPumpManagerSetupViewController + } + + override public func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .white + navigationBar.shadowImage = UIImage() + + if let omnipodPairingViewController = topViewController as? PairPodSetupViewController, let rileyLinkPumpManager = rileyLinkPumpManager { + omnipodPairingViewController.rileyLinkPumpManager = rileyLinkPumpManager + } + } + + private(set) var pumpManager: OmnipodPumpManager? + + /* + 1. RileyLink + - RileyLinkPumpManagerState + + 2. Basal Rates & Delivery Limits + + 3. Pod Pairing/Priming + + 4. Cannula Insertion + + 5. Pod Setup Complete + */ + + override public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { + super.navigationController(navigationController, willShow: viewController, animated: animated) + + // Read state values + let viewControllers = navigationController.viewControllers + let count = navigationController.viewControllers.count + + if count >= 2 { + switch viewControllers[count - 2] { + case let vc as PairPodSetupViewController: + pumpManager = vc.pumpManager + default: + break + } + } + + if let setupViewController = viewController as? SetupTableViewController { + setupViewController.delegate = self + } + + + // Set state values + switch viewController { + case let vc as PairPodSetupViewController: + vc.rileyLinkPumpManager = rileyLinkPumpManager + if let deviceProvider = rileyLinkPumpManager?.rileyLinkDeviceProvider, let basalSchedule = basalSchedule { + let connectionManagerState = rileyLinkPumpManager?.rileyLinkConnectionManagerState + let schedule = BasalSchedule(repeatingScheduleValues: basalSchedule.items) + let pumpManagerState = OmnipodPumpManagerState(podState: nil, timeZone: .currentFixed, basalSchedule: schedule, rileyLinkConnectionManagerState: connectionManagerState) + let pumpManager = OmnipodPumpManager( + state: pumpManagerState, + rileyLinkDeviceProvider: deviceProvider, + rileyLinkConnectionManager: rileyLinkPumpManager?.rileyLinkConnectionManager) + vc.pumpManager = pumpManager + setupDelegate?.pumpManagerSetupViewController(self, didSetUpPumpManager: pumpManager) + } + case let vc as InsertCannulaSetupViewController: + vc.pumpManager = pumpManager + case let vc as PodSetupCompleteViewController: + vc.pumpManager = pumpManager + default: + break + } + } + + override open func finishedSetup() { + if let pumpManager = pumpManager { + let settings = OmnipodSettingsViewController(pumpManager: pumpManager) + setViewControllers([settings], animated: true) + } + } + + public func finishedSettingsDisplay() { + completionDelegate?.completionNotifyingDidComplete(self) + } +} + +extension OmnipodPumpManagerSetupViewController: SetupTableViewControllerDelegate { + public func setupTableViewControllerCancelButtonPressed(_ viewController: SetupTableViewController) { + completionDelegate?.completionNotifyingDidComplete(self) + } +} diff --git a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift new file mode 100644 index 000000000..74c10d26a --- /dev/null +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -0,0 +1,805 @@ +// +// OmnipodSettingsViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 8/5/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import RileyLinkKitUI +import LoopKit +import OmniKit +import LoopKitUI + +class OmnipodSettingsViewController: RileyLinkSettingsViewController { + + let pumpManager: OmnipodPumpManager + + var statusError: Error? + + var podState: PodState? + + var pumpManagerStatus: PumpManagerStatus? + + private var bolusProgressTimer: Timer? + + init(pumpManager: OmnipodPumpManager) { + self.pumpManager = pumpManager + podState = pumpManager.state.podState + pumpManagerStatus = pumpManager.status + + let devicesSectionIndex = OmnipodSettingsViewController.sectionList(podState).firstIndex(of: .rileyLinks)! + + super.init(rileyLinkPumpManager: pumpManager, devicesSectionIndex: devicesSectionIndex, style: .grouped) + + pumpManager.addStatusObserver(self, queue: .main) + pumpManager.addPodStateObserver(self, queue: .main) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public var podImage: UIImage? { + return UIImage(named: "PodLarge", in: Bundle(for: OmnipodSettingsViewController.self), compatibleWith: nil)! + } + + lazy var suspendResumeTableViewCell: SuspendResumeTableViewCell = { + let cell = SuspendResumeTableViewCell(style: .default, reuseIdentifier: nil) + cell.basalDeliveryState = pumpManager.status.basalDeliveryState + return cell + }() + + override func viewDidLoad() { + super.viewDidLoad() + + title = LocalizedString("Pod Settings", comment: "Title of the pod settings view controller") + + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 44 + + tableView.sectionHeaderHeight = UITableView.automaticDimension + tableView.estimatedSectionHeaderHeight = 55 + + tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className) + tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className) + tableView.register(AlarmsTableViewCell.self, forCellReuseIdentifier: AlarmsTableViewCell.className) + tableView.register(ExpirationReminderDateTableViewCell.nib(), forCellReuseIdentifier: ExpirationReminderDateTableViewCell.className) + + let imageView = UIImageView(image: podImage) + imageView.contentMode = .center + imageView.frame.size.height += 18 // feels right + tableView.tableHeaderView = imageView + tableView.tableHeaderView?.backgroundColor = UIColor.white + + let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:))) + self.navigationItem.setRightBarButton(button, animated: false) + } + + @objc func doneTapped(_ sender: Any) { + done() + } + + private func done() { + if let nav = navigationController as? SettingsNavigationViewController { + nav.notifyComplete() + } + if let nav = navigationController as? OmnipodPumpManagerSetupViewController { + nav.finishedSettingsDisplay() + } + } + + override func viewWillAppear(_ animated: Bool) { + if clearsSelectionOnViewWillAppear { + // Manually invoke the delegate for rows deselecting on appear + for indexPath in tableView.indexPathsForSelectedRows ?? [] { + _ = tableView(tableView, willDeselectRowAt: indexPath) + } + } + + if let configSectionIdx = self.sections.firstIndex(of: .configuration) { + self.tableView.reloadRows(at: [IndexPath(row: ConfigurationRow.reminder.rawValue, section: configSectionIdx)], with: .none) + } + + super.viewWillAppear(animated) + } + + // MARK: - Formatters + + private lazy var dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.timeStyle = .short + dateFormatter.dateStyle = .medium + dateFormatter.doesRelativeDateFormatting = true + //dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "EEEE 'at' hm", options: 0, locale: nil) + return dateFormatter + }() + + + // MARK: - Data Source + + private enum Section: Int, CaseIterable { + case podDetails = 0 + case actions + case configuration + case status + case rileyLinks + case deletePumpManager + } + + private class func sectionList(_ podState: PodState?) -> [Section] { + if let podState = podState { + if podState.unfinishedPairing { + return [.actions, .rileyLinks] + } else { + return [.podDetails, .actions, .configuration, .status, .rileyLinks] + } + } else { + return [.actions, .rileyLinks, .deletePumpManager] + } + } + + private var sections: [Section] { + return OmnipodSettingsViewController.sectionList(podState) + } + + private enum PodDetailsRow: Int, CaseIterable { + case activatedAt = 0 + case expiresAt + case podAddress + case podLot + case podTid + case piVersion + case pmVersion + } + + private enum ActionsRow: Int, CaseIterable { + case suspendResume = 0 + case replacePod + } + + private var actions: [ActionsRow] { + if podState == nil || podState?.unfinishedPairing == true { + return [.replacePod] + } else { + return ActionsRow.allCases + } + } + + private enum ConfigurationRow: Int, CaseIterable { + case reminder = 0 + case timeZoneOffset + case testCommand + } + + fileprivate enum StatusRow: Int, CaseIterable { + case bolus = 0 + case basal + case alarms + case reservoirLevel + case deliveredInsulin + } + + // MARK: UITableViewDataSource + + override func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch sections[section] { + case .podDetails: + return PodDetailsRow.allCases.count + case .actions: + return actions.count + case .configuration: + return ConfigurationRow.allCases.count + case .status: + return StatusRow.allCases.count + case .rileyLinks: + return super.tableView(tableView, numberOfRowsInSection: section) + case .deletePumpManager: + return 1 + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch sections[section] { + case .podDetails: + return LocalizedString("Device Information", comment: "The title of the device information section in settings") + case .actions: + return nil + case .configuration: + return LocalizedString("Configuration", comment: "The title of the configuration section in settings") + case .status: + return LocalizedString("Status", comment: "The title of the status section in settings") + case .rileyLinks: + return super.tableView(tableView, titleForHeaderInSection: section) + case .deletePumpManager: + return " " // Use an empty string for more dramatic spacing + } + } + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + switch sections[section] { + case .rileyLinks: + return super.tableView(tableView, viewForHeaderInSection: section) + case .podDetails, .actions, .configuration, .status, .deletePumpManager: + return nil + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch sections[indexPath.section] { + case .podDetails: + let podState = self.podState! + switch PodDetailsRow(rawValue: indexPath.row)! { + case .activatedAt: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Active Time", comment: "The title of the cell showing the pod activated at time") + cell.setDetailAge(podState.activatedAt?.timeIntervalSinceNow) + return cell + case .expiresAt: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + if let expiresAt = podState.expiresAt { + if expiresAt.timeIntervalSinceNow > 0 { + cell.textLabel?.text = LocalizedString("Expires", comment: "The title of the cell showing the pod expiration") + } else { + cell.textLabel?.text = LocalizedString("Expired", comment: "The title of the cell showing the pod expiration after expiry") + } + } + cell.setDetailDate(podState.expiresAt, formatter: dateFormatter) + return cell + case .podAddress: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Assigned Address", comment: "The title text for the address assigned to the pod") + cell.detailTextLabel?.text = String(format:"%04X", podState.address) + return cell + case .podLot: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Lot", comment: "The title of the cell showing the pod lot id") + cell.detailTextLabel?.text = String(format:"L%d", podState.lot) + return cell + case .podTid: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("TID", comment: "The title of the cell showing the pod TID") + cell.detailTextLabel?.text = String(format:"%07d", podState.tid) + return cell + case .piVersion: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("PI Version", comment: "The title of the cell showing the pod pi version") + cell.detailTextLabel?.text = podState.piVersion + return cell + case .pmVersion: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("PM Version", comment: "The title of the cell showing the pod pm version") + cell.detailTextLabel?.text = podState.pmVersion + return cell + } + case .actions: + + switch actions[indexPath.row] { + case .suspendResume: + return suspendResumeTableViewCell + case .replacePod: + let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell + + if podState == nil { + cell.textLabel?.text = LocalizedString("Pair New Pod", comment: "The title of the command to pair new pod") + } else if podState?.fault != nil { + cell.textLabel?.text = LocalizedString("Replace Pod Now", comment: "The title of the command to replace pod when there is a pod fault") + } else if let podState = podState, podState.unfinishedPairing { + cell.textLabel?.text = LocalizedString("Finish pod setup", comment: "The title of the command to finish pod setup") + } else { + cell.textLabel?.text = LocalizedString("Replace Pod", comment: "The title of the command to replace pod") + cell.tintColor = .deleteColor + } + + cell.isEnabled = true + return cell + } + case .configuration: + + switch ConfigurationRow(rawValue: indexPath.row)! { + case .reminder: + let cell = tableView.dequeueReusableCell(withIdentifier: ExpirationReminderDateTableViewCell.className, for: indexPath) as! ExpirationReminderDateTableViewCell + if let podState = podState, let reminderDate = pumpManager.expirationReminderDate { + cell.titleLabel.text = LocalizedString("Expiration Reminder", comment: "The title of the cell showing the pod expiration reminder date") + cell.date = reminderDate + cell.datePicker.datePickerMode = .dateAndTime + cell.datePicker.maximumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMinTimeBeforeExpiration) + cell.datePicker.minimumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMaxTimeBeforeExpiration) + cell.datePicker.minuteInterval = 1 + cell.delegate = self + print("cell selection style: \(cell.selectionStyle)") + } + return cell + case .timeZoneOffset: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Change Time Zone", comment: "The title of the command to change pump time zone") + + let localTimeZone = TimeZone.current + let localTimeZoneName = localTimeZone.abbreviation() ?? localTimeZone.identifier + + if let timeZone = pumpManagerStatus?.timeZone { + let timeZoneDiff = TimeInterval(timeZone.secondsFromGMT() - localTimeZone.secondsFromGMT()) + let formatter = DateComponentsFormatter() + formatter.allowedUnits = [.hour, .minute] + let diffString = timeZoneDiff != 0 ? formatter.string(from: abs(timeZoneDiff)) ?? String(abs(timeZoneDiff)) : "" + + cell.detailTextLabel?.text = String(format: LocalizedString("%1$@%2$@%3$@", comment: "The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00)"), localTimeZoneName, timeZoneDiff != 0 ? (timeZoneDiff < 0 ? "-" : "+") : "", diffString) + } + cell.accessoryType = .disclosureIndicator + return cell + case .testCommand: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Test Command", comment: "The title of the command to run the test command") + cell.accessoryType = .disclosureIndicator + return cell + } + + case .status: + let podState = self.podState! + let statusRow = StatusRow(rawValue: indexPath.row)! + if statusRow == .alarms { + let cell = tableView.dequeueReusableCell(withIdentifier: AlarmsTableViewCell.className, for: indexPath) as! AlarmsTableViewCell + cell.textLabel?.text = LocalizedString("Alarms", comment: "The title of the cell showing alarm status") + cell.alerts = podState.activeAlerts + return cell + } else { + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + + switch statusRow { + case .bolus: + cell.textLabel?.text = LocalizedString("Bolus Delivery", comment: "The title of the cell showing pod bolus status") + + let deliveredUnits: Double? + if let dose = podState.unfinalizedBolus { + deliveredUnits = pumpManager.roundToSupportedBolusVolume(units: dose.progress * dose.units) + } else { + deliveredUnits = nil + } + + cell.setDetailBolus(suspended: podState.suspended, dose: podState.unfinalizedBolus, deliveredUnits: deliveredUnits) + // TODO: This timer is in the wrong context; should be part of a custom bolus progress cell +// if bolusProgressTimer == nil { +// bolusProgressTimer = Timer.scheduledTimer(withTimeInterval: .seconds(2), repeats: true) { [weak self] (_) in +// self?.tableView.reloadRows(at: [indexPath], with: .none) +// } +// } + case .basal: + cell.textLabel?.text = LocalizedString("Basal Delivery", comment: "The title of the cell showing pod basal status") + cell.setDetailBasal(suspended: podState.suspended, dose: podState.unfinalizedTempBasal) + case .reservoirLevel: + cell.textLabel?.text = LocalizedString("Reservoir", comment: "The title of the cell showing reservoir status") + cell.setReservoirDetail(podState.lastInsulinMeasurements) + case .deliveredInsulin: + cell.textLabel?.text = LocalizedString("Insulin Delivered", comment: "The title of the cell showing delivered insulin") + cell.setDeliveredInsulinDetail(podState.lastInsulinMeasurements) + default: + break + } + return cell + } + case .rileyLinks: + return super.tableView(tableView, cellForRowAt: indexPath) + case .deletePumpManager: + let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell + + cell.textLabel?.text = LocalizedString("Switch from Omnipod Pumps", comment: "Title text for the button to delete Omnipod PumpManager") + cell.textLabel?.textAlignment = .center + cell.tintColor = .deleteColor + cell.isEnabled = true + return cell + } + } + + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + switch sections[indexPath.section] { + case .podDetails: + return false + case .status: + switch StatusRow(rawValue: indexPath.row)! { + case .alarms: + return true + default: + return false + } + case .actions, .configuration, .rileyLinks, .deletePumpManager: + return true + } + } + + + override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + if indexPath == IndexPath(row: ConfigurationRow.reminder.rawValue, section: Section.configuration.rawValue) { + tableView.beginUpdates() + } + return indexPath + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let sender = tableView.cellForRow(at: indexPath) + + switch sections[indexPath.section] { + case .podDetails: + break + case .actions: + switch actions[indexPath.row] { + case .suspendResume: + suspendResumeTapped() + tableView.deselectRow(at: indexPath, animated: true) + case .replacePod: + let vc: UIViewController + if podState == nil || podState!.setupProgress.primingNeeded { + vc = PodReplacementNavigationController.instantiateNewPodFlow(pumpManager) + } else if podState?.fault != nil { + vc = PodReplacementNavigationController.instantiatePodReplacementFlow(pumpManager) + } else if let podState = podState, podState.unfinishedPairing { + vc = PodReplacementNavigationController.instantiateInsertCannulaFlow(pumpManager) + } else { + vc = PodReplacementNavigationController.instantiatePodReplacementFlow(pumpManager) + } + if var completionNotifying = vc as? CompletionNotifying { + completionNotifying.completionDelegate = self + } + self.navigationController?.present(vc, animated: true, completion: nil) + } + case .status: + switch StatusRow(rawValue: indexPath.row)! { + case .alarms: + if let cell = tableView.cellForRow(at: indexPath) as? AlarmsTableViewCell { + cell.isLoading = true + cell.isEnabled = false + let activeSlots = AlertSet(slots: Array(cell.alerts.keys)) + if activeSlots.count > 0 { + pumpManager.acknowledgeAlerts(activeSlots) { (updatedAlerts) in + DispatchQueue.main.async { + cell.isLoading = false + cell.isEnabled = true + if let updatedAlerts = updatedAlerts { + cell.alerts = updatedAlerts + } + } + } + } + } + default: + break + } + case .configuration: + switch ConfigurationRow(rawValue: indexPath.row)! { + case .reminder: + tableView.deselectRow(at: indexPath, animated: true) + tableView.endUpdates() + break + case .timeZoneOffset: + let vc = CommandResponseViewController.changeTime(pumpManager: pumpManager) + vc.title = sender?.textLabel?.text + show(vc, sender: indexPath) + case .testCommand: + let vc = CommandResponseViewController.testCommand(pumpManager: pumpManager) + vc.title = sender?.textLabel?.text + show(vc, sender: indexPath) + } + case .rileyLinks: + let device = devicesDataSource.devices[indexPath.row] + let vc = RileyLinkDeviceTableViewController(device: device) + self.show(vc, sender: sender) + case .deletePumpManager: + let confirmVC = UIAlertController(pumpManagerDeletionHandler: { + self.pumpManager.notifyDelegateOfDeactivation { + DispatchQueue.main.async { + self.done() + } + } + }) + + present(confirmVC, animated: true) { + tableView.deselectRow(at: indexPath, animated: true) + } + } + } + + override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { + switch sections[indexPath.section] { + case .podDetails, .actions, .status: + break + case .configuration: + switch ConfigurationRow(rawValue: indexPath.row)! { + case .timeZoneOffset, .testCommand: + tableView.reloadRows(at: [indexPath], with: .fade) + case .reminder: + break + } + case .rileyLinks: + break + case .deletePumpManager: + break + } + + return indexPath + } + + private func suspendResumeTapped() { + switch suspendResumeTableViewCell.shownAction { + case .resume: + pumpManager.resumeDelivery { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error Resuming", comment: "The alert title for a resume error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + } + case .suspend: + pumpManager.suspendDelivery { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error Suspending", comment: "The alert title for a suspend error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + } + } + } +} + +extension OmnipodSettingsViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController { + vc.dismiss(animated: false, completion: nil) + } + } +} + +extension OmnipodSettingsViewController: RadioSelectionTableViewControllerDelegate { + func radioSelectionTableViewControllerDidChangeSelectedIndex(_ controller: RadioSelectionTableViewController) { + guard let indexPath = self.tableView.indexPathForSelectedRow else { + return + } + + switch sections[indexPath.section] { + case .configuration: + switch ConfigurationRow(rawValue: indexPath.row)! { + default: + assertionFailure() + } + default: + assertionFailure() + } + + tableView.reloadRows(at: [indexPath], with: .none) + } +} + +extension OmnipodSettingsViewController: PodStateObserver { + func podStateDidUpdate(_ state: PodState?) { + let newSections = OmnipodSettingsViewController.sectionList(state) + let sectionsChanged = OmnipodSettingsViewController.sectionList(self.podState) != newSections + + let oldActionsCount = self.actions.count + let oldState = self.podState + self.podState = state + + if sectionsChanged { + self.devicesDataSource.devicesSectionIndex = self.sections.firstIndex(of: .rileyLinks)! + self.tableView.reloadData() + } else { + if oldActionsCount != self.actions.count, let idx = newSections.firstIndex(of: .actions) { + self.tableView.reloadSections([idx], with: .fade) + } + } + + guard let statusIdx = newSections.firstIndex(of: .status) else { + return + } + + let reloadRows: [StatusRow] = [.bolus, .basal, .reservoirLevel, .deliveredInsulin] + self.tableView.reloadRows(at: reloadRows.map({ IndexPath(row: $0.rawValue, section: statusIdx) }), with: .none) + + if oldState?.activeAlerts != state?.activeAlerts, + let alerts = state?.activeAlerts, + let alertCell = self.tableView.cellForRow(at: IndexPath(row: StatusRow.alarms.rawValue, section: statusIdx)) as? AlarmsTableViewCell + { + alertCell.alerts = alerts + } + } +} + +extension OmnipodSettingsViewController: PumpManagerStatusObserver { + func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) { + self.pumpManagerStatus = status + self.suspendResumeTableViewCell.basalDeliveryState = status.basalDeliveryState + if let statusSectionIdx = self.sections.firstIndex(of: .status) { + self.tableView.reloadSections([statusSectionIdx], with: .none) + } + } +} + +extension OmnipodSettingsViewController: DatePickerTableViewCellDelegate { + func datePickerTableViewCellDidUpdateDate(_ cell: DatePickerTableViewCell) { + pumpManager.expirationReminderDate = cell.date + } +} + +private extension UIAlertController { + convenience init(pumpManagerDeletionHandler handler: @escaping () -> Void) { + self.init( + title: nil, + message: LocalizedString("Are you sure you want to stop using Omnipod?", comment: "Confirmation message for removing Omnipod PumpManager"), + preferredStyle: .actionSheet + ) + + addAction(UIAlertAction( + title: LocalizedString("Delete Omnipod", comment: "Button title to delete Omnipod PumpManager"), + style: .destructive, + handler: { (_) in + handler() + } + )) + + let cancel = LocalizedString("Cancel", comment: "The title of the cancel action in an action sheet") + addAction(UIAlertAction(title: cancel, style: .cancel, handler: nil)) + } +} + +private extension TimeInterval { + func format(using units: NSCalendar.Unit) -> String? { + let formatter = DateComponentsFormatter() + formatter.allowedUnits = units + formatter.unitsStyle = .full + formatter.zeroFormattingBehavior = .dropLeading + formatter.maximumUnitCount = 2 + + return formatter.string(from: self) + } +} + +class AlarmsTableViewCell: LoadingTableViewCell { + + private var defaultDetailColor: UIColor? + + override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: .value1, reuseIdentifier: reuseIdentifier) + detailTextLabel?.tintAdjustmentMode = .automatic + defaultDetailColor = detailTextLabel?.textColor + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + private func updateColor() { + if alerts == .none { + detailTextLabel?.textColor = defaultDetailColor + } else { + detailTextLabel?.textColor = tintColor + } + } + + public var isEnabled = true { + didSet { + selectionStyle = isEnabled ? .default : .none + } + } + + override public func loadingStatusChanged() { + self.detailTextLabel?.isHidden = isLoading + } + + var alerts = [AlertSlot: PodAlert]() { + didSet { + updateColor() + detailTextLabel?.text = alerts.map { slot, alert in String.init(describing: alert) }.joined(separator: ", ") + } + } + + open override func tintColorDidChange() { + super.tintColorDidChange() + updateColor() + } + + open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateColor() + } + +} + + +private extension UITableViewCell { + + private var insulinFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 3 + return formatter + } + + private var percentFormatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + return formatter + } + + + func setDetailDate(_ date: Date?, formatter: DateFormatter) { + if let date = date { + detailTextLabel?.text = formatter.string(from: date) + } else { + detailTextLabel?.text = "-" + } + } + + func setDetailAge(_ age: TimeInterval?) { + if let age = age { + detailTextLabel?.text = fabs(age).format(using: [.day, .hour, .minute]) + } else { + detailTextLabel?.text = "" + } + } + + func setDetailBasal(suspended: Bool, dose: UnfinalizedDose?) { + if suspended { + detailTextLabel?.text = LocalizedString("Suspended", comment: "The detail text of the basal row when pod is suspended") + } else if let dose = dose { + if let rate = insulinFormatter.string(from: dose.rate) { + detailTextLabel?.text = String(format: LocalizedString("%@ U/hour", comment: "Format string for temp basal rate. (1: The localized amount)"), rate) + } + } else { + detailTextLabel?.text = LocalizedString("Schedule", comment: "The detail text of the basal row when pod is running scheduled basal") + } + } + + func setDetailBolus(suspended: Bool, dose: UnfinalizedDose?, deliveredUnits: Double?) { + guard let dose = dose, let delivered = deliveredUnits, !suspended else { + detailTextLabel?.text = LocalizedString("None", comment: "The detail text for bolus delivery when no bolus is being delivered") + return + } + + let progress = dose.progress + if let units = self.insulinFormatter.string(from: dose.units), let deliveredUnits = self.insulinFormatter.string(from: delivered) { + if progress >= 1 { + self.detailTextLabel?.text = String(format: LocalizedString("%@ U (Finished)", comment: "Format string for bolus progress when finished. (1: The localized amount)"), units) + } else { + let progressFormatted = percentFormatter.string(from: progress * 100.0) ?? "" + let progressStr = String(format: LocalizedString("%@%%", comment: "Format string for bolus percent progress. (1: Percent progress)"), progressFormatted) + self.detailTextLabel?.text = String(format: LocalizedString("%@ U of %@ U (%@)", comment: "Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress)"), deliveredUnits, units, progressStr) + } + } + + + } + + func setDeliveredInsulinDetail(_ measurements: PodInsulinMeasurements?) { + guard let measurements = measurements else { + detailTextLabel?.text = LocalizedString("Unknown", comment: "The detail text for delivered insulin when no measurement is available") + return + } + if let units = insulinFormatter.string(from: measurements.delivered) { + detailTextLabel?.text = String(format: LocalizedString("%@ U", comment: "Format string for delivered insulin. (1: The localized amount)"), units) + } + } + + func setReservoirDetail(_ measurements: PodInsulinMeasurements?) { + guard let measurements = measurements else { + detailTextLabel?.text = LocalizedString("Unknown", comment: "The detail text for delivered insulin when no measurement is available") + return + } + if measurements.reservoirVolume == nil { + if let units = insulinFormatter.string(from: Pod.maximumReservoirReading) { + detailTextLabel?.text = String(format: LocalizedString("%@+ U", comment: "Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount)"), units) + } + } else { + if let reservoirValue = measurements.reservoirVolume, + let units = insulinFormatter.string(from: reservoirValue) + { + detailTextLabel?.text = String(format: LocalizedString("%@ U", comment: "Format string for insulin remaining in reservoir. (1: The localized amount)"), units) + } + } + } +} + diff --git a/OmniKitUI/ViewControllers/PairPodSetupViewController.swift b/OmniKitUI/ViewControllers/PairPodSetupViewController.swift new file mode 100644 index 000000000..5cbc72db0 --- /dev/null +++ b/OmniKitUI/ViewControllers/PairPodSetupViewController.swift @@ -0,0 +1,239 @@ +// +// PairPodSetupViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 9/18/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKit +import LoopKitUI +import RileyLinkKit +import OmniKit +import os.log + +class PairPodSetupViewController: SetupTableViewController { + + var rileyLinkPumpManager: RileyLinkPumpManager! + + var pumpManager: OmnipodPumpManager! { + didSet { + if oldValue == nil && pumpManager != nil { + pumpManagerWasSet() + } + } + } + + private let log = OSLog(category: "PairPodSetupViewController") + + // MARK: - + + @IBOutlet weak var activityIndicator: SetupIndicatorView! + + @IBOutlet weak var loadingLabel: UILabel! + + private var loadingText: String? { + didSet { + tableView.beginUpdates() + loadingLabel.text = loadingText + + let isHidden = (loadingText == nil) + loadingLabel.isHidden = isHidden + tableView.endUpdates() + } + } + + override func viewDidLoad() { + super.viewDidLoad() + continueState = .initial + } + + private func pumpManagerWasSet() { + // Still priming? + let primeFinishesAt = pumpManager.state.podState?.primeFinishTime + let currentTime = Date() + if let finishTime = primeFinishesAt, finishTime > currentTime { + self.continueState = .pairing + let delay = finishTime.timeIntervalSince(currentTime) + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + self.continueState = .ready + } + } + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if case .pairing = continueState { + return + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + // MARK: - State + + private enum State { + case initial + case pairing + case priming(finishTime: TimeInterval) + case fault + case ready + } + + private var continueState: State = .initial { + didSet { + log.default("Changed continueState from %{public}@ to %{public}@", String(describing: oldValue), String(describing: continueState)) + + switch continueState { + case .initial: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.setPairTitle() + case .pairing: + activityIndicator.state = .indeterminantProgress + footerView.primaryButton.isEnabled = false + footerView.primaryButton.setPairTitle() + lastError = nil + loadingText = LocalizedString("Pairing…", comment: "The text of the loading label when pairing") + case .priming(let finishTime): + activityIndicator.state = .timedProgress(finishTime: CACurrentMediaTime() + finishTime) + footerView.primaryButton.isEnabled = false + footerView.primaryButton.setPairTitle() + lastError = nil + loadingText = LocalizedString("Priming…", comment: "The text of the loading label when priming") + case .fault: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.setDeactivateTitle() + case .ready: + activityIndicator.state = .completed + footerView.primaryButton.isEnabled = true + footerView.primaryButton.resetTitle() + lastError = nil + loadingText = LocalizedString("Primed", comment: "The text of the loading label when pod is primed") + } + } + } + + private var lastError: Error? { + didSet { + guard oldValue != nil || lastError != nil else { + return + } + + var errorText = lastError?.localizedDescription + + if let error = lastError as? LocalizedError { + let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ") + "." + + if !localizedText.isEmpty { + errorText = localizedText + } + } + + loadingText = errorText + + // If we have an error, update the continue state + if let podCommsError = lastError as? PodCommsError, + case PodCommsError.podFault = podCommsError + { + continueState = .fault + } else if lastError != nil { + continueState = .initial + } + } + } + + // MARK: - Navigation + + private func navigateToReplacePod() { + log.default("Navigating to ReplacePod screen") + performSegue(withIdentifier: "ReplacePod", sender: nil) + } + + override func continueButtonPressed(_ sender: Any) { + switch continueState { + case .initial: + pair() + case .ready: + super.continueButtonPressed(sender) + case .fault: + navigateToReplacePod() + default: + break + } + + } + + override func cancelButtonPressed(_ sender: Any) { + let podState = pumpManager.state.podState + + if podState != nil { + let confirmVC = UIAlertController(pumpDeletionHandler: { + self.navigateToReplacePod() + }) + self.present(confirmVC, animated: true) {} + } else { + super.cancelButtonPressed(sender) + } + } + + // MARK: - + + private func pair() { + self.continueState = .pairing + + pumpManager.pairAndPrime() { (result) in + DispatchQueue.main.async { + switch result { + case .success(let finishTime): + self.log.default("Pairing succeeded, finishing in %{public}@ sec", String(describing: finishTime)) + if finishTime > 0 { + self.continueState = .priming(finishTime: finishTime) + DispatchQueue.main.asyncAfter(deadline: .now() + finishTime) { + self.continueState = .ready + } + } else { + self.continueState = .ready + } + case .failure(let error): + self.log.default("Pairing failed with error: %{public}@", String(describing: error)) + self.lastError = error + } + } + } + } +} + +private extension SetupButton { + func setPairTitle() { + setTitle(LocalizedString("Pair", comment: "Button title to pair with pod during setup"), for: .normal) + } + + func setDeactivateTitle() { + setTitle(LocalizedString("Deactivate", comment: "Button title to deactivate pod because of fault during setup"), for: .normal) + } +} + +private extension UIAlertController { + convenience init(pumpDeletionHandler handler: @escaping () -> Void) { + self.init( + title: nil, + message: LocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"), + preferredStyle: .actionSheet + ) + + addAction(UIAlertAction( + title: LocalizedString("Deactivate Pod", comment: "Button title to deactivate pod"), + style: .destructive, + handler: { (_) in + handler() + } + )) + + let exit = LocalizedString("Continue", comment: "The title of the continue action in an action sheet") + addAction(UIAlertAction(title: exit, style: .default, handler: nil)) + } +} diff --git a/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift b/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift new file mode 100644 index 000000000..b1b46ea02 --- /dev/null +++ b/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift @@ -0,0 +1,73 @@ +// +// PodReplacementNavigationController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 11/28/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation +import OmniKit +import LoopKitUI + +class PodReplacementNavigationController: UINavigationController, UINavigationControllerDelegate, CompletionNotifying { + + weak var completionDelegate: CompletionDelegate? + + class func instantiatePodReplacementFlow(_ pumpManager: OmnipodPumpManager) -> PodReplacementNavigationController { + let vc = UIStoryboard(name: "OmnipodPumpManager", bundle: Bundle(for: PodReplacementNavigationController.self)).instantiateViewController(withIdentifier: "PodReplacementFlow") as! PodReplacementNavigationController + vc.pumpManager = pumpManager + return vc + } + + class func instantiateNewPodFlow(_ pumpManager: OmnipodPumpManager) -> PodReplacementNavigationController { + let vc = UIStoryboard(name: "OmnipodPumpManager", bundle: Bundle(for: PodReplacementNavigationController.self)).instantiateViewController(withIdentifier: "NewPodFlow") as! PodReplacementNavigationController + vc.pumpManager = pumpManager + return vc + } + + class func instantiateInsertCannulaFlow(_ pumpManager: OmnipodPumpManager) -> PodReplacementNavigationController { + let vc = UIStoryboard(name: "OmnipodPumpManager", bundle: Bundle(for: PodReplacementNavigationController.self)).instantiateViewController(withIdentifier: "InsertCannulaFlow") as! PodReplacementNavigationController + vc.pumpManager = pumpManager + return vc + } + + private(set) var pumpManager: OmnipodPumpManager! + + override func viewDidLoad() { + super.viewDidLoad() + + delegate = self + } + + func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { + + if let setupViewController = viewController as? SetupTableViewController { + setupViewController.delegate = self + } + + switch viewController { + case let vc as ReplacePodViewController: + vc.pumpManager = pumpManager + case let vc as PairPodSetupViewController: + vc.pumpManager = pumpManager + case let vc as InsertCannulaSetupViewController: + vc.pumpManager = pumpManager + case let vc as PodSetupCompleteViewController: + vc.pumpManager = pumpManager + default: + break + } + + } + + func completeSetup() { + completionDelegate?.completionNotifyingDidComplete(self) + } +} + +extension PodReplacementNavigationController: SetupTableViewControllerDelegate { + func setupTableViewControllerCancelButtonPressed(_ viewController: SetupTableViewController) { + self.dismiss(animated: true, completion: nil) + } +} diff --git a/OmniKitUI/ViewControllers/PodSettingsSetupViewController.swift b/OmniKitUI/ViewControllers/PodSettingsSetupViewController.swift new file mode 100644 index 000000000..1e1fa68fa --- /dev/null +++ b/OmniKitUI/ViewControllers/PodSettingsSetupViewController.swift @@ -0,0 +1,204 @@ +// +// PodSettingsSetupViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 9/25/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +import UIKit +import HealthKit +import LoopKit +import LoopKitUI +import OmniKit + +class PodSettingsSetupViewController: SetupTableViewController { + + private var pumpManagerSetupViewController: OmnipodPumpManagerSetupViewController? { + return navigationController as? OmnipodPumpManagerSetupViewController + } + + override func viewDidLoad() { + super.viewDidLoad() + + updateContinueButton() + + tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className) + } + + fileprivate lazy var quantityFormatter: QuantityFormatter = { + let quantityFormatter = QuantityFormatter() + quantityFormatter.numberFormatter.minimumFractionDigits = 0 + quantityFormatter.numberFormatter.maximumFractionDigits = 3 + + return quantityFormatter + }() + + func updateContinueButton() { + let enabled: Bool + if pumpManagerSetupViewController?.maxBolusUnits == nil || pumpManagerSetupViewController?.maxBasalRateUnitsPerHour == nil { + enabled = false + } + else if let basalSchedule = pumpManagerSetupViewController?.basalSchedule { + enabled = !basalSchedule.items.isEmpty && !scheduleHasError + } else { + enabled = false + } + footerView.primaryButton.isEnabled = enabled + } + + var scheduleHasError: Bool { + return scheduleErrorMessage != nil + } + + var scheduleErrorMessage: String? { + if let basalRateSchedule = pumpManagerSetupViewController?.basalSchedule { + if basalRateSchedule.items.count > Pod.maximumBasalScheduleEntryCount { + return LocalizedString("Too many entries", comment: "The error message shown when Loop's basal schedule has more entries than the pod can support") + } + let allowedRates = Pod.supportedBasalRates + if basalRateSchedule.items.contains(where: {!allowedRates.contains($0.value)}) { + return LocalizedString("Invalid entry", comment: "The error message shown when Loop's basal schedule has an unsupported rate") + } + } + return nil + } + + // MARK: - Table view data source + + private enum Section: Int, CaseIterable { + case description + case configuration + } + + private enum ConfigurationRow: Int, CaseIterable { + case deliveryLimits + case basalRates + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return Section.allCases.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch Section(rawValue: section)! { + case .description: + return 1 + case .configuration: + return 2 + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + switch Section(rawValue: indexPath.section)! { + case .description: + return tableView.dequeueReusableCell(withIdentifier: "DescriptionCell", for: indexPath) + case .configuration: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + + switch ConfigurationRow(rawValue: indexPath.row)! { + case .basalRates: + cell.textLabel?.text = LocalizedString("Basal Rates", comment: "The title text for the basal rate schedule") + + if let basalRateSchedule = pumpManagerSetupViewController?.basalSchedule, !basalRateSchedule.items.isEmpty { + if let errorMessage = scheduleErrorMessage { + cell.detailTextLabel?.text = errorMessage + } else { + let unit = HKUnit.internationalUnit() + let total = HKQuantity(unit: unit, doubleValue: basalRateSchedule.total()) + cell.detailTextLabel?.text = quantityFormatter.string(from: total, for: unit) + } + } else { + cell.detailTextLabel?.text = SettingsTableViewCell.TapToSetString + } + case .deliveryLimits: + cell.textLabel?.text = LocalizedString("Delivery Limits", comment: "Title text for delivery limits") + + if pumpManagerSetupViewController?.maxBolusUnits == nil || pumpManagerSetupViewController?.maxBasalRateUnitsPerHour == nil { + cell.detailTextLabel?.text = SettingsTableViewCell.TapToSetString + } else { + cell.detailTextLabel?.text = SettingsTableViewCell.EnabledString + } + } + + cell.accessoryType = .disclosureIndicator + + return cell + } + } + + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + switch Section(rawValue: indexPath.section)! { + case .description: + return false + case .configuration: + return true + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let sender = tableView.cellForRow(at: indexPath) + + switch Section(rawValue: indexPath.section)! { + case .description: + break + case .configuration: + switch ConfigurationRow(rawValue: indexPath.row)! { + case .basalRates: + let vc = BasalScheduleTableViewController(allowedBasalRates: Pod.supportedBasalRates, maximumScheduleItemCount: Pod.maximumBasalScheduleEntryCount, minimumTimeInterval: Pod.minimumBasalScheduleEntryDuration) + + if let profile = pumpManagerSetupViewController?.basalSchedule { + vc.scheduleItems = profile.items + vc.timeZone = profile.timeZone + } else { + vc.scheduleItems = [] + vc.timeZone = .currentFixed + } + + vc.title = sender?.textLabel?.text + vc.delegate = self + + show(vc, sender: sender) + case .deliveryLimits: + let vc = DeliveryLimitSettingsTableViewController(style: .grouped) + + vc.maximumBasalRatePerHour = pumpManagerSetupViewController?.maxBasalRateUnitsPerHour + vc.maximumBolus = pumpManagerSetupViewController?.maxBolusUnits + + vc.title = sender?.textLabel?.text + vc.delegate = self + + show(vc, sender: sender) + } + } + } +} + +extension PodSettingsSetupViewController: DailyValueScheduleTableViewControllerDelegate { + func dailyValueScheduleTableViewControllerWillFinishUpdating(_ controller: DailyValueScheduleTableViewController) { + if let controller = controller as? BasalScheduleTableViewController { + + pumpManagerSetupViewController?.basalSchedule = BasalRateSchedule(dailyItems: controller.scheduleItems, timeZone: controller.timeZone) + + footerView.primaryButton.isEnabled = controller.scheduleItems.count > 0 && !scheduleHasError + } + + tableView.reloadRows(at: [[Section.configuration.rawValue, ConfigurationRow.basalRates.rawValue]], with: .none) + } +} + +extension PodSettingsSetupViewController: DeliveryLimitSettingsTableViewControllerDelegate { + func deliveryLimitSettingsTableViewControllerDidUpdateMaximumBasalRatePerHour(_ vc: DeliveryLimitSettingsTableViewController) { + pumpManagerSetupViewController?.maxBasalRateUnitsPerHour = vc.maximumBasalRatePerHour + + tableView.reloadRows(at: [[Section.configuration.rawValue, ConfigurationRow.deliveryLimits.rawValue]], with: .none) + } + + func deliveryLimitSettingsTableViewControllerDidUpdateMaximumBolus(_ vc: DeliveryLimitSettingsTableViewController) { + pumpManagerSetupViewController?.maxBolusUnits = vc.maximumBolus + + tableView.reloadRows(at: [[Section.configuration.rawValue, ConfigurationRow.deliveryLimits.rawValue]], with: .none) + } +} diff --git a/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift new file mode 100644 index 000000000..06dff65ef --- /dev/null +++ b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift @@ -0,0 +1,67 @@ +// +// PodSetupCompleteViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 9/18/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKitUI +import OmniKit + +class PodSetupCompleteViewController: SetupTableViewController { + + @IBOutlet weak var expirationReminderDateCell: ExpirationReminderDateTableViewCell! + + var pumpManager: OmnipodPumpManager! { + didSet { + if let expirationReminderDate = pumpManager.expirationReminderDate, let podState = pumpManager.state.podState { + expirationReminderDateCell.date = expirationReminderDate + expirationReminderDateCell.datePicker.maximumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMinTimeBeforeExpiration) + expirationReminderDateCell.datePicker.minimumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMaxTimeBeforeExpiration) + } + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.padFooterToBottom = false + + self.navigationItem.hidesBackButton = true + self.navigationItem.rightBarButtonItem = nil + + expirationReminderDateCell.datePicker.datePickerMode = .dateAndTime + expirationReminderDateCell.titleLabel.text = LocalizedString("Expiration Reminder", comment: "The title of the cell showing the pod expiration reminder date") + expirationReminderDateCell.datePicker.minuteInterval = 1 + expirationReminderDateCell.delegate = self + } + + override func continueButtonPressed(_ sender: Any) { + if let setupVC = navigationController as? OmnipodPumpManagerSetupViewController { + setupVC.finishedSetup() + } + if let replaceVC = navigationController as? PodReplacementNavigationController { + replaceVC.completeSetup() + } + } + + override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + print("willSelectRowAt") + tableView.beginUpdates() + return indexPath + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + tableView.endUpdates() + } + +} + +extension PodSetupCompleteViewController: DatePickerTableViewCellDelegate { + func datePickerTableViewCellDidUpdateDate(_ cell: DatePickerTableViewCell) { + pumpManager.expirationReminderDate = cell.date + } +} diff --git a/OmniKitUI/ViewControllers/ReplacePodViewController.swift b/OmniKitUI/ViewControllers/ReplacePodViewController.swift new file mode 100644 index 000000000..d43a64b3a --- /dev/null +++ b/OmniKitUI/ViewControllers/ReplacePodViewController.swift @@ -0,0 +1,221 @@ +// +// ReplacePodViewController.swift +// OmniKitUI +// +// Created by Pete Schwamb on 11/28/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKitUI +import OmniKit + + +class ReplacePodViewController: SetupTableViewController { + + enum PodReplacementReason { + case normal + case fault(_ faultCode: FaultEventCode) + case canceledPairingBeforeApplication + case canceledPairing + } + + var replacementReason: PodReplacementReason = .normal { + didSet { + updateButtonTint() + switch replacementReason { + case .normal: + break // Text set in interface builder + case .fault(let faultCode): + instructionsLabel.text = String(format: LocalizedString("%1$@. Insulin delivery has stopped. Please deactivate and remove pod.", comment: "Format string providing instructions for replacing pod due to a fault. (1: The fault description)"), faultCode.localizedDescription) + case .canceledPairingBeforeApplication: + instructionsLabel.text = LocalizedString("Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod.", comment: "Instructions when deactivating pod that has been paired, but not attached.") + case .canceledPairing: + instructionsLabel.text = LocalizedString("Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod.", comment: "Instructions when deactivating pod that has been paired and possibly attached.") + } + + tableView.reloadData() + } + } + + var pumpManager: OmnipodPumpManager! { + didSet { + let podState = pumpManager.state.podState + + if let podFault = podState?.fault { + self.replacementReason = .fault(podFault.currentStatus) + } else if podState?.setupProgress.primingNeeded == true { + self.replacementReason = .canceledPairingBeforeApplication + } else if podState?.setupProgress.needsCannulaInsertion == true { + self.replacementReason = .canceledPairing + } else { + self.replacementReason = .normal + } + } + } + + // MARK: - + + @IBOutlet weak var activityIndicator: SetupIndicatorView! + + @IBOutlet weak var loadingLabel: UILabel! + + @IBOutlet weak var instructionsLabel: UILabel! + + + private var tryCount: Int = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + continueState = .initial + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard continueState != .deactivating else { + return + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + + // MARK: - Navigation + + private enum State { + case initial + case deactivating + case deactivationFailed + case continueAfterFailure + case ready + } + + private var continueState: State = .initial { + didSet { + updateButtonTint() + switch continueState { + case .initial: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.setDeactivateTitle() + case .deactivating: + activityIndicator.state = .indeterminantProgress + footerView.primaryButton.isEnabled = false + lastError = nil + case .deactivationFailed: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.setRetryTitle() + case .continueAfterFailure: + activityIndicator.state = .hidden + footerView.primaryButton.isEnabled = true + footerView.primaryButton.resetTitle() + tableView.beginUpdates() + loadingLabel.text = LocalizedString("Unable to deactivate pod. Please continue and pair a new one.", comment: "Instructions when pod cannot be deactivated") + loadingLabel.isHidden = false + tableView.endUpdates() + case .ready: + navigationItem.rightBarButtonItem = nil + activityIndicator.state = .completed + footerView.primaryButton.isEnabled = true + footerView.primaryButton.resetTitle() + footerView.primaryButton.tintColor = nil + lastError = nil + } + } + } + + private var lastError: Error? { + didSet { + guard oldValue != nil || lastError != nil else { + return + } + + var errorText = lastError?.localizedDescription + + if let error = lastError as? LocalizedError { + let localizedText = [error.errorDescription, error.failureReason, error.recoverySuggestion].compactMap({ $0 }).joined(separator: ". ") + "." + + if !localizedText.isEmpty { + errorText = localizedText + } + } + + tableView.beginUpdates() + loadingLabel.text = errorText + + let isHidden = (errorText == nil) + loadingLabel.isHidden = isHidden + tableView.endUpdates() + } + } + + override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { + return continueState == .ready || continueState == .continueAfterFailure + } + + override func continueButtonPressed(_ sender: Any) { + switch continueState { + case .ready, .continueAfterFailure: + super.continueButtonPressed(sender) + case .initial, .deactivationFailed: + continueState = .deactivating + deactivate() + case .deactivating: + break + } + } + + func deactivate() { + tryCount += 1 + + let continueAfterFailure = tryCount > 1 + pumpManager.deactivatePod(forgetPodOnFail: continueAfterFailure) { (error) in + DispatchQueue.main.async { + if let error = error { + if continueAfterFailure { + self.continueState = .continueAfterFailure + } else { + self.lastError = error + self.continueState = .deactivationFailed + } + } else { + self.continueState = .ready + } + } + } + } + + override func cancelButtonPressed(_ sender: Any) { + self.dismiss(animated: true, completion: nil) + } + + private func updateButtonTint() { + let buttonTint: UIColor? + if case .normal = replacementReason, case .initial = continueState { + buttonTint = .deleteColor + } else { + buttonTint = nil + } + footerView.primaryButton.tintColor = buttonTint + } + +} + +private extension SetupButton { + func setDeactivateTitle() { + setTitle(LocalizedString("Deactivate Pod", comment: "Button title for pod deactivation"), for: .normal) + } + + func setRetryTitle() { + setTitle(LocalizedString("Retry Pod Deactivation", comment: "Button title for retrying pod deactivation"), for: .normal) + } +} + + diff --git a/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift new file mode 100644 index 000000000..b34f3d055 --- /dev/null +++ b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift @@ -0,0 +1,58 @@ +// +// ExpirationReminderDateTableViewCell.swift +// OmniKitUI +// +// Created by Pete Schwamb on 4/11/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKitUI + +public class ExpirationReminderDateTableViewCell: DatePickerTableViewCell { + + public weak var delegate: DatePickerTableViewCellDelegate? + + @IBOutlet public weak var titleLabel: UILabel! + + @IBOutlet public weak var dateLabel: UILabel! + + var maximumDate: Date? { + set { + datePicker.maximumDate = newValue + } + get { + return datePicker.maximumDate + } + } + + var minimumDate: Date? { + set { + datePicker.minimumDate = newValue + } + get { + return datePicker.minimumDate + } + } + + private lazy var formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.timeStyle = .short + formatter.dateStyle = .medium + formatter.doesRelativeDateFormatting = true + + return formatter + }() + + public override func updateDateLabel() { + dateLabel.text = formatter.string(from: date) + } + + public override func dateChanged(_ sender: UIDatePicker) { + super.dateChanged(sender) + + delegate?.datePickerTableViewCellDidUpdateDate(self) + } +} + +extension ExpirationReminderDateTableViewCell: NibLoadable { } diff --git a/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib new file mode 100644 index 000000000..27f62e7fc --- /dev/null +++ b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OmniKitUI/Views/HUDAssets.xcassets/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json new file mode 100644 index 000000000..dd4164b76 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "PodLifeBG.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "original", + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/PodLifeBG.pdf b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/PodLifeBG.pdf new file mode 100644 index 0000000000000000000000000000000000000000..51ca20cba7af7787b60954859830c99546a75f9b GIT binary patch literal 3551 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 WRG(l7B~?Tqfe;ym!L0LiLj4cte>ivm literal 0 HcmV?d00001 diff --git a/OmniKitUI/Views/HUDAssets.xcassets/reservoir/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/Contents.json new file mode 100644 index 000000000..aba98e7f1 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "reservoir.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/reservoir.pdf b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/reservoir.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6230150465cdb233c691dffa021aa7b0e6a1850e GIT binary patch literal 4978 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/Contents.json new file mode 100644 index 000000000..98eba7389 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "reservoir_mask.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/reservoir_mask.pdf b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/reservoir_mask.pdf new file mode 100644 index 0000000000000000000000000000000000000000..04076939773d5b05d5212779f437dd2b22ea346a GIT binary patch literal 5537 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/Views/OmnipodReservoirView.swift b/OmniKitUI/Views/OmnipodReservoirView.swift new file mode 100644 index 000000000..0c2349c7a --- /dev/null +++ b/OmniKitUI/Views/OmnipodReservoirView.swift @@ -0,0 +1,147 @@ +// +// OmnipodReservoirView.swift +// OmniKit +// +// Created by Pete Schwamb on 10/22/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKitUI +import OmniKit + +public final class OmnipodReservoirView: LevelHUDView, NibLoadable { + + override public var orderPriority: HUDViewOrderPriority { + return 11 + } + + @IBOutlet private weak var volumeLabel: UILabel! + + @IBOutlet private weak var alertLabel: UILabel! { + didSet { + alertLabel.alpha = 0 + alertLabel.textColor = UIColor.white + alertLabel.layer.cornerRadius = 9 + alertLabel.clipsToBounds = true + } + } + + public class func instantiate() -> OmnipodReservoirView { + return nib().instantiate(withOwner: nil, options: nil)[0] as! OmnipodReservoirView + } + + override public func awakeFromNib() { + super.awakeFromNib() + + volumeLabel.isHidden = true + } + + private var reservoirLevel: Double? { + didSet { + level = reservoirLevel + + switch reservoirLevel { + case .none: + volumeLabel.isHidden = true + case let x? where x > 0.25: + volumeLabel.isHidden = true + case let x? where x > 0.10: + volumeLabel.textColor = tintColor + volumeLabel.isHidden = false + default: + volumeLabel.textColor = tintColor + volumeLabel.isHidden = false + } + } + } + + override public func tintColorDidChange() { + super.tintColorDidChange() + + volumeLabel.textColor = tintColor + } + + + private func updateColor() { + switch reservoirAlertState { + case .lowReservoir, .empty: + alertLabel.backgroundColor = stateColors?.warning + case .ok: + alertLabel.backgroundColor = stateColors?.normal + } + } + + override public func stateColorsDidUpdate() { + super.stateColorsDidUpdate() + updateColor() + } + + + private var reservoirAlertState = ReservoirAlertState.ok { + didSet { + var alertLabelAlpha: CGFloat = 1 + + switch reservoirAlertState { + case .ok: + alertLabelAlpha = 0 + case .lowReservoir, .empty: + alertLabel.text = "!" + } + + updateColor() + + if self.superview == nil { + self.alertLabel.alpha = alertLabelAlpha + } else { + UIView.animate(withDuration: 0.25, animations: { + self.alertLabel.alpha = alertLabelAlpha + }) + } + } + } + + private lazy var timeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + + return formatter + }() + + private lazy var numberFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 0 + + return formatter + }() + + private let insulinFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 3 + return formatter + }() + + + public func update(volume: Double?, at date: Date, level: Double?, reservoirAlertState: ReservoirAlertState) { + self.reservoirLevel = level + + let time = timeFormatter.string(from: date) + caption?.text = time + + if let volume = volume { + if let units = numberFormatter.string(from: volume) { + volumeLabel.text = String(format: LocalizedString("%@U", comment: "Format string for reservoir volume. (1: The localized volume)"), units) + + accessibilityValue = String(format: LocalizedString("%1$@ units remaining at %2$@", comment: "Accessibility format string for (1: localized volume)(2: time)"), units, time) + } + } else if let maxReservoirReading = insulinFormatter.string(from: Pod.maximumReservoirReading) { + accessibilityValue = String(format: LocalizedString("Greater than %1$@ units remaining at %2$@", comment: "Accessibility format string for (1: localized volume)(2: time)"), maxReservoirReading, time) + } + self.reservoirAlertState = reservoirAlertState + } +} + + diff --git a/OmniKitUI/Views/OmnipodReservoirView.xib b/OmniKitUI/Views/OmnipodReservoirView.xib new file mode 100644 index 000000000..cfc1d18d6 --- /dev/null +++ b/OmniKitUI/Views/OmnipodReservoirView.xib @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OmniKitUI/Views/PodLifeHUDView.swift b/OmniKitUI/Views/PodLifeHUDView.swift new file mode 100644 index 000000000..01bf0a80f --- /dev/null +++ b/OmniKitUI/Views/PodLifeHUDView.swift @@ -0,0 +1,159 @@ +// +// PodLifeHUDView.swift +// OmniKitUI +// +// Created by Pete Schwamb on 10/22/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import UIKit +import LoopKitUI +import MKRingProgressView + +public enum PodAlertState { + case none + case warning + case fault +} + +public class PodLifeHUDView: BaseHUDView, NibLoadable { + + override public var orderPriority: HUDViewOrderPriority { + return 12 + } + + @IBOutlet private weak var timeLabel: UILabel! + @IBOutlet private weak var progressRing: RingProgressView! + + @IBOutlet private weak var alertLabel: UILabel! { + didSet { + alertLabel.alpha = 0 + alertLabel.textColor = UIColor.white + alertLabel.layer.cornerRadius = 9 + alertLabel.clipsToBounds = true + } + } + + private var startTime: Date? + private var lifetime: TimeInterval? + private var timer: Timer? + + public var alertState: PodAlertState = .none { + didSet { + updateAlertStateLabel() + } + } + + public class func instantiate() -> PodLifeHUDView { + return nib().instantiate(withOwner: nil, options: nil)[0] as! PodLifeHUDView + } + + public func setPodLifeCycle(startTime: Date, lifetime: TimeInterval) { + self.startTime = startTime + self.lifetime = lifetime + updateProgressCircle() + + if timer == nil { + self.timer = Timer.scheduledTimer(withTimeInterval: .seconds(10), repeats: true) { [weak self] _ in + self?.updateProgressCircle() + } + } + } + + override open func stateColorsDidUpdate() { + super.stateColorsDidUpdate() + updateProgressCircle() + updateAlertStateLabel() + } + + private var endColor: UIColor? { + didSet { + let primaryColor = endColor ?? UIColor(red: 198 / 255, green: 199 / 255, blue: 201 / 255, alpha: 1) + self.progressRing.endColor = primaryColor + self.progressRing.startColor = primaryColor + } + } + + private lazy var timeFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + + formatter.allowedUnits = [.hour, .minute] + formatter.maximumUnitCount = 1 + formatter.unitsStyle = .abbreviated + + return formatter + }() + + private func updateAlertStateLabel() { + var alertLabelAlpha: CGFloat = 1 + + if alertState == .fault { + timer = nil + } + + switch alertState { + case .fault: + alertLabel.text = "!" + alertLabel.backgroundColor = stateColors?.error + case .warning: + alertLabel.text = "!" + alertLabel.backgroundColor = stateColors?.warning + case .none: + alertLabelAlpha = 0 + } + alertLabel.alpha = alertLabelAlpha + UIView.animate(withDuration: 0.25, animations: { + self.alertLabel.alpha = alertLabelAlpha + }) + } + + private func updateProgressCircle() { + + if let startTime = startTime, let lifetime = lifetime { + let age = -startTime.timeIntervalSinceNow + let progress = Double(age / lifetime) + progressRing.progress = progress + + if progress < 0.75 { + self.endColor = stateColors?.normal + progressRing.shadowOpacity = 0 + } else if progress < 1.0 { + self.endColor = stateColors?.warning + progressRing.shadowOpacity = 1 + } else { + self.endColor = stateColors?.error + progressRing.shadowOpacity = 1 + } + + let remaining = (lifetime - age) + + // Update time label and caption + if alertState == .fault { + timeLabel.isHidden = true + caption.text = "Fault" + } else if remaining > .hours(24) { + timeLabel.isHidden = true + caption.text = LocalizedString("Pod Age", comment: "Label describing pod age view") + } else if remaining > 0 { + let remainingFlooredToHour = remaining > .hours(1) ? remaining - remaining.truncatingRemainder(dividingBy: .hours(1)) : remaining + if let timeString = timeFormatter.string(from: remainingFlooredToHour) { + timeLabel.isHidden = false + timeLabel.text = timeString + } + caption.text = LocalizedString("Remaining", comment: "Label describing time remaining view") + } else { + timeLabel.isHidden = true + caption.text = LocalizedString("Replace Pod", comment: "Label indicating pod replacement necessary") + } + } + } + + func pauseUpdates() { + timer?.invalidate() + timer = nil + } + + override public func awakeFromNib() { + super.awakeFromNib() + } +} diff --git a/OmniKitUI/Views/PodLifeHUDView.xib b/OmniKitUI/Views/PodLifeHUDView.xib new file mode 100644 index 000000000..111a4f3ea --- /dev/null +++ b/OmniKitUI/Views/PodLifeHUDView.xib @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 27c7c8bb9..783a87c58 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -260,12 +260,21 @@ 7D70768B1FE09310004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70768D1FE09310004AC8EA /* Localizable.strings */; }; 7D7076901FE09311004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076921FE09311004AC8EA /* InfoPlist.strings */; }; 7D7076951FE09311004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076971FE09311004AC8EA /* Localizable.strings */; }; + C104A9C1217E603E006E3C3E /* OmnipodReservoirView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */; }; + C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */; }; + C104A9C3217E611F006E3C3E /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */; }; + C104A9C5217E645C006E3C3E /* HUDAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C4217E645C006E3C3E /* HUDAssets.xcassets */; }; + C104A9C7217E9F35006E3C3E /* PodLifeHUDView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C6217E9F34006E3C3E /* PodLifeHUDView.xib */; }; + C104A9C9217E9F6C006E3C3E /* PodLifeHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C104A9C8217E9F6C006E3C3E /* PodLifeHUDView.swift */; }; C10AB08D1C855613000F102E /* FindDeviceMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10AB08C1C855613000F102E /* FindDeviceMessageBody.swift */; }; C10AB08F1C855F34000F102E /* DeviceLinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10AB08E1C855F34000F102E /* DeviceLinkMessageBody.swift */; }; C10D9BC41C8269D500378342 /* MinimedKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C10D9BC31C8269D500378342 /* MinimedKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; C10D9BCB1C8269D500378342 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; C10D9BD61C8269D500378342 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; C10D9BD71C8269D500378342 /* MinimedKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C110A0E6221BBAEF0016560B /* GetPumpFirmwareVersionMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB4272216E5DFD00FAB378 /* GetPumpFirmwareVersionMessageBody.swift */; }; + C11166AE2180D834000EEAAB /* AlertSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11166AD2180D834000EEAAB /* AlertSlot.swift */; }; + C11F6B7E21C9646300752BBC /* FaultConfigCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11F6B7D21C9646300752BBC /* FaultConfigCommand.swift */; }; C121985F1C8DE77D00BC374C /* FindDeviceMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C121985E1C8DE77D00BC374C /* FindDeviceMessageBodyTests.swift */; }; C12198611C8DEB7B00BC374C /* DeviceLinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12198601C8DEB7B00BC374C /* DeviceLinkMessageBodyTests.swift */; }; C12198631C8DF4C800BC374C /* HistoryPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12198621C8DF4C800BC374C /* HistoryPageTests.swift */; }; @@ -287,6 +296,9 @@ C1274F791D823A550002912B /* ChangeMeterIDPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1274F781D823A550002912B /* ChangeMeterIDPumpEvent.swift */; }; C1274F801D82411C0002912B /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1274F7C1D82411C0002912B /* MainViewController.swift */; }; C1274F861D8242BE0002912B /* PumpRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1274F851D8242BE0002912B /* PumpRegion.swift */; }; + C127D924215C002B0031799D /* PodSettingsSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C127D923215C002A0031799D /* PodSettingsSetupViewController.swift */; }; + C127D925215C00320031799D /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; }; + C127D927215C00420031799D /* PodCommsSession+LoopKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C127D926215C00420031799D /* PodCommsSession+LoopKit.swift */; }; C12EA23B198B436800309FA4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23A198B436800309FA4 /* Foundation.framework */; }; C12EA23D198B436800309FA4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23C198B436800309FA4 /* CoreGraphics.framework */; }; C12EA23F198B436800309FA4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23E198B436800309FA4 /* UIKit.framework */; }; @@ -298,11 +310,18 @@ C12EA260198B436900309FA4 /* RileyLinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C12EA25F198B436900309FA4 /* RileyLinkTests.m */; }; C1330F431DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */; }; C133CF931D5943780034B82D /* PredictedBG.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133CF921D5943780034B82D /* PredictedBG.swift */; }; + C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */; }; + C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */; }; C13D155A1DAACE8400ADC044 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13D15591DAACE8400ADC044 /* Either.swift */; }; + C13FD2F4215E7338005FC495 /* FaultEventCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13FD2F3215E7338005FC495 /* FaultEventCode.swift */; }; + C13FD2F6215E743C005FC495 /* PodProgressStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13FD2F5215E743C005FC495 /* PodProgressStatus.swift */; }; C14303161C97C98000A40450 /* PumpAckMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303151C97C98000A40450 /* PumpAckMessageBody.swift */; }; C14303181C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */; }; C143031A1C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */; }; C145BF9F2219F37200A977CB /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C145BF9D2219F2EC00A977CB /* Comparable.swift */; }; + C145BFA4221BBA7F00A977CB /* SuspendResumeMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C145BFA3221BBA7E00A977CB /* SuspendResumeMessageBody.swift */; }; + C14B42FA21FF78840073A836 /* MessageLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14B42F921FF78840073A836 /* MessageLog.swift */; }; + C14CD28E21B5872D00F259DB /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; C14D2B051C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D2B041C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift */; }; C14D2B091C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D2B081C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift */; }; C14FFC4A1D3AF1AC0049CF85 /* JournalEntryInsulinMarkerPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC491D3AF1AC0049CF85 /* JournalEntryInsulinMarkerPumpEvent.swift */; }; @@ -319,9 +338,11 @@ C15AF2AD1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AC1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift */; }; C15AF2AF1D7498930031FC9D /* RestoreMystery54PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */; }; C15AF2B11D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */; }; + C168C40021AEF8DE00ADE90E /* PodReplacementNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168C3FF21AEF8DE00ADE90E /* PodReplacementNavigationController.swift */; }; + C168C40221AFACA600ADE90E /* ReplacePodViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168C40121AFACA600ADE90E /* ReplacePodViewController.swift */; }; C16A08311D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16A08301D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift */; }; - C16E611D2203CB7A0069F357 /* SuspendResumeMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */; }; - C16E611E2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */; }; + C16C3D4421AC40AF00401105 /* OmnipodHUDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16C3D4321AC40AF00401105 /* OmnipodHUDProvider.swift */; }; + C16E190D224EA33000DD9B9D /* PodDoseProgressEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E190C224EA33000DD9B9D /* PodDoseProgressEstimator.swift */; }; C16E611F22065B8E0069F357 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; C16E61222208C7A80069F357 /* ReservoirReading.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E61212208C7A80069F357 /* ReservoirReading.swift */; }; C16E61262208EC580069F357 /* MinimedHUDProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16E61252208EC580069F357 /* MinimedHUDProvider.swift */; }; @@ -332,6 +353,10 @@ C178845D1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */; }; C178845F1D5166BE00405663 /* COBStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178845E1D5166BE00405663 /* COBStatus.swift */; }; C17884611D519F1E00405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884601D519F1E00405663 /* BatteryIndicator.swift */; }; + C17C5C0F21447383002A06F8 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; }; + C17EDC4F2134D0CC0031D9F0 /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; + C1814B88225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */; }; + C1814B8A225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */; }; C1842BBB1C8E184300DB42AC /* PumpModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBA1C8E184300DB42AC /* PumpModel.swift */; }; C1842BBD1C8E7C6E00DB42AC /* PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */; }; C1842BBF1C8E855A00DB42AC /* PumpEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */; }; @@ -394,6 +419,7 @@ C1A492691D4A66C0008964FF /* LoopEnacted.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A492681D4A66C0008964FF /* LoopEnacted.swift */; }; C1A721621EC3E0500080FAD7 /* PumpErrorMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A721611EC3E0500080FAD7 /* PumpErrorMessageBody.swift */; }; C1A721661EC4BCE30080FAD7 /* PartialDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A721651EC4BCE30080FAD7 /* PartialDecode.swift */; }; + C1ABE397224947C000570E82 /* PodCommsSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1ABE396224947C000570E82 /* PodCommsSessionTests.swift */; }; C1AF21E21D4838C90088C41D /* DeviceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E11D4838C90088C41D /* DeviceStatus.swift */; }; C1AF21E41D4865320088C41D /* LoopStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E31D4865320088C41D /* LoopStatus.swift */; }; C1AF21E61D48667F0088C41D /* UploaderStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1AF21E51D48667F0088C41D /* UploaderStatus.swift */; }; @@ -413,11 +439,58 @@ C1B383361CD1BA8100CE7782 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B383351CD1BA8100CE7782 /* DeviceDataManager.swift */; }; C1B44CA7224BDFDF00DE47E5 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; C1BAD1181E63984C009BA1C6 /* RadioAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BAD1171E63984C009BA1C6 /* RadioAdapter.swift */; }; + C1BB128821CB5603009A29B5 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BB128721CB5603009A29B5 /* main.swift */; }; + C1BB129421CB564D009A29B5 /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA0213323E800C50C1D /* CRC8.swift */; }; + C1BB129521CB564D009A29B5 /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9E213323E700C50C1D /* CRC16.swift */; }; + C1BB129621CB564D009A29B5 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB5213323E900C50C1D /* Packet.swift */; }; + C1BB129821CB564D009A29B5 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9C213323E700C50C1D /* Message.swift */; }; + C1BB129921CB5654009A29B5 /* AcknowledgeAlertCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C06B2921506BF300B602AD /* AcknowledgeAlertCommand.swift */; }; + C1BB129A21CB5654009A29B5 /* AssignAddressCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */; }; + C1BB129B21CB5654009A29B5 /* BasalScheduleExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */; }; + C1BB129C21CB5654009A29B5 /* BolusExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */; }; + C1BB129D21CB5654009A29B5 /* CancelDeliveryCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */; }; + C1BB129E21CB5654009A29B5 /* ConfigureAlertsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */; }; + C1BB129F21CB5654009A29B5 /* DeactivatePodCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */; }; + C1BB12A021CB5654009A29B5 /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */; }; + C1BB12A121CB5654009A29B5 /* GetStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */; }; + C1BB12A221CB5654009A29B5 /* MessageBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA8213323E800C50C1D /* MessageBlock.swift */; }; + C1BB12A321CB5654009A29B5 /* PlaceholderMessageBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */; }; + C1BB12A421CB5654009A29B5 /* PodInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EE336B214ED01200888876 /* PodInfo.swift */; }; + C1BB12A521CB5654009A29B5 /* PodInfoConfiguredAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C06B252150371700B602AD /* PodInfoConfiguredAlerts.swift */; }; + C1BB12A621CB5654009A29B5 /* PodInfoDataLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E54AB52156B2D500E319B8 /* PodInfoDataLog.swift */; }; + C1BB12A721CB5654009A29B5 /* PodInfoFault.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EDD474215AFFF300A103D1 /* PodInfoFault.swift */; }; + C1BB12A821CB5654009A29B5 /* PodInfoFaultEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EE336D214ED01200888876 /* PodInfoFaultEvent.swift */; }; + C1BB12A921CB5654009A29B5 /* PodInfoFlashLogRecent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95D065F215D76E40072157B /* PodInfoFlashLogRecent.swift */; }; + C1BB12AA21CB5654009A29B5 /* PodInfoResetStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E54AB321542E8A00E319B8 /* PodInfoResetStatus.swift */; }; + C1BB12AB21CB5654009A29B5 /* PodInfoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA5213323E800C50C1D /* PodInfoResponse.swift */; }; + C1BB12AC21CB5654009A29B5 /* PodInfoTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EDD476215BED3500A103D1 /* PodInfoTester.swift */; }; + C1BB12AD21CB5654009A29B5 /* SetInsulinScheduleCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */; }; + C1BB12AE21CB5654009A29B5 /* ConfigurePodCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAF213323E800C50C1D /* ConfigurePodCommand.swift */; }; + C1BB12AF21CB5654009A29B5 /* StatusResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAB213323E800C50C1D /* StatusResponse.swift */; }; + C1BB12B021CB5654009A29B5 /* TempBasalExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */; }; + C1BB12B121CB5654009A29B5 /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA4213323E800C50C1D /* VersionResponse.swift */; }; + C1BB12B221CB5654009A29B5 /* FaultConfigCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11F6B7D21C9646300752BBC /* FaultConfigCommand.swift */; }; + C1BB12B421CB5697009A29B5 /* Packet+RFPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BB12B321CB5697009A29B5 /* Packet+RFPacket.swift */; }; + C1BB12B521CB56D2009A29B5 /* FaultEventCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13FD2F3215E7338005FC495 /* FaultEventCode.swift */; }; + C1BB12B621CB56D2009A29B5 /* PodProgressStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13FD2F5215E743C005FC495 /* PodProgressStatus.swift */; }; + C1BB12B721CB56E2009A29B5 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; + C1BB12B821CB56F3009A29B5 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; + C1BB12B921CB571C009A29B5 /* Pod.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF98213323E600C50C1D /* Pod.swift */; }; + C1BB12BA21CB5758009A29B5 /* AlertSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11166AD2180D834000EEAAB /* AlertSlot.swift */; }; + C1BB12BB21CB5767009A29B5 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; + C1BB12BC21CB577E009A29B5 /* LogEventErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993D18B217E455000E489BF /* LogEventErrorCode.swift */; }; + C1BB12BD21CB5796009A29B5 /* BasalDeliveryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */; }; + C1BB12BE21CB57AA009A29B5 /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */; }; C1C3578F1C927303009BDD4F /* MeterMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C3578E1C927303009BDD4F /* MeterMessage.swift */; }; C1C357911C92733A009BDD4F /* MeterMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C357901C92733A009BDD4F /* MeterMessageTests.swift */; }; C1C73F1D1DE6306A0022FC89 /* BatteryChemistryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C73F1C1DE6306A0022FC89 /* BatteryChemistryType.swift */; }; + C1CB13A521383F1E00F9EEDA /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; + C1CB13A72138453B00F9EEDA /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */; }; C1D00E9D1E8986A400B733B7 /* PumpSuspendTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D00E9C1E8986A400B733B7 /* PumpSuspendTreatment.swift */; }; C1D00EA11E8986F900B733B7 /* PumpResumeTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D00EA01E8986F900B733B7 /* PumpResumeTreatment.swift */; }; + C1E163D52135EC0100EB89AE /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; }; + C1E163D62135ED0300EB89AE /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; + C1E163D72135FF9A00EB89AE /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; C1E5BEAD1D5E26F200BD4390 /* RileyLinkStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E5BEAC1D5E26F200BD4390 /* RileyLinkStatus.swift */; }; C1EAD6B31C826B6D006DBA60 /* MySentryAlertType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6AE1C826B6D006DBA60 /* MySentryAlertType.swift */; }; C1EAD6B41C826B6D006DBA60 /* MessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6AF1C826B6D006DBA60 /* MessageBody.swift */; }; @@ -447,17 +520,101 @@ C1F0004C1EBE68A600F65163 /* DataFrameMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0004B1EBE68A600F65163 /* DataFrameMessageBody.swift */; }; C1F000501EBE727C00F65163 /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0004F1EBE727C00F65163 /* BasalScheduleTests.swift */; }; C1F000521EBE73F400F65163 /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F000511EBE73F400F65163 /* BasalSchedule.swift */; }; + C1F1A5EA215164FA00F0B820 /* PairPodSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */; }; + C1F1A5EC2151F73F00F0B820 /* InsertCannulaSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */; }; + C1F1A5EE2151FBA700F0B820 /* PodSetupCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */; }; C1F6EB871F89C3B100CFE393 /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6DD1C82B78C006DBA60 /* CRC8.swift */; }; C1F6EB891F89C3E200CFE393 /* FourByteSixByteEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB881F89C3E200CFE393 /* FourByteSixByteEncoding.swift */; }; C1F6EB8B1F89C41200CFE393 /* MinimedPacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB8A1F89C41200CFE393 /* MinimedPacket.swift */; }; C1F6EB8D1F89C45500CFE393 /* MinimedPacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */; }; C1F8B1DF223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */; }; C1FC49EC2135CB2D007D0788 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1FC49EB2135CB2D007D0788 /* LaunchScreen.storyboard */; }; + C1FC49EE2135DD56007D0788 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */; }; C1FDFCA91D964A3E00ADBC31 /* BolusReminderPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */; }; C1FFAF4D212944F600C50C1D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF4B212944F600C50C1D /* Localizable.strings */; }; C1FFAF64212B126E00C50C1D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF62212B126E00C50C1D /* InfoPlist.strings */; }; C1FFAF6F212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF6E212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift */; }; C1FFAF72212FAAEF00C50C1D /* RileyLink.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF71212FAAEF00C50C1D /* RileyLink.xcassets */; }; + C1FFAF81213323CC00C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; + C1FFAF8A213323CC00C50C1D /* OmniKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FFAF7A213323CC00C50C1D /* OmniKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C1FFAF8D213323CC00C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; + C1FFAF8E213323CC00C50C1D /* OmniKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1FFAFB6213323E900C50C1D /* OmnipodPumpManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF96213323E600C50C1D /* OmnipodPumpManagerState.swift */; }; + C1FFAFB7213323E900C50C1D /* OmnipodPumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF97213323E600C50C1D /* OmnipodPumpManager.swift */; }; + C1FFAFB8213323E900C50C1D /* Pod.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF98213323E600C50C1D /* Pod.swift */; }; + C1FFAFB9213323E900C50C1D /* PodCommsSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF99213323E600C50C1D /* PodCommsSession.swift */; }; + C1FFAFBA213323E900C50C1D /* BasalDeliveryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */; }; + C1FFAFBB213323E900C50C1D /* MessageTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9B213323E700C50C1D /* MessageTransport.swift */; }; + C1FFAFBC213323E900C50C1D /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9C213323E700C50C1D /* Message.swift */; }; + C1FFAFBD213323E900C50C1D /* PodState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9D213323E700C50C1D /* PodState.swift */; }; + C1FFAFBE213323E900C50C1D /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9E213323E700C50C1D /* CRC16.swift */; }; + C1FFAFBF213323E900C50C1D /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */; }; + C1FFAFC0213323E900C50C1D /* CRC8.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA0213323E800C50C1D /* CRC8.swift */; }; + C1FFAFC1213323E900C50C1D /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA2213323E800C50C1D /* Notification.swift */; }; + C1FFAFC2213323E900C50C1D /* VersionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA4213323E800C50C1D /* VersionResponse.swift */; }; + C1FFAFC3213323E900C50C1D /* PodInfoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA5213323E800C50C1D /* PodInfoResponse.swift */; }; + C1FFAFC4213323E900C50C1D /* TempBasalExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */; }; + C1FFAFC5213323E900C50C1D /* DeactivatePodCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */; }; + C1FFAFC6213323E900C50C1D /* MessageBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA8213323E800C50C1D /* MessageBlock.swift */; }; + C1FFAFC7213323E900C50C1D /* PlaceholderMessageBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */; }; + C1FFAFC8213323E900C50C1D /* BolusExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */; }; + C1FFAFC9213323E900C50C1D /* StatusResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAB213323E800C50C1D /* StatusResponse.swift */; }; + C1FFAFCA213323E900C50C1D /* GetStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */; }; + C1FFAFCB213323E900C50C1D /* BasalScheduleExtraCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */; }; + C1FFAFCC213323E900C50C1D /* CancelDeliveryCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */; }; + C1FFAFCD213323E900C50C1D /* ConfigurePodCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFAF213323E800C50C1D /* ConfigurePodCommand.swift */; }; + C1FFAFCE213323E900C50C1D /* AssignAddressCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */; }; + C1FFAFCF213323E900C50C1D /* ErrorResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */; }; + C1FFAFD0213323E900C50C1D /* SetInsulinScheduleCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */; }; + C1FFAFD1213323E900C50C1D /* ConfigureAlertsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */; }; + C1FFAFD2213323E900C50C1D /* PodComms.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB4213323E800C50C1D /* PodComms.swift */; }; + C1FFAFD3213323E900C50C1D /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFB5213323E900C50C1D /* Packet.swift */; }; + C1FFAFEB213323FA00C50C1D /* OmniKitUI.h in Headers */ = {isa = PBXBuildFile; fileRef = C1FFAFDB213323F900C50C1D /* OmniKitUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C1FFAFEE213323FA00C50C1D /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; }; + C1FFAFEF213323FA00C50C1D /* OmniKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1FFAFFE2133241700C50C1D /* MessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF62133241500C50C1D /* MessageTests.swift */; }; + C1FFAFFF2133241700C50C1D /* PodStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF72133241500C50C1D /* PodStateTests.swift */; }; + C1FFB0002133241700C50C1D /* TempBasalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF82133241500C50C1D /* TempBasalTests.swift */; }; + C1FFB0012133241700C50C1D /* CRC8Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFF92133241600C50C1D /* CRC8Tests.swift */; }; + C1FFB0022133241700C50C1D /* PacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFA2133241600C50C1D /* PacketTests.swift */; }; + C1FFB0032133241700C50C1D /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */; }; + C1FFB0042133241700C50C1D /* CRC16Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */; }; + C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */; }; + C1FFB00E2133242C00C50C1D /* OmniPodPumpManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */; }; + C1FFB00F2133242C00C50C1D /* OmniKitUI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */; }; + C1FFB0102133242C00C50C1D /* OmnipodSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */; }; + C1FFB0132133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */; }; + C1FFB0142133249300C50C1D /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; + C1FFB0152133249900C50C1D /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; }; + C1FFB016213324BC00C50C1D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; + C1FFB01721332A1F00C50C1D /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; + C1FFB01821332A2A00C50C1D /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; }; + C1FFB01921332A7100C50C1D /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; }; + C1FFB01A21332AFE00C50C1D /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */; }; + C1FFB01B21332DD900C50C1D /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */; }; + C1FFB01C21332E0900C50C1D /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */; }; + C1FFB01D21332E1700C50C1D /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; + C1FFB01E2133860E00C50C1D /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC601D3D75470049CF85 /* UIColor.swift */; }; + C1FFB02121343E6D00C50C1D /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; + C1FFB02421343EC200C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; + C1FFB02521343F0300C50C1D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; + C1FFB02621343F0600C50C1D /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610A20DDF55E002B996B /* LoopKitUI.framework */; }; + D807D7D82289135D006BCDF0 /* BeepType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D807D7D72289135D006BCDF0 /* BeepType.swift */; }; + D807D7DA228913EC006BCDF0 /* BeepConfigCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D807D7D9228913EC006BCDF0 /* BeepConfigCommand.swift */; }; + E95D0660215D76E40072157B /* PodInfoFlashLogRecent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E95D065F215D76E40072157B /* PodInfoFlashLogRecent.swift */; }; + E993D18C217E455000E489BF /* LogEventErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993D18B217E455000E489BF /* LogEventErrorCode.swift */; }; + E9C06B262150371700B602AD /* PodInfoConfiguredAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C06B252150371700B602AD /* PodInfoConfiguredAlerts.swift */; }; + E9C06B2821506A9200B602AD /* AcknowledgeAlertsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C06B2721506A9200B602AD /* AcknowledgeAlertsTests.swift */; }; + E9C06B2A21506BF300B602AD /* AcknowledgeAlertCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C06B2921506BF300B602AD /* AcknowledgeAlertCommand.swift */; }; + E9C06B2C21513B2600B602AD /* PodInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9C06B2B21513B2600B602AD /* PodInfoTests.swift */; }; + E9E54AB421542E8A00E319B8 /* PodInfoResetStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E54AB321542E8A00E319B8 /* PodInfoResetStatus.swift */; }; + E9E54AB62156B2D500E319B8 /* PodInfoDataLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E54AB52156B2D500E319B8 /* PodInfoDataLog.swift */; }; + E9EDD475215AFFF300A103D1 /* PodInfoFault.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EDD474215AFFF300A103D1 /* PodInfoFault.swift */; }; + E9EDD477215BED3500A103D1 /* PodInfoTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EDD476215BED3500A103D1 /* PodInfoTester.swift */; }; + E9EE3368214ECFF900888876 /* StatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EE3367214ECFF900888876 /* StatusTests.swift */; }; + E9EE336A214ED00400888876 /* BolusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EE3369214ED00400888876 /* BolusTests.swift */; }; + E9EE336E214ED01200888876 /* PodInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EE336B214ED01200888876 /* PodInfo.swift */; }; + E9EE3370214ED01200888876 /* PodInfoFaultEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EE336D214ED01200888876 /* PodInfoFaultEvent.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -517,6 +674,27 @@ remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; remoteInfo = RileyLinkKit; }; + 437DE4FC229BB0D1003B1074 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; + }; + 437DE4FE229BB0E3003B1074 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1FFAF77213323CC00C50C1D; + remoteInfo = OmniKit; + }; + 437DE500229BB0E9003B1074 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; + }; 43C246A21D891D6C0031F8D1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -643,6 +821,27 @@ remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; + C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1FFAF77213323CC00C50C1D; + remoteInfo = OmniKit; + }; + C1FFAF8B213323CC00C50C1D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1FFAF77213323CC00C50C1D; + remoteInfo = OmniKit; + }; + C1FFAFEC213323FA00C50C1D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1FFAFD8213323F900C50C1D; + remoteInfo = OmniKitUI; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -652,10 +851,12 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + C1FFAF8E213323CC00C50C1D /* OmniKit.framework in Embed Frameworks */, 43722FC41CB9F7640038B7F2 /* RileyLinkKit.framework in Embed Frameworks */, 43C246AA1D8A31540031F8D1 /* Crypto.framework in Embed Frameworks */, C10D9BD71C8269D500378342 /* MinimedKit.framework in Embed Frameworks */, 43D5E7961FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Embed Frameworks */, + C1FFAFEF213323FA00C50C1D /* OmniKitUI.framework in Embed Frameworks */, C1B383211CD0665D00CE7782 /* NightscoutUploadKit.framework in Embed Frameworks */, 4352A72D20DEC9B700CAC200 /* MinimedKitUI.framework in Embed Frameworks */, 431CE7851F98564200255374 /* RileyLinkBLEKit.framework in Embed Frameworks */, @@ -663,6 +864,15 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + C1BB128321CB5603009A29B5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -741,6 +951,7 @@ 43722FB71CB9F7640038B7F2 /* RileyLinkKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RileyLinkKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43722FC01CB9F7640038B7F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 437462381FA9287A00643383 /* RileyLinkDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkDevice.swift; sourceTree = ""; }; + 437DE508229C8A05003B1074 /* copy-frameworks.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-frameworks.sh"; sourceTree = ""; }; 4384C8C51FB92F8100D916E6 /* HistoryPage+PumpOpsSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HistoryPage+PumpOpsSession.swift"; sourceTree = ""; }; 4384C8C71FB937E500D916E6 /* PumpSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpSettings.swift; sourceTree = ""; }; 438D39211D19011700D40CA4 /* PlaceholderPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderPumpEvent.swift; sourceTree = ""; }; @@ -1005,6 +1216,11 @@ 7D70768C1FE09310004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 7D7076911FE09311004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D7076961FE09311004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodReservoirView.swift; sourceTree = ""; }; + C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OmnipodReservoirView.xib; sourceTree = ""; }; + C104A9C4217E645C006E3C3E /* HUDAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = HUDAssets.xcassets; sourceTree = ""; }; + C104A9C6217E9F34006E3C3E /* PodLifeHUDView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PodLifeHUDView.xib; sourceTree = ""; }; + C104A9C8217E9F6C006E3C3E /* PodLifeHUDView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodLifeHUDView.swift; sourceTree = ""; }; C10AB08C1C855613000F102E /* FindDeviceMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindDeviceMessageBody.swift; sourceTree = ""; }; C10AB08E1C855F34000F102E /* DeviceLinkMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceLinkMessageBody.swift; sourceTree = ""; }; C10D9BC11C8269D500378342 /* MinimedKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MinimedKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1012,6 +1228,8 @@ C10D9BC51C8269D500378342 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C10D9BCA1C8269D500378342 /* MinimedKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MinimedKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C10D9BD31C8269D500378342 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C11166AD2180D834000EEAAB /* AlertSlot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertSlot.swift; sourceTree = ""; }; + C11F6B7D21C9646300752BBC /* FaultConfigCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaultConfigCommand.swift; sourceTree = ""; }; C121985E1C8DE77D00BC374C /* FindDeviceMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindDeviceMessageBodyTests.swift; sourceTree = ""; }; C12198601C8DEB7B00BC374C /* DeviceLinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceLinkMessageBodyTests.swift; sourceTree = ""; }; C12198621C8DF4C800BC374C /* HistoryPageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryPageTests.swift; sourceTree = ""; }; @@ -1032,6 +1250,8 @@ C1274F781D823A550002912B /* ChangeMeterIDPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeMeterIDPumpEvent.swift; sourceTree = ""; }; C1274F7C1D82411C0002912B /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; C1274F851D8242BE0002912B /* PumpRegion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpRegion.swift; sourceTree = ""; }; + C127D923215C002A0031799D /* PodSettingsSetupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodSettingsSetupViewController.swift; sourceTree = ""; }; + C127D926215C00420031799D /* PodCommsSession+LoopKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PodCommsSession+LoopKit.swift"; sourceTree = ""; }; C12EA237198B436800309FA4 /* RileyLink.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RileyLink.app; sourceTree = BUILT_PRODUCTS_DIR; }; C12EA23A198B436800309FA4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; C12EA23C198B436800309FA4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -1045,11 +1265,17 @@ C12EA25F198B436900309FA4 /* RileyLinkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RileyLinkTests.m; sourceTree = ""; }; C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeSensorAlarmSilenceConfigPumpEvent.swift; sourceTree = ""; }; C133CF921D5943780034B82D /* PredictedBG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictedBG.swift; sourceTree = ""; }; + C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInsulinMeasurements.swift; sourceTree = ""; }; + C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfinalizedDose.swift; sourceTree = ""; }; C13D15591DAACE8400ADC044 /* Either.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; + C13FD2F3215E7338005FC495 /* FaultEventCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaultEventCode.swift; sourceTree = ""; }; + C13FD2F5215E743C005FC495 /* PodProgressStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodProgressStatus.swift; sourceTree = ""; }; C14303151C97C98000A40450 /* PumpAckMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpAckMessageBody.swift; sourceTree = ""; }; C14303171C97CC6B00A40450 /* GetPumpModelCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpModelCarelinkMessageBodyTests.swift; sourceTree = ""; }; C14303191C9A610B00A40450 /* GetBatteryCarelinkMessageBodyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBatteryCarelinkMessageBodyTests.swift; sourceTree = ""; }; C145BF9D2219F2EC00A977CB /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = ""; }; + C145BFA3221BBA7E00A977CB /* SuspendResumeMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuspendResumeMessageBody.swift; sourceTree = ""; }; + C14B42F921FF78840073A836 /* MessageLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLog.swift; sourceTree = ""; }; C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NightscoutPumpEventsTests.swift; path = ../RileyLinkTests/NightscoutPumpEventsTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14D2B041C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TempBasalDurationPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14D2B081C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChangeTempBasalTypePumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -1066,9 +1292,11 @@ C15AF2AC1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeBolusWizardSetupPumpEvent.swift; sourceTree = ""; }; C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreMystery54PumpEvent.swift; sourceTree = ""; }; C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreMystery55PumpEvent.swift; sourceTree = ""; }; + C168C3FF21AEF8DE00ADE90E /* PodReplacementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodReplacementNavigationController.swift; sourceTree = ""; }; + C168C40121AFACA600ADE90E /* ReplacePodViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplacePodViewController.swift; sourceTree = ""; }; C16A08301D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JournalEntryMealMarkerPumpEvent.swift; sourceTree = ""; }; - C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuspendResumeMessageBody.swift; sourceTree = ""; }; - C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPumpFirmwareVersionMessageBody.swift; sourceTree = ""; }; + C16C3D4321AC40AF00401105 /* OmnipodHUDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmnipodHUDProvider.swift; sourceTree = ""; }; + C16E190C224EA33000DD9B9D /* PodDoseProgressEstimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodDoseProgressEstimator.swift; sourceTree = ""; }; C16E61212208C7A80069F357 /* ReservoirReading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservoirReading.swift; sourceTree = ""; }; C16E61252208EC580069F357 /* MinimedHUDProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedHUDProvider.swift; sourceTree = ""; }; C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBPeripheralState.swift; sourceTree = ""; }; @@ -1080,6 +1308,8 @@ C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadPumpStatusMessageBody.swift; sourceTree = ""; }; C178845E1D5166BE00405663 /* COBStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = COBStatus.swift; sourceTree = ""; }; C17884601D519F1E00405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = ""; }; + C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpirationReminderDateTableViewCell.swift; sourceTree = ""; }; + C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ExpirationReminderDateTableViewCell.xib; sourceTree = ""; }; C1842BBA1C8E184300DB42AC /* PumpModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpModel.swift; sourceTree = ""; }; C1842BBC1C8E7C6E00DB42AC /* PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpEvent.swift; sourceTree = ""; }; C1842BBE1C8E855A00DB42AC /* PumpEventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PumpEventType.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -1145,6 +1375,7 @@ C1A7215F1EC29C0B0080FAD7 /* PumpOpsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpOpsError.swift; sourceTree = ""; }; C1A721611EC3E0500080FAD7 /* PumpErrorMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpErrorMessageBody.swift; sourceTree = ""; }; C1A721651EC4BCE30080FAD7 /* PartialDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PartialDecode.swift; sourceTree = ""; }; + C1ABE396224947C000570E82 /* PodCommsSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodCommsSessionTests.swift; sourceTree = ""; }; C1AF21E11D4838C90088C41D /* DeviceStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceStatus.swift; sourceTree = ""; }; C1AF21E31D4865320088C41D /* LoopStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopStatus.swift; sourceTree = ""; }; C1AF21E51D48667F0088C41D /* UploaderStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploaderStatus.swift; sourceTree = ""; }; @@ -1162,6 +1393,9 @@ C1B383351CD1BA8100CE7782 /* DeviceDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceDataManager.swift; sourceTree = ""; }; C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; C1BAD1171E63984C009BA1C6 /* RadioAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioAdapter.swift; sourceTree = ""; }; + C1BB128521CB5603009A29B5 /* OmniKitPacketParser */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = OmniKitPacketParser; sourceTree = BUILT_PRODUCTS_DIR; }; + C1BB128721CB5603009A29B5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + C1BB12B321CB5697009A29B5 /* Packet+RFPacket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Packet+RFPacket.swift"; sourceTree = ""; }; C1C3578E1C927303009BDD4F /* MeterMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeterMessage.swift; sourceTree = ""; }; C1C357901C92733A009BDD4F /* MeterMessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MeterMessageTests.swift; path = Messages/MeterMessageTests.swift; sourceTree = ""; }; C1C659181E16BA9D0025CC58 /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; @@ -1201,11 +1435,16 @@ C1F0004B1EBE68A600F65163 /* DataFrameMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataFrameMessageBody.swift; sourceTree = ""; }; C1F0004F1EBE727C00F65163 /* BasalScheduleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleTests.swift; sourceTree = ""; }; C1F000511EBE73F400F65163 /* BasalSchedule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalSchedule.swift; sourceTree = ""; }; + C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairPodSetupViewController.swift; sourceTree = ""; }; + C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsertCannulaSetupViewController.swift; sourceTree = ""; }; + C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodSetupCompleteViewController.swift; sourceTree = ""; }; C1F6EB881F89C3E200CFE393 /* FourByteSixByteEncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FourByteSixByteEncoding.swift; sourceTree = ""; }; C1F6EB8A1F89C41200CFE393 /* MinimedPacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPacket.swift; sourceTree = ""; }; C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimedPacketTests.swift; sourceTree = ""; }; C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedDoseProgressEstimator.swift; sourceTree = ""; }; + C1FB4272216E5DFD00FAB378 /* GetPumpFirmwareVersionMessageBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPumpFirmwareVersionMessageBody.swift; sourceTree = ""; }; C1FC49EB2135CB2D007D0788 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandResponseViewController.swift; sourceTree = ""; }; C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusReminderPumpEvent.swift; sourceTree = ""; }; C1FFAF4C212944F600C50C1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; C1FFAF4F212944FF00C50C1D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; @@ -1227,6 +1466,73 @@ C1FFAF6D212B128C00C50C1D /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; C1FFAF6E212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkConnectionManagerState.swift; sourceTree = ""; }; C1FFAF71212FAAEF00C50C1D /* RileyLink.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = RileyLink.xcassets; sourceTree = ""; }; + C1FFAF78213323CC00C50C1D /* OmniKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OmniKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C1FFAF7A213323CC00C50C1D /* OmniKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OmniKit.h; sourceTree = ""; }; + C1FFAF7B213323CC00C50C1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OmniKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C1FFAF89213323CC00C50C1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1FFAF96213323E600C50C1D /* OmnipodPumpManagerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodPumpManagerState.swift; sourceTree = ""; }; + C1FFAF97213323E600C50C1D /* OmnipodPumpManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodPumpManager.swift; sourceTree = ""; }; + C1FFAF98213323E600C50C1D /* Pod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pod.swift; sourceTree = ""; }; + C1FFAF99213323E600C50C1D /* PodCommsSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodCommsSession.swift; sourceTree = ""; }; + C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalDeliveryTable.swift; sourceTree = ""; }; + C1FFAF9B213323E700C50C1D /* MessageTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTransport.swift; sourceTree = ""; }; + C1FFAF9C213323E700C50C1D /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + C1FFAF9D213323E700C50C1D /* PodState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodState.swift; sourceTree = ""; }; + C1FFAF9E213323E700C50C1D /* CRC16.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC16.swift; sourceTree = ""; }; + C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalSchedule.swift; sourceTree = ""; }; + C1FFAFA0213323E800C50C1D /* CRC8.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC8.swift; sourceTree = ""; }; + C1FFAFA2213323E800C50C1D /* Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; + C1FFAFA4213323E800C50C1D /* VersionResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionResponse.swift; sourceTree = ""; }; + C1FFAFA5213323E800C50C1D /* PodInfoResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodInfoResponse.swift; sourceTree = ""; }; + C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempBasalExtraCommand.swift; sourceTree = ""; }; + C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeactivatePodCommand.swift; sourceTree = ""; }; + C1FFAFA8213323E800C50C1D /* MessageBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageBlock.swift; sourceTree = ""; }; + C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderMessageBlock.swift; sourceTree = ""; }; + C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusExtraCommand.swift; sourceTree = ""; }; + C1FFAFAB213323E800C50C1D /* StatusResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusResponse.swift; sourceTree = ""; }; + C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetStatusCommand.swift; sourceTree = ""; }; + C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleExtraCommand.swift; sourceTree = ""; }; + C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CancelDeliveryCommand.swift; sourceTree = ""; }; + C1FFAFAF213323E800C50C1D /* ConfigurePodCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurePodCommand.swift; sourceTree = ""; }; + C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssignAddressCommand.swift; sourceTree = ""; }; + C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorResponse.swift; sourceTree = ""; }; + C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetInsulinScheduleCommand.swift; sourceTree = ""; }; + C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigureAlertsCommand.swift; sourceTree = ""; }; + C1FFAFB4213323E800C50C1D /* PodComms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodComms.swift; sourceTree = ""; }; + C1FFAFB5213323E900C50C1D /* Packet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Packet.swift; sourceTree = ""; }; + C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OmniKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C1FFAFDB213323F900C50C1D /* OmniKitUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OmniKitUI.h; sourceTree = ""; }; + C1FFAFDC213323F900C50C1D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1FFAFF62133241500C50C1D /* MessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTests.swift; sourceTree = ""; }; + C1FFAFF72133241500C50C1D /* PodStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodStateTests.swift; sourceTree = ""; }; + C1FFAFF82133241500C50C1D /* TempBasalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TempBasalTests.swift; sourceTree = ""; }; + C1FFAFF92133241600C50C1D /* CRC8Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC8Tests.swift; sourceTree = ""; }; + C1FFAFFA2133241600C50C1D /* PacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTests.swift; sourceTree = ""; }; + C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleTests.swift; sourceTree = ""; }; + C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC16Tests.swift; sourceTree = ""; }; + C1FFAFFD2133241600C50C1D /* OmniKitTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OmniKitTests-Bridging-Header.h"; sourceTree = ""; }; + C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = OmnipodPumpManager.storyboard; sourceTree = ""; }; + C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OmniPodPumpManager+UI.swift"; sourceTree = ""; }; + C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OmniKitUI.xcassets; sourceTree = ""; }; + C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodSettingsViewController.swift; sourceTree = ""; }; + C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodPumpManagerSetupViewController.swift; sourceTree = ""; }; + D807D7D72289135D006BCDF0 /* BeepType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeepType.swift; sourceTree = ""; }; + D807D7D9228913EC006BCDF0 /* BeepConfigCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeepConfigCommand.swift; sourceTree = ""; }; + E95D065F215D76E40072157B /* PodInfoFlashLogRecent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodInfoFlashLogRecent.swift; sourceTree = ""; }; + E993D18B217E455000E489BF /* LogEventErrorCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEventErrorCode.swift; sourceTree = ""; }; + E9C06B252150371700B602AD /* PodInfoConfiguredAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodInfoConfiguredAlerts.swift; sourceTree = ""; }; + E9C06B2721506A9200B602AD /* AcknowledgeAlertsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgeAlertsTests.swift; sourceTree = ""; }; + E9C06B2921506BF300B602AD /* AcknowledgeAlertCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcknowledgeAlertCommand.swift; sourceTree = ""; }; + E9C06B2B21513B2600B602AD /* PodInfoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInfoTests.swift; sourceTree = ""; }; + E9E54AB321542E8A00E319B8 /* PodInfoResetStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInfoResetStatus.swift; sourceTree = ""; }; + E9E54AB52156B2D500E319B8 /* PodInfoDataLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInfoDataLog.swift; sourceTree = ""; }; + E9EDD474215AFFF300A103D1 /* PodInfoFault.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodInfoFault.swift; sourceTree = ""; }; + E9EDD476215BED3500A103D1 /* PodInfoTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInfoTester.swift; sourceTree = ""; }; + E9EE3367214ECFF900888876 /* StatusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusTests.swift; sourceTree = ""; }; + E9EE3369214ED00400888876 /* BolusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusTests.swift; sourceTree = ""; }; + E9EE336B214ED01200888876 /* PodInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodInfo.swift; sourceTree = ""; }; + E9EE336D214ED01200888876 /* PodInfoFaultEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodInfoFaultEvent.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1316,6 +1622,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1FFAFEE213323FA00C50C1D /* OmniKitUI.framework in Frameworks */, 43C246A91D8A31540031F8D1 /* Crypto.framework in Frameworks */, C1B383201CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */, C12EA23D198B436800309FA4 /* CoreGraphics.framework in Frameworks */, @@ -1326,6 +1633,7 @@ 431CE7841F98564200255374 /* RileyLinkBLEKit.framework in Frameworks */, C10D9BD61C8269D500378342 /* MinimedKit.framework in Frameworks */, 4352A72C20DEC9B700CAC200 /* MinimedKitUI.framework in Frameworks */, + C1FFAF8D213323CC00C50C1D /* OmniKit.framework in Frameworks */, 4352A74720DED4AF00CAC200 /* LoopKit.framework in Frameworks */, 4352A74620DED4AB00CAC200 /* LoopKitUI.framework in Frameworks */, ); @@ -1362,6 +1670,42 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1BB128221CB5603009A29B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAF74213323CC00C50C1D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1FFB0152133249900C50C1D /* RileyLinkBLEKit.framework in Frameworks */, + C1FFB0142133249300C50C1D /* RileyLinkKit.framework in Frameworks */, + C1FFB016213324BC00C50C1D /* LoopKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAF7D213323CC00C50C1D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1FFAF81213323CC00C50C1D /* OmniKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAFD5213323F900C50C1D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C1E163D52135EC0100EB89AE /* RileyLinkKitUI.framework in Frameworks */, + C1FFB02421343EC200C50C1D /* OmniKit.framework in Frameworks */, + C1FFB02521343F0300C50C1D /* LoopKit.framework in Frameworks */, + C1FFB02621343F0600C50C1D /* LoopKitUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -1506,6 +1850,14 @@ path = RileyLinkKitTests; sourceTree = ""; }; + 437DE504229C898D003B1074 /* Scripts */ = { + isa = PBXGroup; + children = ( + 437DE508229C8A05003B1074 /* copy-frameworks.sh */, + ); + path = Scripts; + sourceTree = ""; + }; 43C246941D8918AE0031F8D1 /* Crypto */ = { isa = PBXGroup; children = ( @@ -1645,6 +1997,20 @@ path = GlucoseEvents; sourceTree = ""; }; + C104A9BE217E6027006E3C3E /* Views */ = { + isa = PBXGroup; + children = ( + C104A9C4217E645C006E3C3E /* HUDAssets.xcassets */, + C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */, + C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */, + C104A9C8217E9F6C006E3C3E /* PodLifeHUDView.swift */, + C104A9C6217E9F34006E3C3E /* PodLifeHUDView.xib */, + C1814B87225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift */, + C1814B89225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib */, + ); + path = Views; + sourceTree = ""; + }; C10D9BC21C8269D500378342 /* MinimedKit */ = { isa = PBXGroup; children = ( @@ -1749,8 +2115,13 @@ 431CE77D1F98564200255374 /* RileyLinkBLEKitTests */, 43D5E78F1FAF7BFB004ACDB7 /* RileyLinkKitUI */, 4352A72620DEC9B700CAC200 /* MinimedKitUI */, + C1FFAF79213323CC00C50C1D /* OmniKit */, + C1FFAF86213323CC00C50C1D /* OmniKitTests */, + C1FFAFDA213323F900C50C1D /* OmniKitUI */, + C1BB128621CB5603009A29B5 /* OmniKitPacketParser */, C12EA239198B436800309FA4 /* Frameworks */, C12EA238198B436800309FA4 /* Products */, + 437DE504229C898D003B1074 /* Scripts */, ); sourceTree = ""; }; @@ -1770,6 +2141,10 @@ 431CE7771F98564200255374 /* RileyLinkBLEKitTests.xctest */, 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */, 4352A72520DEC9B700CAC200 /* MinimedKitUI.framework */, + C1FFAF78213323CC00C50C1D /* OmniKit.framework */, + C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */, + C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */, + C1BB128521CB5603009A29B5 /* OmniKitPacketParser */, ); name = Products; sourceTree = ""; @@ -1840,6 +2215,36 @@ name = "Supporting Files"; sourceTree = ""; }; + C13BD64021403362006D7F19 /* MessageTransport */ = { + isa = PBXGroup; + children = ( + C1FFAFA0213323E800C50C1D /* CRC8.swift */, + C1FFAF9E213323E700C50C1D /* CRC16.swift */, + C1FFAFB5213323E900C50C1D /* Packet.swift */, + C1BB12B321CB5697009A29B5 /* Packet+RFPacket.swift */, + C1FFAF9B213323E700C50C1D /* MessageTransport.swift */, + C1FFAF9C213323E700C50C1D /* Message.swift */, + C1FFAFA3213323E800C50C1D /* MessageBlocks */, + ); + path = MessageTransport; + sourceTree = ""; + }; + C13BD641214033B0006D7F19 /* Model */ = { + isa = PBXGroup; + children = ( + C1FFAF98213323E600C50C1D /* Pod.swift */, + C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */, + C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */, + C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */, + C13FD2F3215E7338005FC495 /* FaultEventCode.swift */, + C13FD2F5215E743C005FC495 /* PodProgressStatus.swift */, + E993D18B217E455000E489BF /* LogEventErrorCode.swift */, + C11166AD2180D834000EEAAB /* AlertSlot.swift */, + D807D7D72289135D006BCDF0 /* BeepType.swift */, + ); + path = Model; + sourceTree = ""; + }; C14FFC4D1D3D6D8E0049CF85 /* Models */ = { isa = PBXGroup; children = ( @@ -1850,6 +2255,15 @@ path = Models; sourceTree = ""; }; + C168C3FE21AEF85400ADE90E /* PumpManager */ = { + isa = PBXGroup; + children = ( + C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */, + C16C3D4321AC40AF00401105 /* OmnipodHUDProvider.swift */, + ); + path = PumpManager; + sourceTree = ""; + }; C1842BB91C8E15C600DB42AC /* PumpEvents */ = { isa = PBXGroup; children = ( @@ -2016,6 +2430,14 @@ name = Managers; sourceTree = ""; }; + C1BB128621CB5603009A29B5 /* OmniKitPacketParser */ = { + isa = PBXGroup; + children = ( + C1BB128721CB5603009A29B5 /* main.swift */, + ); + path = OmniKitPacketParser; + sourceTree = ""; + }; C1EAD6B81C826B92006DBA60 /* Extensions */ = { isa = PBXGroup; children = ( @@ -2029,12 +2451,7 @@ C1EAD6BC1C826B92006DBA60 /* Messages */ = { isa = PBXGroup; children = ( - C16E611C2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift */, - C16E611B2203CB7A0069F357 /* SuspendResumeMessageBody.swift */, - C1EAD6B21C826B6D006DBA60 /* PumpMessage.swift */, - C1EAD6B11C826B6D006DBA60 /* PacketType.swift */, - C1EAD6B01C826B6D006DBA60 /* MessageType.swift */, - 4352A70E20DEC47800CAC200 /* Models */, + C145BFA3221BBA7E00A977CB /* SuspendResumeMessageBody.swift */, C121989E1C8DFC2200BC374C /* BolusCarelinkMessageBody.swift */, C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */, C1EAD6BD1C826B92006DBA60 /* CarelinkMessageBody.swift */, @@ -2049,26 +2466,31 @@ C1711A5B1C953F3000CB25BD /* GetBatteryCarelinkMessageBody.swift */, 54BC44B81DB81D6100340EED /* GetGlucosePageMessageBody.swift */, C1711A5D1C977BD000CB25BD /* GetHistoryPageCarelinkMessageBody.swift */, + C1FB4272216E5DFD00FAB378 /* GetPumpFirmwareVersionMessageBody.swift */, C1711A591C952D2900CB25BD /* GetPumpModelCarelinkMessageBody.swift */, C1EAD6AF1C826B6D006DBA60 /* MessageBody.swift */, + C1EAD6B01C826B6D006DBA60 /* MessageType.swift */, C1C3578E1C927303009BDD4F /* MeterMessage.swift */, + 4352A70E20DEC47800CAC200 /* Models */, C1EAD6BE1C826B92006DBA60 /* MySentryAckMessageBody.swift */, C1EAD6BF1C826B92006DBA60 /* MySentryAlertClearedMessageBody.swift */, C1EAD6C01C826B92006DBA60 /* MySentryAlertMessageBody.swift */, C1EAD6C11C826B92006DBA60 /* MySentryPumpStatusMessageBody.swift */, + C1EAD6B11C826B6D006DBA60 /* PacketType.swift */, C1EAD6C21C826B92006DBA60 /* PowerOnCarelinkMessageBody.swift */, C14303151C97C98000A40450 /* PumpAckMessageBody.swift */, C1A721611EC3E0500080FAD7 /* PumpErrorMessageBody.swift */, + C1EAD6B21C826B6D006DBA60 /* PumpMessage.swift */, 541688DC1DB82213005B1891 /* ReadCurrentGlucosePageMessageBody.swift */, 2FDE1A061E57B12D00B56A27 /* ReadCurrentPageNumberMessageBody.swift */, 43DAD00320A6A677000F8529 /* ReadOtherDevicesIDsMessageBody.swift */, 43DAD00120A6A470000F8529 /* ReadOtherDevicesStatusMessageBody.swift */, C178845C1D4EF3D800405663 /* ReadPumpStatusMessageBody.swift */, 433568751CF67FA800FD9D54 /* ReadRemainingInsulinMessageBody.swift */, + 43CEC07320D0949B00F1BC19 /* ReadRemoteControlIDsMessageBody.swift */, C1EAD6C31C826B92006DBA60 /* ReadSettingsCarelinkMessageBody.swift */, 43CA932C1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift */, 43CA932D1CB8CFA1000026B5 /* ReadTimeCarelinkMessageBody.swift */, - 43CEC07320D0949B00F1BC19 /* ReadRemoteControlIDsMessageBody.swift */, 43BF58AF1FF594CB00499C46 /* SelectBasalProfileMessageBody.swift */, 43CEC07520D099B400F1BC19 /* SetRemoteControlEnabledMessageBody.swift */, C1EAD6C41C826B92006DBA60 /* UnknownMessageBody.swift */, @@ -2076,6 +2498,128 @@ path = Messages; sourceTree = ""; }; + C1FFAF79213323CC00C50C1D /* OmniKit */ = { + isa = PBXGroup; + children = ( + C13BD641214033B0006D7F19 /* Model */, + C13BD64021403362006D7F19 /* MessageTransport */, + C1FFAFA1213323E800C50C1D /* Extensions */, + C1FFAF95213323E600C50C1D /* PumpManager */, + C1FFAF7A213323CC00C50C1D /* OmniKit.h */, + C1FFAF7B213323CC00C50C1D /* Info.plist */, + ); + path = OmniKit; + sourceTree = ""; + }; + C1FFAF86213323CC00C50C1D /* OmniKitTests */ = { + isa = PBXGroup; + children = ( + E9C06B2721506A9200B602AD /* AcknowledgeAlertsTests.swift */, + C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */, + E9EE3369214ED00400888876 /* BolusTests.swift */, + C1FFAFF92133241600C50C1D /* CRC8Tests.swift */, + C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */, + C1FFAF89213323CC00C50C1D /* Info.plist */, + C1FFAFF62133241500C50C1D /* MessageTests.swift */, + C1FFAFFD2133241600C50C1D /* OmniKitTests-Bridging-Header.h */, + C1FFAFFA2133241600C50C1D /* PacketTests.swift */, + C1ABE396224947C000570E82 /* PodCommsSessionTests.swift */, + E9C06B2B21513B2600B602AD /* PodInfoTests.swift */, + C1FFAFF72133241500C50C1D /* PodStateTests.swift */, + E9EE3367214ECFF900888876 /* StatusTests.swift */, + C1FFAFF82133241500C50C1D /* TempBasalTests.swift */, + ); + path = OmniKitTests; + sourceTree = ""; + }; + C1FFAF95213323E600C50C1D /* PumpManager */ = { + isa = PBXGroup; + children = ( + C16E190C224EA33000DD9B9D /* PodDoseProgressEstimator.swift */, + C127D926215C00420031799D /* PodCommsSession+LoopKit.swift */, + C1FFAFB4213323E800C50C1D /* PodComms.swift */, + C1FFAF99213323E600C50C1D /* PodCommsSession.swift */, + C1FFAF96213323E600C50C1D /* OmnipodPumpManagerState.swift */, + C1FFAF97213323E600C50C1D /* OmnipodPumpManager.swift */, + C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */, + C1FFAF9D213323E700C50C1D /* PodState.swift */, + C14B42F921FF78840073A836 /* MessageLog.swift */, + ); + path = PumpManager; + sourceTree = ""; + }; + C1FFAFA1213323E800C50C1D /* Extensions */ = { + isa = PBXGroup; + children = ( + C1FFAFA2213323E800C50C1D /* Notification.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + C1FFAFA3213323E800C50C1D /* MessageBlocks */ = { + isa = PBXGroup; + children = ( + E9C06B2921506BF300B602AD /* AcknowledgeAlertCommand.swift */, + C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */, + C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */, + C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */, + C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */, + C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */, + C1FFAFA7213323E800C50C1D /* DeactivatePodCommand.swift */, + C1FFAFB1213323E800C50C1D /* ErrorResponse.swift */, + C1FFAFAC213323E800C50C1D /* GetStatusCommand.swift */, + C1FFAFA8213323E800C50C1D /* MessageBlock.swift */, + C1FFAFA9213323E800C50C1D /* PlaceholderMessageBlock.swift */, + E9EE336B214ED01200888876 /* PodInfo.swift */, + E9C06B252150371700B602AD /* PodInfoConfiguredAlerts.swift */, + E9E54AB52156B2D500E319B8 /* PodInfoDataLog.swift */, + E9EDD474215AFFF300A103D1 /* PodInfoFault.swift */, + E9EE336D214ED01200888876 /* PodInfoFaultEvent.swift */, + E95D065F215D76E40072157B /* PodInfoFlashLogRecent.swift */, + E9E54AB321542E8A00E319B8 /* PodInfoResetStatus.swift */, + C1FFAFA5213323E800C50C1D /* PodInfoResponse.swift */, + E9EDD476215BED3500A103D1 /* PodInfoTester.swift */, + C1FFAFB2213323E800C50C1D /* SetInsulinScheduleCommand.swift */, + C1FFAFAF213323E800C50C1D /* ConfigurePodCommand.swift */, + C1FFAFAB213323E800C50C1D /* StatusResponse.swift */, + C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */, + C1FFAFA4213323E800C50C1D /* VersionResponse.swift */, + C11F6B7D21C9646300752BBC /* FaultConfigCommand.swift */, + D807D7D9228913EC006BCDF0 /* BeepConfigCommand.swift */, + ); + path = MessageBlocks; + sourceTree = ""; + }; + C1FFAFDA213323F900C50C1D /* OmniKitUI */ = { + isa = PBXGroup; + children = ( + C168C3FE21AEF85400ADE90E /* PumpManager */, + C1FFB0092133242C00C50C1D /* ViewControllers */, + C104A9BE217E6027006E3C3E /* Views */, + C1FFAFDC213323F900C50C1D /* Info.plist */, + C1FFAFDB213323F900C50C1D /* OmniKitUI.h */, + C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */, + C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */, + ); + path = OmniKitUI; + sourceTree = ""; + }; + C1FFB0092133242C00C50C1D /* ViewControllers */ = { + isa = PBXGroup; + children = ( + C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */, + C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */, + C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */, + C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */, + C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */, + C168C3FF21AEF8DE00ADE90E /* PodReplacementNavigationController.swift */, + C127D923215C002A0031799D /* PodSettingsSetupViewController.swift */, + C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */, + C168C40121AFACA600ADE90E /* ReplacePodViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2135,6 +2679,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1FFAF75213323CC00C50C1D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C1FFAF8A213323CC00C50C1D /* OmniKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAFD6213323F900C50C1D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C1FFAFEB213323FA00C50C1D /* OmniKitUI.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ @@ -2329,6 +2889,8 @@ 431CE7831F98564200255374 /* PBXTargetDependency */, 43D5E7941FAF7BFB004ACDB7 /* PBXTargetDependency */, 4352A72B20DEC9B700CAC200 /* PBXTargetDependency */, + C1FFAF8C213323CC00C50C1D /* PBXTargetDependency */, + C1FFAFED213323FA00C50C1D /* PBXTargetDependency */, ); name = RileyLink; productName = RileyLink; @@ -2395,6 +2957,80 @@ productReference = C1B383141CD0665D00CE7782 /* NightscoutUploadKitTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + C1BB128421CB5603009A29B5 /* OmniKitPacketParser */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1BB128B21CB5603009A29B5 /* Build configuration list for PBXNativeTarget "OmniKitPacketParser" */; + buildPhases = ( + C1BB128121CB5603009A29B5 /* Sources */, + C1BB128221CB5603009A29B5 /* Frameworks */, + C1BB128321CB5603009A29B5 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OmniKitPacketParser; + productName = OmniKitPacketParser; + productReference = C1BB128521CB5603009A29B5 /* OmniKitPacketParser */; + productType = "com.apple.product-type.tool"; + }; + C1FFAF77213323CC00C50C1D /* OmniKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1FFAF93213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKit" */; + buildPhases = ( + C1FFAF73213323CC00C50C1D /* Sources */, + C1FFAF74213323CC00C50C1D /* Frameworks */, + C1FFAF75213323CC00C50C1D /* Headers */, + C1FFAF76213323CC00C50C1D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 437DE4FD229BB0D1003B1074 /* PBXTargetDependency */, + ); + name = OmniKit; + productName = OmniKit; + productReference = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; + productType = "com.apple.product-type.framework"; + }; + C1FFAF7F213323CC00C50C1D /* OmniKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1FFAF94213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitTests" */; + buildPhases = ( + C1FFAF7C213323CC00C50C1D /* Sources */, + C1FFAF7D213323CC00C50C1D /* Frameworks */, + C1FFAF7E213323CC00C50C1D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C1FFAF83213323CC00C50C1D /* PBXTargetDependency */, + ); + name = OmniKitTests; + productName = OmniKitTests; + productReference = C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + C1FFAFD8213323F900C50C1D /* OmniKitUI */ = { + isa = PBXNativeTarget; + buildConfigurationList = C1FFAFF0213323FA00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitUI" */; + buildPhases = ( + C1FFAFD4213323F900C50C1D /* Sources */, + C1FFAFD5213323F900C50C1D /* Frameworks */, + C1FFAFD6213323F900C50C1D /* Headers */, + C1FFAFD7213323F900C50C1D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 437DE501229BB0E9003B1074 /* PBXTargetDependency */, + 437DE4FF229BB0E3003B1074 /* PBXTargetDependency */, + ); + name = OmniKitUI; + productName = OmniKitUI; + productReference = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -2407,7 +3043,7 @@ TargetAttributes = { 431CE76E1F98564100255374 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; 431CE7761F98564200255374 = { @@ -2418,12 +3054,12 @@ }; 4352A72420DEC9B700CAC200 = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; 43722FAD1CB9F7630038B7F2 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; 43722FB61CB9F7640038B7F2 = { CreatedOnToolsVersion = 7.3; @@ -2432,17 +3068,17 @@ }; 43C246921D8918AE0031F8D1 = { CreatedOnToolsVersion = 8.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; 43D5E78D1FAF7BFB004ACDB7 = { CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; C10D9BC01C8269D500378342 = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; C10D9BC91C8269D500378342 = { CreatedOnToolsVersion = 7.2.1; @@ -2450,7 +3086,7 @@ TestTargetID = C12EA236198B436800309FA4; }; C12EA236198B436800309FA4 = { - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; C12EA251198B436800309FA4 = { @@ -2459,12 +3095,32 @@ }; C1B3830A1CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; C1B383131CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1000; }; + C1BB128421CB5603009A29B5 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + }; + C1FFAF77213323CC00C50C1D = { + CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1020; + ProvisioningStyle = Manual; + }; + C1FFAF7F213323CC00C50C1D = { + CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1000; + ProvisioningStyle = Automatic; + TestTargetID = C12EA236198B436800309FA4; + }; + C1FFAFD8213323F900C50C1D = { + CreatedOnToolsVersion = 9.4.1; + LastSwiftMigration = 1020; + ProvisioningStyle = Manual; + }; }; }; buildConfigurationList = C12EA232198B436800309FA4 /* Build configuration list for PBXProject "RileyLink" */; @@ -2472,7 +3128,6 @@ developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, es, ru, @@ -2503,6 +3158,10 @@ 431CE7761F98564200255374 /* RileyLinkBLEKitTests */, 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */, 4352A72420DEC9B700CAC200 /* MinimedKitUI */, + C1FFAF77213323CC00C50C1D /* OmniKit */, + C1FFAF7F213323CC00C50C1D /* OmniKitTests */, + C1FFAFD8213323F900C50C1D /* OmniKitUI */, + C1BB128421CB5603009A29B5 /* OmniKitPacketParser */, ); }; /* End PBXProject section */ @@ -2623,6 +3282,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1FFAF76213323CC00C50C1D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAF7E213323CC00C50C1D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAFD7213323F900C50C1D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */, + C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */, + C104A9C7217E9F35006E3C3E /* PodLifeHUDView.xib in Resources */, + C104A9C5217E645C006E3C3E /* HUDAssets.xcassets in Resources */, + C1814B8A225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib in Resources */, + C1FFB00F2133242C00C50C1D /* OmniKitUI.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -2642,7 +3328,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; }; A9B839DC2280A178004E745E /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2844,6 +3530,7 @@ C1EB955D1C887FE5002517DF /* HistoryPage.swift in Sources */, 54BC44921DB47CBA00340EED /* BatteryChangeGlucoseEvent.swift in Sources */, C1EAD6CA1C826B92006DBA60 /* MySentryAlertClearedMessageBody.swift in Sources */, + C110A0E6221BBAEF0016560B /* GetPumpFirmwareVersionMessageBody.swift in Sources */, 7D2366F1212527DA0028B67D /* LocalizedString.swift in Sources */, C1842C181C8FA45100DB42AC /* BolusWizardSetupPumpEvent.swift in Sources */, C1274F791D823A550002912B /* ChangeMeterIDPumpEvent.swift in Sources */, @@ -2867,7 +3554,6 @@ C1842C0F1C8FA45100DB42AC /* ChangeSensorRateOfChangeAlertSetupPumpEvent.swift in Sources */, C1842BFE1C8FA45100DB42AC /* ResultDailyTotalPumpEvent.swift in Sources */, C1FDFCA91D964A3E00ADBC31 /* BolusReminderPumpEvent.swift in Sources */, - C16E611D2203CB7A0069F357 /* SuspendResumeMessageBody.swift in Sources */, 43CA93291CB8CF22000026B5 /* ChangeTempBasalCarelinkMessageBody.swift in Sources */, C1EAD6CD1C826B92006DBA60 /* PowerOnCarelinkMessageBody.swift in Sources */, 43CEC07620D099B400F1BC19 /* SetRemoteControlEnabledMessageBody.swift in Sources */, @@ -2935,7 +3621,6 @@ C1842C001C8FA45100DB42AC /* JournalEntryPumpLowReservoirPumpEvent.swift in Sources */, 54A840D11DB85D0600B1F202 /* UnknownGlucoseEvent.swift in Sources */, 4352A71A20DEC7CB00CAC200 /* CommandSession.swift in Sources */, - C16E611E2203CB7A0069F357 /* GetPumpFirmwareVersionMessageBody.swift in Sources */, C1842BCF1C8F9E5100DB42AC /* PumpAlarmPumpEvent.swift in Sources */, 43D8708F20DE1C23006B549E /* EnliteSensorDisplayable.swift in Sources */, C1EAD6C61C826B92006DBA60 /* Data.swift in Sources */, @@ -2967,6 +3652,7 @@ C1842C0A1C8FA45100DB42AC /* ChangeVariableBolusPumpEvent.swift in Sources */, C1C73F1D1DE6306A0022FC89 /* BatteryChemistryType.swift in Sources */, 43D8709120DE1C3B006B549E /* MySentryPumpStatusMessageBody+CGMManager.swift in Sources */, + C145BFA4221BBA7F00A977CB /* SuspendResumeMessageBody.swift in Sources */, C1842C191C8FA45100DB42AC /* ChangeBolusScrollStepSizePumpEvent.swift in Sources */, C1842C171C8FA45100DB42AC /* ChangeCaptureEventEnablePumpEvent.swift in Sources */, 54BC44B91DB81D6100340EED /* GetGlucosePageMessageBody.swift in Sources */, @@ -3130,6 +3816,171 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C1BB128121CB5603009A29B5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1BB12BB21CB5767009A29B5 /* LocalizedString.swift in Sources */, + C1BB12BE21CB57AA009A29B5 /* BasalSchedule.swift in Sources */, + C1BB129F21CB5654009A29B5 /* DeactivatePodCommand.swift in Sources */, + C1BB12A821CB5654009A29B5 /* PodInfoFaultEvent.swift in Sources */, + C1BB128821CB5603009A29B5 /* main.swift in Sources */, + C1BB12B921CB571C009A29B5 /* Pod.swift in Sources */, + C1BB12B021CB5654009A29B5 /* TempBasalExtraCommand.swift in Sources */, + C1BB129E21CB5654009A29B5 /* ConfigureAlertsCommand.swift in Sources */, + C1BB12AD21CB5654009A29B5 /* SetInsulinScheduleCommand.swift in Sources */, + C1BB129B21CB5654009A29B5 /* BasalScheduleExtraCommand.swift in Sources */, + C1BB129621CB564D009A29B5 /* Packet.swift in Sources */, + C1BB12BD21CB5796009A29B5 /* BasalDeliveryTable.swift in Sources */, + C1BB12B521CB56D2009A29B5 /* FaultEventCode.swift in Sources */, + C1BB12A421CB5654009A29B5 /* PodInfo.swift in Sources */, + C1BB12AA21CB5654009A29B5 /* PodInfoResetStatus.swift in Sources */, + C1BB12B221CB5654009A29B5 /* FaultConfigCommand.swift in Sources */, + C1BB12AF21CB5654009A29B5 /* StatusResponse.swift in Sources */, + C1BB12B721CB56E2009A29B5 /* Data.swift in Sources */, + C1BB129921CB5654009A29B5 /* AcknowledgeAlertCommand.swift in Sources */, + C1BB12A921CB5654009A29B5 /* PodInfoFlashLogRecent.swift in Sources */, + C1BB12B621CB56D2009A29B5 /* PodProgressStatus.swift in Sources */, + C1BB129421CB564D009A29B5 /* CRC8.swift in Sources */, + C1BB12A321CB5654009A29B5 /* PlaceholderMessageBlock.swift in Sources */, + C1BB129C21CB5654009A29B5 /* BolusExtraCommand.swift in Sources */, + C1BB12B121CB5654009A29B5 /* VersionResponse.swift in Sources */, + C1BB12B821CB56F3009A29B5 /* TimeInterval.swift in Sources */, + C1BB12A521CB5654009A29B5 /* PodInfoConfiguredAlerts.swift in Sources */, + C1BB12A221CB5654009A29B5 /* MessageBlock.swift in Sources */, + C1BB12BA21CB5758009A29B5 /* AlertSlot.swift in Sources */, + C1BB12A121CB5654009A29B5 /* GetStatusCommand.swift in Sources */, + C1BB12A621CB5654009A29B5 /* PodInfoDataLog.swift in Sources */, + C1BB129521CB564D009A29B5 /* CRC16.swift in Sources */, + C1BB129D21CB5654009A29B5 /* CancelDeliveryCommand.swift in Sources */, + C1BB12AC21CB5654009A29B5 /* PodInfoTester.swift in Sources */, + C1BB12AE21CB5654009A29B5 /* ConfigurePodCommand.swift in Sources */, + C1BB12BC21CB577E009A29B5 /* LogEventErrorCode.swift in Sources */, + C1BB12AB21CB5654009A29B5 /* PodInfoResponse.swift in Sources */, + C1BB12A021CB5654009A29B5 /* ErrorResponse.swift in Sources */, + C1BB129821CB564D009A29B5 /* Message.swift in Sources */, + C1BB129A21CB5654009A29B5 /* AssignAddressCommand.swift in Sources */, + C1BB12A721CB5654009A29B5 /* PodInfoFault.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAF73213323CC00C50C1D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E993D18C217E455000E489BF /* LogEventErrorCode.swift in Sources */, + C1FFAFBA213323E900C50C1D /* BasalDeliveryTable.swift in Sources */, + C1FFAFC5213323E900C50C1D /* DeactivatePodCommand.swift in Sources */, + C1FFAFB8213323E900C50C1D /* Pod.swift in Sources */, + C1FFAFB7213323E900C50C1D /* OmnipodPumpManager.swift in Sources */, + C1FFAFBB213323E900C50C1D /* MessageTransport.swift in Sources */, + E9C06B262150371700B602AD /* PodInfoConfiguredAlerts.swift in Sources */, + E95D0660215D76E40072157B /* PodInfoFlashLogRecent.swift in Sources */, + C1FFAFD0213323E900C50C1D /* SetInsulinScheduleCommand.swift in Sources */, + C1FFAFC4213323E900C50C1D /* TempBasalExtraCommand.swift in Sources */, + C11166AE2180D834000EEAAB /* AlertSlot.swift in Sources */, + C1FFAFB9213323E900C50C1D /* PodCommsSession.swift in Sources */, + C1FFAFC8213323E900C50C1D /* BolusExtraCommand.swift in Sources */, + C1FFAFC7213323E900C50C1D /* PlaceholderMessageBlock.swift in Sources */, + C17C5C0F21447383002A06F8 /* NumberFormatter.swift in Sources */, + C127D927215C00420031799D /* PodCommsSession+LoopKit.swift in Sources */, + C1FFB01821332A2A00C50C1D /* OSLog.swift in Sources */, + C1FFAFC6213323E900C50C1D /* MessageBlock.swift in Sources */, + C1FFAFC2213323E900C50C1D /* VersionResponse.swift in Sources */, + C13FD2F6215E743C005FC495 /* PodProgressStatus.swift in Sources */, + C1FFB01D21332E1700C50C1D /* Data.swift in Sources */, + C1FFAFBD213323E900C50C1D /* PodState.swift in Sources */, + C1FFAFB6213323E900C50C1D /* OmnipodPumpManagerState.swift in Sources */, + C1FFAFC3213323E900C50C1D /* PodInfoResponse.swift in Sources */, + C1E163D72135FF9A00EB89AE /* TimeZone.swift in Sources */, + E9E54AB62156B2D500E319B8 /* PodInfoDataLog.swift in Sources */, + C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */, + C1FFAFD1213323E900C50C1D /* ConfigureAlertsCommand.swift in Sources */, + C1FFAFBE213323E900C50C1D /* CRC16.swift in Sources */, + D807D7DA228913EC006BCDF0 /* BeepConfigCommand.swift in Sources */, + C1FFAFCD213323E900C50C1D /* ConfigurePodCommand.swift in Sources */, + E9EE3370214ED01200888876 /* PodInfoFaultEvent.swift in Sources */, + E9E54AB421542E8A00E319B8 /* PodInfoResetStatus.swift in Sources */, + E9EE336E214ED01200888876 /* PodInfo.swift in Sources */, + C1FFAFD2213323E900C50C1D /* PodComms.swift in Sources */, + C1FFB01921332A7100C50C1D /* IdentifiableClass.swift in Sources */, + C1FFAFC1213323E900C50C1D /* Notification.swift in Sources */, + C1FFAFC0213323E900C50C1D /* CRC8.swift in Sources */, + D807D7D82289135D006BCDF0 /* BeepType.swift in Sources */, + E9EDD477215BED3500A103D1 /* PodInfoTester.swift in Sources */, + C1FFAFCB213323E900C50C1D /* BasalScheduleExtraCommand.swift in Sources */, + C1CB13A521383F1E00F9EEDA /* LocalizedString.swift in Sources */, + C13FD2F4215E7338005FC495 /* FaultEventCode.swift in Sources */, + C1FFAFBF213323E900C50C1D /* BasalSchedule.swift in Sources */, + C1BB12B421CB5697009A29B5 /* Packet+RFPacket.swift in Sources */, + E9C06B2A21506BF300B602AD /* AcknowledgeAlertCommand.swift in Sources */, + C1FFAFC9213323E900C50C1D /* StatusResponse.swift in Sources */, + C1FFAFCE213323E900C50C1D /* AssignAddressCommand.swift in Sources */, + C1FFAFCC213323E900C50C1D /* CancelDeliveryCommand.swift in Sources */, + C16E190D224EA33000DD9B9D /* PodDoseProgressEstimator.swift in Sources */, + C11F6B7E21C9646300752BBC /* FaultConfigCommand.swift in Sources */, + C14B42FA21FF78840073A836 /* MessageLog.swift in Sources */, + C1FFAFCA213323E900C50C1D /* GetStatusCommand.swift in Sources */, + E9EDD475215AFFF300A103D1 /* PodInfoFault.swift in Sources */, + C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */, + C1FFAFD3213323E900C50C1D /* Packet.swift in Sources */, + C1FFAFBC213323E900C50C1D /* Message.swift in Sources */, + C1FFAFCF213323E900C50C1D /* ErrorResponse.swift in Sources */, + C1FFB01721332A1F00C50C1D /* TimeInterval.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAF7C213323CC00C50C1D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1FFB0032133241700C50C1D /* BasalScheduleTests.swift in Sources */, + E9C06B2821506A9200B602AD /* AcknowledgeAlertsTests.swift in Sources */, + C1FFAFFE2133241700C50C1D /* MessageTests.swift in Sources */, + C1FFAFFF2133241700C50C1D /* PodStateTests.swift in Sources */, + C1FFB0022133241700C50C1D /* PacketTests.swift in Sources */, + C17EDC4F2134D0CC0031D9F0 /* TimeZone.swift in Sources */, + C1FFB0042133241700C50C1D /* CRC16Tests.swift in Sources */, + E9C06B2C21513B2600B602AD /* PodInfoTests.swift in Sources */, + C1ABE397224947C000570E82 /* PodCommsSessionTests.swift in Sources */, + C1FFB0012133241700C50C1D /* CRC8Tests.swift in Sources */, + E9EE3368214ECFF900888876 /* StatusTests.swift in Sources */, + C1FFB0002133241700C50C1D /* TempBasalTests.swift in Sources */, + E9EE336A214ED00400888876 /* BolusTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C1FFAFD4213323F900C50C1D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1F1A5EE2151FBA700F0B820 /* PodSetupCompleteViewController.swift in Sources */, + C127D924215C002B0031799D /* PodSettingsSetupViewController.swift in Sources */, + C1FFB01B21332DD900C50C1D /* TimeZone.swift in Sources */, + C104A9C3217E611F006E3C3E /* NibLoadable.swift in Sources */, + C16C3D4421AC40AF00401105 /* OmnipodHUDProvider.swift in Sources */, + C1CB13A72138453B00F9EEDA /* NumberFormatter.swift in Sources */, + C1814B88225F0A3D008D2D8E /* ExpirationReminderDateTableViewCell.swift in Sources */, + C1FFB02121343E6D00C50C1D /* TimeInterval.swift in Sources */, + C1E163D62135ED0300EB89AE /* LocalizedString.swift in Sources */, + C1FFB0132133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift in Sources */, + C168C40021AEF8DE00ADE90E /* PodReplacementNavigationController.swift in Sources */, + C1F1A5EC2151F73F00F0B820 /* InsertCannulaSetupViewController.swift in Sources */, + C168C40221AFACA600ADE90E /* ReplacePodViewController.swift in Sources */, + C1FFB01A21332AFE00C50C1D /* IdentifiableClass.swift in Sources */, + C104A9C9217E9F6C006E3C3E /* PodLifeHUDView.swift in Sources */, + C1FFB0102133242C00C50C1D /* OmnipodSettingsViewController.swift in Sources */, + C127D925215C00320031799D /* OSLog.swift in Sources */, + C1F1A5EA215164FA00F0B820 /* PairPodSetupViewController.swift in Sources */, + C1FFB01E2133860E00C50C1D /* UIColor.swift in Sources */, + C14CD28E21B5872D00F259DB /* Data.swift in Sources */, + C104A9C1217E603E006E3C3E /* OmnipodReservoirView.swift in Sources */, + C1FFB01C21332E0900C50C1D /* UITableViewCell.swift in Sources */, + C1FFB00E2133242C00C50C1D /* OmniPodPumpManager+UI.swift in Sources */, + C1FC49EE2135DD56007D0788 /* CommandResponseViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -3173,6 +4024,21 @@ target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; targetProxy = 43722FC11CB9F7640038B7F2 /* PBXContainerItemProxy */; }; + 437DE4FD229BB0D1003B1074 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = 437DE4FC229BB0D1003B1074 /* PBXContainerItemProxy */; + }; + 437DE4FF229BB0E3003B1074 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1FFAF77213323CC00C50C1D /* OmniKit */; + targetProxy = 437DE4FE229BB0E3003B1074 /* PBXContainerItemProxy */; + }; + 437DE501229BB0E9003B1074 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; + targetProxy = 437DE500229BB0E9003B1074 /* PBXContainerItemProxy */; + }; 43C246A31D891D6C0031F8D1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43C246921D8918AE0031F8D1 /* Crypto */; @@ -3263,6 +4129,21 @@ target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; + C1FFAF83213323CC00C50C1D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1FFAF77213323CC00C50C1D /* OmniKit */; + targetProxy = C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */; + }; + C1FFAF8C213323CC00C50C1D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1FFAF77213323CC00C50C1D /* OmniKit */; + targetProxy = C1FFAF8B213323CC00C50C1D /* PBXContainerItemProxy */; + }; + C1FFAFED213323FA00C50C1D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1FFAFD8213323F900C50C1D /* OmniKitUI */; + targetProxy = C1FFAFEC213323FA00C50C1D /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -3614,8 +4495,6 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSION_INFO_PREFIX = ""; }; name = Debug; @@ -3652,8 +4531,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSION_INFO_PREFIX = ""; }; name = Release; @@ -3680,7 +4557,6 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; @@ -3707,7 +4583,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; @@ -3724,8 +4599,6 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; @@ -3748,8 +4621,6 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSION_INFO_PREFIX = ""; }; @@ -3766,8 +4637,6 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -3789,8 +4658,6 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSION_INFO_PREFIX = ""; }; @@ -3825,8 +4692,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -3861,8 +4726,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -3889,7 +4752,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; @@ -3915,7 +4777,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; @@ -3945,8 +4806,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSION_INFO_PREFIX = ""; }; name = Debug; @@ -3976,8 +4835,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.Crypto; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSION_INFO_PREFIX = ""; }; name = Release; @@ -4015,8 +4872,6 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSION_INFO_PREFIX = ""; }; @@ -4054,8 +4909,6 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; VERSION_INFO_PREFIX = ""; }; @@ -4090,8 +4943,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4125,8 +4976,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -4153,7 +5002,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; @@ -4179,7 +5027,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; @@ -4244,7 +5091,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; WARNING_CFLAGS = "-Wall"; @@ -4304,7 +5151,7 @@ SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -4333,8 +5180,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "RileyLink/RileyLink-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 1; WRAPPER_EXTENSION = app; }; @@ -4360,8 +5205,6 @@ PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "RileyLink/RileyLink-Bridging-Header.h"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 1; WRAPPER_EXTENSION = app; }; @@ -4430,8 +5273,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSION_INFO_PREFIX = ""; }; name = Debug; @@ -4461,8 +5302,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.NightscoutUploadKit; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; VERSION_INFO_PREFIX = ""; }; name = Release; @@ -4488,7 +5327,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -4513,7 +5351,264 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.NightscoutUploadKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; + }; + name = Release; + }; + C1BB128921CB5603009A29B5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C1BB128A21CB5603009A29B5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; + C1FFAF8F213323CC00C50C1D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 44; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 44; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C1FFAF90213323CC00C50C1D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 44; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 44; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C1FFAF91213323CC00C50C1D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; + }; + name = Debug; + }; + C1FFAF92213323CC00C50C1D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; + }; + name = Release; + }; + C1FFAFF1213323FA00C50C1D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 44; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 44; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKitUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C1FFAFF2213323FA00C50C1D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Manual; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 44; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 44; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKitUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSION_INFO_PREFIX = ""; }; name = Release; }; @@ -4646,6 +5741,42 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C1BB128B21CB5603009A29B5 /* Build configuration list for PBXNativeTarget "OmniKitPacketParser" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1BB128921CB5603009A29B5 /* Debug */, + C1BB128A21CB5603009A29B5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1FFAF93213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1FFAF8F213323CC00C50C1D /* Debug */, + C1FFAF90213323CC00C50C1D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1FFAF94213323CD00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1FFAF91213323CC00C50C1D /* Debug */, + C1FFAF92213323CC00C50C1D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C1FFAFF0213323FA00C50C1D /* Build configuration list for PBXNativeTarget "OmniKitUI" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C1FFAFF1213323FA00C50C1D /* Debug */, + C1FFAFF2213323FA00C50C1D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = C12EA22F198B436800309FA4 /* Project object */; diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme new file mode 100644 index 000000000..dcd0133b4 --- /dev/null +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme index e51b1727e..08158147c 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme @@ -20,6 +20,48 @@ ReferencedContainer = "container:RileyLink.xcodeproj"> + + + + + + + + + + + + @@ -70,9 +112,9 @@ buildForAnalyzing = "YES"> @@ -84,23 +126,23 @@ buildForAnalyzing = "YES"> + buildForRunning = "NO" + buildForProfiling = "NO" + buildForArchiving = "NO" + buildForAnalyzing = "NO"> @@ -162,6 +204,16 @@ ReferencedContainer = "container:RileyLink.xcodeproj"> + + + + Bool in + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .discoverServices = condition { return true } else { @@ -337,7 +337,7 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { commandLock.lock() - if let index = commandConditions.index(where: { (condition) -> Bool in + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .discoverCharacteristicsForService(serviceUUID: service.uuid) = condition { return true } else { @@ -358,7 +358,7 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { commandLock.lock() - if let index = commandConditions.index(where: { (condition) -> Bool in + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .notificationStateUpdate(characteristic: characteristic, enabled: characteristic.isNotifying) = condition { return true } else { @@ -379,7 +379,7 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { commandLock.lock() - if let index = commandConditions.index(where: { (condition) -> Bool in + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .write(characteristic: characteristic) = condition { return true } else { @@ -402,7 +402,7 @@ extension PeripheralManager: CBPeripheralDelegate { var notifyDelegate = false - if let index = commandConditions.index(where: { (condition) -> Bool in + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .valueUpdate(characteristic: characteristic, matching: let matching) = condition { return matching?(characteristic.value) ?? true } else { diff --git a/RileyLinkBLEKit/RileyLinkDeviceManager.swift b/RileyLinkBLEKit/RileyLinkDeviceManager.swift index c59ab4836..b1cdd08df 100644 --- a/RileyLinkBLEKit/RileyLinkDeviceManager.swift +++ b/RileyLinkBLEKit/RileyLinkDeviceManager.swift @@ -201,7 +201,7 @@ extension Array where Element == RileyLinkDevice { } mutating func deprioritize(_ element: Element) { - if let index = self.index(where: { $0 === element }) { + if let index = self.firstIndex(where: { $0 === element }) { self.swapAt(index, self.count - 1) } } diff --git a/RileyLinkKitUI/CBPeripheralState.swift b/RileyLinkKitUI/CBPeripheralState.swift index 92086ea76..89f653989 100644 --- a/RileyLinkKitUI/CBPeripheralState.swift +++ b/RileyLinkKitUI/CBPeripheralState.swift @@ -23,6 +23,8 @@ extension CBPeripheralState { return LocalizedString("Disconnected", comment: "The disconnected state") case .disconnecting: return LocalizedString("Disconnecting", comment: "The in-progress disconnecting state") + @unknown default: + return "Unknown: \(rawValue)" } } } diff --git a/RileyLinkKitUI/RileyLinkDeviceTableViewCell.swift b/RileyLinkKitUI/RileyLinkDeviceTableViewCell.swift index 91fa1add2..28ded4fbc 100644 --- a/RileyLinkKitUI/RileyLinkDeviceTableViewCell.swift +++ b/RileyLinkKitUI/RileyLinkDeviceTableViewCell.swift @@ -72,6 +72,8 @@ public class RileyLinkDeviceTableViewCell: UITableViewCell { connectSwitch?.isOn = false connectSwitch?.isEnabled = true case .disconnecting: + fallthrough + @unknown default: connectSwitch?.isOn = false connectSwitch?.isEnabled = false } diff --git a/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift b/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift index 3a0dcd5a6..1d0332bf3 100644 --- a/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift +++ b/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift @@ -14,7 +14,7 @@ import RileyLinkKit public class RileyLinkDevicesTableViewDataSource: NSObject { public let rileyLinkPumpManager: RileyLinkPumpManager - public let devicesSectionIndex: Int + public var devicesSectionIndex: Int public var tableView: UITableView! { didSet { @@ -103,6 +103,8 @@ public class RileyLinkDevicesTableViewDataSource: NSObject { if !isAutoConnectDevice { state = .disconnected } + @unknown default: + break } return state @@ -126,7 +128,7 @@ public class RileyLinkDevicesTableViewDataSource: NSObject { @objc private func deviceDidUpdate(_ note: Notification) { DispatchQueue.main.async { - if let device = note.object as? RileyLinkDevice, let index = self.devices.index(where: { $0 === device }) { + if let device = note.object as? RileyLinkDevice, let index = self.devices.firstIndex(where: { $0 === device }) { if let rssi = note.userInfo?[RileyLinkDevice.notificationRSSIKey] as? Int { self.deviceRSSI[device.peripheralIdentifier] = rssi } diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh new file mode 100755 index 000000000..a02074bf6 --- /dev/null +++ b/Scripts/copy-frameworks.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +# copy-frameworks.sh +# Loop +# +# Copyright © 2019 LoopKit Authors. All rights reserved. + +CARTHAGE_BUILD_DIR="${SRCROOT}/Carthage/Build" +if [ -n "${IPHONEOS_DEPLOYMENT_TARGET}" ]; then + CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/iOS" +elif [ -n "${WATCHOS_DEPLOYMENT_TARGET}" ]; then + CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/watchOS" +else + echo "ERROR: Unexpected deployment target type" + exit 1 +fi + +for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do + SCRIPT_INPUT_FILE="SCRIPT_INPUT_FILE_${COUNTER}" + CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" + if [ -e "${CARTHAGE_BUILD_FILE}" ]; then + if [ -e "${SCRIPT_INPUT_FILE}" ]; then + echo "ERROR: Duplicate frameworks found at:" + echo " ${SCRIPT_INPUT_FILE}" + echo " ${CARTHAGE_BUILD_FILE}" + exit 1 + fi + echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_FILE}\"" + export ${SCRIPT_INPUT_FILE}="${CARTHAGE_BUILD_FILE}" + elif [ -e "${SCRIPT_INPUT_FILE}" ]; then + echo "Using original path: \"${!SCRIPT_INPUT_FILE}\"" + else + echo "ERROR: Input file not found at \"${!SCRIPT_INPUT_FILE}\"" + exit 1 + fi +done + +echo "Copy Frameworks with Carthage" +carthage copy-frameworks From 71bb554d9d64824032db8e59e1a1bff30aaa6a99 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 16 Aug 2019 09:15:00 -0500 Subject: [PATCH 26/71] Temp basal delivery (#532) * Track delivered units separately from scheduled rate * Rename delayUntilNextPulse to prevent further confusion * Report temp basal status * Report unfinalized boluses * Reconciliation for mdt * Update tests * Remove redundant dose store call * Track suspend and resume times * Bump cartfile for tests * Fix suspendState serialization * Use single, static date formatter for unfinalized doses * Add some debug log messages * Set suspend state only when suspending basal * Report doses after resume * Update resumed basal date when canceling temp basal * Round bolus doses to supported values * Update suspend state after sending basal schedule command * Expire pending doses that never reconcile * Track bolus as pending before pump ack * DoseEntry units -> programmedUnits * Back out early bolus submit * Change build string * NewPumpEvent needs finalized bolus amount * Remove incorrect suspendState transition --- .travis.yml | 2 +- Cartfile | 2 +- Cartfile.resolved | 2 +- Crypto/Info.plist | 2 +- MinimedKit/Info.plist | 2 +- .../Models/TimestampedGlucoseEvent.swift | 4 - .../Models/TimestampedHistoryEvent.swift | 19 +- MinimedKit/Models/PumpModel.swift | 15 +- MinimedKit/PumpManager/DoseStore.swift | 12 +- .../MinimedDoseProgressEstimator.swift | 12 +- .../PumpManager/MinimedPumpManager.swift | 271 ++++++++++--- .../PumpManager/MinimedPumpManagerError.swift | 7 + .../MinimedPumpManagerRecents.swift | 18 +- .../PumpManager/MinimedPumpManagerState.swift | 142 ++++++- MinimedKit/PumpManager/UnfinalizedDose.swift | 227 +++++++++++ MinimedKitTests/Info.plist | 2 +- .../TimestampedHistoryEventTests.swift | 30 +- MinimedKitUI/Info.plist | 2 +- .../MinimedPumpIDSetupViewController.swift | 3 +- NightscoutUploadKit/Info.plist | 2 +- NightscoutUploadKitTests/Info.plist | 2 +- OmniKit/Info.plist | 2 +- .../MessageBlocks/TempBasalExtraCommand.swift | 14 +- .../MessageTransport/MessageTransport.swift | 4 +- OmniKit/Model/UnfinalizedDose.swift | 69 +++- OmniKit/PumpManager/OmnipodPumpManager.swift | 195 ++++++---- .../PumpManager/OmnipodPumpManagerState.swift | 21 +- OmniKit/PumpManager/PodComms.swift | 2 +- OmniKit/PumpManager/PodCommsSession.swift | 32 +- .../PodDoseProgressEstimator.swift | 2 +- OmniKit/PumpManager/PodState.swift | 117 ++++-- OmniKitTests/Info.plist | 2 +- OmniKitTests/PodCommsSessionTests.swift | 2 - OmniKitTests/TempBasalTests.swift | 10 +- OmniKitUI/Info.plist | 2 +- .../OmnipodSettingsViewController.swift | 4 +- RileyLink.xcodeproj/project.pbxproj | 359 +++--------------- .../xcshareddata/xcschemes/RileyLink.xcscheme | 144 +++++++ .../xcshareddata/xcschemes/Shared.xcscheme | 74 ---- RileyLink/DeviceDataManager.swift | 2 +- RileyLink/RileyLink-Info.plist | 2 +- RileyLinkBLEKit/Info.plist | 2 +- RileyLinkBLEKitTests/Info.plist | 2 +- RileyLinkKit/Info.plist | 2 +- RileyLinkKitTests/Info.plist | 2 +- RileyLinkKitUI/Info.plist | 2 +- .../RileyLinkDevicesTableViewDataSource.swift | 4 +- RileyLinkTests/RileyLinkTests-Info.plist | 2 +- 48 files changed, 1187 insertions(+), 666 deletions(-) create mode 100644 MinimedKit/PumpManager/UnfinalizedDose.swift diff --git a/.travis.yml b/.travis.yml index 8090dcef2..8e221d716 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ before_script: - carthage bootstrap script: - - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone SE' test | xcpretty + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme RileyLink build -destination 'name=iPhone SE' test | xcpretty diff --git a/Cartfile b/Cartfile index e2488b425..45023734e 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "dev" +github "LoopKit/LoopKit" "temp-basal-delivery" github "maxkonovalov/MKRingProgressView" ~> 2.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index 237e068c9..c40af6780 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "4eca69b9f2e3f1fc74e5b62484db00777c002cb7" +github "LoopKit/LoopKit" "cbfbee999e946af66140f6ca9152fdd329ba6497" github "maxkonovalov/MKRingProgressView" "2.2.2" diff --git a/Crypto/Info.plist b/Crypto/Info.plist index fb45ece9d..9a384cbc1 100644 --- a/Crypto/Info.plist +++ b/Crypto/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/MinimedKit/Info.plist b/MinimedKit/Info.plist index 22c234462..a60ea4a4e 100644 --- a/MinimedKit/Info.plist +++ b/MinimedKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKit/Messages/Models/TimestampedGlucoseEvent.swift b/MinimedKit/Messages/Models/TimestampedGlucoseEvent.swift index 4a67724b9..5c0247577 100644 --- a/MinimedKit/Messages/Models/TimestampedGlucoseEvent.swift +++ b/MinimedKit/Messages/Models/TimestampedGlucoseEvent.swift @@ -12,10 +12,6 @@ public struct TimestampedGlucoseEvent { public let glucoseEvent: GlucoseEvent public let date: Date - public func isMutable(atDate date: Date = Date()) -> Bool { - return false - } - public init(glucoseEvent: GlucoseEvent, date: Date) { self.glucoseEvent = glucoseEvent self.date = date diff --git a/MinimedKit/Messages/Models/TimestampedHistoryEvent.swift b/MinimedKit/Messages/Models/TimestampedHistoryEvent.swift index 403085347..5249e8258 100644 --- a/MinimedKit/Messages/Models/TimestampedHistoryEvent.swift +++ b/MinimedKit/Messages/Models/TimestampedHistoryEvent.swift @@ -14,21 +14,18 @@ public struct TimestampedHistoryEvent { public let pumpEvent: PumpEvent public let date: Date - public func isMutable(atDate date: Date = Date()) -> Bool { - switch pumpEvent { - case let bolus as BolusNormalPumpEvent: - // Square boluses - let deliveryFinishDate = self.date.addingTimeInterval(bolus.deliveryTime) - return deliveryFinishDate.compare(date) == .orderedDescending - default: - return false - } - } - public init(pumpEvent: PumpEvent, date: Date) { self.pumpEvent = pumpEvent self.date = date } + + public func isMutable(atDate date: Date = Date(), forPump model: PumpModel) -> Bool { + guard let bolus = self.pumpEvent as? BolusNormalPumpEvent else { + return false + } + let deliveryFinishDate = date.addingTimeInterval(bolus.deliveryTime) + return model.appendsSquareWaveToHistoryOnStartOfDelivery && bolus.type == .square && deliveryFinishDate > date + } } diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index d478cb7e2..076aa5d4d 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -108,7 +108,7 @@ public enum PumpModel: String { } } - public var maximumBolusVolume: Double { + public var maximumBolusVolume: Int { return 25 } @@ -117,7 +117,18 @@ public enum PumpModel: String { } public var supportedBolusVolumes: [Double] { - return supportedBasalRates.filter { $0 <= maximumBolusVolume } + if generation >= 23 { + let breakpoints: [Int] = [0,1,10,maximumBolusVolume] + let scales: [Int] = [40,20,10] + let scalingGroups = zip(scales, (zip(breakpoints, breakpoints[1...]).map {($0.0)...$0.1})) + let segments = scalingGroups.map { (scale, range) -> [Double] in + let scaledRanges = ((range.lowerBound*scale+1)...(range.upperBound*scale)) + return scaledRanges.map { Double($0) / Double(scale) } + } + return segments.flatMap { $0 } + } else { + return (1...(maximumBolusVolume*10)).map { Double($0) / 10.0 } + } } public var maximumBasalScheduleEntryCount: Int { diff --git a/MinimedKit/PumpManager/DoseStore.swift b/MinimedKit/PumpManager/DoseStore.swift index f94caa8b2..f3d853ebb 100644 --- a/MinimedKit/PumpManager/DoseStore.swift +++ b/MinimedKit/PumpManager/DoseStore.swift @@ -17,16 +17,22 @@ extension Collection where Element == TimestampedHistoryEvent { // Always assume the sequence may have started rewound. LoopKit will ignore unmatched resume events. var isRewound = true var title: String + let now = Date() for event in self { var dose: DoseEntry? var eventType: LoopKit.PumpEventType? + switch event.pumpEvent { case let bolus as BolusNormalPumpEvent: // For entries in-progress, use the programmed amount - let units = event.isMutable() ? bolus.programmed : bolus.amount - + let units: Double + if event.isMutable(atDate: now, forPump: model) { + units = bolus.programmed + } else { + units = bolus.amount + } dose = DoseEntry(type: .bolus, startDate: event.date, endDate: event.date.addingTimeInterval(bolus.duration), value: units, unit: .units) case is SuspendPumpEvent: dose = DoseEntry(suspendDate: event.date) @@ -93,7 +99,7 @@ extension Collection where Element == TimestampedHistoryEvent { } title = String(describing: event.pumpEvent) - events.append(NewPumpEvent(date: event.date, dose: dose, isMutable: event.isMutable(), raw: event.pumpEvent.rawData, title: title, type: eventType)) + events.append(NewPumpEvent(date: event.date, dose: dose, isMutable: event.isMutable(atDate: now, forPump: model), raw: event.pumpEvent.rawData, title: title, type: eventType)) } return events diff --git a/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift b/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift index dc66427fc..bb5225bf3 100644 --- a/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift +++ b/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift @@ -24,11 +24,11 @@ class MinimedDoseProgressEstimator: DoseProgressTimerEstimator { let unroundedVolume: Double if pumpModel.isDeliveryRateVariable { - if dose.units < 1 { + if dose.programmedUnits < 1 { updateResolution = 40 // Resolution = 0.025 - unroundedVolume = timeProgress * dose.units + unroundedVolume = timeProgress * dose.programmedUnits } else { - var remainingUnits = dose.units + var remainingUnits = dose.programmedUnits var baseDuration: TimeInterval = 0 var overlay1Duration: TimeInterval = 0 var overlay2Duration: TimeInterval = 0 @@ -54,10 +54,10 @@ class MinimedDoseProgressEstimator: DoseProgressTimerEstimator { } else { updateResolution = 20 // Resolution = 0.05 - unroundedVolume = timeProgress * dose.units + unroundedVolume = timeProgress * dose.programmedUnits } let roundedVolume = round(unroundedVolume * updateResolution) / updateResolution - return DoseProgress(deliveredUnits: roundedVolume, percentComplete: roundedVolume / dose.units) + return DoseProgress(deliveredUnits: roundedVolume, percentComplete: roundedVolume / dose.programmedUnits) } init(dose: DoseEntry, pumpModel: PumpModel, reportingQueue: DispatchQueue) { @@ -69,7 +69,7 @@ class MinimedDoseProgressEstimator: DoseProgressTimerEstimator { override func timerParameters() -> (delay: TimeInterval, repeating: TimeInterval) { let timeSinceStart = -dose.startDate.timeIntervalSinceNow let duration = dose.endDate.timeIntervalSince(dose.startDate) - let timeBetweenPulses = duration / (Double(pumpModel.pulsesPerUnit) * dose.units) + let timeBetweenPulses = duration / (Double(pumpModel.pulsesPerUnit) * dose.programmedUnits) let delayUntilNextPulse = timeBetweenPulses - timeSinceStart.remainder(dividingBy: timeBetweenPulses) diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index ec2f5cfba..03d489c72 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -70,7 +70,7 @@ public class MinimedPumpManager: RileyLinkPumpManager { // PumpManagerStatus may have changed if oldValue.timeZone != newValue.timeZone || - oldValue.isPumpSuspended != newValue.isPumpSuspended + oldValue.suspendState != newValue.suspendState { notifyStatusObservers(oldStatus: oldStatus) } @@ -239,6 +239,32 @@ extension MinimedPumpManager { } } + private func runSuspendResumeOnSession(state: SuspendResumeMessageBody.SuspendResumeState, session: PumpOpsSession) throws { + defer { self.recents.suspendEngageState = .stable } + self.recents.suspendEngageState = state == .suspend ? .engaging : .disengaging + + try session.setSuspendResumeState(state) + let date = Date() + switch state { + case .suspend: + self.state.suspendState = .suspended(date) + case .resume: + self.state.suspendState = .resumed(date) + } + + if state == .suspend { + self.state.unfinalizedBolus?.cancel(at: Date()) + if let bolus = self.state.unfinalizedBolus { + self.state.pendingDoses.append(bolus) + } + self.state.unfinalizedBolus = nil + + self.state.pendingDoses.append(UnfinalizedDose(suspendStartTime: Date())) + } else { + self.state.pendingDoses.append(UnfinalizedDose(resumeStartTime: Date())) + } + } + private func setSuspendResumeState(state: SuspendResumeMessageBody.SuspendResumeState, completion: @escaping (Error?) -> Void) { rileyLinkDeviceProvider.getDevices { (devices) in guard let device = devices.firstConnected else { @@ -257,13 +283,10 @@ extension MinimedPumpManager { self.pumpOps.runSession(withName: sessionName, using: device) { (session) in do { - - defer { self.recents.basalDeliveryStateTransitioning = false } - self.recents.basalDeliveryStateTransitioning = true - - try session.setSuspendResumeState(state) - self.state.isPumpSuspended = state == .suspend - completion(nil) + try self.runSuspendResumeOnSession(state: state, session: session) + self.storePendingPumpEvents({ (error) in + completion(error) + }) } catch let error { self.troubleshootPumpComms(using: device) completion(PumpManagerError.communication(error as? LocalizedError)) @@ -391,6 +414,65 @@ extension MinimedPumpManager { } } + + private func reconcilePendingDosesWith(_ events: [NewPumpEvent]) { + // Must be called from the sessionQueue + + var reconcilableEvents = events.filter { !self.state.recentlyReconciledEventIDs.contains($0.raw) } + + let matchingTimeWindow = TimeInterval(minutes: 1) + + func markReconciled(raw: Data, index: Int) -> Void { + self.state.recentlyReconciledEventIDs.insert(raw, at: 0) + self.state.recentlyReconciledEventIDs = Array(self.state.recentlyReconciledEventIDs.prefix(5)) + reconcilableEvents.remove(at: index) + } + + // If we have a bolus in progress, see if it has shown up in history yet + if let bolus = self.state.unfinalizedBolus, let index = reconcilableEvents.firstMatchingIndex(for: bolus, within: matchingTimeWindow) { + let matchingBolus = reconcilableEvents[index] + self.log.debug("Matched unfinalized bolus %@ to history record %@", String(describing: bolus), String(describing: matchingBolus)) + self.state.unfinalizedBolus = nil + markReconciled(raw: matchingBolus.raw, index: index) + } + + // Reconcile temp basal + if let tempBasal = self.state.unfinalizedTempBasal, let index = reconcilableEvents.firstMatchingIndex(for: tempBasal, within: matchingTimeWindow), let dose = events[index].dose { + let matchedTempBasal = reconcilableEvents[index] + self.log.debug("Matched unfinalized temp basal %@ to history record %@", String(describing: tempBasal), String(describing: matchedTempBasal)) + // Update unfinalizedTempBasal to match entry in history, and mark as reconciled + self.state.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: dose.unitsPerHour, startTime: dose.startDate, duration: dose.endDate.timeIntervalSince(dose.startDate), isReconciledWithHistory: true) + markReconciled(raw: matchedTempBasal.raw, index: index) + } + + // Reconcile any pending doses, and remove expired doses + let expirationCutoff = Date().addingTimeInterval(.hours(-24)) + self.state.pendingDoses.removeAll { (dose) -> Bool in + if let index = reconcilableEvents.firstMatchingIndex(for: dose, within: matchingTimeWindow) { + let historyEvent = reconcilableEvents[index] + self.log.debug("Matched pending dose %@ to history record %@", String(describing: dose), String(describing: historyEvent)) + markReconciled(raw: historyEvent.raw, index: index) + return true + } else if dose.finishTime < expirationCutoff { + self.log.debug("Expiring pending dose that did not match any history records: %@", String(describing: dose)) + return true + } + return false + } + + self.state.lastReconciliation = Date() + } + + private var pendingDosesForStorage: [NewPumpEvent] { + // Must be called from the sessionQueue + return (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ (dose) in + guard let dose = dose, !dose.isReconciledWithHistory else { + return nil + } + return NewPumpEvent(dose) + }) + } + /// Polls the pump for new history events and passes them to the loop manager /// /// - Parameters: @@ -411,14 +493,18 @@ extension MinimedPumpManager { preconditionFailure("pumpManagerDelegate cannot be nil") } - let (events, model) = try session.getHistoryEvents(since: startDate) + let (historyEvents, model) = try session.getHistoryEvents(since: startDate) + + // Reconcile history with pending doses + let newPumpEvents = historyEvents.pumpEvents(from: model) + self.reconcilePendingDosesWith(newPumpEvents) self.pumpDelegate.notify({ (delegate) in guard let delegate = delegate else { preconditionFailure("pumpManagerDelegate cannot be nil") } - delegate.pumpManager(self, didReadPumpEvents: events.pumpEvents(from: model), completion: { (error) in + delegate.pumpManager(self, hasNewPumpEvents: newPumpEvents + self.pendingDosesForStorage, lastReconciliation: self.lastReconciliation, completion: { (error) in // Called on an unknown queue by the delegate if error == nil { self.recents.lastAddedPumpEvents = Date() @@ -436,6 +522,25 @@ extension MinimedPumpManager { } } + private func storePendingPumpEvents(_ completion: @escaping (_ error: Error?) -> Void) { + // Must be called from the sessionQueue + let events = pendingDosesForStorage + + log.debug("Storing pending pump events: %{public}@", String(describing: events)) + + self.pumpDelegate.notify({ (delegate) in + guard let delegate = delegate else { + preconditionFailure("pumpManagerDelegate cannot be nil") + } + + delegate.pumpManager(self, hasNewPumpEvents: events, lastReconciliation: self.lastReconciliation, completion: { (error) in + // Called on an unknown queue by the delegate + completion(error) + }) + + }) + } + // Safe to call from any thread private var isPumpDataStale: Bool { // How long should we wait before we poll for new pump data? @@ -534,15 +639,53 @@ extension MinimedPumpManager: PumpManager { return Double(state.pumpModel.reservoirCapacity) } + public var lastReconciliation: Date? { + return state.lastReconciliation + } + public var status: PumpManagerStatus { let state = self.state let recents = self.recents let basalDeliveryState: PumpManagerStatus.BasalDeliveryState - if recents.basalDeliveryStateTransitioning { - basalDeliveryState = state.isPumpSuspended ? .resuming : .suspending - } else { - basalDeliveryState = state.isPumpSuspended ? .suspended : .active + switch recents.suspendEngageState { + case .engaging: + basalDeliveryState = .suspending + case .disengaging: + basalDeliveryState = .resuming + case .stable: + switch recents.tempBasalEngageState { + case .engaging: + basalDeliveryState = .initiatingTempBasal + case .disengaging: + basalDeliveryState = .cancelingTempBasal + case .stable: + switch self.state.suspendState { + case .suspended(let date): + basalDeliveryState = .suspended(date) + case .resumed(let date): + if let tempBasal = state.unfinalizedTempBasal, !tempBasal.finished { + basalDeliveryState = .tempBasal(DoseEntry(tempBasal)) + } else { + basalDeliveryState = .active(date) + } + } + } + } + + let bolusState: PumpManagerStatus.BolusState + + switch recents.bolusEngageState { + case .engaging: + bolusState = .initiating + case .disengaging: + bolusState = .canceling + case .stable: + if let bolus = state.unfinalizedBolus, !bolus.finished { + bolusState = .inProgress(DoseEntry(bolus)) + } else { + bolusState = .none + } } return PumpManagerStatus( @@ -550,7 +693,7 @@ extension MinimedPumpManager: PumpManager { device: hkDevice, pumpBatteryChargeRemaining: state.batteryPercentage, basalDeliveryState: basalDeliveryState, - bolusState: recents.bolusState + bolusState: bolusState ) } @@ -644,7 +787,10 @@ extension MinimedPumpManager: PumpManager { date = newDate } - self.state.isPumpSuspended = status.suspended + if case .resumed = self.state.suspendState, status.suspended { + self.state.suspendState = .suspended(Date()) + } + self.recents.latestPumpStatus = status self.updateReservoirVolume(status.reservoir, at: date, withTimeLeft: nil) @@ -660,11 +806,14 @@ extension MinimedPumpManager: PumpManager { } public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (_ dose: DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { - guard units > 0 else { + let enactUnits = roundToSupportedBolusVolume(units: units) + + guard enactUnits > 0 else { assertionFailure("Invalid zero unit bolus") return } + pumpOps.runSession(withName: "Bolus", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in guard let session = session else { @@ -672,7 +821,15 @@ extension MinimedPumpManager: PumpManager { return } - self.recents.bolusState = .initiating + if let unfinalizedBolus = self.state.unfinalizedBolus { + guard unfinalizedBolus.finished else { + completion(.failure(PumpManagerError.deviceState(MinimedPumpManagerError.bolusInProgress))) + return + } + self.state.pendingDoses.append(unfinalizedBolus) + } + + self.recents.bolusEngageState = .engaging // If we don't have recent pump data, or the pump was recently rewound, read new pump data before bolusing. if self.isReservoirDataOlderThan(timeIntervalSinceNow: .minutes(-6)) { @@ -698,16 +855,16 @@ extension MinimedPumpManager: PumpManager { } return } catch let error { - self.recents.bolusState = .none + self.recents.bolusEngageState = .stable completion(.failure(error)) return } } do { - if self.state.isPumpSuspended { + if case .suspended = self.state.suspendState { do { - try session.setSuspendResumeState(.resume) + try self.runSuspendResumeOnSession(state: .resume, session: session) } catch let error as PumpOpsError { self.log.error("Failed to resume pump for bolus: %{public}@", String(describing: error)) completion(.failure(SetBolusError.certain(error))) @@ -722,44 +879,45 @@ extension MinimedPumpManager: PumpManager { } return } catch let error { - self.recents.bolusState = .none + self.recents.bolusEngageState = .stable completion(.failure(error)) return } } let date = Date() - let deliveryTime = self.state.pumpModel.bolusDeliveryTime(units: units) - let endDate = date.addingTimeInterval(deliveryTime) - let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: units, unit: .units) - willRequest(dose) + let deliveryTime = self.state.pumpModel.bolusDeliveryTime(units: enactUnits) + let requestedDose = UnfinalizedDose(bolusAmount: enactUnits, startTime: date, duration: deliveryTime) + willRequest(DoseEntry(requestedDose)) - try session.setNormalBolus(units: units) + try session.setNormalBolus(units: enactUnits) // Between bluetooth and the radio and firmware, about 2s on average passes before we start tracking let commsOffset = TimeInterval(seconds: -2) - let acknowledgedDate = Date().addingTimeInterval(commsOffset) - let acknowledgedDose = DoseEntry(type: .bolus, startDate: acknowledgedDate, endDate: acknowledgedDate.addingTimeInterval(deliveryTime), value: units, unit: .units) + let doseStart = Date().addingTimeInterval(commsOffset) - self.recents.bolusState = .inProgress(acknowledgedDose) - completion(.success(acknowledgedDose)) + let dose = UnfinalizedDose(bolusAmount: enactUnits, startTime: doseStart, duration: deliveryTime) + self.state.unfinalizedBolus = dose + self.recents.bolusEngageState = .stable + + self.storePendingPumpEvents({ (error) in + completion(.success(DoseEntry(dose))) + }) } catch let error { self.log.error("Failed to bolus: %{public}@", String(describing: error)) - self.recents.bolusState = .none + self.recents.bolusEngageState = .stable completion(.failure(error)) } } } public func cancelBolus(completion: @escaping (PumpManagerResult) -> Void) { - let oldState = self.recents.bolusState - self.recents.bolusState = .canceling + self.recents.bolusEngageState = .disengaging setSuspendResumeState(state: .suspend) { (error) in + self.recents.bolusEngageState = .stable if let error = error { - self.recents.bolusState = oldState completion(.failure(error)) } else { - self.recents.bolusState = .none completion(.success(nil)) } } @@ -773,6 +931,8 @@ extension MinimedPumpManager: PumpManager { return } + self.recents.tempBasalEngageState = .engaging + do { let response = try session.setTempBasal(unitsPerHour, duration: duration) @@ -780,16 +940,28 @@ extension MinimedPumpManager: PumpManager { let endDate = now.addingTimeInterval(response.timeRemaining) let startDate = endDate.addingTimeInterval(-duration) + let dose = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: startDate, duration: duration) + + if duration < .ulpOfOne { + self.state.unfinalizedTempBasal?.cancel(at: startDate) + if let previousTempBasal = self.state.unfinalizedTempBasal, !previousTempBasal.isReconciledWithHistory { + self.state.pendingDoses.append(previousTempBasal) + } + self.state.suspendState = .resumed(startDate) + } + // If we were successful, then we know we aren't suspended - self.state.isPumpSuspended = false + if case .suspended = self.state.suspendState { + self.state.suspendState = .resumed(Date()) + } + + self.state.unfinalizedTempBasal = dose + self.recents.tempBasalEngageState = .stable - completion(.success(DoseEntry( - type: .tempBasal, - startDate: startDate, - endDate: endDate, - value: response.rate, - unit: .unitsPerHour - ))) + + self.storePendingPumpEvents({ (error) in + completion(.success(DoseEntry(dose))) + }) // Continue below } catch let error as PumpCommandError { @@ -799,14 +971,18 @@ extension MinimedPumpManager: PumpManager { if case .arguments(.pumpError(.commandRefused)) = error { do { let status = try session.getCurrentPumpStatus() - self.state.isPumpSuspended = status.suspended + if case .resumed = self.state.suspendState, status.suspended { + self.state.suspendState = .suspended(Date()) + } self.recents.latestPumpStatus = status } catch { self.log.error("Post-basal suspend state fetch failed: %{public}@", String(describing: error)) } } + self.recents.tempBasalEngageState = .stable return } catch { + self.recents.tempBasalEngageState = .stable completion(.failure(error)) return } @@ -838,8 +1014,8 @@ extension MinimedPumpManager: PumpManager { } public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? { - if case .inProgress(let dose) = recents.bolusState { - return MinimedDoseProgressEstimator(dose: dose, pumpModel: state.pumpModel, reportingQueue: dispatchQueue) + if let bolus = self.state.unfinalizedBolus, !bolus.finished { + return MinimedDoseProgressEstimator(dose: DoseEntry(bolus), pumpModel: state.pumpModel, reportingQueue: dispatchQueue) } return nil } @@ -923,4 +1099,3 @@ extension MinimedPumpManager: CGMManager { } } } - diff --git a/MinimedKit/PumpManager/MinimedPumpManagerError.swift b/MinimedKit/PumpManager/MinimedPumpManagerError.swift index dc5ee656e..cd1e5ba54 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerError.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerError.swift @@ -9,6 +9,7 @@ import Foundation public enum MinimedPumpManagerError: Error { case noRileyLink + case bolusInProgress case noDate // TODO: This is less of an error and more of a precondition/assertion state case tuneFailed(LocalizedError) } @@ -19,6 +20,8 @@ extension MinimedPumpManagerError: LocalizedError { switch self { case .noRileyLink: return nil + case .bolusInProgress: + return nil case .noDate: return nil case .tuneFailed(let error): @@ -30,6 +33,8 @@ extension MinimedPumpManagerError: LocalizedError { switch self { case .noRileyLink: return nil + case .bolusInProgress: + return nil case .noDate: return nil case .tuneFailed(let error): @@ -41,6 +46,8 @@ extension MinimedPumpManagerError: LocalizedError { switch self { case .noRileyLink: return LocalizedString("Make sure your RileyLink is nearby and powered on", comment: "Recovery suggestion") + case .bolusInProgress: + return nil case .noDate: return nil case .tuneFailed(let error): diff --git a/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift b/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift index 165e772b5..b551d0eb2 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift @@ -9,9 +9,18 @@ import Foundation import LoopKit struct MinimedPumpManagerRecents: Equatable { - var bolusState: PumpManagerStatus.BolusState = .none - var basalDeliveryStateTransitioning = false + internal enum EngageablePumpState: Equatable { + case engaging + case disengaging + case stable + } + + internal var suspendEngageState: EngageablePumpState = .stable + + internal var bolusEngageState: EngageablePumpState = .stable + + internal var tempBasalEngageState: EngageablePumpState = .stable var lastAddedPumpEvents: Date = .distantPast @@ -32,8 +41,9 @@ extension MinimedPumpManagerRecents: CustomDebugStringConvertible { var debugDescription: String { return """ ### MinimedPumpManagerRecents - bolusState: \(bolusState) - basalDeliveryStateTransitioning: \(basalDeliveryStateTransitioning) + suspendEngageState: \(suspendEngageState) + bolusEngageState: \(bolusEngageState) + tempBasalEngageState: \(tempBasalEngageState) lastAddedPumpEvents: \(lastAddedPumpEvents) latestPumpStatus: \(String(describing: latestPumpStatus)) latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry)) diff --git a/MinimedKit/PumpManager/MinimedPumpManagerState.swift b/MinimedKit/PumpManager/MinimedPumpManagerState.swift index 319e83df3..0f63358ed 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerState.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerState.swift @@ -18,7 +18,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public var batteryPercentage: Double? - public var isPumpSuspended: Bool + public var suspendState: SuspendState public var lastReservoirReading: ReservoirReading? @@ -64,7 +64,19 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public var timeZone: TimeZone - public init(batteryChemistry: BatteryChemistryType = .alkaline, preferredInsulinDataSource: InsulinDataSource = .pumpHistory, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, lastValidFrequency: Measurement? = nil, isPumpSuspended: Bool = false, batteryPercentage: Double? = nil, lastReservoirReading: ReservoirReading? = nil) { + public var unfinalizedBolus: UnfinalizedDose? + + public var unfinalizedTempBasal: UnfinalizedDose? + + // Doses we're tracking that haven't shown up in history yet + public var pendingDoses: [UnfinalizedDose] + + // A record of history events that have recently been used to reconcile unfinalized doses + public var recentlyReconciledEventIDs: [Data] + + public var lastReconciliation: Date? + + public init(batteryChemistry: BatteryChemistryType = .alkaline, preferredInsulinDataSource: InsulinDataSource = .pumpHistory, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, suspendState: SuspendState, lastValidFrequency: Measurement? = nil, batteryPercentage: Double? = nil, lastReservoirReading: ReservoirReading? = nil, unfinalizedBolus: UnfinalizedDose? = nil, unfinalizedTempBasal: UnfinalizedDose? = nil, pendingDoses: [UnfinalizedDose]? = nil, recentlyReconciledEventIDs: [Data]? = nil, lastReconciliation: Date? = nil) { self.batteryChemistry = batteryChemistry self.preferredInsulinDataSource = preferredInsulinDataSource self.pumpColor = pumpColor @@ -74,10 +86,15 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { self.pumpRegion = pumpRegion self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState self.timeZone = timeZone - self.isPumpSuspended = isPumpSuspended + self.suspendState = suspendState self.lastValidFrequency = lastValidFrequency self.batteryPercentage = batteryPercentage self.lastReservoirReading = lastReservoirReading + self.unfinalizedBolus = unfinalizedBolus + self.unfinalizedTempBasal = unfinalizedTempBasal + self.pendingDoses = pendingDoses ?? [] + self.recentlyReconciledEventIDs = recentlyReconciledEventIDs ?? [] + self.lastReconciliation = lastReconciliation } public init?(rawValue: RawValue) { @@ -117,7 +134,19 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { } } - let isPumpSuspended = (rawValue["isPumpSuspended"] as? Bool) ?? false + let suspendState: SuspendState + if let isPumpSuspended = rawValue["isPumpSuspended"] as? Bool { + // migrate + if isPumpSuspended { + suspendState = .suspended(Date()) + } else { + suspendState = .resumed(Date()) + } + } else if let rawSuspendState = rawValue["suspendState"] as? SuspendState.RawValue, let storedSuspendState = SuspendState(rawValue: rawSuspendState) { + suspendState = storedSuspendState + } else { + return nil + } let lastValidFrequency: Measurement? if let frequencyRaw = rawValue["lastValidFrequency"] as? Double { @@ -135,6 +164,38 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { } else { lastReservoirReading = nil } + + let unfinalizedBolus: UnfinalizedDose? + if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue + { + unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus) + } else { + unfinalizedBolus = nil + } + + let unfinalizedTempBasal: UnfinalizedDose? + if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue + { + unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) + } else { + unfinalizedTempBasal = nil + } + + let pendingDoses: [UnfinalizedDose] + if let rawPendingDoses = rawValue["pendingDoses"] as? [UnfinalizedDose.RawValue] { + pendingDoses = rawPendingDoses.compactMap( { UnfinalizedDose(rawValue: $0) } ) + } else { + pendingDoses = [] + } + + let recentlyReconciledEventIDs: [Data] + if let rawRecentlyReconciledEventIDs = rawValue["recentlyReconciledEventIDs"] as? [String] { + recentlyReconciledEventIDs = rawRecentlyReconciledEventIDs.compactMap( { Data(hexadecimalString: $0) } ) + } else { + recentlyReconciledEventIDs = [] + } + + let lastReconciliation = rawValue["lastReconciliation"] as? Date self.init( batteryChemistry: batteryChemistry, @@ -146,10 +207,15 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { pumpRegion: pumpRegion, rileyLinkConnectionManagerState: rileyLinkConnectionManagerState, timeZone: timeZone, + suspendState: suspendState, lastValidFrequency: lastValidFrequency, - isPumpSuspended: isPumpSuspended, batteryPercentage: batteryPercentage, - lastReservoirReading: lastReservoirReading + lastReservoirReading: lastReservoirReading, + unfinalizedBolus: unfinalizedBolus, + unfinalizedTempBasal: unfinalizedTempBasal, + pendingDoses: pendingDoses, + recentlyReconciledEventIDs: recentlyReconciledEventIDs, + lastReconciliation: lastReconciliation ) } @@ -163,14 +229,19 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { "pumpFirmwareVersion": pumpFirmwareVersion, "pumpRegion": pumpRegion.rawValue, "timeZone": timeZone.secondsFromGMT(), - "isPumpSuspended": isPumpSuspended, + "suspendState": suspendState.rawValue, "version": MinimedPumpManagerState.version, + "pendingDoses": pendingDoses.map { $0.rawValue }, + "recentlyReconciledEventIDs": recentlyReconciledEventIDs.map { $0.hexadecimalString }, ] value["batteryPercentage"] = batteryPercentage value["lastReservoirReading"] = lastReservoirReading?.rawValue value["lastValidFrequency"] = lastValidFrequency?.converted(to: .megahertz).value value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState?.rawValue + value["unfinalizedBolus"] = unfinalizedBolus?.rawValue + value["unfinalizedTempBasal"] = unfinalizedTempBasal?.rawValue + value["lastReconciliation"] = lastReconciliation return value } @@ -188,7 +259,7 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible { "## MinimedPumpManagerState", "batteryChemistry: \(batteryChemistry)", "batteryPercentage: \(String(describing: batteryPercentage))", - "isPumpSuspended: \(isPumpSuspended)", + "suspendState: \(suspendState)", "lastValidFrequency: \(String(describing: lastValidFrequency))", "preferredInsulinDataSource: \(preferredInsulinDataSource)", "pumpColor: \(pumpColor)", @@ -198,8 +269,63 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible { "pumpRegion: \(pumpRegion)", "reservoirUnits: \(String(describing: lastReservoirReading?.units))", "reservoirValidAt: \(String(describing: lastReservoirReading?.validAt))", + "unfinalizedBolus: \(String(describing: unfinalizedBolus))", + "unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))", + "pendingDoses: \(pendingDoses)", "timeZone: \(timeZone)", + "recentlyReconciledEventIDs: \(recentlyReconciledEventIDs.map { $0.hexadecimalString })", + "lastReconciliation: \(String(describing: lastReconciliation))", String(reflecting: rileyLinkConnectionManagerState), ].joined(separator: "\n") } } + +public enum SuspendState: Equatable, RawRepresentable { + public typealias RawValue = [String: Any] + + private enum SuspendStateType: Int { + case suspend, resume + } + + case suspended(Date) + case resumed(Date) + + private var identifier: Int { + switch self { + case .suspended: + return 1 + case .resumed: + return 2 + } + } + + public init?(rawValue: RawValue) { + guard let suspendStateType = rawValue["case"] as? SuspendStateType.RawValue, + let date = rawValue["date"] as? Date else { + return nil + } + switch SuspendStateType(rawValue: suspendStateType) { + case .suspend?: + self = .suspended(date) + case .resume?: + self = .resumed(date) + default: + return nil + } + } + + public var rawValue: RawValue { + switch self { + case .suspended(let date): + return [ + "case": SuspendStateType.suspend.rawValue, + "date": date + ] + case .resumed(let date): + return [ + "case": SuspendStateType.resume.rawValue, + "date": date + ] + } + } +} diff --git a/MinimedKit/PumpManager/UnfinalizedDose.swift b/MinimedKit/PumpManager/UnfinalizedDose.swift new file mode 100644 index 000000000..f9190833b --- /dev/null +++ b/MinimedKit/PumpManager/UnfinalizedDose.swift @@ -0,0 +1,227 @@ +// +// UnfinalizedDose.swift +// MinimedKit +// +// Created by Pete Schwamb on 7/31/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConvertible { + public typealias RawValue = [String: Any] + + enum DoseType: Int { + case bolus = 0 + case tempBasal + case suspend + case resume + } + + private static let dateFormatter = ISO8601DateFormatter() + + fileprivate var uniqueKey: Data { + return "\(doseType) \(scheduledUnits ?? units) \(UnfinalizedDose.dateFormatter.string(from: startTime))".data(using: .utf8)! + } + + let doseType: DoseType + public var units: Double + var scheduledUnits: Double? // Set when finalized; tracks original scheduled units + var scheduledTempRate: Double? // Set when finalized; tracks the original temp rate + let startTime: Date + var duration: TimeInterval + var isReconciledWithHistory: Bool + + var finishTime: Date { + get { + return startTime.addingTimeInterval(duration) + } + set { + duration = newValue.timeIntervalSince(startTime) + } + } + + public var progress: Double { + let elapsed = -startTime.timeIntervalSinceNow + return min(elapsed / duration, 1) + } + + public var finished: Bool { + return progress >= 1 + } + + // Units per hour + public var rate: Double { + guard duration.hours > 0 else { + return 0 + } + + return units / duration.hours + } + + public var finalizedUnits: Double? { + guard finished else { + return nil + } + return units + } + + init(bolusAmount: Double, startTime: Date, duration: TimeInterval, isReconciledWithHistory: Bool = false) { + self.doseType = .bolus + self.units = bolusAmount + self.startTime = startTime + self.duration = duration + self.scheduledUnits = nil + self.isReconciledWithHistory = isReconciledWithHistory + } + + init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, isReconciledWithHistory: Bool = false) { + self.doseType = .tempBasal + self.units = tempBasalRate * duration.hours + self.startTime = startTime + self.duration = duration + self.scheduledUnits = nil + self.isReconciledWithHistory = isReconciledWithHistory + } + + init(suspendStartTime: Date, isReconciledWithHistory: Bool = false) { + self.doseType = .suspend + self.units = 0 + self.startTime = suspendStartTime + self.duration = 0 + self.isReconciledWithHistory = isReconciledWithHistory + } + + init(resumeStartTime: Date, isReconciledWithHistory: Bool = false) { + self.doseType = .resume + self.units = 0 + self.startTime = resumeStartTime + self.duration = 0 + self.isReconciledWithHistory = isReconciledWithHistory + } + + public mutating func cancel(at date: Date) { + guard date < finishTime else { + return + } + + scheduledUnits = units + let newDuration = date.timeIntervalSince(startTime) + + switch doseType { + case .bolus: + units = rate * newDuration.hours + case .tempBasal: + scheduledTempRate = rate + units = floor(rate * newDuration.hours * 20) / 20 + default: + break + } + duration = newDuration + } + + public var description: String { + switch doseType { + case .bolus: + return "Bolus units:\(scheduledUnits ?? units) \(startTime)" + case .tempBasal: + return "TempBasal rate:\(scheduledTempRate ?? rate) \(startTime) duration:\(String(describing: duration))" + default: + return "\(String(describing: doseType).capitalized) \(startTime)" + } + } + + // 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 + else { + return nil + } + + self.doseType = doseType + self.units = units + self.startTime = startTime + self.duration = duration + + if let scheduledUnits = rawValue["scheduledUnits"] as? Double { + self.scheduledUnits = scheduledUnits + } + + if let scheduledTempRate = rawValue["scheduledTempRate"] as? Double { + self.scheduledTempRate = scheduledTempRate + } + + self.isReconciledWithHistory = rawValue["isReconciledWithHistory"] as? Bool ?? false + } + + public var rawValue: RawValue { + var rawValue: RawValue = [ + "doseType": doseType.rawValue, + "units": units, + "startTime": startTime, + "duration": duration, + "isReconciledWithHistory": isReconciledWithHistory, + ] + + if let scheduledUnits = scheduledUnits { + rawValue["scheduledUnits"] = scheduledUnits + } + + if let scheduledTempRate = scheduledTempRate { + rawValue["scheduledTempRate"] = scheduledTempRate + } + + return rawValue + } +} + +extension NewPumpEvent { + init(_ dose: UnfinalizedDose) { + let title = String(describing: dose) + let entry = DoseEntry(dose) + self.init(date: dose.startTime, dose: entry, isMutable: true, raw: dose.uniqueKey, title: title) + } +} + +extension DoseEntry { + init (_ dose: UnfinalizedDose) { + switch dose.doseType { + case .bolus: + self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledUnits ?? dose.units, unit: .units, deliveredUnits: dose.finalizedUnits) + case .tempBasal: + self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits) + case .suspend: + self = DoseEntry(suspendDate: dose.startTime) + case .resume: + self = DoseEntry(resumeDate: dose.startTime) + } + } +} + +extension Collection where Element == NewPumpEvent { + /// find matching entry + func firstMatchingIndex(for dose: UnfinalizedDose, within: TimeInterval) -> Self.Index? { + return firstIndex(where: { (event) -> Bool in + guard let type = event.type, let eventDose = event.dose, abs(eventDose.startDate.timeIntervalSince(dose.startTime)) < within else { + return false + } + + switch dose.doseType { + case .bolus: + return type == .bolus && eventDose.programmedUnits == dose.scheduledUnits ?? dose.units + case .tempBasal: + return type == .tempBasal && eventDose.unitsPerHour == dose.scheduledTempRate ?? dose.rate + case .suspend: + return type == .suspend + case .resume: + return type == .resume + } + }) + } +} diff --git a/MinimedKitTests/Info.plist b/MinimedKitTests/Info.plist index 4eb1eb63b..7506bdc29 100644 --- a/MinimedKitTests/Info.plist +++ b/MinimedKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKitTests/TimestampedHistoryEventTests.swift b/MinimedKitTests/TimestampedHistoryEventTests.swift index f118af488..8c857aa34 100644 --- a/MinimedKitTests/TimestampedHistoryEventTests.swift +++ b/MinimedKitTests/TimestampedHistoryEventTests.swift @@ -32,7 +32,7 @@ class TimestampedHistoryEventTests: XCTestCase { let sut = TimestampedHistoryEvent(pumpEvent:event, date:Date()) - XCTAssertFalse(sut.isMutable()) + XCTAssertFalse(sut.isMutable(forPump: .model522)) } func testEventIsNotMutableFor522() { @@ -41,7 +41,7 @@ class TimestampedHistoryEventTests: XCTestCase { let sut = TimestampedHistoryEvent(pumpEvent:event, date:Date()) - XCTAssertFalse(sut.isMutable()) + XCTAssertFalse(sut.isMutable(forPump: .model522)) } func test523EventIsNotMutable() { @@ -53,24 +53,42 @@ class TimestampedHistoryEventTests: XCTestCase { let sut = TimestampedHistoryEvent(pumpEvent: bolusEvent, date: timeStampDate) - XCTAssertFalse(sut.isMutable(atDate: dateToCheck)) + XCTAssertFalse(sut.isMutable(atDate: dateToCheck, forPump: .model523)) } func test523EventIsMutable() { let bolusEvent = getNormalBolusEvent() - let timeStampDate = bolusEvent.timestamp.date! let dateToCheck = timeStampDate.addingTimeInterval(bolusEvent.deliveryTime/2) // within the delivery time let sut = TimestampedHistoryEvent(pumpEvent: bolusEvent, date: timeStampDate) - - XCTAssertTrue(sut.isMutable(atDate: dateToCheck)) + + // normal boluses on x23 are *not* mutable; they are just delayed append. Only square wave boluses are mutable + XCTAssertFalse(sut.isMutable(atDate: dateToCheck, forPump: .model523)) + + } + + func testSquareWaveIsMutableOnX23() { + let squareBolus = BolusNormalPumpEvent(availableData: Data(hexadecimalString: "010080008000240209a24a1510")!, pumpModel: .model523)! + let squareBolusTimestamp = squareBolus.timestamp.date! + let squareBolusTimestampedEvent = TimestampedHistoryEvent(pumpEvent: squareBolus, date: squareBolusTimestamp) + let dateToCheckForSquareBolus = squareBolusTimestamp.addingTimeInterval(squareBolus.deliveryTime/2) // within the delivery time + XCTAssertTrue(squareBolusTimestampedEvent.isMutable(atDate: dateToCheckForSquareBolus, forPump: .model523)) + } + + func testSquareWaveIsNotMutableOnX23AfterDeliveryTime() { + let squareBolus = BolusNormalPumpEvent(availableData: Data(hexadecimalString: "010080008000240209a24a1510")!, pumpModel: .model523)! + let squareBolusTimestamp = squareBolus.timestamp.date! + let squareBolusTimestampedEvent = TimestampedHistoryEvent(pumpEvent: squareBolus, date: squareBolusTimestamp) + let dateToCheckForSquareBolus = squareBolusTimestamp.addingTimeInterval(squareBolus.deliveryTime + 1) // 1s after delivery time + XCTAssertTrue(squareBolusTimestampedEvent.isMutable(atDate: dateToCheckForSquareBolus, forPump: .model523)) } func getNormalBolusEvent() -> BolusNormalPumpEvent { let events = historyPage523.events let bolus = events[1] as! BolusNormalPumpEvent + print("Bolus hex = \(bolus.rawData.hexadecimalString)") return bolus } } diff --git a/MinimedKitUI/Info.plist b/MinimedKitUI/Info.plist index 29bd83f27..548b0a369 100644 --- a/MinimedKitUI/Info.plist +++ b/MinimedKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift index 09131254b..d27e924d9 100644 --- a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift @@ -89,7 +89,8 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { pumpFirmwareVersion: pumpFirmwareVersion, pumpRegion: pumpRegion, rileyLinkConnectionManagerState: rileyLinkPumpManager.rileyLinkConnectionManagerState, - timeZone: timeZone) + timeZone: timeZone, + suspendState: .resumed(Date())) } } diff --git a/NightscoutUploadKit/Info.plist b/NightscoutUploadKit/Info.plist index 22c234462..a60ea4a4e 100644 --- a/NightscoutUploadKit/Info.plist +++ b/NightscoutUploadKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/NightscoutUploadKitTests/Info.plist b/NightscoutUploadKitTests/Info.plist index 09a1b67bb..db502d0cb 100644 --- a/NightscoutUploadKitTests/Info.plist +++ b/NightscoutUploadKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/OmniKit/Info.plist b/OmniKit/Info.plist index 3d809ffe3..548b0a369 100644 --- a/OmniKit/Info.plist +++ b/OmniKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.0 + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift index fa9b70be2..d47e18704 100644 --- a/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift @@ -14,7 +14,7 @@ public struct TempBasalExtraCommand : MessageBlock { public let completionBeep: Bool public let programReminderInterval: TimeInterval public let remainingPulses: Double - public let delayUntilNextPulse: TimeInterval + public let delayUntilFirstTenthOfPulse: TimeInterval public let rateEntries: [RateEntry] public let blockType: MessageBlockType = .tempBasalExtra @@ -29,9 +29,9 @@ public struct TempBasalExtraCommand : MessageBlock { ]) data.appendBigEndian(UInt16(round(remainingPulses * 2) * 5)) if remainingPulses == 0 { - data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds) * 10) + data.appendBigEndian(UInt32(delayUntilFirstTenthOfPulse.hundredthsOfMilliseconds) * 10) } else { - data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds)) + data.appendBigEndian(UInt32(delayUntilFirstTenthOfPulse.hundredthsOfMilliseconds)) } for entry in rateEntries { data.append(entry.data) @@ -51,9 +51,9 @@ public struct TempBasalExtraCommand : MessageBlock { remainingPulses = Double(encodedData[4...].toBigEndian(UInt16.self)) / 10.0 let timerCounter = encodedData[6...].toBigEndian(UInt32.self) if remainingPulses == 0 { - delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter) / 10) + delayUntilFirstTenthOfPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter) / 10) } else { - delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) + delayUntilFirstTenthOfPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) } var entries = [RateEntry]() for entryIndex in (0..= 1 } @@ -88,7 +89,14 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti } return units / duration.hours } - + + public var finalizedUnits: Double? { + guard isFinished else { + return nil + } + return units + } + init(bolusAmount: Double, startTime: Date, scheduledCertainty: ScheduledCertainty) { self.doseType = .bolus self.units = bolusAmount @@ -122,13 +130,37 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti } public mutating func cancel(at date: Date, withRemaining remaining: Double? = nil) { + guard let finishTime = finishTime, date < finishTime else { + return + } + scheduledUnits = units - let oldRate = rate - duration = date.timeIntervalSince(startTime) - if let remaining = remaining { - units = units - remaining - } else if let duration = duration { - units = oldRate * duration.hours + let newDuration = date.timeIntervalSince(startTime) + + switch doseType { + case .bolus: + let oldRate = rate + if let remaining = remaining { + units = units - remaining + } else { + units = oldRate * newDuration.hours + } + case .tempBasal: + scheduledTempRate = rate + units = floor(rate * newDuration.hours * Pod.pulsesPerUnit) / Pod.pulsesPerUnit + print("Temp basal scheduled units: \(String(describing: scheduledUnits)), delivered units: \(units), duration: \(newDuration.minutes)") + default: + break + } + duration = newDuration + } + + public var isMutable: Bool { + switch doseType { + case .bolus, .tempBasal: + return !isFinished + default: + return false } } @@ -145,8 +177,9 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti return String(format: LocalizedString("Bolus: %1$@U %2$@ %3$@ %4$@", comment: "The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty)"), unitsStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) } case .tempBasal: - let rateStr = NumberFormatter.localizedString(from: NSNumber(value: rate), number: .decimal) - return String(format: LocalizedString("TempBasal: %1$@ U/hour %2$@ %3$@ %4$@", comment: "The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: scheduled certainty"), rateStr, startTimeStr, durationStr, scheduledCertainty.localizedDescription) + let volumeStr = insulinFormatter.string(from: units) ?? "?" + let rateStr = NumberFormatter.localizedString(from: NSNumber(value: scheduledTempRate ?? rate), number: .decimal) + return String(format: LocalizedString("TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@", comment: "The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty"), rateStr, startTimeStr, durationStr, volumeStr, scheduledCertainty.localizedDescription) case .suspend: return String(format: LocalizedString("Suspend: %1$@ %2$@", comment: "The format string describing a suspend. (1: Time)(2: Scheduled certainty"), startTimeStr, scheduledCertainty.localizedDescription) case .resume: @@ -176,6 +209,10 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.scheduledUnits = scheduledUnits } + if let scheduledTempRate = rawValue["scheduledTempRate"] as? Double { + self.scheduledTempRate = scheduledTempRate + } + if let duration = rawValue["duration"] as? Double { self.duration = duration } @@ -193,6 +230,10 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti rawValue["scheduledUnits"] = scheduledUnits } + if let scheduledTempRate = scheduledTempRate { + rawValue["scheduledTempRate"] = scheduledTempRate + } + if let duration = duration { rawValue["duration"] = duration } @@ -217,7 +258,7 @@ extension NewPumpEvent { init(_ dose: UnfinalizedDose) { let title = String(describing: dose) let entry = DoseEntry(dose) - self.init(date: dose.startTime, dose: entry, isMutable: false, raw: dose.uniqueKey, title: title) + self.init(date: dose.startTime, dose: entry, isMutable: dose.isMutable, raw: dose.uniqueKey, title: title) } } @@ -225,9 +266,9 @@ extension DoseEntry { init (_ dose: UnfinalizedDose) { switch dose.doseType { case .bolus: - self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.units, unit: .units) + self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledUnits ?? dose.units, unit: .units, deliveredUnits: dose.finalizedUnits) case .tempBasal: - self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.rate, unit: .unitsPerHour) + self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits) case .suspend: self = DoseEntry(suspendDate: dose.startTime) case .resume: diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index b09314778..94b9b9f1f 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -146,8 +146,18 @@ public class OmnipodPumpManager: RileyLinkPumpManager { podStateObservers.forEach { (observer) in observer.podStateDidUpdate(newValue.podState) } + + if oldValue.podState?.lastInsulinMeasurements?.reservoirVolume != newValue.podState?.lastInsulinMeasurements?.reservoirVolume { + if let lastInsulinMeasurements = newValue.podState?.lastInsulinMeasurements, let reservoirVolume = lastInsulinMeasurements.reservoirVolume { + self.pumpDelegate.notify({ (delegate) in + self.log.info("DU: updating reservoir level %{public}@", String(describing: reservoirVolume)) + delegate?.pumpManager(self, didReadReservoirValue: reservoirVolume, at: lastInsulinMeasurements.validTime) { _ in } + }) + } + } } + // Ideally we ensure that oldValue.rawValue != newValue.rawValue, but the types aren't // defined as equatable pumpDelegate.notify { (delegate) in @@ -158,9 +168,6 @@ public class OmnipodPumpManager: RileyLinkPumpManager { let newStatus = status(for: newValue) if oldStatus != newStatus { - // TODO: remove comment - // if oldValue.podState?.suspended != newValue.podState?.suspended || - // oldValue.timeZone != newValue.timeZone notifyStatusObservers(oldStatus: oldStatus) } @@ -283,16 +290,33 @@ extension OmnipodPumpManager { private func basalDeliveryState(for state: OmnipodPumpManagerState) -> PumpManagerStatus.BasalDeliveryState { guard let podState = state.podState else { - return .suspended + return .suspended(state.lastPumpDataReportDate ?? .distantPast) } - switch state.suspendTransition { - case .suspending?: + switch state.suspendEngageState { + case .engaging: return .suspending - case .resuming?: + case .disengaging: return .resuming - case .none: - return podState.suspended ? .suspended : .active + case .stable: + break + } + + switch state.tempBasalEngageState { + case .engaging: + return .initiatingTempBasal + case .disengaging: + return .cancelingTempBasal + case .stable: + if let tempBasal = podState.unfinalizedTempBasal, !tempBasal.isFinished { + return .tempBasal(DoseEntry(tempBasal)) + } + switch podState.suspendState { + case .resumed(let date): + return .active(date) + case .suspended(let date): + return .suspended(date) + } } } @@ -301,18 +325,17 @@ extension OmnipodPumpManager { return .none } - switch state.bolusTransition { - case .initiating?: + switch state.bolusEngageState { + case .engaging: return .initiating - case .canceling?: + case .disengaging: return .canceling - case .none: - if let bolus = podState.unfinalizedBolus, !bolus.finished { + case .stable: + if let bolus = podState.unfinalizedBolus, !bolus.isFinished { return .inProgress(DoseEntry(bolus)) - } else { - return .none } } + return .none } // Thread-safe @@ -615,13 +638,13 @@ extension OmnipodPumpManager { return } - self.getPodStatus(completion) + self.getPodStatus(storeDosesOnSuccess: false, completion: completion) } // MARK: - Pump Commands - private func getPodStatus(_ completion: ((_ result: PumpManagerResult) -> Void)? = nil) { - guard state.podState?.unfinalizedBolus?.finished != false else { + private func getPodStatus(storeDosesOnSuccess: Bool, completion: ((_ result: PumpManagerResult) -> Void)? = nil) { + guard state.podState?.unfinalizedBolus?.isFinished != false else { self.log.info("Skipping status request due to unfinalized bolus in progress.") completion?(.failure(PodCommsError.unfinalizedBolus)) return @@ -633,21 +656,10 @@ extension OmnipodPumpManager { switch result { case .success(let session): let status = try session.getStatus() - - session.dosesForStorage() { (doses) -> Bool in - return self.store(doses: doses, in: session) - } - - if let reservoirLevel = status.reservoirLevel { - let reservoirDate = Date() - // We block the session until the data's confirmed stored by the delegate - let semaphore = DispatchSemaphore(value: 0) - self.pumpDelegate.notify({ (delegate) in - delegate?.pumpManager(self, didReadReservoirValue: reservoirLevel, at: reservoirDate) { (_) in - semaphore.signal() - } + if storeDosesOnSuccess { + session.dosesForStorage({ (doses) -> Bool in + self.store(doses: doses, in: session) }) - semaphore.wait() } completion?(.success(status)) case .failure(let error): @@ -695,7 +707,7 @@ extension OmnipodPumpManager { return OmnipodPumpManagerError.noPodPaired } - guard state.podState?.unfinalizedBolus?.finished != false else { + guard state.podState?.unfinalizedBolus?.isFinished != false else { return PodCommsError.unfinalizedBolus } @@ -734,7 +746,7 @@ extension OmnipodPumpManager { return .success(false) } - guard state.podState?.unfinalizedBolus?.finished != false else { + guard state.podState?.unfinalizedBolus?.isFinished != false else { return .failure(PodCommsError.unfinalizedBolus) } @@ -864,6 +876,7 @@ extension OmnipodPumpManager { // MARK: - PumpManager extension OmnipodPumpManager: PumpManager { + public static let managerIdentifier: String = "Omnipod" public static let localizedTitle = LocalizedString("Omnipod", comment: "Generic title of the omnipod pump manager") @@ -906,6 +919,10 @@ extension OmnipodPumpManager: PumpManager { return Pod.reservoirCapacity } + public var lastReconciliation: Date? { + return self.state.podState?.lastInsulinMeasurements?.validTime + } + public var status: PumpManagerStatus { // Acquire the lock just once let state = self.state @@ -960,11 +977,11 @@ extension OmnipodPumpManager: PumpManager { defer { self.setState({ (state) in - state.suspendTransition = .none + state.suspendEngageState = .stable }) } self.setState({ (state) in - state.suspendTransition = .suspending + state.suspendEngageState = .engaging }) let result = session.cancelDelivery(deliveryType: .all, beepType: .noBeep) @@ -974,10 +991,10 @@ extension OmnipodPumpManager: PumpManager { case .uncertainFailure(let error): completion(error) case .success: - completion(nil) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } + completion(nil) } } } @@ -1002,16 +1019,19 @@ extension OmnipodPumpManager: PumpManager { defer { self.setState({ (state) in - state.suspendTransition = .none + state.suspendEngageState = .stable }) } self.setState({ (state) in - state.suspendTransition = .resuming + state.suspendEngageState = .disengaging }) do { let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) let _ = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } completion(nil) } catch (let error) { completion(error) @@ -1045,7 +1065,7 @@ extension OmnipodPumpManager: PumpManager { return // No active pod case true?: log.default("Fetching status because pumpData is too old") - getPodStatus { (response) in + getPodStatus(storeDosesOnSuccess: true) { (response) in self.pumpDelegate.notify({ (delegate) in switch response { case .success: @@ -1116,11 +1136,11 @@ extension OmnipodPumpManager: PumpManager { // TODO: Move this to the top, since Loop is expecting a status change to cancel its loading indicator? defer { self.setState({ (state) in - state.bolusTransition = nil + state.bolusEngageState = .stable }) } self.setState({ (state) in - state.bolusTransition = .initiating + state.bolusEngageState = .engaging }) let date = Date() @@ -1129,6 +1149,9 @@ extension OmnipodPumpManager: PumpManager { willRequest(dose) let result = session.bolus(units: enactUnits) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } switch result { case .success: @@ -1162,11 +1185,11 @@ extension OmnipodPumpManager: PumpManager { do { defer { self.setState({ (state) in - state.bolusTransition = nil + state.bolusEngageState = .stable }) } self.setState({ (state) in - state.bolusTransition = .canceling + state.bolusEngageState = .disengaging }) let result = session.cancelDelivery(deliveryType: .bolus, beepType: .noBeep) @@ -1176,11 +1199,12 @@ extension OmnipodPumpManager: PumpManager { case .uncertainFailure(let error): throw error case .success(_, let canceledBolus): - let canceledDoseEntry: DoseEntry? = canceledBolus != nil ? DoseEntry(canceledBolus!) : nil - completion(.success(canceledDoseEntry)) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } + + let canceledDoseEntry: DoseEntry? = canceledBolus != nil ? DoseEntry(canceledBolus!) : nil + completion(.success(canceledDoseEntry)) } } catch { completion(.failure(error)) @@ -1211,13 +1235,13 @@ extension OmnipodPumpManager: PumpManager { do { let preError = self.setStateWithResult({ (state) -> PodCommsError? in - guard state.podState?.suspended == false else { - self.log.info("Canceling temp basal because podState indicates pod is suspended.") + if case .some(.suspended) = state.podState?.suspendState { + self.log.info("Not enacting temp basal because podState indicates pod is suspended.") return .podSuspended } - guard state.podState?.unfinalizedBolus?.finished != false else { - self.log.info("Canceling temp basal because podState indicates unfinalized bolus in progress.") + guard state.podState?.unfinalizedBolus?.isFinished != false else { + self.log.info("Not enacting temp basal because podState indicates unfinalized bolus in progress.") return .unfinalizedBolus } @@ -1229,14 +1253,17 @@ extension OmnipodPumpManager: PumpManager { } let status: StatusResponse + let canceledDose: UnfinalizedDose? + let result = session.cancelDelivery(deliveryType: .tempBasal, beepType: .noBeep) switch result { case .certainFailure(let error): throw error case .uncertainFailure(let error): throw error - case .success(let cancelTempStatus,_): + case .success(let cancelTempStatus, let dose): status = cancelTempStatus + canceledDose = dose } guard !status.deliveryStatus.bolusing else { @@ -1248,18 +1275,34 @@ extension OmnipodPumpManager: PumpManager { throw PodCommsError.podSuspended } + defer { + self.setState({ (state) in + state.tempBasalEngageState = .stable + }) + } + if duration < .ulpOfOne { // 0 duration temp basals are used to cancel any existing temp basal - let cancelTime = Date() + self.setState({ (state) in + state.tempBasalEngageState = .disengaging + }) + let cancelTime = canceledDose?.finishTime ?? Date() let dose = DoseEntry(type: .tempBasal, startDate: cancelTime, endDate: cancelTime, value: 0, unit: .unitsPerHour) - completion(.success(dose)) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } + completion(.success(dose)) } else { + self.setState({ (state) in + state.tempBasalEngageState = .engaging + }) + let result = session.setTempBasal(rate: rate, duration: duration, acknowledgementBeep: false, completionBeep: false, programReminderInterval: 0) let basalStart = Date() let dose = DoseEntry(type: .tempBasal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: rate, unit: .unitsPerHour) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } switch result { case .success: completion(.success(dose)) @@ -1269,9 +1312,6 @@ extension OmnipodPumpManager: PumpManager { case .certainFailure(let error): completion(.failure(error)) } - session.dosesForStorage() { (doses) -> Bool in - return self.store(doses: doses, in: session) - } } } catch let error { self.log.error("Error during temp basal: %@", String(describing: error)) @@ -1287,23 +1327,6 @@ extension OmnipodPumpManager: PumpManager { } return nil } -} - -extension OmnipodPumpManager: MessageLogger { - func didSend(_ message: Data) { - setState { (state) in - state.messageLog.record(MessageLogEntry(messageDirection: .send, timestamp: Date(), data: message)) - } - } - - func didReceive(_ message: Data) { - setState { (state) in - state.messageLog.record(MessageLogEntry(messageDirection: .receive, timestamp: Date(), data: message)) - } - } -} - -extension OmnipodPumpManager: PodCommsDelegate { // This cannot be called from within the lockedState lock! func store(doses: [UnfinalizedDose], in session: PodCommsSession) -> Bool { @@ -1319,7 +1342,7 @@ extension OmnipodPumpManager: PodCommsDelegate { } semaphore.wait() - + if success { setState { (state) in state.lastPumpDataReportDate = Date() @@ -1329,23 +1352,41 @@ extension OmnipodPumpManager: PodCommsDelegate { } func store(doses: [UnfinalizedDose], completion: @escaping (_ error: Error?) -> Void) { + let lastPumpReconciliation = lastReconciliation + pumpDelegate.notify { (delegate) in guard let delegate = delegate else { preconditionFailure("pumpManagerDelegate cannot be nil") } - delegate.pumpManager(self, didReadPumpEvents: doses.map { NewPumpEvent($0) }, completion: { (error) in + delegate.pumpManager(self, hasNewPumpEvents: doses.map { NewPumpEvent($0) }, lastReconciliation: lastPumpReconciliation, completion: { (error) in if let error = error { self.log.error("Error storing pod events: %@", String(describing: error)) } else { - self.log.error("Stored pod events: %@", String(describing: doses)) + self.log.info("DU: Stored pod events: %@", String(describing: doses)) } completion(error) }) } } +} + +extension OmnipodPumpManager: MessageLogger { + func didSend(_ message: Data) { + setState { (state) in + state.messageLog.record(MessageLogEntry(messageDirection: .send, timestamp: Date(), data: message)) + } + } + func didReceive(_ message: Data) { + setState { (state) in + state.messageLog.record(MessageLogEntry(messageDirection: .receive, timestamp: Date(), data: message)) + } + } +} + +extension OmnipodPumpManager: PodCommsDelegate { func podComms(_ podComms: PodComms, didChange podState: PodState) { setState { (state) in state.podState = podState diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift index eae34db16..4950ce235 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -32,19 +32,17 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { // Temporal state not persisted - internal enum SuspendTransition { - case suspending - case resuming + internal enum EngageablePumpState: Equatable { + case engaging + case disengaging + case stable } - internal var suspendTransition: SuspendTransition? + internal var suspendEngageState: EngageablePumpState = .stable - internal enum BolusTransition { - case initiating - case canceling - } + internal var bolusEngageState: EngageablePumpState = .stable - internal var bolusTransition: BolusTransition? + internal var tempBasalEngageState: EngageablePumpState = .stable internal var lastPumpDataReportDate: Date? @@ -177,8 +175,9 @@ extension OmnipodPumpManagerState: CustomDebugStringConvertible { "* basalSchedule: \(String(describing: basalSchedule))", "* expirationReminderDate: \(String(describing: expirationReminderDate))", "* unstoredDoses: \(String(describing: unstoredDoses))", - "* suspendTransition: \(String(describing: suspendTransition))", - "* bolusTransition: \(String(describing: bolusTransition))", + "* suspendEngageState: \(String(describing: suspendEngageState))", + "* bolusEngageState: \(String(describing: bolusEngageState))", + "* tempBasalEngageState: \(String(describing: tempBasalEngageState))", "* lastPumpDataReportDate: \(String(describing: lastPumpDataReportDate))", "* isPumpDataStale: \(String(describing: isPumpDataStale))", String(reflecting: podState), diff --git a/OmniKit/PumpManager/PodComms.swift b/OmniKit/PumpManager/PodComms.swift index 4f4668579..0e01afb10 100644 --- a/OmniKit/PumpManager/PodComms.swift +++ b/OmniKit/PumpManager/PodComms.swift @@ -29,7 +29,7 @@ class PodComms: CustomDebugStringConvertible { private var podState: PodState? { didSet { if let newValue = podState, newValue != oldValue { - log.debug("Notifying delegate of new podState: %{public}@", String(reflecting: newValue)) + //log.debug("Notifying delegate of new podState: %{public}@", String(reflecting: newValue)) delegate?.podComms(self, didChange: newValue) } } diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 9f31619de..103af97c7 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -391,7 +391,7 @@ public class PodCommsSession { } public enum CancelDeliveryResult { - case success(statusResponse: StatusResponse, canceledBolus: UnfinalizedDose?) + case success(statusResponse: StatusResponse, canceledDose: UnfinalizedDose?) case certainFailure(error: PodCommsError) case uncertainFailure(error: PodCommsError) } @@ -428,7 +428,7 @@ public class PodCommsSession { let tempBasalCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, tempBasalRate: rate, duration: duration) let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) - guard podState.unfinalizedBolus?.finished != false else { + guard podState.unfinalizedBolus?.isFinished != false else { return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) } @@ -454,32 +454,34 @@ public class PodCommsSession { let now = Date() if deliveryType.contains(.basal) { podState.unfinalizedSuspend = UnfinalizedDose(suspendStartTime: now, scheduledCertainty: .certain) + podState.suspendState = .suspended(now) } + var canceledDose: UnfinalizedDose? = nil + if let unfinalizedTempBasal = podState.unfinalizedTempBasal, let finishTime = unfinalizedTempBasal.finishTime, deliveryType.contains(.tempBasal), - finishTime.compare(now) == .orderedDescending + finishTime > now { podState.unfinalizedTempBasal?.cancel(at: now) - log.info("Interrupted temp basal: %@", String(describing: unfinalizedTempBasal)) + canceledDose = podState.unfinalizedTempBasal + log.info("Interrupted temp basal: %@", String(describing: canceledDose)) } - var canceledBolus: UnfinalizedDose? = nil - if let unfinalizedBolus = podState.unfinalizedBolus, let finishTime = unfinalizedBolus.finishTime, deliveryType.contains(.bolus), - finishTime.compare(now) == .orderedDescending + finishTime > now { podState.unfinalizedBolus?.cancel(at: now, withRemaining: status.insulinNotDelivered) - canceledBolus = podState.unfinalizedBolus - log.info("Interrupted bolus: %@", String(describing: canceledBolus)) + canceledDose = podState.unfinalizedBolus + log.info("Interrupted bolus: %@", String(describing: canceledDose)) } podState.updateFromStatusResponse(status) - return CancelDeliveryResult.success(statusResponse: status, canceledBolus: canceledBolus) + return CancelDeliveryResult.success(statusResponse: status, canceledDose: canceledDose) } catch PodCommsError.nonceResyncFailed { return CancelDeliveryResult.certainFailure(error: PodCommsError.nonceResyncFailed) @@ -516,7 +518,9 @@ public class PodCommsSession { do { let status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) - podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: Date(), scheduledCertainty: .certain) + let now = Date() + podState.suspendState = .resumed(now) + podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: now, scheduledCertainty: .certain) podState.updateFromStatusResponse(status) return status } catch PodCommsError.nonceResyncFailed { @@ -531,6 +535,8 @@ public class PodCommsSession { let status = try setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + podState.suspendState = .resumed(Date()) + return status } @@ -553,7 +559,7 @@ public class PodCommsSession { public func deactivatePod() throws { - if podState.fault == nil && !podState.suspended { + if podState.fault == nil && !podState.isSuspended { let result = cancelDelivery(deliveryType: .all, beepType: .noBeep) switch result { case .certainFailure(let error): @@ -592,7 +598,7 @@ public class PodCommsSession { let dosesToStore = podState.dosesToStore if storageHandler(dosesToStore) { - log.info("Stored %@", String(describing: dosesToStore)) + log.info("Stored doses: %@", String(describing: dosesToStore)) self.podState.finalizedDoses.removeAll() } } diff --git a/OmniKit/PumpManager/PodDoseProgressEstimator.swift b/OmniKit/PumpManager/PodDoseProgressEstimator.swift index c994a9b45..f264aae3e 100644 --- a/OmniKit/PumpManager/PodDoseProgressEstimator.swift +++ b/OmniKit/PumpManager/PodDoseProgressEstimator.swift @@ -19,7 +19,7 @@ class PodDoseProgressEstimator: DoseProgressTimerEstimator { let elapsed = -dose.startDate.timeIntervalSinceNow let duration = dose.endDate.timeIntervalSince(dose.startDate) let percentComplete = min(elapsed / duration, 1) - let delivered = pumpManager?.roundToSupportedBolusVolume(units: percentComplete * dose.units) ?? dose.units + let delivered = pumpManager?.roundToSupportedBolusVolume(units: percentComplete * dose.programmedUnits) ?? dose.programmedUnits return DoseProgress(deliveredUnits: delivered, percentComplete: percentComplete) } diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index a12f72812..baef90aed 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -63,17 +63,18 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl var finalizedDoses: [UnfinalizedDose] public var dosesToStore: [UnfinalizedDose] { - var dosesToStore = finalizedDoses - if let unfinalizedTempBasal = unfinalizedTempBasal { - dosesToStore.append(unfinalizedTempBasal) - } - if let unfinalizedSuspend = unfinalizedSuspend { - dosesToStore.append(unfinalizedSuspend) + return finalizedDoses + [unfinalizedTempBasal, unfinalizedSuspend, unfinalizedBolus].compactMap {$0} + } + + public var suspendState: SuspendState + + public var isSuspended: Bool { + if case .suspended = suspendState { + return true } - return dosesToStore + return false } - public private(set) var suspended: Bool public var fault: PodInfoFaultEvent? public var messageTransportState: MessageTransportState public var primeFinishTime: Date? @@ -99,7 +100,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl self.tid = tid self.lastInsulinMeasurements = nil self.finalizedDoses = [] - self.suspended = false + self.suspendState = .resumed(Date()) self.fault = nil self.activeAlertSlots = .none self.messageTransportState = MessageTransportState(packetNumber: 0, messageNumber: 0) @@ -151,12 +152,12 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } public mutating func finalizeFinishedDoses() { - if let bolus = unfinalizedBolus, bolus.finished { + if let bolus = unfinalizedBolus, bolus.isFinished { finalizedDoses.append(bolus) unfinalizedBolus = nil } - if let tempBasal = unfinalizedTempBasal, tempBasal.finished { + if let tempBasal = unfinalizedTempBasal, tempBasal.isFinished { finalizedDoses.append(tempBasal) unfinalizedTempBasal = nil } @@ -213,8 +214,6 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl unfinalizedResume = nil } } - - suspended = deliveryStatus == .suspended } // MARK: - RawRepresentable @@ -245,41 +244,36 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } if let suspended = rawValue["suspended"] as? Bool { - self.suspended = suspended + // Migrate old value + if suspended { + suspendState = .suspended(Date()) + } else { + suspendState = .resumed(Date()) + } + } else if let rawSuspendState = rawValue["suspendState"] as? SuspendState.RawValue, let suspendState = SuspendState(rawValue: rawSuspendState) { + self.suspendState = suspendState } else { - self.suspended = false + return nil } - if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue, - let unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus) + if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue { - self.unfinalizedBolus = unfinalizedBolus - } else { - self.unfinalizedBolus = nil + self.unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus) } - if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue, - let unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) + if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue { - self.unfinalizedTempBasal = unfinalizedTempBasal - } else { - self.unfinalizedTempBasal = nil + self.unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) } - if let rawUnfinalizedSuspend = rawValue["unfinalizedSuspend"] as? UnfinalizedDose.RawValue, - let unfinalizedSuspend = UnfinalizedDose(rawValue: rawUnfinalizedSuspend) + if let rawUnfinalizedSuspend = rawValue["unfinalizedSuspend"] as? UnfinalizedDose.RawValue { - self.unfinalizedSuspend = unfinalizedSuspend - } else { - self.unfinalizedSuspend = nil + self.unfinalizedSuspend = UnfinalizedDose(rawValue: rawUnfinalizedSuspend) } - if let rawUnfinalizedResume = rawValue["unfinalizedResume"] as? UnfinalizedDose.RawValue, - let unfinalizedResume = UnfinalizedDose(rawValue: rawUnfinalizedResume) + if let rawUnfinalizedResume = rawValue["unfinalizedResume"] as? UnfinalizedDose.RawValue { - self.unfinalizedResume = unfinalizedResume - } else { - self.unfinalizedResume = nil + self.unfinalizedResume = UnfinalizedDose(rawValue: rawUnfinalizedResume) } if let rawLastInsulinMeasurements = rawValue["lastInsulinMeasurements"] as? PodInsulinMeasurements.RawValue { @@ -352,7 +346,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl "pmVersion": pmVersion, "lot": lot, "tid": tid, - "suspended": suspended, + "suspendState": suspendState.rawValue, "finalizedDoses": finalizedDoses.map( { $0.rawValue }), "alerts": activeAlertSlots.rawValue, "messageTransportState": messageTransportState.rawValue, @@ -412,7 +406,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl "* pmVersion: \(pmVersion)", "* lot: \(lot)", "* tid: \(tid)", - "* suspended: \(suspended)", + "* suspendState: \(suspendState)", "* unfinalizedBolus: \(String(describing: unfinalizedBolus))", "* unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))", "* unfinalizedSuspend: \(String(describing: unfinalizedSuspend))", @@ -492,3 +486,52 @@ fileprivate struct NonceState: RawRepresentable, Equatable { } +public enum SuspendState: Equatable, RawRepresentable { + public typealias RawValue = [String: Any] + + private enum SuspendStateType: Int { + case suspend, resume + } + + case suspended(Date) + case resumed(Date) + + private var identifier: Int { + switch self { + case .suspended: + return 1 + case .resumed: + return 2 + } + } + + public init?(rawValue: RawValue) { + guard let suspendStateType = rawValue["case"] as? SuspendStateType.RawValue, + let date = rawValue["date"] as? Date else { + return nil + } + switch SuspendStateType(rawValue: suspendStateType) { + case .suspend?: + self = .suspended(date) + case .resume?: + self = .resumed(date) + default: + return nil + } + } + + public var rawValue: RawValue { + switch self { + case .suspended(let date): + return [ + "case": SuspendStateType.suspend.rawValue, + "date": date + ] + case .resumed(let date): + return [ + "case": SuspendStateType.resume.rawValue, + "date": date + ] + } + } +} diff --git a/OmniKitTests/Info.plist b/OmniKitTests/Info.plist index 9366bbbb5..6b54d75b2 100644 --- a/OmniKitTests/Info.plist +++ b/OmniKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.0 + 2.2.0 CFBundleVersion 1 diff --git a/OmniKitTests/PodCommsSessionTests.swift b/OmniKitTests/PodCommsSessionTests.swift index 5fe623998..e058dac03 100644 --- a/OmniKitTests/PodCommsSessionTests.swift +++ b/OmniKitTests/PodCommsSessionTests.swift @@ -58,8 +58,6 @@ class PodCommsSessionTests: XCTestCase, PodCommsSessionDelegate { // From https://raw.githubusercontent.com/wiki/openaps/openomni/Full-life-of-a-pod-(omni-flo).md - let now = Date() - // 2018-05-25T13:03:51.765792 pod Message(ffffffff seq:01 [OmniKitPacketParser.VersionResponse(blockType: OmniKitPacketParser.MessageBlockType.versionResponse, lot: 43620, tid: 560313, address: Optional(521580830), setupState: OmniKitPacketParser.SetupState.addressAssigned, pmVersion: 2.7.0, piVersion: 2.7.0, data: 23 bytes)]) let podState = PodState(address: 521580830, piVersion: "2.7.0", pmVersion: "2.7.0", lot: 43620, tid: 560313) diff --git a/OmniKitTests/TempBasalTests.swift b/OmniKitTests/TempBasalTests.swift index 320b422a1..55ae8b3c1 100644 --- a/OmniKitTests/TempBasalTests.swift +++ b/OmniKitTests/TempBasalTests.swift @@ -164,7 +164,7 @@ class TempBasalTests: XCTestCase { XCTAssertEqual(false, cmd.acknowledgementBeep) XCTAssertEqual(true, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(0, cmd.remainingPulses) XCTAssertEqual(1, cmd.rateEntries.count) let entry = cmd.rateEntries[0] @@ -188,7 +188,7 @@ class TempBasalTests: XCTestCase { XCTAssertEqual(false, cmd.acknowledgementBeep) XCTAssertEqual(true, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(0, cmd.remainingPulses) XCTAssertEqual(6, cmd.rateEntries.count) let entry = cmd.rateEntries[0] @@ -244,7 +244,7 @@ class TempBasalTests: XCTestCase { XCTAssertEqual(false, cmd.acknowledgementBeep) XCTAssertEqual(true, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 6), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 6), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(300, cmd.remainingPulses) XCTAssertEqual(1, cmd.rateEntries.count) let entry = cmd.rateEntries[0] @@ -292,7 +292,7 @@ class TempBasalTests: XCTestCase { XCTAssertEqual(false, cmd.acknowledgementBeep) XCTAssertEqual(false, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 6), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 6), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(6300, cmd.remainingPulses) XCTAssertEqual(2, cmd.rateEntries.count) let entry = cmd.rateEntries[0] @@ -316,7 +316,7 @@ class TempBasalTests: XCTestCase { XCTAssertEqual(false, cmd.acknowledgementBeep) XCTAssertEqual(false, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 6.01001), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 6.01001), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(6289.5, cmd.remainingPulses) XCTAssertEqual(2, cmd.rateEntries.count) let entry1 = cmd.rateEntries[0] diff --git a/OmniKitUI/Info.plist b/OmniKitUI/Info.plist index 3d809ffe3..548b0a369 100644 --- a/OmniKitUI/Info.plist +++ b/OmniKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.0 + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift index 74c10d26a..3a16021c8 100644 --- a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -361,7 +361,7 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { deliveredUnits = nil } - cell.setDetailBolus(suspended: podState.suspended, dose: podState.unfinalizedBolus, deliveredUnits: deliveredUnits) + cell.setDetailBolus(suspended: podState.isSuspended, dose: podState.unfinalizedBolus, deliveredUnits: deliveredUnits) // TODO: This timer is in the wrong context; should be part of a custom bolus progress cell // if bolusProgressTimer == nil { // bolusProgressTimer = Timer.scheduledTimer(withTimeInterval: .seconds(2), repeats: true) { [weak self] (_) in @@ -370,7 +370,7 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { // } case .basal: cell.textLabel?.text = LocalizedString("Basal Delivery", comment: "The title of the cell showing pod basal status") - cell.setDetailBasal(suspended: podState.suspended, dose: podState.unfinalizedTempBasal) + cell.setDetailBasal(suspended: podState.isSuspended, dose: podState.unfinalizedTempBasal) case .reservoirLevel: cell.textLabel?.text = LocalizedString("Reservoir", comment: "The title of the cell showing reservoir status") cell.setReservoirDetail(podState.lastInsulinMeasurements) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 783a87c58..1120fd15e 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -77,7 +77,6 @@ 4352A74920DED81D00CAC200 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; 4352A74B20DED87F00CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; 4352A74C20DED8C200CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; - 4352A74D20DED8FC00CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; 4352A74F20DEDE8400CAC200 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610A20DDF55E002B996B /* LoopKitUI.framework */; }; 4352A75020DEDE8700CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; 4352A75120DEDE9B00CAC200 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; @@ -111,15 +110,12 @@ 43709AF020E0120F00F941B3 /* SetupImageTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 43709AEF20E0120F00F941B3 /* SetupImageTableViewCell.xib */; }; 43709AF120E0127000F941B3 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */; }; 43722FB11CB9F7640038B7F2 /* RileyLinkKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 43722FB01CB9F7640038B7F2 /* RileyLinkKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 43722FB81CB9F7640038B7F2 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; 43722FC31CB9F7640038B7F2 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; 43722FC41CB9F7640038B7F2 /* RileyLinkKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 437462391FA9287A00643383 /* RileyLinkDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437462381FA9287A00643383 /* RileyLinkDevice.swift */; }; 437F540A1FBFDAA60070FF2C /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; - 4384C8C91FB941FB00D916E6 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; 438D39221D19011700D40CA4 /* PlaceholderPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438D39211D19011700D40CA4 /* PlaceholderPumpEvent.swift */; }; 43A068EC1CF6BA6900F9EFE4 /* ReadRemainingInsulinMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43A068EB1CF6BA6900F9EFE4 /* ReadRemainingInsulinMessageBodyTests.swift */; }; - 43A9E50E1F6B865000307931 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; 43B0ADC01D0FC03200AAD278 /* NSDateComponentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B0ADBF1D0FC03200AAD278 /* NSDateComponentsTests.swift */; }; 43B0ADC21D12454700AAD278 /* TimestampedHistoryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B0ADC11D12454700AAD278 /* TimestampedHistoryEvent.swift */; }; 43B0ADC41D12506A00AAD278 /* NSDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B0ADC31D12506A00AAD278 /* NSDateFormatter.swift */; }; @@ -247,7 +243,6 @@ 7D2366F5212527DA0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; 7D2366F6212527DA0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; 7D2366F7212527DA0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; - 7D23674721252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674521252A5E0028B67D /* InfoPlist.strings */; }; 7D23674A21252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674821252A5E0028B67D /* InfoPlist.strings */; }; 7D23674D21252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674B21252A5E0028B67D /* InfoPlist.strings */; }; 7D23675021252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674E21252A5E0028B67D /* InfoPlist.strings */; }; @@ -290,7 +285,6 @@ C12572922121EEEE0061BA2F /* SettingsImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12572912121EEEE0061BA2F /* SettingsImageTableViewCell.swift */; }; C125729421220FEC0061BA2F /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C125729321220FEC0061BA2F /* MainStoryboard.storyboard */; }; C12572982125FA390061BA2F /* RileyLinkConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12572972125FA390061BA2F /* RileyLinkConnectionManager.swift */; }; - C12616441B685F0A001FAD87 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12616431B685F0A001FAD87 /* CoreData.framework */; }; C1271B071A9A34E900B7C949 /* Log.m in Sources */ = {isa = PBXBuildFile; fileRef = C1271B061A9A34E900B7C949 /* Log.m */; }; C1274F771D8232580002912B /* DailyTotal515PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1274F761D8232580002912B /* DailyTotal515PumpEvent.swift */; }; C1274F791D823A550002912B /* ChangeMeterIDPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1274F781D823A550002912B /* ChangeMeterIDPumpEvent.swift */; }; @@ -303,11 +297,6 @@ C12EA23D198B436800309FA4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23C198B436800309FA4 /* CoreGraphics.framework */; }; C12EA23F198B436800309FA4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23E198B436800309FA4 /* UIKit.framework */; }; C12EA245198B436800309FA4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C12EA243198B436800309FA4 /* InfoPlist.strings */; }; - C12EA254198B436800309FA4 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA253198B436800309FA4 /* XCTest.framework */; }; - C12EA255198B436800309FA4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23A198B436800309FA4 /* Foundation.framework */; }; - C12EA256198B436900309FA4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23E198B436800309FA4 /* UIKit.framework */; }; - C12EA25E198B436900309FA4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C12EA25C198B436900309FA4 /* InfoPlist.strings */; }; - C12EA260198B436900309FA4 /* RileyLinkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C12EA25F198B436900309FA4 /* RileyLinkTests.m */; }; C1330F431DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */; }; C133CF931D5943780034B82D /* PredictedBG.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133CF921D5943780034B82D /* PredictedBG.swift */; }; C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */; }; @@ -338,6 +327,7 @@ C15AF2AD1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AC1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift */; }; C15AF2AF1D7498930031FC9D /* RestoreMystery54PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */; }; C15AF2B11D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */; }; + C164A56222F1F0A6000E3FA5 /* UnfinalizedDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C164A56122F1F0A6000E3FA5 /* UnfinalizedDose.swift */; }; C168C40021AEF8DE00ADE90E /* PodReplacementNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168C3FF21AEF8DE00ADE90E /* PodReplacementNavigationController.swift */; }; C168C40221AFACA600ADE90E /* ReplacePodViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168C40121AFACA600ADE90E /* ReplacePodViewController.swift */; }; C16A08311D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16A08301D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift */; }; @@ -516,7 +506,6 @@ C1EAD6E41C82BA87006DBA60 /* CRC16Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6E31C82BA87006DBA60 /* CRC16Tests.swift */; }; C1EB955D1C887FE5002517DF /* HistoryPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EB955C1C887FE5002517DF /* HistoryPage.swift */; }; C1EF58881B3F93FE001C8C80 /* Config.m in Sources */ = {isa = PBXBuildFile; fileRef = C1EF58871B3F93FE001C8C80 /* Config.m */; }; - C1F000481EBE352900F65163 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; C1F0004C1EBE68A600F65163 /* DataFrameMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0004B1EBE68A600F65163 /* DataFrameMessageBody.swift */; }; C1F000501EBE727C00F65163 /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F0004F1EBE727C00F65163 /* BasalScheduleTests.swift */; }; C1F000521EBE73F400F65163 /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F000511EBE73F400F65163 /* BasalSchedule.swift */; }; @@ -625,13 +614,6 @@ remoteGlobalIDString = 431CE76E1F98564100255374; remoteInfo = RileyLinkBLEKit; }; - 431CE77B1F98564200255374 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; - }; 431CE7821F98564200255374 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -639,20 +621,6 @@ remoteGlobalIDString = 431CE76E1F98564100255374; remoteInfo = RileyLinkBLEKit; }; - 4327EEB820E1E55D002598CB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; - }; - 4327EEBA20E1E562002598CB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; - }; 4352A72A20DEC9B700CAC200 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -660,13 +628,6 @@ remoteGlobalIDString = 4352A72420DEC9B700CAC200; remoteInfo = MinimedKitUI; }; - 43722FB91CB9F7640038B7F2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; - remoteInfo = RileyLinkKit; - }; 43722FC11CB9F7640038B7F2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -716,13 +677,6 @@ remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; remoteInfo = RileyLinkKitUI; }; - A9B839C422809D82004E745E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; - remoteInfo = RileyLinkKit; - }; A9B839C622809D8D004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -793,33 +747,47 @@ remoteGlobalIDString = C10D9BC01C8269D500378342; remoteInfo = MinimedKit; }; - C12EA257198B436900309FA4 /* PBXContainerItemProxy */ = { + C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = C1B3830A1CD0665D00CE7782; + remoteInfo = NightscoutUploadKit; }; - C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; - C1B383181CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C1D289AA22F7CC26003FFBD9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; remoteGlobalIDString = C12EA236198B436800309FA4; remoteInfo = RileyLink; }; - C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C1D289AC22F7CC2A003FFBD9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C1B3830A1CD0665D00CE7782; - remoteInfo = NightscoutUploadKit; + remoteGlobalIDString = C12EA236198B436800309FA4; + remoteInfo = RileyLink; + }; + C1D289AE22F7CC2D003FFBD9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C12EA236198B436800309FA4; + remoteInfo = RileyLink; + }; + C1D289B222F7CC32003FFBD9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C12EA236198B436800309FA4; + remoteInfo = RileyLink; }; C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -948,7 +916,6 @@ 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RileyLinkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43722FB01CB9F7640038B7F2 /* RileyLinkKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RileyLinkKit.h; sourceTree = ""; }; 43722FB21CB9F7640038B7F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 43722FB71CB9F7640038B7F2 /* RileyLinkKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RileyLinkKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43722FC01CB9F7640038B7F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 437462381FA9287A00643383 /* RileyLinkDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkDevice.swift; sourceTree = ""; }; 437DE508229C8A05003B1074 /* copy-frameworks.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-frameworks.sh"; sourceTree = ""; }; @@ -1258,7 +1225,6 @@ C12EA23E198B436800309FA4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; C12EA242198B436800309FA4 /* RileyLink-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RileyLink-Info.plist"; sourceTree = ""; }; C12EA244198B436800309FA4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - C12EA252198B436800309FA4 /* RileyLinkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RileyLinkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C12EA253198B436800309FA4 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; C12EA25B198B436900309FA4 /* RileyLinkTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RileyLinkTests-Info.plist"; sourceTree = ""; }; C12EA25D198B436900309FA4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1292,6 +1258,7 @@ C15AF2AC1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeBolusWizardSetupPumpEvent.swift; sourceTree = ""; }; C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreMystery54PumpEvent.swift; sourceTree = ""; }; C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RestoreMystery55PumpEvent.swift; sourceTree = ""; }; + C164A56122F1F0A6000E3FA5 /* UnfinalizedDose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfinalizedDose.swift; sourceTree = ""; }; C168C3FF21AEF8DE00ADE90E /* PodReplacementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodReplacementNavigationController.swift; sourceTree = ""; }; C168C40121AFACA600ADE90E /* ReplacePodViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplacePodViewController.swift; sourceTree = ""; }; C16A08301D389205001A200C /* JournalEntryMealMarkerPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JournalEntryMealMarkerPumpEvent.swift; sourceTree = ""; }; @@ -1573,15 +1540,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 43722FB41CB9F7640038B7F2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 43722FB81CB9F7640038B7F2 /* RileyLinkKit.framework in Frameworks */, - 4352A74D20DED8FC00CAC200 /* LoopKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 43C2468F1D8918AE0031F8D1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1639,18 +1597,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C12EA24F198B436800309FA4 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - C1F000481EBE352900F65163 /* RileyLinkKit.framework in Frameworks */, - C12616441B685F0A001FAD87 /* CoreData.framework in Frameworks */, - C12EA254198B436800309FA4 /* XCTest.framework in Frameworks */, - C12EA256198B436900309FA4 /* UIKit.framework in Frameworks */, - C12EA255198B436800309FA4 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C1B383071CD0665D00CE7782 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1916,6 +1862,7 @@ C16E61212208C7A80069F357 /* ReservoirReading.swift */, 43D8709420DE1C91006B549E /* RileyLinkDevice.swift */, C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */, + C164A56122F1F0A6000E3FA5 /* UnfinalizedDose.swift */, ); path = PumpManager; sourceTree = ""; @@ -2129,11 +2076,9 @@ isa = PBXGroup; children = ( C12EA237198B436800309FA4 /* RileyLink.app */, - C12EA252198B436800309FA4 /* RileyLinkTests.xctest */, C10D9BC11C8269D500378342 /* MinimedKit.framework */, C10D9BCA1C8269D500378342 /* MinimedKitTests.xctest */, 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */, - 43722FB71CB9F7640038B7F2 /* RileyLinkKitTests.xctest */, C1B3830B1CD0665D00CE7782 /* NightscoutUploadKit.framework */, C1B383141CD0665D00CE7782 /* NightscoutUploadKitTests.xctest */, 43C246931D8918AE0031F8D1 /* Crypto.framework */, @@ -2728,7 +2673,7 @@ ); dependencies = ( 431CE77A1F98564200255374 /* PBXTargetDependency */, - 431CE77C1F98564200255374 /* PBXTargetDependency */, + C1D289AD22F7CC2A003FFBD9 /* PBXTargetDependency */, ); name = RileyLinkBLEKitTests; productName = RileyLinkBLEKitTests; @@ -2774,25 +2719,6 @@ productReference = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; productType = "com.apple.product-type.framework"; }; - 43722FB61CB9F7640038B7F2 /* RileyLinkKitTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 43722FCA1CB9F7640038B7F2 /* Build configuration list for PBXNativeTarget "RileyLinkKitTests" */; - buildPhases = ( - 43722FB31CB9F7640038B7F2 /* Sources */, - 43722FB41CB9F7640038B7F2 /* Frameworks */, - 43722FB51CB9F7640038B7F2 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 43722FBA1CB9F7640038B7F2 /* PBXTargetDependency */, - 4327EEBB20E1E562002598CB /* PBXTargetDependency */, - ); - name = RileyLinkKitTests; - productName = RileyLinkKitTests; - productReference = 43722FB71CB9F7640038B7F2 /* RileyLinkKitTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; 43C246921D8918AE0031F8D1 /* Crypto */ = { isa = PBXNativeTarget; buildConfigurationList = 43C2469E1D8918AE0031F8D1 /* Build configuration list for PBXNativeTarget "Crypto" */; @@ -2862,7 +2788,7 @@ ); dependencies = ( C10D9BCD1C8269D500378342 /* PBXTargetDependency */, - 4327EEB920E1E55D002598CB /* PBXTargetDependency */, + C1D289B322F7CC32003FFBD9 /* PBXTargetDependency */, ); name = MinimedKitTests; productName = MinimedKitTests; @@ -2897,25 +2823,6 @@ productReference = C12EA237198B436800309FA4 /* RileyLink.app */; productType = "com.apple.product-type.application"; }; - C12EA251198B436800309FA4 /* RileyLinkTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C12EA266198B436900309FA4 /* Build configuration list for PBXNativeTarget "RileyLinkTests" */; - buildPhases = ( - C12EA24E198B436800309FA4 /* Sources */, - C12EA24F198B436800309FA4 /* Frameworks */, - C12EA250198B436800309FA4 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - A9B839C522809D82004E745E /* PBXTargetDependency */, - C12EA258198B436900309FA4 /* PBXTargetDependency */, - ); - name = RileyLinkTests; - productName = RileyLinkTests; - productReference = C12EA252198B436800309FA4 /* RileyLinkTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */ = { isa = PBXNativeTarget; buildConfigurationList = C1B383221CD0665D00CE7782 /* Build configuration list for PBXNativeTarget "NightscoutUploadKit" */; @@ -2950,7 +2857,7 @@ dependencies = ( A9B839CF22809DCF004E745E /* PBXTargetDependency */, C1B383171CD0665D00CE7782 /* PBXTargetDependency */, - C1B383191CD0665D00CE7782 /* PBXTargetDependency */, + C1D289AF22F7CC2D003FFBD9 /* PBXTargetDependency */, ); name = NightscoutUploadKitTests; productName = NightscoutUploadKitTests; @@ -3005,6 +2912,7 @@ ); dependencies = ( C1FFAF83213323CC00C50C1D /* PBXTargetDependency */, + C1D289AB22F7CC26003FFBD9 /* PBXTargetDependency */, ); name = OmniKitTests; productName = OmniKitTests; @@ -3061,11 +2969,6 @@ CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1020; }; - 43722FB61CB9F7640038B7F2 = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; - TestTargetID = C12EA236198B436800309FA4; - }; 43C246921D8918AE0031F8D1 = { CreatedOnToolsVersion = 8.0; LastSwiftMigration = 1020; @@ -3089,10 +2992,6 @@ LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; - C12EA251198B436800309FA4 = { - ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; - }; C1B3830A1CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1020; @@ -3100,6 +2999,7 @@ C1B383131CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1000; + TestTargetID = C12EA236198B436800309FA4; }; C1BB128421CB5603009A29B5 = { CreatedOnToolsVersion = 10.1; @@ -3146,11 +3046,9 @@ projectRoot = ""; targets = ( C12EA236198B436800309FA4 /* RileyLink */, - C12EA251198B436800309FA4 /* RileyLinkTests */, C10D9BC01C8269D500378342 /* MinimedKit */, C10D9BC91C8269D500378342 /* MinimedKitTests */, 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */, - 43722FB61CB9F7640038B7F2 /* RileyLinkKitTests */, C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */, C1B383131CD0665D00CE7782 /* NightscoutUploadKitTests */, 43C246921D8918AE0031F8D1 /* Crypto */, @@ -3201,14 +3099,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 43722FB51CB9F7640038B7F2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D23674721252A5E0028B67D /* InfoPlist.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 43C246911D8918AE0031F8D1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3258,14 +3148,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C12EA250198B436800309FA4 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C12EA25E198B436900309FA4 /* InfoPlist.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C1B383091CD0665D00CE7782 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3436,15 +3318,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 43722FB31CB9F7640038B7F2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4384C8C91FB941FB00D916E6 /* TimeInterval.swift in Sources */, - 43A9E50E1F6B865000307931 /* Data.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 43C2468E1D8918AE0031F8D1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3586,6 +3459,7 @@ 43D8709520DE1C91006B549E /* RileyLinkDevice.swift in Sources */, 4352A71820DEC7B900CAC200 /* PumpMessage+PumpOpsSession.swift in Sources */, C1842C071C8FA45100DB42AC /* ClearAlarmPumpEvent.swift in Sources */, + C164A56222F1F0A6000E3FA5 /* UnfinalizedDose.swift in Sources */, C1A721661EC4BCE30080FAD7 /* PartialDecode.swift in Sources */, 43B0ADC21D12454700AAD278 /* TimestampedHistoryEvent.swift in Sources */, C1EAD6C91C826B92006DBA60 /* MySentryAckMessageBody.swift in Sources */, @@ -3760,14 +3634,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C12EA24E198B436800309FA4 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - C12EA260198B436900309FA4 /* RileyLinkTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; C1B383061CD0665D00CE7782 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3989,36 +3855,16 @@ target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; targetProxy = 431CE7791F98564200255374 /* PBXContainerItemProxy */; }; - 431CE77C1F98564200255374 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = 431CE77B1F98564200255374 /* PBXContainerItemProxy */; - }; 431CE7831F98564200255374 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; targetProxy = 431CE7821F98564200255374 /* PBXContainerItemProxy */; }; - 4327EEB920E1E55D002598CB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = 4327EEB820E1E55D002598CB /* PBXContainerItemProxy */; - }; - 4327EEBB20E1E562002598CB /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = 4327EEBA20E1E562002598CB /* PBXContainerItemProxy */; - }; 4352A72B20DEC9B700CAC200 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4352A72420DEC9B700CAC200 /* MinimedKitUI */; targetProxy = 4352A72A20DEC9B700CAC200 /* PBXContainerItemProxy */; }; - 43722FBA1CB9F7640038B7F2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; - targetProxy = 43722FB91CB9F7640038B7F2 /* PBXContainerItemProxy */; - }; 43722FC21CB9F7640038B7F2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; @@ -4054,11 +3900,6 @@ target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; targetProxy = 43D5E7931FAF7BFB004ACDB7 /* PBXContainerItemProxy */; }; - A9B839C522809D82004E745E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; - targetProxy = A9B839C422809D82004E745E /* PBXContainerItemProxy */; - }; A9B839C722809D8D004E745E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; @@ -4109,26 +3950,36 @@ target = C10D9BC01C8269D500378342 /* MinimedKit */; targetProxy = C10D9BD41C8269D500378342 /* PBXContainerItemProxy */; }; - C12EA258198B436900309FA4 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C12EA257198B436900309FA4 /* PBXContainerItemProxy */; - }; C1B383171CD0665D00CE7782 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */; }; - C1B383191CD0665D00CE7782 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1B383181CD0665D00CE7782 /* PBXContainerItemProxy */; - }; C1B3831F1CD0665D00CE7782 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; + C1D289AB22F7CC26003FFBD9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C12EA236198B436800309FA4 /* RileyLink */; + targetProxy = C1D289AA22F7CC26003FFBD9 /* PBXContainerItemProxy */; + }; + C1D289AD22F7CC2A003FFBD9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C12EA236198B436800309FA4 /* RileyLink */; + targetProxy = C1D289AC22F7CC2A003FFBD9 /* PBXContainerItemProxy */; + }; + C1D289AF22F7CC2D003FFBD9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C12EA236198B436800309FA4 /* RileyLink */; + targetProxy = C1D289AE22F7CC2D003FFBD9 /* PBXContainerItemProxy */; + }; + C1D289B322F7CC32003FFBD9 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C12EA236198B436800309FA4 /* RileyLink */; + targetProxy = C1D289B222F7CC32003FFBD9 /* PBXContainerItemProxy */; + }; C1FFAF83213323CC00C50C1D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1FFAF77213323CC00C50C1D /* OmniKit */; @@ -4731,56 +4582,6 @@ }; name = Release; }; - 43722FC71CB9F7640038B7F2 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = RileyLinkKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Debug; - }; - 43722FC81CB9F7640038B7F2 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NONNULL = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); - GCC_NO_COMMON_BLOCKS = YES; - INFOPLIST_FILE = RileyLinkKitTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkKitTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Release; - }; 43C2469C1D8918AE0031F8D1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5210,44 +5011,6 @@ }; name = Release; }; - C12EA267198B436900309FA4 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = "RileyLinkTests/RileyLinkTests-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.rileylink.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RileyLinkTests; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; - }; - name = Debug; - }; - C12EA268198B436900309FA4 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = "RileyLinkTests/RileyLinkTests-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = "com.rileylink.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RileyLinkTests; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; - }; - name = Release; - }; C1B383231CD0665D00CE7782 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5327,6 +5090,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -5351,6 +5115,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.NightscoutUploadKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; @@ -5651,15 +5416,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 43722FCA1CB9F7640038B7F2 /* Build configuration list for PBXNativeTarget "RileyLinkKitTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 43722FC71CB9F7640038B7F2 /* Debug */, - 43722FC81CB9F7640038B7F2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 43C2469E1D8918AE0031F8D1 /* Build configuration list for PBXNativeTarget "Crypto" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -5714,15 +5470,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C12EA266198B436900309FA4 /* Build configuration list for PBXNativeTarget "RileyLinkTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C12EA267198B436900309FA4 /* Debug */, - C12EA268198B436900309FA4 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; C1B383221CD0665D00CE7782 /* Build configuration list for PBXNativeTarget "NightscoutUploadKit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme index 872ce4951..87790fde6 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme @@ -20,6 +20,90 @@ ReferencedContainer = "container:RileyLink.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - Void) { + func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (_ error: Error?) -> Void) { } func pumpManager(_ pumpManager: PumpManager, didReadReservoirValue units: Double, at date: Date, completion: @escaping (_ result: PumpManagerResult<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool)>) -> Void) { diff --git a/RileyLink/RileyLink-Info.plist b/RileyLink/RileyLink-Info.plist index 325e29244..450c2d49c 100644 --- a/RileyLink/RileyLink-Info.plist +++ b/RileyLink/RileyLink-Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKit/Info.plist b/RileyLinkBLEKit/Info.plist index 29bd83f27..548b0a369 100644 --- a/RileyLinkBLEKit/Info.plist +++ b/RileyLinkBLEKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLinkBLEKitTests/Info.plist b/RileyLinkBLEKitTests/Info.plist index 766804127..6b54d75b2 100644 --- a/RileyLinkBLEKitTests/Info.plist +++ b/RileyLinkBLEKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleVersion 1 diff --git a/RileyLinkKit/Info.plist b/RileyLinkKit/Info.plist index 22c234462..a60ea4a4e 100644 --- a/RileyLinkKit/Info.plist +++ b/RileyLinkKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKitTests/Info.plist b/RileyLinkKitTests/Info.plist index 4eb1eb63b..7506bdc29 100644 --- a/RileyLinkKitTests/Info.plist +++ b/RileyLinkKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKitUI/Info.plist b/RileyLinkKitUI/Info.plist index 29bd83f27..548b0a369 100644 --- a/RileyLinkKitUI/Info.plist +++ b/RileyLinkKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift b/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift index 1d0332bf3..0efb5f7b5 100644 --- a/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift +++ b/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift @@ -120,8 +120,8 @@ public class RileyLinkDevicesTableViewDataSource: NSObject { @objc private func reloadDevices() { rileyLinkPumpManager.rileyLinkDeviceProvider.getDevices { (devices) in - DispatchQueue.main.async { - self.devices = devices + DispatchQueue.main.async { [weak self] in + self?.devices = devices } } } diff --git a/RileyLinkTests/RileyLinkTests-Info.plist b/RileyLinkTests/RileyLinkTests-Info.plist index bae54fa16..b6c35c5c8 100644 --- a/RileyLinkTests/RileyLinkTests-Info.plist +++ b/RileyLinkTests/RileyLinkTests-Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0dev + 2.2.0 CFBundleSignature ???? CFBundleVersion From 3a5a92c9a69565dc32c916294826b4e7ae02297e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 16 Aug 2019 09:25:02 -0500 Subject: [PATCH 27/71] Update dependencies --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index 45023734e..e2488b425 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "temp-basal-delivery" +github "LoopKit/LoopKit" "dev" github "maxkonovalov/MKRingProgressView" ~> 2.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index c40af6780..b643e87df 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "cbfbee999e946af66140f6ca9152fdd329ba6497" +github "LoopKit/LoopKit" "eaed65166138c5ea1b8d8dddc7b14f1032f3da06" github "maxkonovalov/MKRingProgressView" "2.2.2" From 475ffef52ce22bba82f6ebaf99da5ab7c8fcba4a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 17 Aug 2019 17:34:16 -0500 Subject: [PATCH 28/71] Add structures for representing Loop settings in NS (#533) --- NightscoutUploadKit/NightscoutProfile.swift | 97 ++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/NightscoutUploadKit/NightscoutProfile.swift b/NightscoutUploadKit/NightscoutProfile.swift index cbee2faa9..cd8266127 100644 --- a/NightscoutUploadKit/NightscoutProfile.swift +++ b/NightscoutUploadKit/NightscoutProfile.swift @@ -36,7 +36,7 @@ public class ProfileSet { let targetLow : [ScheduleItem] let targetHigh : [ScheduleItem] let units: String - + public init(timezone: TimeZone, dia: TimeInterval, sensitivity: [ScheduleItem], carbratio: [ScheduleItem], basal: [ScheduleItem], targetLow: [ScheduleItem], targetHigh: [ScheduleItem], units: String) { self.timezone = timezone self.dia = dia @@ -69,13 +69,15 @@ public class ProfileSet { let enteredBy: String let defaultProfile: String let store: [String: Profile] + let settings: LoopSettings - public init(startDate: Date, units: String, enteredBy: String, defaultProfile: String, store: [String: Profile]) { + public init(startDate: Date, units: String, enteredBy: String, defaultProfile: String, store: [String: Profile], settings: LoopSettings) { self.startDate = startDate self.units = units self.enteredBy = enteredBy self.defaultProfile = defaultProfile self.store = store + self.settings = settings } public var dictionaryRepresentation: [String: Any] { @@ -91,9 +93,100 @@ public class ProfileSet { "mills": mills, "units": units, "enteredBy": enteredBy, + "loopSettings": settings.dictionaryRepresentation, "store": dictProfiles ] return rval } } + +public struct TemporaryScheduleOverride { + let targetRange: ClosedRange? + let insulinNeedsScaleFactor: Double? + let symbol: String? + let duration: TimeInterval + let name: String? + + public init(targetRange: ClosedRange?, insulinNeedsScaleFactor: Double?, symbol: String?, duration: TimeInterval, name: String?) { + self.targetRange = targetRange + self.insulinNeedsScaleFactor = insulinNeedsScaleFactor + self.symbol = symbol + self.duration = duration + self.name = name + } + + public var dictionaryRepresentation: [String: Any] { + var rval: [String: Any] = [ + "duration": duration, + ] + + if let symbol = symbol { + rval["symbol"] = symbol + } + + if let targetRange = targetRange { + rval["targetRange"] = [targetRange.lowerBound, targetRange.upperBound] + } + + if let insulinNeedsScaleFactor = insulinNeedsScaleFactor { + rval["insulinNeedsScaleFactor"] = insulinNeedsScaleFactor + } + + if let name = name { + rval["name"] = name + } + + return rval + } +} + +public struct LoopSettings { + let dosingEnabled: Bool + let overridePresets: [TemporaryScheduleOverride] + let scheduleOverride: TemporaryScheduleOverride? + let minimumBGGuard: Double? + let preMealTargetRange: ClosedRange? + let maximumBasalRatePerHour: Double? + let maximumBolus: Double? + + public init(dosingEnabled: Bool, overridePresets: [TemporaryScheduleOverride], scheduleOverride: TemporaryScheduleOverride?, minimumBGGuard: Double?, preMealTargetRange: ClosedRange?, maximumBasalRatePerHour: Double?, maximumBolus: Double?) { + self.dosingEnabled = dosingEnabled + self.overridePresets = overridePresets + self.scheduleOverride = scheduleOverride + self.minimumBGGuard = minimumBGGuard + self.preMealTargetRange = preMealTargetRange + self.maximumBasalRatePerHour = maximumBasalRatePerHour + self.maximumBolus = maximumBolus + } + + public var dictionaryRepresentation: [String: Any] { + + var rval: [String: Any] = [ + "dosingEnabled": dosingEnabled, + "overridePresets": overridePresets.map { $0.dictionaryRepresentation }, + ] + + if let minimumBGGuard = minimumBGGuard { + rval["minimumBGGuard"] = minimumBGGuard + } + + if let scheduleOverride = scheduleOverride { + rval["scheduleOverride"] = scheduleOverride.dictionaryRepresentation + } + + if let preMealTargetRange = preMealTargetRange { + rval["preMealTargetRange"] = [preMealTargetRange.lowerBound, preMealTargetRange.upperBound] + } + + if let maximumBasalRatePerHour = maximumBasalRatePerHour { + rval["maximumBasalRatePerHour"] = maximumBasalRatePerHour + } + + if let maximumBolus = maximumBolus { + rval["maximumBolus"] = maximumBolus + } + + return rval + } +} From 7fc5e851c15eb5166ec9b7012bb12f4d78702491 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 17 Aug 2019 22:42:55 -0500 Subject: [PATCH 29/71] Add new files to OmnikitPacketParser --- RileyLink.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 1120fd15e..859581331 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -324,6 +324,8 @@ C14FFC671D3D7E390049CF85 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC661D3D7E390049CF85 /* KeychainManager.swift */; }; C14FFC691D3D7E560049CF85 /* KeychainManager+RileyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC681D3D7E560049CF85 /* KeychainManager+RileyLink.swift */; }; C154EA7F2146F41900B24AF8 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; + C15500272308FA1000345FCC /* BeepType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D807D7D72289135D006BCDF0 /* BeepType.swift */; }; + C15500282308FA2F00345FCC /* BeepConfigCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D807D7D9228913EC006BCDF0 /* BeepConfigCommand.swift */; }; C15AF2AD1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AC1D74929D0031FC9D /* ChangeBolusWizardSetupPumpEvent.swift */; }; C15AF2AF1D7498930031FC9D /* RestoreMystery54PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2AE1D7498930031FC9D /* RestoreMystery54PumpEvent.swift */; }; C15AF2B11D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15AF2B01D7498DD0031FC9D /* RestoreMystery55PumpEvent.swift */; }; @@ -3695,6 +3697,7 @@ C1BB12B021CB5654009A29B5 /* TempBasalExtraCommand.swift in Sources */, C1BB129E21CB5654009A29B5 /* ConfigureAlertsCommand.swift in Sources */, C1BB12AD21CB5654009A29B5 /* SetInsulinScheduleCommand.swift in Sources */, + C15500272308FA1000345FCC /* BeepType.swift in Sources */, C1BB129B21CB5654009A29B5 /* BasalScheduleExtraCommand.swift in Sources */, C1BB129621CB564D009A29B5 /* Packet.swift in Sources */, C1BB12BD21CB5796009A29B5 /* BasalDeliveryTable.swift in Sources */, @@ -3717,6 +3720,7 @@ C1BB12BA21CB5758009A29B5 /* AlertSlot.swift in Sources */, C1BB12A121CB5654009A29B5 /* GetStatusCommand.swift in Sources */, C1BB12A621CB5654009A29B5 /* PodInfoDataLog.swift in Sources */, + C15500282308FA2F00345FCC /* BeepConfigCommand.swift in Sources */, C1BB129521CB564D009A29B5 /* CRC16.swift in Sources */, C1BB129D21CB5654009A29B5 /* CancelDeliveryCommand.swift in Sources */, C1BB12AC21CB5654009A29B5 /* PodInfoTester.swift in Sources */, From 64513224c34b9ce796c2a88f1b94e1c2fe107a59 Mon Sep 17 00:00:00 2001 From: katie disimone Date: Sun, 18 Aug 2019 17:08:40 -0700 Subject: [PATCH 30/71] override uploading (#531) --- .../DeviceStatus/DeviceStatus.swift | 8 ++- .../DeviceStatus/OverrideStatus.swift | 57 +++++++++++++++++++ RileyLink.xcodeproj/project.pbxproj | 4 ++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100755 NightscoutUploadKit/DeviceStatus/OverrideStatus.swift diff --git a/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift b/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift index f2a011d10..755127e7b 100644 --- a/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift +++ b/NightscoutUploadKit/DeviceStatus/DeviceStatus.swift @@ -15,14 +15,16 @@ public struct DeviceStatus { let uploaderStatus: UploaderStatus? let loopStatus: LoopStatus? let radioAdapter: RadioAdapter? + let overrideStatus: OverrideStatus? - public init(device: String, timestamp: Date, pumpStatus: PumpStatus? = nil, uploaderStatus: UploaderStatus? = nil, loopStatus: LoopStatus? = nil, radioAdapter: RadioAdapter? = nil) { + public init(device: String, timestamp: Date, pumpStatus: PumpStatus? = nil, uploaderStatus: UploaderStatus? = nil, loopStatus: LoopStatus? = nil, radioAdapter: RadioAdapter? = nil, overrideStatus: OverrideStatus? = nil) { self.device = device self.timestamp = timestamp self.pumpStatus = pumpStatus self.uploaderStatus = uploaderStatus self.loopStatus = loopStatus self.radioAdapter = radioAdapter + self.overrideStatus = overrideStatus } public var dictionaryRepresentation: [String: Any] { @@ -46,6 +48,10 @@ public struct DeviceStatus { if let radioAdapter = radioAdapter { rval["radioAdapter"] = radioAdapter.dictionaryRepresentation } + + if let override = overrideStatus { + rval["override"] = override.dictionaryRepresentation + } return rval } diff --git a/NightscoutUploadKit/DeviceStatus/OverrideStatus.swift b/NightscoutUploadKit/DeviceStatus/OverrideStatus.swift new file mode 100755 index 000000000..e8bbdb424 --- /dev/null +++ b/NightscoutUploadKit/DeviceStatus/OverrideStatus.swift @@ -0,0 +1,57 @@ +// +// OverrideStatus.swift +// NightscoutUploadKit +// +// Created by Kenneth Stack on 5/6/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import HealthKit + +public struct OverrideStatus { + let name: String? + let timestamp: Date + let active: Bool + let currentCorrectionRange: CorrectionRange? + let duration: TimeInterval? + let multiplier: Double? + + + public init(name: String? = nil, timestamp: Date, active: Bool, currentCorrectionRange: CorrectionRange? = nil, duration: TimeInterval? = nil, multiplier: Double? = nil) { + self.name = name + self.timestamp = timestamp + self.active = active + self.currentCorrectionRange = currentCorrectionRange + self.duration = duration + self.multiplier = multiplier + } + + public var dictionaryRepresentation: [String: Any] { + var rval = [String: Any]() + + rval["timestamp"] = TimeFormat.timestampStrFromDate(timestamp) + rval["active"] = active + + if let name = name { + rval["name"] = name + } + + if let currentCorrectionRange = currentCorrectionRange { + rval["currentCorrectionRange"] = currentCorrectionRange.dictionaryRepresentation + } + + if let duration = duration { + rval["duration"] = duration + } + + if let multiplier = multiplier { + rval["multiplier"] = multiplier + } + + return rval + } + + + +} diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 859581331..6243719bd 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -255,6 +255,7 @@ 7D70768B1FE09310004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70768D1FE09310004AC8EA /* Localizable.strings */; }; 7D7076901FE09311004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076921FE09311004AC8EA /* InfoPlist.strings */; }; 7D7076951FE09311004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076971FE09311004AC8EA /* Localizable.strings */; }; + 7DEFE05322ED1C2400FCD378 /* OverrideStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */; }; C104A9C1217E603E006E3C3E /* OmnipodReservoirView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */; }; C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */; }; C104A9C3217E611F006E3C3E /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */; }; @@ -1185,6 +1186,7 @@ 7D70768C1FE09310004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 7D7076911FE09311004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D7076961FE09311004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideStatus.swift; sourceTree = ""; }; C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodReservoirView.swift; sourceTree = ""; }; C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OmnipodReservoirView.xib; sourceTree = ""; }; C104A9C4217E645C006E3C3E /* HUDAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = HUDAssets.xcassets; sourceTree = ""; }; @@ -2304,6 +2306,7 @@ isa = PBXGroup; children = ( C1AF21E11D4838C90088C41D /* DeviceStatus.swift */, + 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */, C1AF21E51D48667F0088C41D /* UploaderStatus.swift */, C1AF21E71D4866960088C41D /* PumpStatus.swift */, C1A492621D4A5A19008964FF /* IOBStatus.swift */, @@ -3661,6 +3664,7 @@ 43F348061D596270009933DC /* HKUnit.swift in Sources */, C133CF931D5943780034B82D /* PredictedBG.swift in Sources */, C1D00E9D1E8986A400B733B7 /* PumpSuspendTreatment.swift in Sources */, + 7DEFE05322ED1C2400FCD378 /* OverrideStatus.swift in Sources */, C1AF21E41D4865320088C41D /* LoopStatus.swift in Sources */, 43EBE4541EAD23EC0073A0B5 /* TimeInterval.swift in Sources */, C13D155A1DAACE8400ADC044 /* Either.swift in Sources */, From 4f642912e47d72710e710273e22393265275631c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 19 Aug 2019 15:03:49 -0500 Subject: [PATCH 31/71] Add MKRingProgressView for RL app build, fix copy-frameworks.sh --- RileyLink.xcodeproj/project.pbxproj | 4 ++++ Scripts/copy-frameworks.sh | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 6243719bd..24899f04b 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -303,6 +303,7 @@ C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */; }; C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */; }; C13D155A1DAACE8400ADC044 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13D15591DAACE8400ADC044 /* Either.swift */; }; + C13F0437230B1DE6001413FF /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C13F0436230B1DE6001413FF /* MKRingProgressView.framework */; }; C13FD2F4215E7338005FC495 /* FaultEventCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13FD2F3215E7338005FC495 /* FaultEventCode.swift */; }; C13FD2F6215E743C005FC495 /* PodProgressStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13FD2F5215E743C005FC495 /* PodProgressStatus.swift */; }; C14303161C97C98000A40450 /* PumpAckMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14303151C97C98000A40450 /* PumpAckMessageBody.swift */; }; @@ -1238,6 +1239,7 @@ C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInsulinMeasurements.swift; sourceTree = ""; }; C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfinalizedDose.swift; sourceTree = ""; }; C13D15591DAACE8400ADC044 /* Either.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; + C13F0436230B1DE6001413FF /* MKRingProgressView.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C13FD2F3215E7338005FC495 /* FaultEventCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaultEventCode.swift; sourceTree = ""; }; C13FD2F5215E743C005FC495 /* PodProgressStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodProgressStatus.swift; sourceTree = ""; }; C14303151C97C98000A40450 /* PumpAckMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpAckMessageBody.swift; sourceTree = ""; }; @@ -1588,6 +1590,7 @@ 43C246A91D8A31540031F8D1 /* Crypto.framework in Frameworks */, C1B383201CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */, C12EA23D198B436800309FA4 /* CoreGraphics.framework in Frameworks */, + C13F0437230B1DE6001413FF /* MKRingProgressView.framework in Frameworks */, 43D5E7951FAF7BFB004ACDB7 /* RileyLinkKitUI.framework in Frameworks */, C12EA23F198B436800309FA4 /* UIKit.framework in Frameworks */, 43722FC31CB9F7640038B7F2 /* RileyLinkKit.framework in Frameworks */, @@ -2101,6 +2104,7 @@ C12EA239198B436800309FA4 /* Frameworks */ = { isa = PBXGroup; children = ( + C13F0436230B1DE6001413FF /* MKRingProgressView.framework */, 43FB610B20DDF55F002B996B /* LoopKit.framework */, 43FB610A20DDF55E002B996B /* LoopKitUI.framework */, 43CA93241CB8BB33000026B5 /* CoreBluetooth.framework */, diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh index a02074bf6..a8c503f8e 100755 --- a/Scripts/copy-frameworks.sh +++ b/Scripts/copy-frameworks.sh @@ -5,6 +5,8 @@ # # Copyright © 2019 LoopKit Authors. All rights reserved. +date + CARTHAGE_BUILD_DIR="${SRCROOT}/Carthage/Build" if [ -n "${IPHONEOS_DEPLOYMENT_TARGET}" ]; then CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/iOS" @@ -27,7 +29,7 @@ for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do fi echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_FILE}\"" export ${SCRIPT_INPUT_FILE}="${CARTHAGE_BUILD_FILE}" - elif [ -e "${SCRIPT_INPUT_FILE}" ]; then + elif [ -e "${!SCRIPT_INPUT_FILE}" ]; then echo "Using original path: \"${!SCRIPT_INPUT_FILE}\"" else echo "ERROR: Input file not found at \"${!SCRIPT_INPUT_FILE}\"" From 557f74a16bf31189a275e2610e0b8e922c0cdf92 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 20 Aug 2019 23:41:48 -0500 Subject: [PATCH 32/71] Convert fixed offset timezone identifiers to moment.js supported versions (#535) --- NightscoutUploadKit/NightscoutProfile.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NightscoutUploadKit/NightscoutProfile.swift b/NightscoutUploadKit/NightscoutProfile.swift index cd8266127..5ad6debe7 100644 --- a/NightscoutUploadKit/NightscoutProfile.swift +++ b/NightscoutUploadKit/NightscoutProfile.swift @@ -5,6 +5,12 @@ import Foundation +fileprivate let timeZoneMap = (-18...18).reduce(into: [String: String]()) { (dict, hour) in + let from = TimeZone(secondsFromGMT: 3600 * hour)!.identifier + let to = String(format: "ETC/GMT%+d", hour * -1) + dict[from] = to +} + public class ProfileSet { public struct ScheduleItem { @@ -53,7 +59,7 @@ public class ProfileSet { "dia": dia.hours, "carbs_hr": "0", "delay": "0", - "timezone": timezone.identifier, + "timezone": timeZoneMap[timezone.identifier] ?? timezone.identifier, "target_low": targetLow.map { $0.dictionaryRepresentation }, "target_high": targetHigh.map { $0.dictionaryRepresentation }, "sens": sensitivity.map { $0.dictionaryRepresentation }, From ab53b05ad5e6b4703ca3c1444826ffda87750c33 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Aug 2019 23:10:04 -0500 Subject: [PATCH 33/71] Pluginize (#536) * Add plugin for OmniKit * Add MinimedKitPlugin * Add MinimedKitPlugin to shared scheme * During plugin builds, it's ok for frameworks to be in Carthage/Builds and Build Products * Update to MKRingProgressView marked application extension safe * OmniKitUI should link to MKRingProgressView framework * Update tests to run independently of RileyLink test app --- .travis.yml | 2 +- Cartfile | 2 +- Cartfile.resolved | 4 +- MinimedKitPlugin/Info.plist | 26 + MinimedKitPlugin/MinimedKitPlugin.h | 19 + MinimedKitPlugin/MinimedKitPlugin.swift | 30 + OmniKitPlugin/Info.plist | 26 + OmniKitPlugin/OmniKitPlugin.h | 19 + OmniKitPlugin/OmniKitPlugin.swift | 30 + RileyLink.xcodeproj/project.pbxproj | 647 ++++++++++++++++-- .../xcshareddata/xcschemes/Shared.xcscheme | 68 ++ Scripts/copy-frameworks.sh | 25 +- 12 files changed, 818 insertions(+), 80 deletions(-) create mode 100644 MinimedKitPlugin/Info.plist create mode 100644 MinimedKitPlugin/MinimedKitPlugin.h create mode 100644 MinimedKitPlugin/MinimedKitPlugin.swift create mode 100644 OmniKitPlugin/Info.plist create mode 100644 OmniKitPlugin/OmniKitPlugin.h create mode 100644 OmniKitPlugin/OmniKitPlugin.swift diff --git a/.travis.yml b/.travis.yml index 8e221d716..8090dcef2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ before_script: - carthage bootstrap script: - - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme RileyLink build -destination 'name=iPhone SE' test | xcpretty + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone SE' test | xcpretty diff --git a/Cartfile b/Cartfile index e2488b425..df64400f1 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "LoopKit/LoopKit" "dev" -github "maxkonovalov/MKRingProgressView" ~> 2.2 +github "LoopKit/MKRingProgressView" "appex-safe" diff --git a/Cartfile.resolved b/Cartfile.resolved index b643e87df..305f969e7 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "eaed65166138c5ea1b8d8dddc7b14f1032f3da06" -github "maxkonovalov/MKRingProgressView" "2.2.2" +github "LoopKit/LoopKit" "6f3c1e174a6d5bc952c62f4540567c13da54c2ce" +github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" diff --git a/MinimedKitPlugin/Info.plist b/MinimedKitPlugin/Info.plist new file mode 100644 index 000000000..d18af78ea --- /dev/null +++ b/MinimedKitPlugin/Info.plist @@ -0,0 +1,26 @@ + + + + + com.loopkit.Loop.PumpManagerDisplayName + Minimed 500/700 Series + com.loopkit.Loop.PumpManagerIdentifier + Minimed500 + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/MinimedKitPlugin/MinimedKitPlugin.h b/MinimedKitPlugin/MinimedKitPlugin.h new file mode 100644 index 000000000..6e5b334c5 --- /dev/null +++ b/MinimedKitPlugin/MinimedKitPlugin.h @@ -0,0 +1,19 @@ +// +// MinimedKitPlugin.h +// MinimedKitPlugin +// +// Created by Pete Schwamb on 8/24/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +#import + +//! Project version number for MinimedKitPlugin. +FOUNDATION_EXPORT double MinimedKitPluginVersionNumber; + +//! Project version string for MinimedKitPlugin. +FOUNDATION_EXPORT const unsigned char MinimedKitPluginVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/MinimedKitPlugin/MinimedKitPlugin.swift b/MinimedKitPlugin/MinimedKitPlugin.swift new file mode 100644 index 000000000..ccbbbe850 --- /dev/null +++ b/MinimedKitPlugin/MinimedKitPlugin.swift @@ -0,0 +1,30 @@ +// +// MinimedKitPlugin.swift +// MinimedKitPlugin +// +// Created by Pete Schwamb on 8/24/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKitUI +import MinimedKit +import MinimedKitUI +import os.log + +class MinimedKitPlugin: NSObject, LoopUIPlugin { + private let log = OSLog(category: "MinimedKitPlugin") + + public var pumpManagerType: PumpManagerUI.Type? { + return MinimedPumpManager.self + } + + public var cgmManagerType: CGMManagerUI.Type? { + return nil + } + + override init() { + super.init() + log.default("MinimedKitPlugin Instantiated") + } +} diff --git a/OmniKitPlugin/Info.plist b/OmniKitPlugin/Info.plist new file mode 100644 index 000000000..55e5988a7 --- /dev/null +++ b/OmniKitPlugin/Info.plist @@ -0,0 +1,26 @@ + + + + + com.loopkit.Loop.PumpManagerIdentifier + Omnipod + com.loopkit.Loop.PumpManagerDisplayName + Omnipod + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/OmniKitPlugin/OmniKitPlugin.h b/OmniKitPlugin/OmniKitPlugin.h new file mode 100644 index 000000000..3f908879c --- /dev/null +++ b/OmniKitPlugin/OmniKitPlugin.h @@ -0,0 +1,19 @@ +// +// OmniKitPlugin.h +// OmniKitPlugin +// +// Created by Pete Schwamb on 8/24/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +#import + +//! Project version number for OmniKitPlugin. +FOUNDATION_EXPORT double OmniKitPluginVersionNumber; + +//! Project version string for OmniKitPlugin. +FOUNDATION_EXPORT const unsigned char OmniKitPluginVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/OmniKitPlugin/OmniKitPlugin.swift b/OmniKitPlugin/OmniKitPlugin.swift new file mode 100644 index 000000000..a950fbe84 --- /dev/null +++ b/OmniKitPlugin/OmniKitPlugin.swift @@ -0,0 +1,30 @@ +// +// OmniKitPlugin.swift +// OmniKitPlugin +// +// Created by Pete Schwamb on 8/24/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKitUI +import OmniKit +import OmniKitUI +import os.log + +class OmniKitPlugin: NSObject, LoopUIPlugin { + private let log = OSLog(category: "OmniKitPlugin") + + public var pumpManagerType: PumpManagerUI.Type? { + return OmnipodPumpManager.self + } + + public var cgmManagerType: CGMManagerUI.Type? { + return nil + } + + override init() { + super.init() + log.default("OmniKitPlugin Instantiated") + } +} diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 24899f04b..c31da006d 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -300,6 +300,21 @@ C12EA245198B436800309FA4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C12EA243198B436800309FA4 /* InfoPlist.strings */; }; C1330F431DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */; }; C133CF931D5943780034B82D /* PredictedBG.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133CF921D5943780034B82D /* PredictedBG.swift */; }; + C136AA3A23116E32008A320D /* OmniKitPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = C136AA2C23116E32008A320D /* OmniKitPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C136AA4223116E7B008A320D /* OmniKitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C136AA4123116E7B008A320D /* OmniKitPlugin.swift */; }; + C136AA442311704A008A320D /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; }; + C136AA51231176D4008A320D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; + C136AA52231176D8008A320D /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610A20DDF55E002B996B /* LoopKitUI.framework */; }; + C136AA53231176E8008A320D /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43722FAE1CB9F7630038B7F2 /* RileyLinkKit.framework */; }; + C136AA54231176EC008A320D /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 431CE76F1F98564100255374 /* RileyLinkBLEKit.framework */; }; + C136AA55231176F0008A320D /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D5E78E1FAF7BFB004ACDB7 /* RileyLinkKitUI.framework */; }; + C136AA56231176F7008A320D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; + C136AA57231176FA008A320D /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */; }; + C136AA62231187B0008A320D /* MinimedKitPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = C136AA60231187B0008A320D /* MinimedKitPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C136AA6723118817008A320D /* MinimedKitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C136AA6623118817008A320D /* MinimedKitPlugin.swift */; }; + C136AA732311899D008A320D /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; }; + C136AA752311CFC3008A320D /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C13F0436230B1DE6001413FF /* MKRingProgressView.framework */; }; + C136AA76231234E1008A320D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */; }; C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */; }; C13D155A1DAACE8400ADC044 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13D15591DAACE8400ADC044 /* Either.swift */; }; @@ -751,47 +766,89 @@ remoteGlobalIDString = C10D9BC01C8269D500378342; remoteInfo = MinimedKit; }; - C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C136AA4523117228008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C1B3830A1CD0665D00CE7782; - remoteInfo = NightscoutUploadKit; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; }; - C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C136AA4723117228008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C1B3830A1CD0665D00CE7782; - remoteInfo = NightscoutUploadKit; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; + }; + C136AA4923117228008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; + }; + C136AA4B23117228008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1FFAF77213323CC00C50C1D; + remoteInfo = OmniKit; + }; + C136AA4D23117228008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1FFAFD8213323F900C50C1D; + remoteInfo = OmniKitUI; + }; + C136AA68231188F1008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; + }; + C136AA6A231188F1008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; }; - C1D289AA22F7CC26003FFBD9 /* PBXContainerItemProxy */ = { + C136AA6C231188F1008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; }; - C1D289AC22F7CC2A003FFBD9 /* PBXContainerItemProxy */ = { + C136AA6E231188F1008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = C10D9BC01C8269D500378342; + remoteInfo = MinimedKit; }; - C1D289AE22F7CC2D003FFBD9 /* PBXContainerItemProxy */ = { + C136AA70231188F1008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = 4352A72420DEC9B700CAC200; + remoteInfo = MinimedKitUI; }; - C1D289B222F7CC32003FFBD9 /* PBXContainerItemProxy */ = { + C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = C1B3830A1CD0665D00CE7782; + remoteInfo = NightscoutUploadKit; + }; + C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C1B3830A1CD0665D00CE7782; + remoteInfo = NightscoutUploadKit; }; C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -1236,6 +1293,14 @@ C12EA25F198B436900309FA4 /* RileyLinkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RileyLinkTests.m; sourceTree = ""; }; C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeSensorAlarmSilenceConfigPumpEvent.swift; sourceTree = ""; }; C133CF921D5943780034B82D /* PredictedBG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictedBG.swift; sourceTree = ""; }; + C136AA2A23116E32008A320D /* OmniKitPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OmniKitPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; }; + C136AA2C23116E32008A320D /* OmniKitPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OmniKitPlugin.h; sourceTree = ""; }; + C136AA2D23116E32008A320D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C136AA4123116E7B008A320D /* OmniKitPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniKitPlugin.swift; sourceTree = ""; }; + C136AA5E231187B0008A320D /* MinimedKitPlugin.loopplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MinimedKitPlugin.loopplugin; sourceTree = BUILT_PRODUCTS_DIR; }; + C136AA60231187B0008A320D /* MinimedKitPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MinimedKitPlugin.h; sourceTree = ""; }; + C136AA61231187B0008A320D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C136AA6623118817008A320D /* MinimedKitPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MinimedKitPlugin.swift; sourceTree = ""; }; C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PodInsulinMeasurements.swift; sourceTree = ""; }; C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfinalizedDose.swift; sourceTree = ""; }; C13D15591DAACE8400ADC044 /* Either.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = ""; }; @@ -1523,6 +1588,7 @@ buildActionMask = 2147483647; files = ( 431CE7781F98564200255374 /* RileyLinkBLEKit.framework in Frameworks */, + C136AA76231234E1008A320D /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1604,6 +1670,27 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C136AA2723116E32008A320D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C136AA57231176FA008A320D /* OmniKitUI.framework in Frameworks */, + C136AA53231176E8008A320D /* RileyLinkKit.framework in Frameworks */, + C136AA55231176F0008A320D /* RileyLinkKitUI.framework in Frameworks */, + C136AA51231176D4008A320D /* LoopKit.framework in Frameworks */, + C136AA52231176D8008A320D /* LoopKitUI.framework in Frameworks */, + C136AA56231176F7008A320D /* OmniKit.framework in Frameworks */, + C136AA54231176EC008A320D /* RileyLinkBLEKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C136AA5B231187B0008A320D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C1B383071CD0665D00CE7782 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1656,6 +1743,7 @@ C1FFB02421343EC200C50C1D /* OmniKit.framework in Frameworks */, C1FFB02521343F0300C50C1D /* LoopKit.framework in Frameworks */, C1FFB02621343F0600C50C1D /* LoopKitUI.framework in Frameworks */, + C136AA752311CFC3008A320D /* MKRingProgressView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2073,6 +2161,8 @@ C1FFAF86213323CC00C50C1D /* OmniKitTests */, C1FFAFDA213323F900C50C1D /* OmniKitUI */, C1BB128621CB5603009A29B5 /* OmniKitPacketParser */, + C136AA2B23116E32008A320D /* OmniKitPlugin */, + C136AA5F231187B0008A320D /* MinimedKitPlugin */, C12EA239198B436800309FA4 /* Frameworks */, C12EA238198B436800309FA4 /* Products */, 437DE504229C898D003B1074 /* Scripts */, @@ -2097,6 +2187,8 @@ C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */, C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */, C1BB128521CB5603009A29B5 /* OmniKitPacketParser */, + C136AA2A23116E32008A320D /* OmniKitPlugin.loopplugin */, + C136AA5E231187B0008A320D /* MinimedKitPlugin.loopplugin */, ); name = Products; sourceTree = ""; @@ -2168,6 +2260,26 @@ name = "Supporting Files"; sourceTree = ""; }; + C136AA2B23116E32008A320D /* OmniKitPlugin */ = { + isa = PBXGroup; + children = ( + C136AA2C23116E32008A320D /* OmniKitPlugin.h */, + C136AA2D23116E32008A320D /* Info.plist */, + C136AA4123116E7B008A320D /* OmniKitPlugin.swift */, + ); + path = OmniKitPlugin; + sourceTree = ""; + }; + C136AA5F231187B0008A320D /* MinimedKitPlugin */ = { + isa = PBXGroup; + children = ( + C136AA60231187B0008A320D /* MinimedKitPlugin.h */, + C136AA61231187B0008A320D /* Info.plist */, + C136AA6623118817008A320D /* MinimedKitPlugin.swift */, + ); + path = MinimedKitPlugin; + sourceTree = ""; + }; C13BD64021403362006D7F19 /* MessageTransport */ = { isa = PBXGroup; children = ( @@ -2625,6 +2737,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C136AA2523116E32008A320D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C136AA3A23116E32008A320D /* OmniKitPlugin.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C136AA59231187B0008A320D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C136AA62231187B0008A320D /* MinimedKitPlugin.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C1B383081CD0665D00CE7782 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -2677,12 +2805,12 @@ 431CE7731F98564200255374 /* Sources */, 431CE7741F98564200255374 /* Frameworks */, 431CE7751F98564200255374 /* Resources */, + C136AA7723123A5D008A320D /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( 431CE77A1F98564200255374 /* PBXTargetDependency */, - C1D289AD22F7CC2A003FFBD9 /* PBXTargetDependency */, ); name = RileyLinkBLEKitTests; productName = RileyLinkBLEKitTests; @@ -2792,12 +2920,12 @@ C10D9BC61C8269D500378342 /* Sources */, C10D9BC71C8269D500378342 /* Frameworks */, C10D9BC81C8269D500378342 /* Resources */, + C136AA7823123B8C008A320D /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( C10D9BCD1C8269D500378342 /* PBXTargetDependency */, - C1D289B322F7CC32003FFBD9 /* PBXTargetDependency */, ); name = MinimedKitTests; productName = MinimedKitTests; @@ -2832,6 +2960,54 @@ productReference = C12EA237198B436800309FA4 /* RileyLink.app */; productType = "com.apple.product-type.application"; }; + C136AA2923116E32008A320D /* OmniKitPlugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = C136AA3F23116E32008A320D /* Build configuration list for PBXNativeTarget "OmniKitPlugin" */; + buildPhases = ( + C136AA2523116E32008A320D /* Headers */, + C136AA2623116E32008A320D /* Sources */, + C136AA2723116E32008A320D /* Frameworks */, + C136AA2823116E32008A320D /* Resources */, + C136AA4323116F4D008A320D /* Copy Frameworks with Carthage */, + ); + buildRules = ( + ); + dependencies = ( + C136AA4623117228008A320D /* PBXTargetDependency */, + C136AA4823117228008A320D /* PBXTargetDependency */, + C136AA4A23117228008A320D /* PBXTargetDependency */, + C136AA4C23117228008A320D /* PBXTargetDependency */, + C136AA4E23117228008A320D /* PBXTargetDependency */, + ); + name = OmniKitPlugin; + productName = OmniKitPlugin; + productReference = C136AA2A23116E32008A320D /* OmniKitPlugin.loopplugin */; + productType = "com.apple.product-type.framework"; + }; + C136AA5D231187B0008A320D /* MinimedKitPlugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = C136AA63231187B0008A320D /* Build configuration list for PBXNativeTarget "MinimedKitPlugin" */; + buildPhases = ( + C136AA59231187B0008A320D /* Headers */, + C136AA5A231187B0008A320D /* Sources */, + C136AA5B231187B0008A320D /* Frameworks */, + C136AA5C231187B0008A320D /* Resources */, + C136AA72231188FC008A320D /* Copy Frameworks with Carthage */, + ); + buildRules = ( + ); + dependencies = ( + C136AA69231188F1008A320D /* PBXTargetDependency */, + C136AA6B231188F1008A320D /* PBXTargetDependency */, + C136AA6D231188F1008A320D /* PBXTargetDependency */, + C136AA6F231188F1008A320D /* PBXTargetDependency */, + C136AA71231188F1008A320D /* PBXTargetDependency */, + ); + name = MinimedKitPlugin; + productName = MinimedKitPlugin; + productReference = C136AA5E231187B0008A320D /* MinimedKitPlugin.loopplugin */; + productType = "com.apple.product-type.framework"; + }; C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */ = { isa = PBXNativeTarget; buildConfigurationList = C1B383221CD0665D00CE7782 /* Build configuration list for PBXNativeTarget "NightscoutUploadKit" */; @@ -2866,7 +3042,6 @@ dependencies = ( A9B839CF22809DCF004E745E /* PBXTargetDependency */, C1B383171CD0665D00CE7782 /* PBXTargetDependency */, - C1D289AF22F7CC2D003FFBD9 /* PBXTargetDependency */, ); name = NightscoutUploadKitTests; productName = NightscoutUploadKitTests; @@ -2916,12 +3091,12 @@ C1FFAF7C213323CC00C50C1D /* Sources */, C1FFAF7D213323CC00C50C1D /* Frameworks */, C1FFAF7E213323CC00C50C1D /* Resources */, + C14A538023123CF500C86755 /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( C1FFAF83213323CC00C50C1D /* PBXTargetDependency */, - C1D289AB22F7CC26003FFBD9 /* PBXTargetDependency */, ); name = OmniKitTests; productName = OmniKitTests; @@ -2954,7 +3129,7 @@ C12EA22F198B436800309FA4 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0940; + LastSwiftUpdateCheck = 1030; LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Pete Schwamb"; TargetAttributes = { @@ -2966,8 +3141,7 @@ 431CE7761F98564200255374 = { CreatedOnToolsVersion = 9.0; LastSwiftMigration = 1000; - ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; + ProvisioningStyle = Manual; }; 4352A72420DEC9B700CAC200 = { CreatedOnToolsVersion = 9.4.1; @@ -2995,12 +3169,22 @@ C10D9BC91C8269D500378342 = { CreatedOnToolsVersion = 7.2.1; LastSwiftMigration = 1000; - TestTargetID = C12EA236198B436800309FA4; + ProvisioningStyle = Manual; }; C12EA236198B436800309FA4 = { LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; + C136AA2923116E32008A320D = { + CreatedOnToolsVersion = 10.3; + LastSwiftMigration = 1030; + ProvisioningStyle = Automatic; + }; + C136AA5D231187B0008A320D = { + CreatedOnToolsVersion = 10.3; + LastSwiftMigration = 1030; + ProvisioningStyle = Automatic; + }; C1B3830A1CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1020; @@ -3008,7 +3192,7 @@ C1B383131CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1000; - TestTargetID = C12EA236198B436800309FA4; + ProvisioningStyle = Manual; }; C1BB128421CB5603009A29B5 = { CreatedOnToolsVersion = 10.1; @@ -3023,7 +3207,6 @@ CreatedOnToolsVersion = 9.4.1; LastSwiftMigration = 1000; ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; }; C1FFAFD8213323F900C50C1D = { CreatedOnToolsVersion = 9.4.1; @@ -3055,19 +3238,21 @@ projectRoot = ""; targets = ( C12EA236198B436800309FA4 /* RileyLink */, - C10D9BC01C8269D500378342 /* MinimedKit */, - C10D9BC91C8269D500378342 /* MinimedKitTests */, - 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */, - C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */, - C1B383131CD0665D00CE7782 /* NightscoutUploadKitTests */, 43C246921D8918AE0031F8D1 /* Crypto */, 431CE76E1F98564100255374 /* RileyLinkBLEKit */, 431CE7761F98564200255374 /* RileyLinkBLEKitTests */, + 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */, 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */, + C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */, + C1B383131CD0665D00CE7782 /* NightscoutUploadKitTests */, + C10D9BC01C8269D500378342 /* MinimedKit */, 4352A72420DEC9B700CAC200 /* MinimedKitUI */, + C10D9BC91C8269D500378342 /* MinimedKitTests */, + C136AA5D231187B0008A320D /* MinimedKitPlugin */, C1FFAF77213323CC00C50C1D /* OmniKit */, C1FFAF7F213323CC00C50C1D /* OmniKitTests */, C1FFAFD8213323F900C50C1D /* OmniKitUI */, + C136AA2923116E32008A320D /* OmniKitPlugin */, C1BB128421CB5603009A29B5 /* OmniKitPacketParser */, ); }; @@ -3157,6 +3342,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C136AA2823116E32008A320D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C136AA5C231187B0008A320D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C1B383091CD0665D00CE7782 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -3239,7 +3438,111 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; + }; + C136AA4323116F4D008A320D /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/OmniKit.framework", + "$(BUILT_PRODUCTS_DIR)/OmniKitUI.framework", + "$(BUILT_PRODUCTS_DIR)/RileyLinkKit.framework", + "$(BUILT_PRODUCTS_DIR)/RileyLinkBLEKit.framework", + "$(BUILT_PRODUCTS_DIR)/RileyLinkKitUI.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + }; + C136AA72231188FC008A320D /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/RileyLinkKit.framework", + "$(BUILT_PRODUCTS_DIR)/RileyLinkKitUI.framework", + "$(BUILT_PRODUCTS_DIR)/RileyLinkBLEKit.framework", + "$(BUILT_PRODUCTS_DIR)/MinimedKit.framework", + "$(BUILT_PRODUCTS_DIR)/MinimedKitUI.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + }; + C136AA7723123A5D008A320D /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + }; + C136AA7823123B8C008A320D /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/MKRingProgressView.framework", + "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + }; + C14A538023123CF500C86755 /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -3643,6 +3946,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C136AA2623116E32008A320D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C136AA442311704A008A320D /* OSLog.swift in Sources */, + C136AA4223116E7B008A320D /* OmniKitPlugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C136AA5A231187B0008A320D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C136AA732311899D008A320D /* OSLog.swift in Sources */, + C136AA6723118817008A320D /* MinimedKitPlugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C1B383061CD0665D00CE7782 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -3962,35 +4283,65 @@ target = C10D9BC01C8269D500378342 /* MinimedKit */; targetProxy = C10D9BD41C8269D500378342 /* PBXContainerItemProxy */; }; - C1B383171CD0665D00CE7782 /* PBXTargetDependency */ = { + C136AA4623117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; - targetProxy = C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = C136AA4523117228008A320D /* PBXContainerItemProxy */; }; - C1B3831F1CD0665D00CE7782 /* PBXTargetDependency */ = { + C136AA4823117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; - targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; + target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; + targetProxy = C136AA4723117228008A320D /* PBXContainerItemProxy */; }; - C1D289AB22F7CC26003FFBD9 /* PBXTargetDependency */ = { + C136AA4A23117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1D289AA22F7CC26003FFBD9 /* PBXContainerItemProxy */; + target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; + targetProxy = C136AA4923117228008A320D /* PBXContainerItemProxy */; }; - C1D289AD22F7CC2A003FFBD9 /* PBXTargetDependency */ = { + C136AA4C23117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1D289AC22F7CC2A003FFBD9 /* PBXContainerItemProxy */; + target = C1FFAF77213323CC00C50C1D /* OmniKit */; + targetProxy = C136AA4B23117228008A320D /* PBXContainerItemProxy */; }; - C1D289AF22F7CC2D003FFBD9 /* PBXTargetDependency */ = { + C136AA4E23117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1D289AE22F7CC2D003FFBD9 /* PBXContainerItemProxy */; + target = C1FFAFD8213323F900C50C1D /* OmniKitUI */; + targetProxy = C136AA4D23117228008A320D /* PBXContainerItemProxy */; }; - C1D289B322F7CC32003FFBD9 /* PBXTargetDependency */ = { + C136AA69231188F1008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1D289B222F7CC32003FFBD9 /* PBXContainerItemProxy */; + target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; + targetProxy = C136AA68231188F1008A320D /* PBXContainerItemProxy */; + }; + C136AA6B231188F1008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = C136AA6A231188F1008A320D /* PBXContainerItemProxy */; + }; + C136AA6D231188F1008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; + targetProxy = C136AA6C231188F1008A320D /* PBXContainerItemProxy */; + }; + C136AA6F231188F1008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C10D9BC01C8269D500378342 /* MinimedKit */; + targetProxy = C136AA6E231188F1008A320D /* PBXContainerItemProxy */; + }; + C136AA71231188F1008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4352A72420DEC9B700CAC200 /* MinimedKitUI */; + targetProxy = C136AA70231188F1008A320D /* PBXContainerItemProxy */; + }; + C1B383171CD0665D00CE7782 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; + targetProxy = C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */; + }; + C1B3831F1CD0665D00CE7782 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; + targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; C1FFAF83213323CC00C50C1D /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -4408,7 +4759,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -4417,11 +4768,11 @@ MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -4435,7 +4786,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -4445,9 +4796,9 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; @@ -4800,6 +5151,7 @@ CLANG_ENABLE_MODULES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4813,9 +5165,9 @@ MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -4825,6 +5177,7 @@ CLANG_ENABLE_MODULES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -4839,8 +5192,8 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; @@ -5023,6 +5376,158 @@ }; name = Release; }; + C136AA3B23116E32008A320D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = NO; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKitPlugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_DYLIB_INSTALL_NAME = ""; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitPlugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + STRIP_STYLE = "non-global"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSION_INFO_PREFIX = ""; + WRAPPER_EXTENSION = loopplugin; + }; + name = Debug; + }; + C136AA3C23116E32008A320D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = NO; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = OmniKitPlugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_DYLIB_INSTALL_NAME = ""; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.OmniKitPlugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + STRIP_STYLE = "non-global"; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSION_INFO_PREFIX = ""; + WRAPPER_EXTENSION = loopplugin; + }; + name = Release; + }; + C136AA64231187B0008A320D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = NO; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MinimedKitPlugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKitPlugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSION_INFO_PREFIX = ""; + WRAPPER_EXTENSION = loopplugin; + }; + name = Debug; + }; + C136AA65231187B0008A320D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = NO; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MinimedKitPlugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKitPlugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSION_INFO_PREFIX = ""; + WRAPPER_EXTENSION = loopplugin; + }; + name = Release; + }; C1B383231CD0665D00CE7782 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5087,6 +5592,7 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5100,9 +5606,9 @@ MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.NightscoutUploadKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -5112,6 +5618,7 @@ CLANG_ANALYZER_NONNULL = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -5126,8 +5633,8 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.NightscoutUploadKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; @@ -5280,7 +5787,6 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -5308,7 +5814,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; @@ -5482,6 +5987,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C136AA3F23116E32008A320D /* Build configuration list for PBXNativeTarget "OmniKitPlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C136AA3B23116E32008A320D /* Debug */, + C136AA3C23116E32008A320D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C136AA63231187B0008A320D /* Build configuration list for PBXNativeTarget "MinimedKitPlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C136AA64231187B0008A320D /* Debug */, + C136AA65231187B0008A320D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C1B383221CD0665D00CE7782 /* Build configuration list for PBXNativeTarget "NightscoutUploadKit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme index 7c7190fdb..4c669824b 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme @@ -132,6 +132,34 @@ ReferencedContainer = "container:RileyLink.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + Date: Sun, 25 Aug 2019 22:22:22 -0500 Subject: [PATCH 34/71] Update NightscoutUploadKit to not depend on MinimedKit, add profile timezone test (#537) --- .../DeviceStatus/BatteryStatus.swift | 15 --- NightscoutUploadKit/NightscoutEntry.swift | 9 -- .../NightscoutPumpEvents.swift | 100 -------------- NightscoutUploadKit/NightscoutUploader.swift | 127 ------------------ .../Treatments/NightscoutTreatment.swift | 8 +- .../NightscoutProfileTests.swift | 31 +++++ RileyLink.xcodeproj/project.pbxproj | 62 +++------ .../NightscoutPumpEventsTests.swift | 88 ------------ 8 files changed, 54 insertions(+), 386 deletions(-) delete mode 100644 NightscoutUploadKit/NightscoutPumpEvents.swift create mode 100644 NightscoutUploadKitTests/NightscoutProfileTests.swift delete mode 100644 RileyLinkTests/NightscoutPumpEventsTests.swift diff --git a/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift b/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift index 3c18d4e22..0a2363273 100644 --- a/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift +++ b/NightscoutUploadKit/DeviceStatus/BatteryStatus.swift @@ -7,7 +7,6 @@ // import Foundation -import MinimedKit public enum BatteryIndicator: String { case low = "low" @@ -15,20 +14,6 @@ public enum BatteryIndicator: String { } -extension BatteryIndicator { - public init?(batteryStatus: MinimedKit.BatteryStatus) { - switch batteryStatus { - case .low: - self = .low - case .normal: - self = .normal - default: - return nil - } - } -} - - public struct BatteryStatus { let percent: Int? let voltage: Double? diff --git a/NightscoutUploadKit/NightscoutEntry.swift b/NightscoutUploadKit/NightscoutEntry.swift index 2f81a2704..ac370bfb1 100644 --- a/NightscoutUploadKit/NightscoutEntry.swift +++ b/NightscoutUploadKit/NightscoutEntry.swift @@ -7,7 +7,6 @@ // import Foundation -import MinimedKit public class NightscoutEntry: DictionaryRepresentable { @@ -36,14 +35,6 @@ public class NightscoutEntry: DictionaryRepresentable { self.glucoseType = glucoseType } - convenience init?(event: TimestampedGlucoseEvent, device: String) { - if let glucoseSensorData = event.glucoseEvent as? SensorValueGlucoseEvent { - self.init(glucose: glucoseSensorData.sgv, timestamp: event.date, device: device, glucoseType: .Sensor) - } else { - return nil - } - } - public var dictionaryRepresentation: [String: Any] { var representation: [String: Any] = [ "device": device, diff --git a/NightscoutUploadKit/NightscoutPumpEvents.swift b/NightscoutUploadKit/NightscoutPumpEvents.swift deleted file mode 100644 index 4408bde4e..000000000 --- a/NightscoutUploadKit/NightscoutPumpEvents.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// NightscoutPumpEvents.swift -// RileyLink -// -// Created by Pete Schwamb on 3/9/16. -// Copyright © 2016 Pete Schwamb. All rights reserved. -// - -import Foundation -import MinimedKit - -public class NightscoutPumpEvents: NSObject { - - public class func translate(_ events: [TimestampedHistoryEvent], eventSource: String, includeCarbs: Bool = true) -> [NightscoutTreatment] { - var results = [NightscoutTreatment]() - var lastBolusWizard: BolusWizardEstimatePumpEvent? - var lastBolusWizardDate: Date? - var lastBasalRate: TempBasalPumpEvent? - var lastBasalRateDate: Date? - var lastBasalDuration: TempBasalDurationPumpEvent? - var lastBasalDurationDate: Date? - - for event in events { - switch event.pumpEvent { - case let bgReceived as BGReceivedPumpEvent: - let entry = BGCheckNightscoutTreatment( - timestamp: event.date, - enteredBy: eventSource, - glucose: bgReceived.amount, - glucoseType: .Meter, - units: .MGDL) // TODO: can we tell this from the pump? - results.append(entry) - case let bolusNormal as BolusNormalPumpEvent: - var carbs = 0 - var ratio = 0.0 - - if let wizard = lastBolusWizard, - let bwDate = lastBolusWizardDate, - event.date.timeIntervalSince(bwDate) <= 2, - includeCarbs - { - carbs = wizard.carbohydrates - ratio = wizard.carbRatio - } - let entry = BolusNightscoutTreatment( - timestamp: event.date, - enteredBy: eventSource, - bolusType: bolusNormal.duration > 0 ? .Square : .Normal, - amount: bolusNormal.amount, - programmed: bolusNormal.programmed, - unabsorbed: bolusNormal.unabsorbedInsulinTotal, - duration: bolusNormal.duration, - carbs: carbs, - ratio: ratio) - - results.append(entry) - case let bolusWizard as BolusWizardEstimatePumpEvent: - lastBolusWizard = bolusWizard - lastBolusWizardDate = event.date - case let tempBasal as TempBasalPumpEvent: - lastBasalRate = tempBasal - lastBasalRateDate = event.date - case let tempBasalDuration as TempBasalDurationPumpEvent: - lastBasalDuration = tempBasalDuration - lastBasalDurationDate = event.date - case is SuspendPumpEvent: - let entry = PumpSuspendTreatment(timestamp: event.date, enteredBy: eventSource) - results.append(entry) - case is ResumePumpEvent: - let entry = PumpResumeTreatment(timestamp: event.date, enteredBy: eventSource) - results.append(entry) - default: - break - } - - if let basalRate = lastBasalRate, let basalDuration = lastBasalDuration, let basalRateDate = lastBasalRateDate, let basalDurationDate = lastBasalDurationDate - , fabs(basalRateDate.timeIntervalSince(basalDurationDate)) <= 2 { - let entry = basalPairToNSTreatment(basalRate, basalDuration: basalDuration, eventSource: eventSource, timestamp: event.date) - results.append(entry) - lastBasalRate = nil - lastBasalRateDate = nil - lastBasalDuration = nil - lastBasalDurationDate = nil - } - } - return results - } - - private class func basalPairToNSTreatment(_ basalRate: TempBasalPumpEvent, basalDuration: TempBasalDurationPumpEvent, eventSource: String, timestamp: Date) -> TempBasalNightscoutTreatment { - let absolute: Double? = basalRate.rateType == .Absolute ? basalRate.rate : nil - return TempBasalNightscoutTreatment( - timestamp: timestamp, - enteredBy: eventSource, - temp: basalRate.rateType == .Absolute ? .Absolute : .Percentage, - rate: basalRate.rate, - absolute: absolute, - duration: basalDuration.duration) - } -} - diff --git a/NightscoutUploadKit/NightscoutUploader.swift b/NightscoutUploadKit/NightscoutUploader.swift index 63c8499cd..6c1d6cfbf 100644 --- a/NightscoutUploadKit/NightscoutUploader.swift +++ b/NightscoutUploadKit/NightscoutUploader.swift @@ -6,7 +6,6 @@ // Copyright © 2016 Pete Schwamb. All rights reserved. // -import MinimedKit import Crypto public enum UploadError: Error { @@ -48,46 +47,6 @@ public class NightscoutUploader { self.apiSecret = APISecret } - // MARK: - Processing data from pump - - /** - Enqueues pump history events for upload, with automatic retry management. - - - parameter events: An array of timestamped history events. Only types with known Nightscout mappings will be uploaded. - - parameter source: The device identifier to display in Nightscout - - parameter pumpModel: The pump model info associated with the events - */ - public func processPumpEvents(_ events: [TimestampedHistoryEvent], source: String, pumpModel: PumpModel) { - for treatment in NightscoutPumpEvents.translate(events, eventSource: source) { - treatmentsQueue.append(treatment) - } - self.flushAll() - } - - /** - Enqueues pump glucose events for upload, with automatic retry management. - - - parameter events: An array of timestamped glucose events. Only sensor glucose data will be uploaded. - - parameter source: The device identifier to display in Nightscout - */ - public func processGlucoseEvents(_ events: [TimestampedGlucoseEvent], source: String) -> Date? { - for event in events { - if let entry = NightscoutEntry(event: event, device: source) { - entries.append(entry) - } - } - - var timestamp: Date? = nil - - if let lastEntry = entries.last { - timestamp = lastEntry.timestamp - } - - self.flushAll() - - return timestamp - } - /// Attempts to upload nightscout treatment objects. /// This method will not retry if the network task failed. /// @@ -157,73 +116,6 @@ public class NightscoutUploader { flushAll() } - // Entries [ { sgv: 375, - // date: 1432421525000, - // dateString: '2015-05-23T22:52:05.000Z', - // trend: 1, - // direction: 'DoubleUp', - // device: 'share2', - // type: 'sgv' } ] - - public func uploadSGVFromMySentryPumpStatus(_ status: MySentryPumpStatusMessageBody, device: String) { - - var recordSGV = true - let glucose: Int = { - switch status.glucose { - case .active(glucose: let glucose): - return glucose - case .highBG: - return 401 - case .weakSignal: - return DexcomSensorError.badRF.rawValue - case .meterBGNow, .calError: - return DexcomSensorError.sensorNotCalibrated.rawValue - case .lost, .missing, .ended, .unknown, .off, .warmup: - recordSGV = false - return DexcomSensorError.sensorNotActive.rawValue - } - }() - - - // Create SGV entry from this mysentry packet - if (recordSGV) { - - guard let sensorDateComponents = status.glucoseDateComponents, let sensorDate = sensorDateComponents.date else { - return - } - - let previousSGV: Int? - let previousSGVNotActive: Bool? - - switch status.previousGlucose { - case .active(glucose: let previousGlucose): - previousSGV = previousGlucose - previousSGVNotActive = nil - default: - previousSGV = nil - previousSGVNotActive = true - } - let direction: String = { - switch status.glucoseTrend { - case .up: - return "SingleUp" - case .upUp: - return "DoubleUp" - case .down: - return "SingleDown" - case .downDown: - return "DoubleDown" - case .flat: - return "Flat" - } - }() - - let entry = NightscoutEntry(glucose: glucose, timestamp: sensorDate, device: device, glucoseType: .Sensor, previousSGV: previousSGV, previousSGVNotActive: previousSGVNotActive, direction: direction) - entries.append(entry) - } - flushAll() - } - public func uploadSGV(glucoseMGDL: Int, at date: Date, direction: String?, device: String) { let entry = NightscoutEntry( glucose: glucoseMGDL, @@ -237,25 +129,6 @@ public class NightscoutUploader { entries.append(entry) } - public func handleMeterMessage(_ msg: MeterMessage) { - - // TODO: Should only accept meter messages from specified meter ids. - // Need to add an interface to allow user to specify linked meters. - - if msg.ackFlag { - return - } - - let date = Date() - - // Skip duplicates - if lastMeterMessageRxTime == nil || lastMeterMessageRxTime!.timeIntervalSinceNow.minutes < -3 { - let entry = NightscoutEntry(glucose: msg.glucose, timestamp: date, device: "Contour Next Link", glucoseType: .Meter) - entries.append(entry) - lastMeterMessageRxTime = date - } - } - // MARK: - Profiles public func uploadProfile(profileSet: ProfileSet, completion: @escaping (Either<[String],Error>) -> Void) { diff --git a/NightscoutUploadKit/Treatments/NightscoutTreatment.swift b/NightscoutUploadKit/Treatments/NightscoutTreatment.swift index 9711d8c37..eda837053 100644 --- a/NightscoutUploadKit/Treatments/NightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/NightscoutTreatment.swift @@ -6,7 +6,13 @@ // Copyright © 2016 Pete Schwamb. All rights reserved. // -import MinimedKit +import Foundation + +public protocol DictionaryRepresentable { + var dictionaryRepresentation: [String: Any] { + get + } +} public class NightscoutTreatment: DictionaryRepresentable { diff --git a/NightscoutUploadKitTests/NightscoutProfileTests.swift b/NightscoutUploadKitTests/NightscoutProfileTests.swift new file mode 100644 index 000000000..1bf0f6634 --- /dev/null +++ b/NightscoutUploadKitTests/NightscoutProfileTests.swift @@ -0,0 +1,31 @@ +// +// NightscoutProfileTests.swift +// NightscoutUploadKitTests +// +// Created by Pete Schwamb on 8/25/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import XCTest +@testable import NightscoutUploadKit + +class NightscoutProfileTests: XCTestCase { + + func testFixedOffsetTimezoneIdentifierConversion() { + // This verifies that fixed offset timezones are encoded in a moment.js compatibile way + // I.e. GMT-0500 -> "ETC/GMT+5" + + let timeZone = TimeZone(secondsFromGMT: -5 * 60 * 60)! // GMT-0500 (fixed) + let isfSchedule = [ProfileSet.ScheduleItem(offset: .hours(0), value: 85)] + let carbRatioSchedule = [ProfileSet.ScheduleItem(offset: .hours(0), value: 12)] + let basalSchedule = [ProfileSet.ScheduleItem(offset: .hours(0), value: 1.2)] + let targetLowSchedule = [ProfileSet.ScheduleItem(offset: .hours(0), value: 100)] + let targetHighSchedule = [ProfileSet.ScheduleItem(offset: .hours(0), value: 110)] + let profile = ProfileSet.Profile(timezone: timeZone, dia: .hours(6), sensitivity: isfSchedule, carbratio: carbRatioSchedule, basal: basalSchedule, targetLow: targetLowSchedule, targetHigh: targetHighSchedule, units: "mg/dL") + + let json = profile.dictionaryRepresentation + + XCTAssertEqual("ETC/GMT+5", json["timezone"] as? String) + } + +} diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index c31da006d..4f0e01046 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -122,7 +122,6 @@ 43B0ADC91D1268B300AAD278 /* TimeFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B0ADC81D1268B300AAD278 /* TimeFormat.swift */; }; 43B0ADCB1D126B1100AAD278 /* SelectBasalProfilePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B0ADCA1D126B1100AAD278 /* SelectBasalProfilePumpEvent.swift */; }; 43B0ADCC1D126E3000AAD278 /* NSDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B0ADC31D12506A00AAD278 /* NSDateFormatter.swift */; }; - 43B6E0121D24E2320022E6D7 /* NightscoutPumpEventsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */; }; 43BA719B202591A70058961E /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BA719A202591A70058961E /* Response.swift */; }; 43BA719D2026C9B00058961E /* ResponseBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BA719C2026C9B00058961E /* ResponseBuffer.swift */; }; 43BF58B01FF594CB00499C46 /* SelectBasalProfileMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BF58AF1FF594CB00499C46 /* SelectBasalProfileMessageBody.swift */; }; @@ -134,7 +133,6 @@ 43C246A61D891DBF0031F8D1 /* Crypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246931D8918AE0031F8D1 /* Crypto.framework */; }; 43C246A91D8A31540031F8D1 /* Crypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246931D8918AE0031F8D1 /* Crypto.framework */; }; 43C246AA1D8A31540031F8D1 /* Crypto.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246931D8918AE0031F8D1 /* Crypto.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 43C9071B1D863772002BAD29 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; 43CA93291CB8CF22000026B5 /* ChangeTempBasalCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA93281CB8CF22000026B5 /* ChangeTempBasalCarelinkMessageBody.swift */; }; 43CA932B1CB8CF76000026B5 /* ChangeTimeCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA932A1CB8CF76000026B5 /* ChangeTimeCarelinkMessageBody.swift */; }; 43CA932E1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CA932C1CB8CFA1000026B5 /* ReadTempBasalCarelinkMessageBody.swift */; }; @@ -443,8 +441,6 @@ C1B383201CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1B3830B1CD0665D00CE7782 /* NightscoutUploadKit.framework */; }; C1B383211CD0665D00CE7782 /* NightscoutUploadKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C1B3830B1CD0665D00CE7782 /* NightscoutUploadKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C1B383281CD0668600CE7782 /* NightscoutUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */; }; - C1B383291CD0668600CE7782 /* NightscoutPumpEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */; }; - C1B383301CD0680800CE7782 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10D9BC11C8269D500378342 /* MinimedKit.framework */; }; C1B383361CD1BA8100CE7782 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B383351CD1BA8100CE7782 /* DeviceDataManager.swift */; }; C1B44CA7224BDFDF00DE47E5 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; C1BAD1181E63984C009BA1C6 /* RadioAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BAD1171E63984C009BA1C6 /* RadioAdapter.swift */; }; @@ -490,6 +486,7 @@ C1BB12BC21CB577E009A29B5 /* LogEventErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993D18B217E455000E489BF /* LogEventErrorCode.swift */; }; C1BB12BD21CB5796009A29B5 /* BasalDeliveryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9A213323E700C50C1D /* BasalDeliveryTable.swift */; }; C1BB12BE21CB57AA009A29B5 /* BasalSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF9F213323E800C50C1D /* BasalSchedule.swift */; }; + C1BC259823135EDB00E80E3F /* NightscoutProfileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1BC259723135EDB00E80E3F /* NightscoutProfileTests.swift */; }; C1C3578F1C927303009BDD4F /* MeterMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C3578E1C927303009BDD4F /* MeterMessage.swift */; }; C1C357911C92733A009BDD4F /* MeterMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C357901C92733A009BDD4F /* MeterMessageTests.swift */; }; C1C73F1D1DE6306A0022FC89 /* BatteryChemistryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C73F1C1DE6306A0022FC89 /* BatteryChemistryType.swift */; }; @@ -717,20 +714,6 @@ remoteGlobalIDString = 431CE76E1F98564100255374; remoteInfo = RileyLinkBLEKit; }; - A9B839CC22809DC3004E745E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C10D9BC01C8269D500378342; - remoteInfo = MinimedKit; - }; - A9B839CE22809DCF004E745E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = C12EA22F198B436800309FA4 /* Project object */; - proxyType = 1; - remoteGlobalIDString = C10D9BC01C8269D500378342; - remoteInfo = MinimedKit; - }; A9B839D022809DE7004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; @@ -836,14 +819,14 @@ remoteGlobalIDString = 4352A72420DEC9B700CAC200; remoteInfo = MinimedKitUI; }; - C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; - C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C1BC25992313843700E80E3F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; @@ -1313,7 +1296,6 @@ C145BF9D2219F2EC00A977CB /* Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comparable.swift; sourceTree = ""; }; C145BFA3221BBA7E00A977CB /* SuspendResumeMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuspendResumeMessageBody.swift; sourceTree = ""; }; C14B42F921FF78840073A836 /* MessageLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLog.swift; sourceTree = ""; }; - C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NightscoutPumpEventsTests.swift; path = ../RileyLinkTests/NightscoutPumpEventsTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14D2B041C9F5D5800C98E4C /* TempBasalDurationPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TempBasalDurationPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14D2B081C9F5EDA00C98E4C /* ChangeTempBasalTypePumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChangeTempBasalTypePumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C14FFC491D3AF1AC0049CF85 /* JournalEntryInsulinMarkerPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JournalEntryInsulinMarkerPumpEvent.swift; sourceTree = ""; }; @@ -1401,7 +1383,6 @@ C1842BFA1C8FA45100DB42AC /* BatteryPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BatteryPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C1842BFB1C8FA45100DB42AC /* AlarmSensorPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AlarmSensorPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutUploader.swift; sourceTree = ""; }; - C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutPumpEvents.swift; sourceTree = ""; }; C184875B20BC232F00ABE9E7 /* CorrectionRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorrectionRange.swift; sourceTree = ""; }; C184875D20BCDB0000ABE9E7 /* ForecastError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastError.swift; sourceTree = ""; }; C18C8C521D64123400E043FB /* EnableBolusWizardPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableBolusWizardPumpEvent.swift; sourceTree = ""; }; @@ -1434,6 +1415,7 @@ C1BB128521CB5603009A29B5 /* OmniKitPacketParser */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = OmniKitPacketParser; sourceTree = BUILT_PRODUCTS_DIR; }; C1BB128721CB5603009A29B5 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; C1BB12B321CB5697009A29B5 /* Packet+RFPacket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Packet+RFPacket.swift"; sourceTree = ""; }; + C1BC259723135EDB00E80E3F /* NightscoutProfileTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutProfileTests.swift; sourceTree = ""; }; C1C3578E1C927303009BDD4F /* MeterMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeterMessage.swift; sourceTree = ""; }; C1C357901C92733A009BDD4F /* MeterMessageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MeterMessageTests.swift; path = Messages/MeterMessageTests.swift; sourceTree = ""; }; C1C659181E16BA9D0025CC58 /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; @@ -1696,7 +1678,6 @@ buildActionMask = 2147483647; files = ( 43C246A61D891DBF0031F8D1 /* Crypto.framework in Frameworks */, - C1B383301CD0680800CE7782 /* MinimedKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1704,7 +1685,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 43C9071B1D863772002BAD29 /* MinimedKit.framework in Frameworks */, C1B383151CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */, 4352A74B20DED87F00CAC200 /* LoopKit.framework in Frameworks */, ); @@ -2464,7 +2444,6 @@ 43F348051D596270009933DC /* HKUnit.swift */, C1B3830F1CD0665D00CE7782 /* Info.plist */, 546145C01DCEB47600DC6DEB /* NightscoutEntry.swift */, - C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */, C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */, C1B3830D1CD0665D00CE7782 /* NightscoutUploadKit.h */, 43B0ADC81D1268B300AAD278 /* TimeFormat.swift */, @@ -2477,9 +2456,9 @@ C1B3831A1CD0665D00CE7782 /* NightscoutUploadKitTests */ = { isa = PBXGroup; children = ( - C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */, C1B3831D1CD0665D00CE7782 /* Info.plist */, 7D23675121252A5E0028B67D /* InfoPlist.strings */, + C1BC259723135EDB00E80E3F /* NightscoutProfileTests.swift */, ); path = NightscoutUploadKitTests; sourceTree = ""; @@ -3021,7 +3000,6 @@ ); dependencies = ( 43C246A51D891DB80031F8D1 /* PBXTargetDependency */, - A9B839CD22809DC3004E745E /* PBXTargetDependency */, ); name = NightscoutUploadKit; productName = NightscoutUploadKit; @@ -3040,8 +3018,7 @@ buildRules = ( ); dependencies = ( - A9B839CF22809DCF004E745E /* PBXTargetDependency */, - C1B383171CD0665D00CE7782 /* PBXTargetDependency */, + C1BC259A2313843700E80E3F /* PBXTargetDependency */, ); name = NightscoutUploadKitTests; productName = NightscoutUploadKitTests; @@ -3191,7 +3168,7 @@ }; C1B383131CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; + LastSwiftMigration = 1030; ProvisioningStyle = Manual; }; C1BB128421CB5603009A29B5 = { @@ -3968,7 +3945,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1B383291CD0668600CE7782 /* NightscoutPumpEvents.swift in Sources */, C18EB742207EE20100EA002B /* NightscoutProfile.swift in Sources */, 7D2366F3212527DA0028B67D /* LocalizedString.swift in Sources */, 43B0ADCC1D126E3000AAD278 /* NSDateFormatter.swift in Sources */, @@ -4009,7 +3985,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 43B6E0121D24E2320022E6D7 /* NightscoutPumpEventsTests.swift in Sources */, + C1BC259823135EDB00E80E3F /* NightscoutProfileTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4248,16 +4224,6 @@ target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; targetProxy = A9B839CA22809DB3004E745E /* PBXContainerItemProxy */; }; - A9B839CD22809DC3004E745E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C10D9BC01C8269D500378342 /* MinimedKit */; - targetProxy = A9B839CC22809DC3004E745E /* PBXContainerItemProxy */; - }; - A9B839CF22809DCF004E745E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = C10D9BC01C8269D500378342 /* MinimedKit */; - targetProxy = A9B839CE22809DCF004E745E /* PBXContainerItemProxy */; - }; A9B839D122809DE7004E745E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; @@ -4333,15 +4299,15 @@ target = 4352A72420DEC9B700CAC200 /* MinimedKitUI */; targetProxy = C136AA70231188F1008A320D /* PBXContainerItemProxy */; }; - C1B383171CD0665D00CE7782 /* PBXTargetDependency */ = { + C1B3831F1CD0665D00CE7782 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; - targetProxy = C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */; + targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; - C1B3831F1CD0665D00CE7782 /* PBXTargetDependency */ = { + C1BC259A2313843700E80E3F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; - targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; + targetProxy = C1BC25992313843700E80E3F /* PBXContainerItemProxy */; }; C1FFAF83213323CC00C50C1D /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -5590,6 +5556,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -5609,6 +5576,7 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -5616,6 +5584,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; @@ -5635,6 +5604,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/RileyLinkTests/NightscoutPumpEventsTests.swift b/RileyLinkTests/NightscoutPumpEventsTests.swift deleted file mode 100644 index 2a54d37b7..000000000 --- a/RileyLinkTests/NightscoutPumpEventsTests.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// NightscoutPumpEventsTests.swift -// RileyLink -// -// Created by Pete Schwamb on 3/18/16. -// Copyright © 2016 Pete Schwamb. All rights reserved. -// - -import XCTest -@testable import MinimedKit -@testable import NightscoutUploadKit - -class NightscoutPumpEventsTests: XCTestCase { - - func testBgCheckFromMeter() { - let pumpEvent = BGReceivedPumpEvent( - availableData: Data(hexadecimalString: "3f2122938d7510c527ad")!, - pumpModel: PumpModel.model523 - )! - var timestamp = pumpEvent.timestamp - timestamp.timeZone = TimeZone(secondsFromGMT: -5 * 60 * 60) - - let events = [ - TimestampedHistoryEvent(pumpEvent: pumpEvent, date: timestamp.date!) - ] - let treatments = NightscoutPumpEvents.translate(events, eventSource: "testing") - XCTAssertEqual(1, treatments.count) - let bgCheck = treatments[0] as! BGCheckNightscoutTreatment - XCTAssertEqual(bgCheck.glucose, 268) - XCTAssertEqual(bgCheck.glucoseType, NightscoutTreatment.GlucoseType.Meter) - XCTAssertEqual(bgCheck.enteredBy, "testing") - XCTAssertEqual(bgCheck.units, NightscoutTreatment.Units.MGDL) - } - - func testStandaloneBolus() { - let pumpEvent = BolusNormalPumpEvent( - availableData: Data(hexadecimalString: "010080008000240009a24a1510")!, - pumpModel: PumpModel.model551 - )! - var timestamp = pumpEvent.timestamp - timestamp.timeZone = TimeZone(secondsFromGMT: -5 * 60 * 60) - - let events = [ - TimestampedHistoryEvent(pumpEvent: pumpEvent, date: timestamp.date!) - ] - let treatments = NightscoutPumpEvents.translate(events, eventSource: "testing") - XCTAssertEqual(1, treatments.count) - let bolus = treatments[0] as! BolusNightscoutTreatment - XCTAssertEqual(bolus.amount, 3.2) - XCTAssertEqual(bolus.bolusType, BolusNightscoutTreatment.BolusType.Normal) - XCTAssertEqual(bolus.duration, 0) - XCTAssertEqual(bolus.programmed, 3.2) - XCTAssertEqual(bolus.unabsorbed, 0.9) - } - - func testBolusWizardAndBolusOffByOneSecond() { - let bwEvent = BolusWizardEstimatePumpEvent( - availableData: Data(hexadecimalString: "5b6489340b10102850006e3c64000090000058009064")!, - pumpModel: PumpModel.model523 - )! - - let bolus = BolusNormalPumpEvent( - availableData: Data(hexadecimalString: "01009000900058008a344b1010")!, - pumpModel: PumpModel.model523 - )! - - let events: [TimestampedPumpEvent] = [bwEvent, bolus] - let timezone = TimeZone(secondsFromGMT: -5 * 60 * 60) - - let timestampedEvents = events.map({ (e: TimestampedPumpEvent) -> TimestampedHistoryEvent in - var timestamp = e.timestamp - timestamp.timeZone = timezone - return TimestampedHistoryEvent(pumpEvent: e, date: timestamp.date!) - }) - - - let treatments = NightscoutPumpEvents.translate(timestampedEvents, eventSource: "testing") - XCTAssertEqual(1, treatments.count) - let treatment = treatments[0] as! BolusNightscoutTreatment - XCTAssertEqual(treatment.amount, 3.6) - XCTAssertEqual(treatment.bolusType, BolusNightscoutTreatment.BolusType.Normal) - XCTAssertEqual(treatment.duration, 0) - XCTAssertEqual(treatment.programmed, 3.6) - XCTAssertEqual(treatment.unabsorbed, 2.2) - XCTAssertEqual(treatment.carbs, 40) - XCTAssertEqual(treatment.ratio, 11.0) - } -} From 0db7fac34e0d6ae80ec68190ed5d33de2b3f8308 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 1 Sep 2019 08:16:21 -0500 Subject: [PATCH 35/71] Bolus errors should be wrapped in SetBolusError for certainty (#538) --- MinimedKit/PumpManager/MinimedPumpManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 03d489c72..d6e6d0601 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -817,13 +817,13 @@ extension MinimedPumpManager: PumpManager { pumpOps.runSession(withName: "Bolus", using: rileyLinkDeviceProvider.firstConnectedDevice) { (session) in guard let session = session else { - completion(.failure(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink))) + completion(.failure(SetBolusError.certain(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)))) return } if let unfinalizedBolus = self.state.unfinalizedBolus { guard unfinalizedBolus.finished else { - completion(.failure(PumpManagerError.deviceState(MinimedPumpManagerError.bolusInProgress))) + completion(.failure(SetBolusError.certain(PumpManagerError.deviceState(MinimedPumpManagerError.bolusInProgress)))) return } self.state.pendingDoses.append(unfinalizedBolus) From 13e3ebd698f3eaa2af7000d1c0ae0031c50e4c57 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 2 Sep 2019 01:54:06 -0500 Subject: [PATCH 36/71] Add flag to separate removal/insertion events (#529) --- MinimedKit/PumpEvents/BatteryPumpEvent.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MinimedKit/PumpEvents/BatteryPumpEvent.swift b/MinimedKit/PumpEvents/BatteryPumpEvent.swift index bf0489bf5..1125bd160 100644 --- a/MinimedKit/PumpEvents/BatteryPumpEvent.swift +++ b/MinimedKit/PumpEvents/BatteryPumpEvent.swift @@ -11,6 +11,7 @@ import Foundation public struct BatteryPumpEvent: TimestampedPumpEvent { public let length: Int public let rawData: Data + public let isPresent: Bool public let timestamp: DateComponents public init?(availableData: Data, pumpModel: PumpModel) { @@ -21,6 +22,8 @@ public struct BatteryPumpEvent: TimestampedPumpEvent { } rawData = availableData.subdata(in: 0.. Date: Mon, 2 Sep 2019 21:39:15 -0500 Subject: [PATCH 37/71] Bolus beeps with UI Toggle (#540) * new Pod Settings buttons & internal variables to control and test beeping: [Enable/Disable Confirmation Beeps] [Enable/Disable Optional Pod Alarms] [Check Beeps] Optional cancelNone() which will verifies the nonce to replace getStatus(), flash logs read support, beep on Pod startup completion to verify piezo * update some switch logic for new buttons * Revert back to using hexWordLog for testing * Various updates to address feedback received, "Check Beeps" is now "Play Test Beeps" * Update for new BasalScheduleExtraCommand() parameter list * Restructures to have comfirmationBeeps and optionalPodAlarms persistent and included in OmnipodPumpManager state and not PodCommsSessions state * Various improvements and cleanup + better commenting and routine names for some odd Pod beep conditions + some code improvements for clarity and brevity + remove some now unneeded & unused routines + reworked so that the pump flash log can be read on a faulted Pod using Test Command if enabled + add new hasSetupCompletedPod and isSetupCompleted variables for faulted Pods + merge {enable,disable}ConfirmationBeeps() into new setConfirmationBeeps() + merge {enable,disable}OptionalPodAlarms() into new setOptionalPodAlarms() + use confirmation beeps for priming and cannula insertion if enabled + don't try to give confirmation beeps when suspending yet to avoid potential issues * updated for BolusExtraCommand() argment order * Fix totally broken test for PodInfoFlashLogRecent which included a duplicate packet and was missing items in another list Add new test for PodInfoFlashLogPrevious from the same Pod run * acknowledgementBeep and confirmationBeep don't work for priming and insert cannula updated comments and some minor code improvements for beep handling and faults * Use new BeepConfigType for $1E Beep Config and BeepType for $19 Configure Alerts and $1F Cancel Have Disable Confirmation Beeps use BeepConfigType .beepConfig_NoBeep to silently disable completion beeps * Remove global vars, show spinner while changing bolus beep option * Update beep label to make it clear it's only for boluses * Remove optionalPodAlarms code. Will look at this later. * Renaming * Update user facing strings --- .../BasalScheduleExtraCommand.swift | 16 +- .../MessageBlocks/BeepConfigCommand.swift | 12 +- .../MessageBlocks/BolusExtraCommand.swift | 2 +- .../MessageBlocks/PodInfo.swift | 2 +- .../MessageBlocks/PodInfoFlashLogRecent.swift | 53 ++++++- .../MessageBlocks/TempBasalExtraCommand.swift | 2 +- OmniKit/Model/AlertSlot.swift | 14 +- OmniKit/Model/BeepType.swift | 22 ++- OmniKit/PumpManager/OmnipodPumpManager.swift | 89 +++++++++++- .../PumpManager/OmnipodPumpManagerState.swift | 11 ++ OmniKit/PumpManager/PodCommsSession.swift | 137 ++++++++++++------ OmniKit/PumpManager/PodState.swift | 7 +- OmniKitTests/BasalScheduleTests.swift | 2 +- OmniKitTests/BolusTests.swift | 6 +- OmniKitTests/PodInfoTests.swift | 17 ++- .../CommandResponseViewController.swift | 20 ++- .../OmnipodSettingsViewController.swift | 110 ++++++++++++-- 17 files changed, 411 insertions(+), 111 deletions(-) diff --git a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift index bf467ca9d..92829d3e7 100644 --- a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift @@ -61,21 +61,18 @@ public struct BasalScheduleExtraCommand : MessageBlock { } rateEntries = entries } - - public init(acknowledgementBeep: Bool, completionBeep: Bool, programReminderInterval: TimeInterval, currentEntryIndex: UInt8, remainingPulses: Double, delayUntilNextTenthOfPulse: TimeInterval, rateEntries: [RateEntry]) { - self.acknowledgementBeep = acknowledgementBeep - self.completionBeep = completionBeep - self.programReminderInterval = programReminderInterval + + public init(currentEntryIndex: UInt8, remainingPulses: Double, delayUntilNextTenthOfPulse: TimeInterval, rateEntries: [RateEntry], acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) { self.currentEntryIndex = currentEntryIndex self.remainingPulses = remainingPulses self.delayUntilNextTenthOfPulse = delayUntilNextTenthOfPulse self.rateEntries = rateEntries - } - - public init(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool, completionBeep: Bool, programReminderInterval: TimeInterval) { self.acknowledgementBeep = acknowledgementBeep self.completionBeep = completionBeep self.programReminderInterval = programReminderInterval + } + + public init(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) { var rateEntries = [RateEntry]() let mergedSchedule = BasalSchedule(entries: schedule.entries.adjacentEqualRatesMerged()) @@ -93,5 +90,8 @@ public struct BasalScheduleExtraCommand : MessageBlock { let timeBetweenPulses = TimeInterval(hours: 1) / pulsesPerHour self.delayUntilNextTenthOfPulse = timeRemainingInEntry.truncatingRemainder(dividingBy: (timeBetweenPulses / 10)) self.remainingPulses = pulsesPerHour * (timeRemainingInEntry-self.delayUntilNextTenthOfPulse) / .hours(1) + 0.1 + self.acknowledgementBeep = acknowledgementBeep + self.completionBeep = completionBeep + self.programReminderInterval = programReminderInterval } } diff --git a/OmniKit/MessageTransport/MessageBlocks/BeepConfigCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BeepConfigCommand.swift index 595bea89a..37f097a22 100644 --- a/OmniKit/MessageTransport/MessageBlocks/BeepConfigCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/BeepConfigCommand.swift @@ -13,7 +13,7 @@ public struct BeepConfigCommand : MessageBlock { // 1e 04 AABBCCDD public let blockType: MessageBlockType = .beepConfig - public let beepType: BeepType + public let beepConfigType: BeepConfigType public let basalCompletionBeep: Bool public let basalIntervalBeep: TimeInterval public let tempBasalCompletionBeep: Bool @@ -21,8 +21,8 @@ public struct BeepConfigCommand : MessageBlock { public let bolusCompletionBeep: Bool public let bolusIntervalBeep: TimeInterval - public init(beepType: BeepType, basalCompletionBeep: Bool = false, basalIntervalBeep: TimeInterval = 0, tempBasalCompletionBeep: Bool = false, tempBasalIntervalBeep: TimeInterval = 0, bolusCompletionBeep: Bool = false, bolusIntervalBeep: TimeInterval = 0) { - self.beepType = beepType + public init(beepConfigType: BeepConfigType, basalCompletionBeep: Bool = false, basalIntervalBeep: TimeInterval = 0, tempBasalCompletionBeep: Bool = false, tempBasalIntervalBeep: TimeInterval = 0, bolusCompletionBeep: Bool = false, bolusIntervalBeep: TimeInterval = 0) { + self.beepConfigType = beepConfigType self.basalCompletionBeep = basalCompletionBeep self.basalIntervalBeep = basalIntervalBeep self.tempBasalCompletionBeep = tempBasalCompletionBeep @@ -35,8 +35,8 @@ public struct BeepConfigCommand : MessageBlock { if encodedData.count < 6 { throw MessageBlockError.notEnoughData } - if let beepType = BeepType.init(rawValue: encodedData[2]) { - self.beepType = beepType + if let beepConfigType = BeepConfigType.init(rawValue: encodedData[2]) { + self.beepConfigType = beepConfigType } else { throw MessageBlockError.parseError } @@ -53,7 +53,7 @@ public struct BeepConfigCommand : MessageBlock { blockType.rawValue, 4, ]) - data.append(beepType.rawValue) + data.append(beepConfigType.rawValue) data.append((basalCompletionBeep ? (1<<6) : 0) + (UInt8(basalIntervalBeep.minutes) & 0x3f)) data.append((tempBasalCompletionBeep ? (1<<6) : 0) + (UInt8(tempBasalIntervalBeep.minutes) & 0x3f)) data.append((bolusCompletionBeep ? (1<<6) : 0) + (UInt8(bolusIntervalBeep.minutes) & 0x3f)) diff --git a/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift index 4e8de8a7f..8d6c1beb0 100644 --- a/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift @@ -64,7 +64,7 @@ public struct BolusExtraCommand : MessageBlock { squareWaveDuration = timeBetweenExtendedPulses * Double(pulseCountX10) / 10 } - public init(acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0, units: Double, timeBetweenPulses: TimeInterval = 2, squareWaveUnits: Double = 0.0, squareWaveDuration: TimeInterval = 0) { + public init(units: Double, timeBetweenPulses: TimeInterval = 2, squareWaveUnits: Double = 0.0, squareWaveDuration: TimeInterval = 0, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) { self.acknowledgementBeep = acknowledgementBeep self.completionBeep = completionBeep self.programReminderInterval = programReminderInterval diff --git a/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift b/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift index 99dc0f735..dc79e1fef 100644 --- a/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift +++ b/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift @@ -45,7 +45,7 @@ public enum PodInfoResponseSubType: UInt8, Equatable { case .flashLogRecent: return PodInfoFlashLogRecent.self case .dumpOlderFlashlog: - return PodInfoFlashLogRecent.self + return PodInfoFlashLogPrevious.self } } } diff --git a/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift b/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift index af78d52e5..a8c98a39d 100644 --- a/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift +++ b/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift @@ -8,24 +8,61 @@ import Foundation +fileprivate let maxPumpEntriesReturned = 50 + +// read (up to) the most recent 50 32-bit pump entries from flash log public struct PodInfoFlashLogRecent : PodInfo { // CMD 1 2 3 4 5 6 7 8 // DATA 0 1 2 3 4 5 6 // 02 LL 50 IIII XXXXXXXX ... - // 02 LL 51 NNNN XXXXXXXX ... public var podInfoType : PodInfoResponseSubType = .flashLogRecent - public let indexLastEntry: UInt8 - public let hexWordLog : Data public let data : Data + public let indexLastEntry: UInt16 // how many 32-bit pump log entries total in Pod + public let hexWordLog : Data // TODO make a 32-bit pump log entry type + + public init(encodedData: Data) throws { + if encodedData.count < 3 || ((encodedData.count - 3) & 0x3) != 0 { + throw MessageBlockError.notEnoughData // first 3 bytes missing or non-integral # of log entries + } + let nLogBytesReturned = encodedData.count - 3 + let nLogEntriesReturned = nLogBytesReturned / 4 + let lastPumpEntry = UInt16((encodedData[1] << 8) | encodedData[2]) + if lastPumpEntry < maxPumpEntriesReturned && nLogEntriesReturned < lastPumpEntry { + throw MessageBlockError.notEnoughData // small count and we didn't recieve them all + } + self.data = encodedData + self.indexLastEntry = lastPumpEntry + self.hexWordLog = encodedData.subdata(in: 3.. nLogEntriesCalculated) { + throw MessageBlockError.notEnoughData // some entry count mismatch } - self.indexLastEntry = encodedData[2] - self.hexWordLog = encodedData.subdata(in: 3.. Void) { - guard self.hasActivePod else { + // use hasSetupCompletePod instead of hasActivePod so we don't fail on a faulted Pod + guard self.hasSetupCompletePod else { completion(OmnipodPumpManagerError.noPodPaired) return } @@ -862,7 +880,63 @@ extension OmnipodPumpManager { switch result { case .success(let session): do { - let _ = try session.testingCommands() + try session.testingCommands() + completion(nil) + } catch let error { + completion(error) + } + case .failure(let error): + completion(error) + } + } + } + + public func playTestBeeps(completion: @escaping (Error?) -> Void) { + guard self.hasActivePod else { + completion(OmnipodPumpManagerError.noPodPaired) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Play Test Beeps", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + guard self.state.podState?.unfinalizedBolus?.isFinished != false else { + self.log.info("Unfinalized bolus, skipping play test beeps") + throw PodCommsError.unfinalizedBolus + } + + try session.beepConfig(beepConfigType: .bipBeepBipBeepBipBeepBipBeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: self.bolusBeeps) + // .fiveSecondBeep could be used for a PDM style "Check alarms", but this only works if the pod is suspended! + try session.beepConfig(beepConfigType: .beeeeeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: self.bolusBeeps) + completion(nil) + } catch let error { + completion(error) + } + case .failure(let error): + completion(error) + } + } + } + + public func setBolusBeeps(enabled: Bool, completion: @escaping (Error?) -> Void) { + self.log.info("Set Bolus Beeps to %s", enabled ? "true" : "false") + + guard self.hasActivePod else { + completion(nil) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + let name: String = enabled ? "Enable Confirmation Beeps" : "Disable Confirmation Beeps" + self.podComms.runSession(withName: name, using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + do { + let beepConfigType: BeepConfigType = enabled ? .bipBip : .noBeep + try session.beepConfig(beepConfigType: beepConfigType, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: enabled) + self.bolusBeeps = enabled completion(nil) } catch let error { completion(error) @@ -984,6 +1058,7 @@ extension OmnipodPumpManager: PumpManager { state.suspendEngageState = .engaging }) + // N.B. with a deliveryType of .all and a beepType other then .noBeep, the Pod will emit 3 beeps! let result = session.cancelDelivery(deliveryType: .all, beepType: .noBeep) switch result { case .certainFailure(let error): @@ -1148,7 +1223,7 @@ extension OmnipodPumpManager: PumpManager { let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: enactUnits, unit: .units) willRequest(dose) - let result = session.bolus(units: enactUnits) + let result = session.bolus(units: enactUnits, acknowledgementBeep: self.bolusBeeps, completionBeep: self.bolusBeeps) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } @@ -1192,7 +1267,9 @@ extension OmnipodPumpManager: PumpManager { state.bolusEngageState = .disengaging }) - let result = session.cancelDelivery(deliveryType: .bolus, beepType: .noBeep) + // when cancelling a bolus give a type 6 beeeeeep to match PDM if doing bolus confirmation beeps + let beeptype: BeepType = self.bolusBeeps ? .beeeeeep : .noBeep + let result = session.cancelDelivery(deliveryType: .bolus, beepType: beeptype) switch result { case .certainFailure(let error): throw error @@ -1297,7 +1374,7 @@ extension OmnipodPumpManager: PumpManager { state.tempBasalEngageState = .engaging }) - let result = session.setTempBasal(rate: rate, duration: duration, acknowledgementBeep: false, completionBeep: false, programReminderInterval: 0) + let result = session.setTempBasal(rate: rate, duration: duration) let basalStart = Date() let dose = DoseEntry(type: .tempBasal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: rate, unit: .unitsPerHour) session.dosesForStorage() { (doses) -> Bool in diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift index 4950ce235..4ad25f630 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -30,6 +30,8 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { public var expirationReminderDate: Date? + public var bolusBeeps: Bool + // Temporal state not persisted internal enum EngageablePumpState: Equatable { @@ -54,6 +56,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { self.basalSchedule = basalSchedule self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState self.unstoredDoses = [] + self.bolusBeeps = false } public init?(rawValue: RawValue) { @@ -127,6 +130,8 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { } else { self.unstoredDoses = [] } + + self.bolusBeeps = rawValue["bolusBeeps"] as? Bool ?? false } public var rawValue: RawValue { @@ -136,6 +141,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { "basalSchedule": basalSchedule.rawValue, "messageLog": messageLog.rawValue, "unstoredDoses": unstoredDoses.map { $0.rawValue }, + "bolusBeeps": bolusBeeps, ] if let podState = podState { @@ -159,6 +165,10 @@ extension OmnipodPumpManagerState { return podState?.isActive == true } + var hasSetupCompletePod: Bool { + return podState?.isSetupComplete == true + } + var isPumpDataStale: Bool { let pumpStatusAgeTolerance = TimeInterval(minutes: 6) let pumpDataAge = -(self.lastPumpDataReportDate ?? .distantPast).timeIntervalSinceNow @@ -180,6 +190,7 @@ extension OmnipodPumpManagerState: CustomDebugStringConvertible { "* tempBasalEngageState: \(String(describing: tempBasalEngageState))", "* lastPumpDataReportDate: \(String(describing: lastPumpDataReportDate))", "* isPumpDataStale: \(String(describing: isPumpDataStale))", + "* bolusBeeps: \(String(describing: bolusBeeps))", String(reflecting: podState), String(reflecting: rileyLinkConnectionManagerState), String(reflecting: messageLog), diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 103af97c7..6498f5068 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -137,13 +137,13 @@ extension PodCommsError: LocalizedError { } } - - public protocol PodCommsSessionDelegate: class { func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) } public class PodCommsSession { + private let useCancelNoneForStatus: Bool = false // whether to always use a cancel none to get status + private let podLowReservoirLevel: Double = 20 // default pod low reservoir alert value public let log = OSLog(category: "PodCommsSession") @@ -164,6 +164,16 @@ public class PodCommsSession { self.transport.delegate = self } + private func handlePodFault(fault: PodInfoFaultEvent) { + self.podState.fault = fault + log.error("Pod Fault: %@", String(describing: fault)) + if fault.deliveryStatus == .suspended { + let now = Date() + podState.unfinalizedTempBasal?.cancel(at: now) + podState.unfinalizedBolus?.cancel(at: now, withRemaining: fault.insulinNotDelivered) + } + } + /// Performs a message exchange, handling nonce resync, pod faults /// /// - Parameters: @@ -230,17 +240,9 @@ public class PodCommsSession { }) podState.advanceToNextNonce() } else if let fault = response.fault { - self.podState.fault = fault - log.error("Pod Fault: %@", String(describing: fault)) - let now = Date() - if fault.deliveryStatus == .suspended { - podState.unfinalizedTempBasal?.cancel(at: now) - podState.unfinalizedBolus?.cancel(at: now, withRemaining: fault.insulinNotDelivered) - } - + handlePodFault(fault: fault) throw PodCommsError.podFault(fault: fault) - } - else { + } else { log.error("Unexpected response: %@", String(describing: response.messageBlocks[0])) throw PodCommsError.unexpectedResponse(response: responseType) } @@ -260,11 +262,6 @@ public class PodCommsSession { // The following will set Tab5[$16] to 0 during pairing, which disables $6x faults. let _: StatusResponse = try send([FaultConfigCommand(nonce: podState.currentNonce, tab5Sub16: 0, tab5Sub17: 0)]) - - // Uncomment to get an audible pod alert for low reservoir -// let lowReservoirAlarm = PodAlert.lowReservoirAlarm(20) // Alarm at 20 units remaining -// let _ = try configureAlerts([lowReservoirAlarm]) - let finishSetupReminder = PodAlert.finishSetupReminder let _ = try configureAlerts([finishSetupReminder]) } else { @@ -302,16 +299,11 @@ public class PodCommsSession { podState.setupProgress = .initialBasalScheduleSet return } - } else { - // Uncomment the following to get an audible expiration notice before the expiration advisory alert -// let timeUntilExpirationAlert = (podState.activatedAt + Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow - Pod.expirationAlertWindow).timeIntervalSinceNow -// let expirationAlert = PodAlert.expirationAlert(timeUntilExpirationAlert) -// let _ = try configureAlerts([expirationAlert]) } podState.setupProgress = .settingInitialBasalSchedule // Set basal schedule - let _ = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(0)) + let _ = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset) podState.setupProgress = .initialBasalScheduleSet podState.finalizedDoses.append(UnfinalizedDose(resumeStartTime: Date(), scheduledCertainty: .certain)) } @@ -326,7 +318,18 @@ public class PodCommsSession { podState.updateFromStatusResponse(status) return status } - + + // emits the specified beep type and sets the completion beep flags based on the specified confirmationBeep value + public func beepConfig(beepConfigType: BeepConfigType, basalCompletionBeep: Bool, tempBasalCompletionBeep: Bool, bolusCompletionBeep: Bool) throws { + guard self.podState.fault == nil else { + return // skip if already faulted to avoid a Beep Config Command error response + } + + let beepConfigCommand = BeepConfigCommand(beepConfigType: beepConfigType, basalCompletionBeep: basalCompletionBeep, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) + let statusResponse: StatusResponse = try send([beepConfigCommand]) + podState.updateFromStatusResponse(statusResponse) + } + public func insertCannula() throws -> TimeInterval { let insertionWait: TimeInterval = .seconds(10) @@ -347,7 +350,7 @@ public class PodCommsSession { return TimeInterval(0) // Already done; no need to wait } } else { - // Configure Alerts + // Configure all the non-optional Pod Alarms let endOfServiceTime = activatedAt + Pod.serviceDuration let timeUntilExpirationAdvisory = (endOfServiceTime - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow).timeIntervalSinceNow let expirationAdvisoryAlarm = PodAlert.expirationAdvisoryAlarm(alarmTime: timeUntilExpirationAdvisory, duration: Pod.expirationAdvisoryWindow) @@ -397,7 +400,7 @@ public class PodCommsSession { } - public func bolus(units: Double) -> DeliveryCommandResult { + public func bolus(units: Double, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { let timeBetweenPulses = TimeInterval(seconds: 2) let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: units, timeBetweenPulses: timeBetweenPulses) @@ -408,7 +411,7 @@ public class PodCommsSession { } // 17 0d 00 0064 0001 86a0000000000000 - let bolusExtraCommand = BolusExtraCommand(units: units) + let bolusExtraCommand = BolusExtraCommand(units: units, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) do { // Between bluetooth and the radio and firmware, about 1.2s on average passes before we start tracking let commsOffset = TimeInterval(seconds: -1.5) @@ -423,10 +426,10 @@ public class PodCommsSession { } } - public func setTempBasal(rate: Double, duration: TimeInterval, acknowledgementBeep: Bool, completionBeep: Bool, programReminderInterval: TimeInterval) -> DeliveryCommandResult { + public func setTempBasal(rate: Double, duration: TimeInterval, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { let tempBasalCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, tempBasalRate: rate, duration: duration) - let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) guard podState.unfinalizedBolus?.isFinished != false else { return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) @@ -446,7 +449,7 @@ public class PodCommsSession { } public func cancelDelivery(deliveryType: CancelDeliveryCommand.DeliveryType, beepType: BeepType) -> CancelDeliveryResult { - + let cancelDelivery = CancelDeliveryCommand(nonce: podState.currentNonce, deliveryType: deliveryType, beepType: beepType) do { @@ -492,9 +495,8 @@ public class PodCommsSession { } public func testingCommands() throws { - let _ = try getStatus() - // uncomment the next line to enable pod check alarms - // let _ = try checkAlarms() + // try readFlashLogs() + let _ = try cancelNone() // a functional replacement for getStatus() which also verifies & advances nonce } public func setTime(timeZone: TimeZone, basalSchedule: BasalSchedule, date: Date) throws -> StatusResponse { @@ -506,15 +508,15 @@ public class PodCommsSession { throw error case .success: let scheduleOffset = timeZone.scheduleOffset(forDate: date) - let status = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: false, completionBeep: false, programReminderInterval: .minutes(0)) + let status = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset) return status } } - public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool, completionBeep: Bool, programReminderInterval: TimeInterval) throws -> StatusResponse { + public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { let basalScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, basalSchedule: schedule, scheduleOffset: scheduleOffset) - let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) + let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: 0) do { let status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) @@ -540,21 +542,62 @@ public class PodCommsSession { return status } + public func cancelNone() throws -> StatusResponse { + var statusResponse: StatusResponse + + // use cancelDelivery .none to get status AND validate & advance the nonce + let cancelResult: CancelDeliveryResult = cancelDelivery(deliveryType: .none, beepType: .noBeep) + switch cancelResult { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + case .success(let response, _): + statusResponse = response + } + podState.updateFromStatusResponse(statusResponse) + return statusResponse + } + public func getStatus() throws -> StatusResponse { - let response: StatusResponse = try send([GetStatusCommand()]) - podState.updateFromStatusResponse(response) - return response + if useCancelNoneForStatus { + return try cancelNone() // functional replacement for getStatus() + } + let statusResponse: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(statusResponse) + return statusResponse } - public func checkAlarms() throws -> StatusResponse { - var response: StatusResponse + private func readFlashLogsRequest(podInfoResponseSubType: PodInfoResponseSubType) throws { + + let blocksToSend = [GetStatusCommand(podInfoType: podInfoResponseSubType)] + let message = Message(address: podState.address, messageBlocks: blocksToSend, sequenceNum: transport.messageNumber) + let messageResponse = try transport.sendMessage(message) + + if let podInfoResponseMessageBlock = messageResponse.messageBlocks[0] as? PodInfoResponse { + log.info("Pod flash log: %@", String(describing: podInfoResponseMessageBlock)) + } else if let fault = messageResponse.fault { + handlePodFault(fault: fault) + throw PodCommsError.podFault(fault: fault) + } else { + log.error("Unexpected Pod flash log response: %@", String(describing: messageResponse.messageBlocks[0])) + throw PodCommsError.unexpectedResponse(response: messageResponse.messageBlocks[0].blockType) + } + } + + public func readFlashLogs() throws { + if self.podState.fault == nil { + let _ = try cancelNone() + guard podState.unfinalizedBolus?.isFinished != false else { + log.info("Unfinalized bolus, skipping read flash logs") + throw PodCommsError.unfinalizedBolus + } + } - response = try send([BeepConfigCommand(beepType: .bipBeepBipBeepBipBeepBipBeep)]) - podState.updateFromStatusResponse(response) - // Could use .fiveSecondBeep for PDM style "Check alarms", but this can only be successfully used if pod is suspended - response = try send([BeepConfigCommand(beepType: .beeeeeep)]) - podState.updateFromStatusResponse(response) - return response + // read up to the most recent 50 entries from flash log + try readFlashLogsRequest(podInfoResponseSubType: .flashLogRecent) + // read up to the previous 50 entries from flash log + try readFlashLogsRequest(podInfoResponseSubType: .dumpOlderFlashlog) } public func deactivatePod() throws { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index baef90aed..1b764b8f9 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -119,11 +119,16 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } return !setupProgress.primingNeeded && primeFinishTime.timeIntervalSinceNow < 0 } - + public var isActive: Bool { return setupProgress == .completed && fault == nil } + // variation on isActive that doesn't care if Pod is faulted + public var isSetupComplete: Bool { + return setupProgress == .completed + } + public mutating func advanceToNextNonce() { nonceState.advanceToNextNonce() } diff --git a/OmniKitTests/BasalScheduleTests.swift b/OmniKitTests/BasalScheduleTests.swift index d91f9b733..1e122195b 100644 --- a/OmniKitTests/BasalScheduleTests.swift +++ b/OmniKitTests/BasalScheduleTests.swift @@ -97,7 +97,7 @@ class BasalScheduleTests: XCTestCase { // Encode let rateEntries = RateEntry.makeEntries(rate: 3.0, duration: TimeInterval(hours: 24)) - let cmd = BasalScheduleExtraCommand(acknowledgementBeep: false, completionBeep: true, programReminderInterval: 0, currentEntryIndex: 0, remainingPulses: 689, delayUntilNextTenthOfPulse: TimeInterval(seconds: 20), rateEntries: rateEntries) + let cmd = BasalScheduleExtraCommand(currentEntryIndex: 0, remainingPulses: 689, delayUntilNextTenthOfPulse: TimeInterval(seconds: 20), rateEntries: rateEntries, acknowledgementBeep: false, completionBeep: true, programReminderInterval: 0) XCTAssertEqual("130e40001aea01312d003840005b8d80", cmd.data.hexadecimalString) diff --git a/OmniKitTests/BolusTests.swift b/OmniKitTests/BolusTests.swift index 28210765e..f5bdf3c9a 100644 --- a/OmniKitTests/BolusTests.swift +++ b/OmniKitTests/BolusTests.swift @@ -65,7 +65,7 @@ class BolusTests: XCTestCase { func testBolusExtraOddPulseCount() { // 17 0d 7c 00fa 00030d40 000000000000 - let cmd = BolusExtraCommand(acknowledgementBeep: false, completionBeep: true, programReminderInterval: .hours(1), units: 1.25) + let cmd = BolusExtraCommand(units: 1.25, acknowledgementBeep: false, completionBeep: true, programReminderInterval: .hours(1)) XCTAssertEqual("170d7c00fa00030d40000000000000", cmd.data.hexadecimalString) } @@ -110,7 +110,7 @@ class BolusTests: XCTestCase { let bolusCommand = SetInsulinScheduleCommand(nonce: 0x31204ba7, deliverySchedule: scheduleEntry) XCTAssertEqual("1a0e31204ba702014801257002570257", bolusCommand.data.hexadecimalString) - let bolusExtraCommand = BolusExtraCommand(acknowledgementBeep: false, completionBeep: true, programReminderInterval: .hours(1), units: bolusAmount) + let bolusExtraCommand = BolusExtraCommand(units: bolusAmount, acknowledgementBeep: false, completionBeep: true, programReminderInterval: .hours(1)) XCTAssertEqual("170d7c176600030d40000000000000", bolusExtraCommand.data.hexadecimalString) } @@ -126,7 +126,7 @@ class BolusTests: XCTestCase { // 17 LL RR NNNN XXXXXXXX // 17 0d 3c 019a 00030d40 0000 00000000 - let bolusExtraCommand = BolusExtraCommand(acknowledgementBeep: false, completionBeep: false, programReminderInterval: .hours(1), units: bolusAmount) + let bolusExtraCommand = BolusExtraCommand(units: bolusAmount, acknowledgementBeep: false, completionBeep: false, programReminderInterval: .hours(1)) XCTAssertEqual("170d3c019a00030d40000000000000", bolusExtraCommand.data.hexadecimalString) } diff --git a/OmniKitTests/PodInfoTests.swift b/OmniKitTests/PodInfoTests.swift index 58369cb84..4184fe624 100644 --- a/OmniKitTests/PodInfoTests.swift +++ b/OmniKitTests/PodInfoTests.swift @@ -347,10 +347,23 @@ class PodInfoTests: XCTestCase { //02 cb 50 0086 34212e00 39203100 3c212d00 41203000 44202c00 49212e00 4c212b00 51202f00 54212c00 59203080 5c202d80 61203080 00212e80 05213180 08202f80 0d203280 10202f80 15213180 18202f80 1d213180 20202e80 25213300 28203200 2d213500 30213100 35213400 38213100 3d203500 40203100 45213300 48203000 4d213200 50212f00 55203300 58203080 5d213280 60202f800 12030800 4202c800 92131800 c2130801 12132801 42031801 92133801 c2031802 12032802 42132002 92035002 c2131003 12134000 3c3801c2 03180212 03280242 13200292 035002c2 13100312 1340003c 3 do { // Decode - let decoded = try PodInfoFlashLogRecent(encodedData: Data(hexadecimalString: "50008634212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f0055203300582030805d21328060202f800120308004202c80092131800c2130801121328014203180192133801c2031802120328024213200292035002c2131003121340003c3801c2031802120328024213200292035002c2131003121340003c3")!) + let decoded = try PodInfoFlashLogRecent(encodedData: Data(hexadecimalString: "50008634212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f0055203300582030805d21328060202f800120308004202c80092131800c2130801121328014203180192133801c2031802120328024213200292035002c21310031213400")!) XCTAssertEqual(.flashLogRecent, decoded.podInfoType) XCTAssertEqual(134, decoded.indexLastEntry) - XCTAssertEqual(Data(hexadecimalString:"34212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f"), decoded.hexWordLog) + XCTAssertEqual(Data(hexadecimalString:"34212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f0055203300582030805d21328060202f800120308004202c80092131800c2130801121328014203180192133801c2031802120328024213200292035002c21310031213400"), decoded.hexWordLog) + } catch (let error) { + XCTFail("message decoding threw error: \(error)") + } + } + + func testPodInfoFlashLogPrevious() { + //02 cb 51 0032 14602500 19612800 1c612400 21612800 24612500 29612900 2c602600 31602a00 34602600 39612a80 3c612680 41602c80 00602780 05632880 08602580 0d612880 10612580 15612780 18602380 1d602680 20612280 25602700 28612400 2d212800 30202700 35202a00 38202700 3d202a00 40202900 45202c00 48202a00 4d212c00 50212900 55212c00 58212980 5d202b80 60202880 01202d80 04212a80 09202d80 0c212980 11212a80 14212980 1921801c 212a8021 212c8024 202c0029 212f002c 212d0031 20310082 + do { + // Decode + let decoded = try PodInfoFlashLogPrevious(encodedData: Data(hexadecimalString: "51003214602500196128001c6124002161280024612500296129002c60260031602a003460260039612a803c61268041602c800060278005632880086025800d6128801061258015612780186023801d6026802061228025602700286124002d2128003020270035202a00382027003d202a004020290045202c0048202a004d212c005021290055212c00582129805d202b806020288001202d8004212a8009202d800c21298011212a80142129801921801c212a8021212c8024202c0029212f002c212d003120310082")!) + XCTAssertEqual(.dumpOlderFlashlog, decoded.podInfoType) + XCTAssertEqual(50, decoded.nEntries) + XCTAssertEqual(Data(hexadecimalString:"14602500196128001c6124002161280024612500296129002c60260031602a003460260039612a803c61268041602c800060278005632880086025800d6128801061258015612780186023801d6026802061228025602700286124002d2128003020270035202a00382027003d202a004020290045202c0048202a004d212c005021290055212c00582129805d202b806020288001202d8004212a8009202d800c21298011212a80142129801921801c212a8021212c8024202c0029212f002c212d003120310082"), decoded.hexWordLog) } catch (let error) { XCTFail("message decoding threw error: \(error)") } diff --git a/OmniKitUI/ViewControllers/CommandResponseViewController.swift b/OmniKitUI/ViewControllers/CommandResponseViewController.swift index 55a67635b..5f9e11dc4 100644 --- a/OmniKitUI/ViewControllers/CommandResponseViewController.swift +++ b/OmniKitUI/ViewControllers/CommandResponseViewController.swift @@ -44,8 +44,7 @@ extension CommandResponseViewController { } } - - static func testCommand(pumpManager: OmnipodPumpManager) -> T { + static func testingCommands(pumpManager: OmnipodPumpManager) -> T { return T { (completionHandler) -> String in pumpManager.testingCommands() { (error) in let response: String @@ -61,4 +60,21 @@ extension CommandResponseViewController { return LocalizedString("Testing Commands…", comment: "Progress message for testing commands.") } } + + static func playTestBeeps(pumpManager: OmnipodPumpManager) -> T { + return T { (completionHandler) -> String in + pumpManager.playTestBeeps() { (error) in + let response: String + if let error = error { + response = String(describing: error) + } else { + response = self.successText + } + DispatchQueue.main.async { + completionHandler(response) + } + } + return LocalizedString("Play Test Beeps…", comment: "Progress message for play test beeps.") + } + } } diff --git a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift index 3a16021c8..3f5701044 100644 --- a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -12,6 +12,21 @@ import LoopKit import OmniKit import LoopKitUI +public class ConfirmationBeepsTableViewCell: TextButtonTableViewCell { + + public func updateTextLabel(enabled: Bool) { + if enabled { + self.textLabel?.text = LocalizedString("Disable Bolus Beeps", comment: "Title text for button to disable bolus beeps") + } else { + self.textLabel?.text = LocalizedString("Enable Bolus Beeps", comment: "Title text for button to enable bolus beeps") + } + } + + override public func loadingStatusChanged() { + self.isEnabled = !isLoading + } +} + class OmnipodSettingsViewController: RileyLinkSettingsViewController { let pumpManager: OmnipodPumpManager @@ -50,7 +65,13 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { cell.basalDeliveryState = pumpManager.status.basalDeliveryState return cell }() - + + lazy var confirmationBeepsTableViewCell: ConfirmationBeepsTableViewCell = { + let cell = ConfirmationBeepsTableViewCell(style: .default, reuseIdentifier: nil) + cell.updateTextLabel(enabled: pumpManager.bolusBeeps) + return cell + }() + override func viewDidLoad() { super.viewDidLoad() @@ -156,6 +177,8 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { private enum ActionsRow: Int, CaseIterable { case suspendResume = 0 + case testCommand + case playTestBeeps case replacePod } @@ -170,7 +193,7 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { private enum ConfigurationRow: Int, CaseIterable { case reminder = 0 case timeZoneOffset - case testCommand + case enableDisableConfirmationBeeps } fileprivate enum StatusRow: Int, CaseIterable { @@ -282,6 +305,16 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { switch actions[indexPath.row] { case .suspendResume: return suspendResumeTableViewCell + case .testCommand: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Test Command", comment: "The title of the command to run the test command") + cell.accessoryType = .disclosureIndicator + return cell + case .playTestBeeps: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Play Test Beeps", comment: "The title of the command to play test beeps") + cell.accessoryType = .disclosureIndicator + return cell case .replacePod: let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell @@ -332,11 +365,8 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { } cell.accessoryType = .disclosureIndicator return cell - case .testCommand: - let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) - cell.textLabel?.text = LocalizedString("Test Command", comment: "The title of the command to run the test command") - cell.accessoryType = .disclosureIndicator - return cell + case .enableDisableConfirmationBeeps: + return confirmationBeepsTableViewCell } case .status: @@ -430,6 +460,14 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { case .suspendResume: suspendResumeTapped() tableView.deselectRow(at: indexPath, animated: true) + case .testCommand: + let vc = CommandResponseViewController.testingCommands(pumpManager: pumpManager) + vc.title = sender?.textLabel?.text + show(vc, sender: indexPath) + case .playTestBeeps: + let vc = CommandResponseViewController.playTestBeeps(pumpManager: pumpManager) + vc.title = sender?.textLabel?.text + show(vc, sender: indexPath) case .replacePod: let vc: UIViewController if podState == nil || podState!.setupProgress.primingNeeded { @@ -478,10 +516,9 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { let vc = CommandResponseViewController.changeTime(pumpManager: pumpManager) vc.title = sender?.textLabel?.text show(vc, sender: indexPath) - case .testCommand: - let vc = CommandResponseViewController.testCommand(pumpManager: pumpManager) - vc.title = sender?.textLabel?.text - show(vc, sender: indexPath) + case .enableDisableConfirmationBeeps: + confirmationBeepsTapped() + tableView.deselectRow(at: indexPath, animated: true) } case .rileyLinks: let device = devicesDataSource.devices[indexPath.row] @@ -504,14 +541,21 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { switch sections[indexPath.section] { - case .podDetails, .actions, .status: + case .podDetails, .status: break + case .actions: + switch ActionsRow(rawValue: indexPath.row)! { + case .suspendResume, .replacePod: + break + case .testCommand, .playTestBeeps: + tableView.reloadRows(at: [indexPath], with: .fade) + } case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { - case .timeZoneOffset, .testCommand: - tableView.reloadRows(at: [indexPath], with: .fade) - case .reminder: + case .reminder, .enableDisableConfirmationBeeps: break + case .timeZoneOffset: + tableView.reloadRows(at: [indexPath], with: .fade) } case .rileyLinks: break @@ -544,6 +588,42 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { } } } + + private func confirmationBeepsTapped() { + let confirmationBeeps: Bool = pumpManager.bolusBeeps + + func done() { + DispatchQueue.main.async { [weak self] in + if let self = self { + self.confirmationBeepsTableViewCell.updateTextLabel(enabled: self.pumpManager.bolusBeeps) + self.confirmationBeepsTableViewCell.isLoading = false + } + } + } + + confirmationBeepsTableViewCell.isLoading = true + if confirmationBeeps { + pumpManager.setBolusBeeps(enabled: false, completion: { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error disabling bolus beeps", comment: "The alert title for disable bolus beeps error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + done() + }) + } else { + pumpManager.setBolusBeeps(enabled: true, completion: { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error enabling bolus beeps", comment: "The alert title for enable bolus beeps error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + done() + }) + } + } } extension OmnipodSettingsViewController: CompletionDelegate { From 218ed584792e094826f2e7a83317a7da33a4d5f2 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 2 Sep 2019 21:41:47 -0500 Subject: [PATCH 38/71] Dose identifiers (#541) * upload doses with identifiers for later upsert * Use UUID as raw identifier, and keep startTimes static for NS * prevent negative durations * Stop using surrogate ids until nightscout supports them --- .../PumpManager/MinimedPumpManager.swift | 46 +++++++---- .../PumpManager/MinimedPumpManagerState.swift | 60 +++++++++++--- MinimedKit/PumpManager/UnfinalizedDose.swift | 82 +++++++++++++------ .../Treatments/BolusNightscoutTreatment.swift | 5 +- .../TempBasalNightscoutTreatment.swift | 5 +- 5 files changed, 145 insertions(+), 53 deletions(-) diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index d6e6d0601..d3ee0a056 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -415,17 +415,16 @@ extension MinimedPumpManager { } - private func reconcilePendingDosesWith(_ events: [NewPumpEvent]) { + private func reconcilePendingDosesWith(_ events: [NewPumpEvent]) -> [NewPumpEvent] { // Must be called from the sessionQueue - var reconcilableEvents = events.filter { !self.state.recentlyReconciledEventIDs.contains($0.raw) } + var reconcilableEvents = events.filter { !self.state.reconciliationMappings.keys.contains($0.raw) } let matchingTimeWindow = TimeInterval(minutes: 1) - func markReconciled(raw: Data, index: Int) -> Void { - self.state.recentlyReconciledEventIDs.insert(raw, at: 0) - self.state.recentlyReconciledEventIDs = Array(self.state.recentlyReconciledEventIDs.prefix(5)) - reconcilableEvents.remove(at: index) + func markReconciled(startTime: Date, uuid: UUID, eventRaw: Data, index: Int) -> Void { + let mapping = ReconciledDoseMapping(startTime: startTime, uuid: uuid, eventRaw: eventRaw) + self.state.reconciliationMappings[eventRaw] = mapping } // If we have a bolus in progress, see if it has shown up in history yet @@ -433,25 +432,30 @@ extension MinimedPumpManager { let matchingBolus = reconcilableEvents[index] self.log.debug("Matched unfinalized bolus %@ to history record %@", String(describing: bolus), String(describing: matchingBolus)) self.state.unfinalizedBolus = nil - markReconciled(raw: matchingBolus.raw, index: index) + markReconciled(startTime: bolus.startTime, uuid: bolus.uniqueId, eventRaw: matchingBolus.raw, index: index) } // Reconcile temp basal - if let tempBasal = self.state.unfinalizedTempBasal, let index = reconcilableEvents.firstMatchingIndex(for: tempBasal, within: matchingTimeWindow), let dose = events[index].dose { + if let tempBasal = self.state.unfinalizedTempBasal, let index = reconcilableEvents.firstMatchingIndex(for: tempBasal, within: matchingTimeWindow) { let matchedTempBasal = reconcilableEvents[index] self.log.debug("Matched unfinalized temp basal %@ to history record %@", String(describing: tempBasal), String(describing: matchedTempBasal)) + // Update unfinalizedTempBasal to match entry in history, and mark as reconciled - self.state.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: dose.unitsPerHour, startTime: dose.startDate, duration: dose.endDate.timeIntervalSince(dose.startDate), isReconciledWithHistory: true) - markReconciled(raw: matchedTempBasal.raw, index: index) + //self.state.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: dose.unitsPerHour, startTime: tempBasal.startDate, duration: dose.endDate.timeIntervalSince(dose.startDate), isReconciledWithHistory: true) + + // Temporary: Keeping original command time here, instead of pump time, for NS natural key restriction + self.state.unfinalizedTempBasal?.isReconciledWithHistory = true + + markReconciled(startTime: tempBasal.startTime, uuid: tempBasal.uniqueId, eventRaw: matchedTempBasal.raw, index: index) } // Reconcile any pending doses, and remove expired doses - let expirationCutoff = Date().addingTimeInterval(.hours(-24)) + let expirationCutoff = Date().addingTimeInterval(.hours(-12)) self.state.pendingDoses.removeAll { (dose) -> Bool in if let index = reconcilableEvents.firstMatchingIndex(for: dose, within: matchingTimeWindow) { let historyEvent = reconcilableEvents[index] self.log.debug("Matched pending dose %@ to history record %@", String(describing: dose), String(describing: historyEvent)) - markReconciled(raw: historyEvent.raw, index: index) + markReconciled(startTime: dose.startTime, uuid: dose.uniqueId, eventRaw: historyEvent.raw, index: index) return true } else if dose.finishTime < expirationCutoff { self.log.debug("Expiring pending dose that did not match any history records: %@", String(describing: dose)) @@ -461,6 +465,20 @@ extension MinimedPumpManager { } self.state.lastReconciliation = Date() + + let recentReconciliationMappings = self.state.reconciliationMappings.filter { (key, value) -> Bool in + return value.startTime >= expirationCutoff + } + self.state.reconciliationMappings = recentReconciliationMappings + + let mappings = self.state.reconciliationMappings + return events.map({ (event) -> NewPumpEvent in + guard let mapping = mappings[event.raw] else { + return event + } + // We want to be using pump event date for reconciled events, but NS won't let us update startTime + return event.replacingRawAndDate(newRaw: mapping.uuid.asRaw, newDate: mapping.startTime) + }) } private var pendingDosesForStorage: [NewPumpEvent] { @@ -497,14 +515,14 @@ extension MinimedPumpManager { // Reconcile history with pending doses let newPumpEvents = historyEvents.pumpEvents(from: model) - self.reconcilePendingDosesWith(newPumpEvents) + let reconciledEvents = self.reconcilePendingDosesWith(newPumpEvents) self.pumpDelegate.notify({ (delegate) in guard let delegate = delegate else { preconditionFailure("pumpManagerDelegate cannot be nil") } - delegate.pumpManager(self, hasNewPumpEvents: newPumpEvents + self.pendingDosesForStorage, lastReconciliation: self.lastReconciliation, completion: { (error) in + delegate.pumpManager(self, hasNewPumpEvents: reconciledEvents + self.pendingDosesForStorage, lastReconciliation: self.lastReconciliation, completion: { (error) in // Called on an unknown queue by the delegate if error == nil { self.recents.lastAddedPumpEvents = Date() diff --git a/MinimedKit/PumpManager/MinimedPumpManagerState.swift b/MinimedKit/PumpManager/MinimedPumpManagerState.swift index 0f63358ed..45eea791a 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerState.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerState.swift @@ -9,6 +9,40 @@ import LoopKit import RileyLinkKit import RileyLinkBLEKit +public struct ReconciledDoseMapping: Equatable { + let startTime: Date + let uuid: UUID + let eventRaw: Data +} + +extension ReconciledDoseMapping: RawRepresentable { + public typealias RawValue = [String:Any] + + public init?(rawValue: [String : Any]) { + guard + let startTime = rawValue["startTime"] as? Date, + let uuidString = rawValue["uuid"] as? String, + let uuid = UUID(uuidString: uuidString), + let eventRawString = rawValue["eventRaw"] as? String, + let eventRaw = Data(hexadecimalString: eventRawString) else + + { + return nil + } + self.startTime = startTime + self.uuid = uuid + self.eventRaw = eventRaw + } + + public var rawValue: [String : Any] { + return [ + "startTime": startTime, + "uuid": uuid.uuidString, + "eventRaw": eventRaw.hexadecimalString, + ] + } +} + public struct MinimedPumpManagerState: RawRepresentable, Equatable { public typealias RawValue = PumpManager.RawStateValue @@ -71,12 +105,12 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { // Doses we're tracking that haven't shown up in history yet public var pendingDoses: [UnfinalizedDose] - // A record of history events that have recently been used to reconcile unfinalized doses - public var recentlyReconciledEventIDs: [Data] + // Maps + public var reconciliationMappings: [Data:ReconciledDoseMapping] public var lastReconciliation: Date? - public init(batteryChemistry: BatteryChemistryType = .alkaline, preferredInsulinDataSource: InsulinDataSource = .pumpHistory, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, suspendState: SuspendState, lastValidFrequency: Measurement? = nil, batteryPercentage: Double? = nil, lastReservoirReading: ReservoirReading? = nil, unfinalizedBolus: UnfinalizedDose? = nil, unfinalizedTempBasal: UnfinalizedDose? = nil, pendingDoses: [UnfinalizedDose]? = nil, recentlyReconciledEventIDs: [Data]? = nil, lastReconciliation: Date? = nil) { + public init(batteryChemistry: BatteryChemistryType = .alkaline, preferredInsulinDataSource: InsulinDataSource = .pumpHistory, pumpColor: PumpColor, pumpID: String, pumpModel: PumpModel, pumpFirmwareVersion: String, pumpRegion: PumpRegion, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?, timeZone: TimeZone, suspendState: SuspendState, lastValidFrequency: Measurement? = nil, batteryPercentage: Double? = nil, lastReservoirReading: ReservoirReading? = nil, unfinalizedBolus: UnfinalizedDose? = nil, unfinalizedTempBasal: UnfinalizedDose? = nil, pendingDoses: [UnfinalizedDose]? = nil, recentlyReconciledEvents: [Data:ReconciledDoseMapping]? = nil, lastReconciliation: Date? = nil) { self.batteryChemistry = batteryChemistry self.preferredInsulinDataSource = preferredInsulinDataSource self.pumpColor = pumpColor @@ -93,7 +127,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { self.unfinalizedBolus = unfinalizedBolus self.unfinalizedTempBasal = unfinalizedTempBasal self.pendingDoses = pendingDoses ?? [] - self.recentlyReconciledEventIDs = recentlyReconciledEventIDs ?? [] + self.reconciliationMappings = recentlyReconciledEvents ?? [:] self.lastReconciliation = lastReconciliation } @@ -188,13 +222,15 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { pendingDoses = [] } - let recentlyReconciledEventIDs: [Data] - if let rawRecentlyReconciledEventIDs = rawValue["recentlyReconciledEventIDs"] as? [String] { - recentlyReconciledEventIDs = rawRecentlyReconciledEventIDs.compactMap( { Data(hexadecimalString: $0) } ) + + let recentlyReconciledEvents: [Data:ReconciledDoseMapping] + if let rawRecentlyReconciledEvents = rawValue["recentlyReconciledEvents"] as? [ReconciledDoseMapping.RawValue] { + let mappings = rawRecentlyReconciledEvents.compactMap { ReconciledDoseMapping(rawValue: $0) } + recentlyReconciledEvents = Dictionary(mappings.map{ ($0.eventRaw, $0) }, uniquingKeysWith: { (old, new) in new } ) } else { - recentlyReconciledEventIDs = [] + recentlyReconciledEvents = [:] } - + let lastReconciliation = rawValue["lastReconciliation"] as? Date self.init( @@ -214,7 +250,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { unfinalizedBolus: unfinalizedBolus, unfinalizedTempBasal: unfinalizedTempBasal, pendingDoses: pendingDoses, - recentlyReconciledEventIDs: recentlyReconciledEventIDs, + recentlyReconciledEvents: recentlyReconciledEvents, lastReconciliation: lastReconciliation ) } @@ -232,7 +268,7 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { "suspendState": suspendState.rawValue, "version": MinimedPumpManagerState.version, "pendingDoses": pendingDoses.map { $0.rawValue }, - "recentlyReconciledEventIDs": recentlyReconciledEventIDs.map { $0.hexadecimalString }, + "recentlyReconciledEvents": reconciliationMappings.values.map { $0.rawValue }, ] value["batteryPercentage"] = batteryPercentage @@ -273,7 +309,7 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible { "unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))", "pendingDoses: \(pendingDoses)", "timeZone: \(timeZone)", - "recentlyReconciledEventIDs: \(recentlyReconciledEventIDs.map { $0.hexadecimalString })", + "recentlyReconciledEvents: \(reconciliationMappings.values.map { "\($0.eventRaw.hexadecimalString) -> \($0.uuid)" })", "lastReconciliation: \(String(describing: lastReconciliation))", String(reflecting: rileyLinkConnectionManagerState), ].joined(separator: "\n") diff --git a/MinimedKit/PumpManager/UnfinalizedDose.swift b/MinimedKit/PumpManager/UnfinalizedDose.swift index f9190833b..c3879cc0c 100644 --- a/MinimedKit/PumpManager/UnfinalizedDose.swift +++ b/MinimedKit/PumpManager/UnfinalizedDose.swift @@ -19,19 +19,14 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti case resume } - private static let dateFormatter = ISO8601DateFormatter() - - fileprivate var uniqueKey: Data { - return "\(doseType) \(scheduledUnits ?? units) \(UnfinalizedDose.dateFormatter.string(from: startTime))".data(using: .utf8)! - } - let doseType: DoseType public var units: Double - var scheduledUnits: Double? // Set when finalized; tracks original scheduled units - var scheduledTempRate: Double? // Set when finalized; tracks the original temp rate + var programmedUnits: Double? // Set when finalized; tracks programmed units + var programmedTempRate: Double? // Set when finalized; tracks programmed temp rate let startTime: Date var duration: TimeInterval var isReconciledWithHistory: Bool + var uniqueId: UUID var finishTime: Date { get { @@ -72,8 +67,9 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.units = bolusAmount self.startTime = startTime self.duration = duration - self.scheduledUnits = nil + self.programmedUnits = nil self.isReconciledWithHistory = isReconciledWithHistory + self.uniqueId = UUID() } init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, isReconciledWithHistory: Bool = false) { @@ -81,8 +77,9 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.units = tempBasalRate * duration.hours self.startTime = startTime self.duration = duration - self.scheduledUnits = nil + self.programmedUnits = nil self.isReconciledWithHistory = isReconciledWithHistory + self.uniqueId = UUID() } init(suspendStartTime: Date, isReconciledWithHistory: Bool = false) { @@ -91,6 +88,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.startTime = suspendStartTime self.duration = 0 self.isReconciledWithHistory = isReconciledWithHistory + self.uniqueId = UUID() } init(resumeStartTime: Date, isReconciledWithHistory: Bool = false) { @@ -99,6 +97,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.startTime = resumeStartTime self.duration = 0 self.isReconciledWithHistory = isReconciledWithHistory + self.uniqueId = UUID() } public mutating func cancel(at date: Date) { @@ -106,14 +105,14 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti return } - scheduledUnits = units + programmedUnits = units let newDuration = date.timeIntervalSince(startTime) switch doseType { case .bolus: units = rate * newDuration.hours case .tempBasal: - scheduledTempRate = rate + programmedTempRate = rate units = floor(rate * newDuration.hours * 20) / 20 default: break @@ -124,9 +123,9 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti public var description: String { switch doseType { case .bolus: - return "Bolus units:\(scheduledUnits ?? units) \(startTime)" + return "Bolus units:\(programmedUnits ?? units) \(startTime)" case .tempBasal: - return "TempBasal rate:\(scheduledTempRate ?? rate) \(startTime) duration:\(String(describing: duration))" + return "TempBasal rate:\(programmedTempRate ?? rate) \(startTime) duration:\(String(describing: duration))" default: return "\(String(describing: doseType).capitalized) \(startTime)" } @@ -150,11 +149,21 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.duration = duration if let scheduledUnits = rawValue["scheduledUnits"] as? Double { - self.scheduledUnits = scheduledUnits + self.programmedUnits = scheduledUnits } if let scheduledTempRate = rawValue["scheduledTempRate"] as? Double { - self.scheduledTempRate = scheduledTempRate + self.programmedTempRate = scheduledTempRate + } + + if let uuidString = rawValue["uniqueId"] as? String { + if let uuid = UUID(uuidString: uuidString) { + self.uniqueId = uuid + } else { + return nil + } + } else { + self.uniqueId = UUID() } self.isReconciledWithHistory = rawValue["isReconciledWithHistory"] as? Bool ?? false @@ -167,13 +176,14 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti "startTime": startTime, "duration": duration, "isReconciledWithHistory": isReconciledWithHistory, + "uniqueId": uniqueId.uuidString, ] - if let scheduledUnits = scheduledUnits { + if let scheduledUnits = programmedUnits { rawValue["scheduledUnits"] = scheduledUnits } - if let scheduledTempRate = scheduledTempRate { + if let scheduledTempRate = programmedTempRate { rawValue["scheduledTempRate"] = scheduledTempRate } @@ -185,7 +195,13 @@ extension NewPumpEvent { init(_ dose: UnfinalizedDose) { let title = String(describing: dose) let entry = DoseEntry(dose) - self.init(date: dose.startTime, dose: entry, isMutable: true, raw: dose.uniqueKey, title: title) + let raw = dose.uniqueId.asRaw + self.init(date: dose.startTime, dose: entry, isMutable: true, raw: raw, title: title) + } + + func replacingRawAndDate(newRaw: Data, newDate: Date) -> NewPumpEvent { + let newDose = dose?.replacingStartDate(newDate) + return NewPumpEvent(date: newDate, dose: newDose, isMutable: isMutable, raw: newRaw, title: title, type: type) } } @@ -193,15 +209,27 @@ extension DoseEntry { init (_ dose: UnfinalizedDose) { switch dose.doseType { case .bolus: - self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledUnits ?? dose.units, unit: .units, deliveredUnits: dose.finalizedUnits) + self = DoseEntry(type: .bolus, startDate: dose.startTime, endDate: dose.finishTime, value: dose.programmedUnits ?? dose.units, unit: .units, deliveredUnits: dose.finalizedUnits) case .tempBasal: - self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.scheduledTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits) + self = DoseEntry(type: .tempBasal, startDate: dose.startTime, endDate: dose.finishTime, value: dose.programmedTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits) case .suspend: self = DoseEntry(suspendDate: dose.startTime) case .resume: self = DoseEntry(resumeDate: dose.startTime) } } + + func replacingStartDate(_ newStartDate: Date) -> DoseEntry { + let value: Double + switch unit { + case .units: + value = programmedUnits + case .unitsPerHour: + value = unitsPerHour + } + let newEndDate = max(newStartDate, endDate) + return DoseEntry(type: type, startDate: newStartDate, endDate: newEndDate, value: value, unit: unit, deliveredUnits: deliveredUnits, description: description, syncIdentifier: syncIdentifier) + } } extension Collection where Element == NewPumpEvent { @@ -214,9 +242,9 @@ extension Collection where Element == NewPumpEvent { switch dose.doseType { case .bolus: - return type == .bolus && eventDose.programmedUnits == dose.scheduledUnits ?? dose.units + return type == .bolus && eventDose.programmedUnits == dose.programmedUnits ?? dose.units case .tempBasal: - return type == .tempBasal && eventDose.unitsPerHour == dose.scheduledTempRate ?? dose.rate + return type == .tempBasal && eventDose.unitsPerHour == dose.programmedTempRate ?? dose.rate case .suspend: return type == .suspend case .resume: @@ -225,3 +253,11 @@ extension Collection where Element == NewPumpEvent { }) } } + +extension UUID { + var asRaw: Data { + return withUnsafePointer(to: self) { + Data(bytes: $0, count: MemoryLayout.size(ofValue: self)) + } + } +} diff --git a/NightscoutUploadKit/Treatments/BolusNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/BolusNightscoutTreatment.swift index 0fa2f7fa7..18c9f4f95 100644 --- a/NightscoutUploadKit/Treatments/BolusNightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/BolusNightscoutTreatment.swift @@ -24,7 +24,7 @@ public class BolusNightscoutTreatment: NightscoutTreatment { let carbs: Int let ratio: Double - public init(timestamp: Date, enteredBy: String, bolusType: BolusType, amount: Double, programmed: Double, unabsorbed: Double, duration: TimeInterval, carbs: Int, ratio: Double, notes: String? = nil) { + public init(timestamp: Date, enteredBy: String, bolusType: BolusType, amount: Double, programmed: Double, unabsorbed: Double, duration: TimeInterval, carbs: Int, ratio: Double, notes: String? = nil, id: String?) { self.bolusType = bolusType self.amount = amount self.programmed = programmed @@ -32,7 +32,8 @@ public class BolusNightscoutTreatment: NightscoutTreatment { self.duration = duration self.carbs = carbs self.ratio = ratio - super.init(timestamp: timestamp, enteredBy: enteredBy, notes: notes, + // Commenting out usage of surrogate ID until Nightscout supports it. + super.init(timestamp: timestamp, enteredBy: enteredBy, notes: notes, /* id: id, */ eventType: (carbs > 0) ? "Meal Bolus" : "Correction Bolus") } diff --git a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift index 6e5204908..84e50821e 100644 --- a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift @@ -21,13 +21,14 @@ public class TempBasalNightscoutTreatment: NightscoutTreatment { let temp: RateType let duration: Int - public init(timestamp: Date, enteredBy: String, temp: RateType, rate: Double, absolute: Double?, duration: Int) { + public init(timestamp: Date, enteredBy: String, temp: RateType, rate: Double, absolute: Double?, duration: Int, id: String? = nil) { self.rate = rate self.absolute = absolute self.temp = temp self.duration = duration - super.init(timestamp: timestamp, enteredBy: enteredBy, eventType: "Temp Basal") + // Commenting out usage of surrogate ID until supported by Nightscout + super.init(timestamp: timestamp, enteredBy: enteredBy, /*id: id,*/ eventType: "Temp Basal") } override public var dictionaryRepresentation: [String: Any] { From 193bbd2878119325658411c63173ae51711e40df Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 2 Sep 2019 21:45:58 -0500 Subject: [PATCH 39/71] Bump Cartfile.resolved revisions --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 305f969e7..c3055a000 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "6f3c1e174a6d5bc952c62f4540567c13da54c2ce" +github "LoopKit/LoopKit" "b47d60e6ed202ca30e4adc63c6d237ac100e72b4" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" From d7f6ca653ff80f3cc133ad9f7d6814cdd7a118ad Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 8 Sep 2019 07:24:01 -0500 Subject: [PATCH 40/71] Upload delivered volume for temp basals to Nightscout (#542) --- .../Treatments/TempBasalNightscoutTreatment.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift index 84e50821e..43ae01317 100644 --- a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift @@ -17,15 +17,17 @@ public class TempBasalNightscoutTreatment: NightscoutTreatment { let rate: Double + let amount: Double? let absolute: Double? let temp: RateType let duration: Int - public init(timestamp: Date, enteredBy: String, temp: RateType, rate: Double, absolute: Double?, duration: Int, id: String? = nil) { + public init(timestamp: Date, enteredBy: String, temp: RateType, rate: Double, absolute: Double?, duration: Int, amount: Double? = nil, id: String? = nil) { self.rate = rate self.absolute = absolute self.temp = temp self.duration = duration + self.amount = amount // Commenting out usage of surrogate ID until supported by Nightscout super.init(timestamp: timestamp, enteredBy: enteredBy, /*id: id,*/ eventType: "Temp Basal") @@ -35,10 +37,9 @@ public class TempBasalNightscoutTreatment: NightscoutTreatment { var rval = super.dictionaryRepresentation rval["temp"] = temp.rawValue rval["rate"] = rate - if let absolute = absolute { - rval["absolute"] = absolute - } + rval["absolute"] = absolute rval["duration"] = duration + rval["amount"] = amount return rval } } From 43789bd2d25e8ce32a0ccae31a4b85f419eb8aa9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 8 Sep 2019 13:16:11 -0500 Subject: [PATCH 41/71] Medtronic dose volume and duration updates (#543) * Track volume and duration for MDT for NS * Use model specific dose progress estimation --- MinimedKit/Models/PumpModel.swift | 49 +++ .../MinimedDoseProgressEstimator.swift | 43 +- .../PumpManager/MinimedPumpManager.swift | 375 +++++++++++------- .../PumpManager/MinimedPumpManagerState.swift | 1 - MinimedKit/PumpManager/UnfinalizedDose.swift | 53 +-- .../TempBasalNightscoutTreatment.swift | 6 +- 6 files changed, 307 insertions(+), 220 deletions(-) diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 076aa5d4d..4ee8fa835 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -159,6 +159,55 @@ public enum PumpModel: String { } return TimeInterval(minutes: units / unitsPerMinute) } + + public func estimateTempBasalProgress(unitsPerHour: Double, duration: TimeInterval, elapsed: TimeInterval) -> (deliveredUnits: Double, progress: Double) { + let roundedVolume = floor(unitsPerHour * elapsed.hours * Double(pulsesPerUnit)) / Double(pulsesPerUnit) + return (deliveredUnits: roundedVolume, progress: min(elapsed / duration, 1)) + } + + public func estimateBolusProgress(elapsed: TimeInterval, programmedUnits: Double) -> (deliveredUnits: Double, progress: Double) { + let duration = bolusDeliveryTime(units: programmedUnits) + let timeProgress = min(elapsed / duration, 1) + + let updateResolution: Double + let unroundedVolume: Double + + if isDeliveryRateVariable { + if programmedUnits < 1 { + updateResolution = 40 // Resolution = 0.025 + unroundedVolume = timeProgress * programmedUnits + } else { + var remainingUnits = programmedUnits + var baseDuration: TimeInterval = 0 + var overlay1Duration: TimeInterval = 0 + var overlay2Duration: TimeInterval = 0 + let baseDeliveryRate = 1.5 / TimeInterval(minutes: 1) + + baseDuration = min(duration, remainingUnits / baseDeliveryRate) + remainingUnits -= baseDuration * baseDeliveryRate + + overlay1Duration = min(duration, remainingUnits / baseDeliveryRate) + remainingUnits -= overlay1Duration * baseDeliveryRate + + overlay2Duration = min(duration, remainingUnits / baseDeliveryRate) + remainingUnits -= overlay2Duration * baseDeliveryRate + + unroundedVolume = (min(elapsed, baseDuration) + min(elapsed, overlay1Duration) + min(elapsed, overlay2Duration)) * baseDeliveryRate + + if overlay1Duration > elapsed { + updateResolution = 10 // Resolution = 0.1 + } else { + updateResolution = 20 // Resolution = 0.05 + } + } + + } else { + updateResolution = 20 // Resolution = 0.05 + unroundedVolume = timeProgress * programmedUnits + } + let roundedVolume = round(unroundedVolume * updateResolution) / updateResolution + return (deliveredUnits: roundedVolume, progress: roundedVolume / programmedUnits) + } } diff --git a/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift b/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift index bb5225bf3..9a3692d07 100644 --- a/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift +++ b/MinimedKit/PumpManager/MinimedDoseProgressEstimator.swift @@ -17,47 +17,10 @@ class MinimedDoseProgressEstimator: DoseProgressTimerEstimator { override var progress: DoseProgress { let elapsed = -dose.startDate.timeIntervalSinceNow - let duration = dose.endDate.timeIntervalSince(dose.startDate) - let timeProgress = min(elapsed / duration, 1) - - let updateResolution: Double - let unroundedVolume: Double - - if pumpModel.isDeliveryRateVariable { - if dose.programmedUnits < 1 { - updateResolution = 40 // Resolution = 0.025 - unroundedVolume = timeProgress * dose.programmedUnits - } else { - var remainingUnits = dose.programmedUnits - var baseDuration: TimeInterval = 0 - var overlay1Duration: TimeInterval = 0 - var overlay2Duration: TimeInterval = 0 - let baseDeliveryRate = 1.5 / TimeInterval(minutes: 1) - - baseDuration = min(duration, remainingUnits / baseDeliveryRate) - remainingUnits -= baseDuration * baseDeliveryRate - - overlay1Duration = min(duration, remainingUnits / baseDeliveryRate) - remainingUnits -= overlay1Duration * baseDeliveryRate - - overlay2Duration = min(duration, remainingUnits / baseDeliveryRate) - remainingUnits -= overlay2Duration * baseDeliveryRate - - unroundedVolume = (min(elapsed, baseDuration) + min(elapsed, overlay1Duration) + min(elapsed, overlay2Duration)) * baseDeliveryRate - - if overlay1Duration > elapsed { - updateResolution = 10 // Resolution = 0.1 - } else { - updateResolution = 20 // Resolution = 0.05 - } - } + + let (deliveredUnits, progress) = pumpModel.estimateBolusProgress(elapsed: elapsed, programmedUnits: dose.programmedUnits) - } else { - updateResolution = 20 // Resolution = 0.05 - unroundedVolume = timeProgress * dose.programmedUnits - } - let roundedVolume = round(unroundedVolume * updateResolution) / updateResolution - return DoseProgress(deliveredUnits: roundedVolume, percentComplete: roundedVolume / dose.programmedUnits) + return DoseProgress(deliveredUnits: deliveredUnits, percentComplete: progress) } init(dose: DoseEntry, pumpModel: PumpModel, reportingQueue: DispatchQueue) { diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index d3ee0a056..066dd31b5 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -59,33 +59,53 @@ public class MinimedPumpManager: RileyLinkPumpManager { public let stateObservers = WeakSynchronizedSet() - private(set) public var state: MinimedPumpManagerState { - get { - return lockedState.value + public var state: MinimedPumpManagerState { + return lockedState.value + } + private let lockedState: Locked + + private func setState(_ changes: (_ state: inout MinimedPumpManagerState) -> Void) -> Void { + return setStateWithResult(changes) + } + + private func mutateState(_ changes: (_ state: inout MinimedPumpManagerState) -> Void) -> MinimedPumpManagerState { + return setStateWithResult({ (state) -> MinimedPumpManagerState in + changes(&state) + return state + }) + } + + private func setStateWithResult(_ changes: (_ state: inout MinimedPumpManagerState) -> ReturnType) -> ReturnType { + var oldValue: MinimedPumpManagerState! + var returnValue: ReturnType! + let newValue = lockedState.mutate { (state) in + oldValue = state + returnValue = changes(&state) } - set { - let oldValue = lockedState.value - let oldStatus = status - lockedState.value = newValue - - // PumpManagerStatus may have changed - if oldValue.timeZone != newValue.timeZone || - oldValue.suspendState != newValue.suspendState - { - notifyStatusObservers(oldStatus: oldStatus) - } + + guard oldValue != newValue else { + return returnValue + } + + let recents = self.recents + let oldStatus = status(for: oldValue, recents: recents) + let newStatus = status(for: newValue, recents: recents) - if oldValue != newValue { - pumpDelegate.notify { (delegate) in - delegate?.pumpManagerDidUpdateState(self) - } - stateObservers.forEach { (observer) in - observer.didUpdatePumpManagerState(newValue) - } - } + // PumpManagerStatus may have changed + if oldStatus != newStatus + { + notifyStatusObservers(oldStatus: oldStatus) + } + + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerDidUpdateState(self) } + stateObservers.forEach { (observer) in + observer.didUpdatePumpManagerState(newValue) + } + return returnValue } - private let lockedState: Locked + /// Temporal state of the manager private var recents: MinimedPumpManagerRecents { @@ -114,11 +134,11 @@ public class MinimedPumpManager: RileyLinkPumpManager { } if oldBatteryPercentage != newBatteryPercentage { - state.batteryPercentage = newBatteryPercentage + setState { (state) in + state.batteryPercentage = newBatteryPercentage + } } } - - // PumpManagerStatus may have changed if oldStatus != status { notifyStatusObservers(oldStatus: oldStatus) } @@ -154,7 +174,9 @@ public class MinimedPumpManager: RileyLinkPumpManager { return state.rileyLinkConnectionManagerState } set { - state.rileyLinkConnectionManagerState = newValue + setState { (state) in + state.rileyLinkConnectionManagerState = newValue + } } } @@ -239,29 +261,33 @@ extension MinimedPumpManager { } } - private func runSuspendResumeOnSession(state: SuspendResumeMessageBody.SuspendResumeState, session: PumpOpsSession) throws { + private func runSuspendResumeOnSession(suspendResumeState: SuspendResumeMessageBody.SuspendResumeState, session: PumpOpsSession) throws { defer { self.recents.suspendEngageState = .stable } - self.recents.suspendEngageState = state == .suspend ? .engaging : .disengaging - - try session.setSuspendResumeState(state) - let date = Date() - switch state { - case .suspend: - self.state.suspendState = .suspended(date) - case .resume: - self.state.suspendState = .resumed(date) - } + self.recents.suspendEngageState = suspendResumeState == .suspend ? .engaging : .disengaging - if state == .suspend { - self.state.unfinalizedBolus?.cancel(at: Date()) - if let bolus = self.state.unfinalizedBolus { - self.state.pendingDoses.append(bolus) + try session.setSuspendResumeState(suspendResumeState) + + setState { (state) in + let date = Date() + switch suspendResumeState { + case .suspend: + state.suspendState = .suspended(date) + case .resume: + state.suspendState = .resumed(date) + } + + if suspendResumeState == .suspend { + let pumpModel = state.pumpModel + state.unfinalizedBolus?.cancel(at: Date(), pumpModel: pumpModel) + if let bolus = state.unfinalizedBolus { + state.pendingDoses.append(bolus) + } + state.unfinalizedBolus = nil + + state.pendingDoses.append(UnfinalizedDose(suspendStartTime: Date())) + } else { + state.pendingDoses.append(UnfinalizedDose(resumeStartTime: Date())) } - self.state.unfinalizedBolus = nil - - self.state.pendingDoses.append(UnfinalizedDose(suspendStartTime: Date())) - } else { - self.state.pendingDoses.append(UnfinalizedDose(resumeStartTime: Date())) } } @@ -283,7 +309,7 @@ extension MinimedPumpManager { self.pumpOps.runSession(withName: sessionName, using: device) { (session) in do { - try self.runSuspendResumeOnSession(state: state, session: session) + try self.runSuspendResumeOnSession(suspendResumeState: state, session: session) self.storePendingPumpEvents({ (error) in completion(error) }) @@ -375,7 +401,9 @@ extension MinimedPumpManager { private func updateReservoirVolume(_ units: Double, at date: Date, withTimeLeft timeLeft: TimeInterval?) { // Must be called from the sessionQueue - state.lastReservoirReading = ReservoirReading(units: units, validAt: date) + setState { (state) in + state.lastReservoirReading = ReservoirReading(units: units, validAt: date) + } pumpDelegate.notify { (delegate) in delegate?.pumpManager(self, didReadReservoirValue: units, at: date) { (result) in @@ -414,81 +442,83 @@ extension MinimedPumpManager { } } - private func reconcilePendingDosesWith(_ events: [NewPumpEvent]) -> [NewPumpEvent] { // Must be called from the sessionQueue - - var reconcilableEvents = events.filter { !self.state.reconciliationMappings.keys.contains($0.raw) } - - let matchingTimeWindow = TimeInterval(minutes: 1) - - func markReconciled(startTime: Date, uuid: UUID, eventRaw: Data, index: Int) -> Void { - let mapping = ReconciledDoseMapping(startTime: startTime, uuid: uuid, eventRaw: eventRaw) - self.state.reconciliationMappings[eventRaw] = mapping - } - - // If we have a bolus in progress, see if it has shown up in history yet - if let bolus = self.state.unfinalizedBolus, let index = reconcilableEvents.firstMatchingIndex(for: bolus, within: matchingTimeWindow) { - let matchingBolus = reconcilableEvents[index] - self.log.debug("Matched unfinalized bolus %@ to history record %@", String(describing: bolus), String(describing: matchingBolus)) - self.state.unfinalizedBolus = nil - markReconciled(startTime: bolus.startTime, uuid: bolus.uniqueId, eventRaw: matchingBolus.raw, index: index) - } - - // Reconcile temp basal - if let tempBasal = self.state.unfinalizedTempBasal, let index = reconcilableEvents.firstMatchingIndex(for: tempBasal, within: matchingTimeWindow) { - let matchedTempBasal = reconcilableEvents[index] - self.log.debug("Matched unfinalized temp basal %@ to history record %@", String(describing: tempBasal), String(describing: matchedTempBasal)) + + var mergedPumpEvents: [NewPumpEvent] = [] + + self.lockedState.mutate { (state) in + var reconcilableEvents = events.filter { !state.reconciliationMappings.keys.contains($0.raw) } - // Update unfinalizedTempBasal to match entry in history, and mark as reconciled - //self.state.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: dose.unitsPerHour, startTime: tempBasal.startDate, duration: dose.endDate.timeIntervalSince(dose.startDate), isReconciledWithHistory: true) + let matchingTimeWindow = TimeInterval(minutes: 1) - // Temporary: Keeping original command time here, instead of pump time, for NS natural key restriction - self.state.unfinalizedTempBasal?.isReconciledWithHistory = true + func addReconciliationMapping(startTime: Date, uuid: UUID, eventRaw: Data, index: Int) -> Void { + let mapping = ReconciledDoseMapping(startTime: startTime, uuid: uuid, eventRaw: eventRaw) + self.log.debug("Adding reconciliation mapping %@ -> %@", eventRaw.hexadecimalString, uuid.asRaw.hexadecimalString) + state.reconciliationMappings[eventRaw] = mapping + } - markReconciled(startTime: tempBasal.startTime, uuid: tempBasal.uniqueId, eventRaw: matchedTempBasal.raw, index: index) - } - - // Reconcile any pending doses, and remove expired doses - let expirationCutoff = Date().addingTimeInterval(.hours(-12)) - self.state.pendingDoses.removeAll { (dose) -> Bool in - if let index = reconcilableEvents.firstMatchingIndex(for: dose, within: matchingTimeWindow) { - let historyEvent = reconcilableEvents[index] - self.log.debug("Matched pending dose %@ to history record %@", String(describing: dose), String(describing: historyEvent)) - markReconciled(startTime: dose.startTime, uuid: dose.uniqueId, eventRaw: historyEvent.raw, index: index) - return true - } else if dose.finishTime < expirationCutoff { - self.log.debug("Expiring pending dose that did not match any history records: %@", String(describing: dose)) - return true + // Reconcile any pending doses + state.pendingDoses = state.pendingDoses.map { (dose) -> UnfinalizedDose in + if let index = reconcilableEvents.firstMatchingIndex(for: dose, within: matchingTimeWindow) { + let historyEvent = reconcilableEvents[index] + self.log.debug("Matched pending dose %@ to history record %@", String(describing: dose), String(describing: historyEvent)) + addReconciliationMapping(startTime: dose.startTime, uuid: dose.uuid, eventRaw: historyEvent.raw, index: index) + var reconciledDose = dose + reconciledDose.isReconciledWithHistory = true + return reconciledDose + } + return dose } - return false - } - - self.state.lastReconciliation = Date() + + // Reconcile current bolus + if let bolus = state.unfinalizedBolus, let index = reconcilableEvents.firstMatchingIndex(for: bolus, within: matchingTimeWindow) { + let matchingBolus = reconcilableEvents[index] + self.log.debug("Matched unfinalized bolus %@ to history record %@", String(describing: bolus), String(describing: matchingBolus)) + state.unfinalizedBolus?.isReconciledWithHistory = true + addReconciliationMapping(startTime: bolus.startTime, uuid: bolus.uuid, eventRaw: matchingBolus.raw, index: index) + } + + // Reconcile current temp basal + if let tempBasal = state.unfinalizedTempBasal, let index = reconcilableEvents.firstMatchingIndex(for: tempBasal, within: matchingTimeWindow) { + let matchedTempBasal = reconcilableEvents[index] + self.log.debug("Matched unfinalized temp basal %@ to history record %@", String(describing: tempBasal), String(describing: matchedTempBasal)) + + // Eventual TODO: We are keeping original command time here, instead of pump time, for NS natural key restriction, but eventually, we + // would like to update the dose time to be reflective of the pump's time (i.e. use the matchedTempBasal.date) + state.unfinalizedTempBasal?.isReconciledWithHistory = true + addReconciliationMapping(startTime: tempBasal.startTime, uuid: tempBasal.uuid, eventRaw: matchedTempBasal.raw, index: index) + } + + // Remove old reconciliation mappings + let expirationCutoff = Date().addingTimeInterval(.hours(-12)) + let recentReconciliationMappings = state.reconciliationMappings.filter { (key, value) -> Bool in + return value.startTime >= expirationCutoff + } + state.reconciliationMappings = recentReconciliationMappings + + // Update any history events to be tracked with uuids from appropriate pending doses. + let allPending = (state.pendingDoses + [state.unfinalizedTempBasal, state.unfinalizedBolus]).compactMap({ $0 }) + var pendingDosesByUUID = Dictionary(uniqueKeysWithValues: allPending.map({ ($0.uuid, $0) })) + let reconciledHistoryEvents = events.map({ (event) -> NewPumpEvent in + guard let mapping = recentReconciliationMappings[event.raw], let pendingDose = pendingDosesByUUID[mapping.uuid] else { + return event + } + pendingDosesByUUID.removeValue(forKey: mapping.uuid) + // We want to be using pump event date for reconciled events, but NS won't let us update startTime + return event.replacingAttributes(raw: mapping.uuid.asRaw, date: pendingDose.startTime, duration: pendingDose.duration, mutable: !pendingDose.isFinished) + }) + + state.lastReconciliation = Date() - let recentReconciliationMappings = self.state.reconciliationMappings.filter { (key, value) -> Bool in - return value.startTime >= expirationCutoff + mergedPumpEvents = reconciledHistoryEvents + pendingDosesByUUID.values.map { NewPumpEvent($0) } } - self.state.reconciliationMappings = recentReconciliationMappings - - let mappings = self.state.reconciliationMappings - return events.map({ (event) -> NewPumpEvent in - guard let mapping = mappings[event.raw] else { - return event - } - // We want to be using pump event date for reconciled events, but NS won't let us update startTime - return event.replacingRawAndDate(newRaw: mapping.uuid.asRaw, newDate: mapping.startTime) - }) + return mergedPumpEvents } private var pendingDosesForStorage: [NewPumpEvent] { // Must be called from the sessionQueue - return (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ (dose) in - guard let dose = dose, !dose.isReconciledWithHistory else { - return nil - } - return NewPumpEvent(dose) - }) + return (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent }) } /// Polls the pump for new history events and passes them to the loop manager @@ -522,12 +552,24 @@ extension MinimedPumpManager { preconditionFailure("pumpManagerDelegate cannot be nil") } - delegate.pumpManager(self, hasNewPumpEvents: reconciledEvents + self.pendingDosesForStorage, lastReconciliation: self.lastReconciliation, completion: { (error) in + delegate.pumpManager(self, hasNewPumpEvents: reconciledEvents, lastReconciliation: self.lastReconciliation, completion: { (error) in // Called on an unknown queue by the delegate if error == nil { self.recents.lastAddedPumpEvents = Date() + + self.setState({ (state) in + // Remove any pending doses that have been reconciled and are finished + if let bolus = state.unfinalizedBolus, bolus.isReconciledWithHistory, bolus.isFinished { + state.unfinalizedBolus = nil + } + if let tempBasal = state.unfinalizedTempBasal, tempBasal.isReconciledWithHistory, tempBasal.isFinished { + state.unfinalizedTempBasal = nil + } + state.pendingDoses.removeAll(where: { (dose) -> Bool in + return dose.isReconciledWithHistory && dose.isFinished + }) + }) } - completion(error) }) }) @@ -600,7 +642,9 @@ extension MinimedPumpManager { return state.preferredInsulinDataSource } set { - state.preferredInsulinDataSource = newValue + setState { (state) in + state.preferredInsulinDataSource = newValue + } } } @@ -610,7 +654,9 @@ extension MinimedPumpManager { return state.batteryChemistry } set { - state.batteryChemistry = newValue + setState { (state) in + state.batteryChemistry = newValue + } } } @@ -619,6 +665,7 @@ extension MinimedPumpManager { // MARK: - PumpManager extension MinimedPumpManager: PumpManager { + public static let managerIdentifier: String = "Minimed500" public static let localizedTitle = LocalizedString("Minimed 500/700 Series", comment: "Generic title of the minimed pump manager") @@ -660,12 +707,10 @@ extension MinimedPumpManager: PumpManager { public var lastReconciliation: Date? { return state.lastReconciliation } - - public var status: PumpManagerStatus { - let state = self.state - let recents = self.recents + + private func status(for state: MinimedPumpManagerState, recents: MinimedPumpManagerRecents) -> PumpManagerStatus { let basalDeliveryState: PumpManagerStatus.BasalDeliveryState - + switch recents.suspendEngageState { case .engaging: basalDeliveryState = .suspending @@ -682,7 +727,7 @@ extension MinimedPumpManager: PumpManager { case .suspended(let date): basalDeliveryState = .suspended(date) case .resumed(let date): - if let tempBasal = state.unfinalizedTempBasal, !tempBasal.finished { + if let tempBasal = state.unfinalizedTempBasal, !tempBasal.isFinished { basalDeliveryState = .tempBasal(DoseEntry(tempBasal)) } else { basalDeliveryState = .active(date) @@ -690,22 +735,22 @@ extension MinimedPumpManager: PumpManager { } } } - + let bolusState: PumpManagerStatus.BolusState - + switch recents.bolusEngageState { case .engaging: bolusState = .initiating case .disengaging: bolusState = .canceling case .stable: - if let bolus = state.unfinalizedBolus, !bolus.finished { + if let bolus = state.unfinalizedBolus, !bolus.isFinished { bolusState = .inProgress(DoseEntry(bolus)) } else { bolusState = .none } } - + return PumpManagerStatus( timeZone: state.timeZone, device: hkDevice, @@ -714,6 +759,14 @@ extension MinimedPumpManager: PumpManager { bolusState: bolusState ) } + + public var status: PumpManagerStatus { + // Acquire the locks just once + let state = self.state + let recents = self.recents + + return status(for: state, recents: recents) + } public var rawState: PumpManager.RawStateValue { return state.rawValue @@ -805,9 +858,11 @@ extension MinimedPumpManager: PumpManager { date = newDate } - if case .resumed = self.state.suspendState, status.suspended { - self.state.suspendState = .suspended(Date()) - } + self.setState({ (state) in + if case .resumed = state.suspendState, status.suspended { + state.suspendState = .suspended(Date()) + } + }) self.recents.latestPumpStatus = status @@ -840,11 +895,14 @@ extension MinimedPumpManager: PumpManager { } if let unfinalizedBolus = self.state.unfinalizedBolus { - guard unfinalizedBolus.finished else { + guard unfinalizedBolus.isFinished else { completion(.failure(SetBolusError.certain(PumpManagerError.deviceState(MinimedPumpManagerError.bolusInProgress)))) return } - self.state.pendingDoses.append(unfinalizedBolus) + + self.setState({ (state) in + state.pendingDoses.append(unfinalizedBolus) + }) } self.recents.bolusEngageState = .engaging @@ -882,7 +940,7 @@ extension MinimedPumpManager: PumpManager { do { if case .suspended = self.state.suspendState { do { - try self.runSuspendResumeOnSession(state: .resume, session: session) + try self.runSuspendResumeOnSession(suspendResumeState: .resume, session: session) } catch let error as PumpOpsError { self.log.error("Failed to resume pump for bolus: %{public}@", String(describing: error)) completion(.failure(SetBolusError.certain(error))) @@ -915,7 +973,9 @@ extension MinimedPumpManager: PumpManager { let doseStart = Date().addingTimeInterval(commsOffset) let dose = UnfinalizedDose(bolusAmount: enactUnits, startTime: doseStart, duration: deliveryTime) - self.state.unfinalizedBolus = dose + self.setState({ (state) in + state.unfinalizedBolus = dose + }) self.recents.bolusEngageState = .stable self.storePendingPumpEvents({ (error) in @@ -959,23 +1019,28 @@ extension MinimedPumpManager: PumpManager { let startDate = endDate.addingTimeInterval(-duration) let dose = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: startDate, duration: duration) - - if duration < .ulpOfOne { - self.state.unfinalizedTempBasal?.cancel(at: startDate) - if let previousTempBasal = self.state.unfinalizedTempBasal, !previousTempBasal.isReconciledWithHistory { - self.state.pendingDoses.append(previousTempBasal) - } - self.state.suspendState = .resumed(startDate) - } - - // If we were successful, then we know we aren't suspended - if case .suspended = self.state.suspendState { - self.state.suspendState = .resumed(Date()) - } - - self.state.unfinalizedTempBasal = dose + self.recents.tempBasalEngageState = .stable + // If we were successful, then we know we aren't suspended + self.setState({ (state) in + if case .suspended = state.suspendState { + state.suspendState = .resumed(startDate) + } + + let pumpModel = state.pumpModel + + state.unfinalizedTempBasal?.cancel(at: startDate, pumpModel: pumpModel) + if let previousTempBasal = state.unfinalizedTempBasal { + state.pendingDoses.append(previousTempBasal) + } + + if duration < .ulpOfOne { + state.unfinalizedTempBasal = nil + } else { + state.unfinalizedTempBasal = dose + } + }) self.storePendingPumpEvents({ (error) in completion(.success(DoseEntry(dose))) @@ -989,9 +1054,11 @@ extension MinimedPumpManager: PumpManager { if case .arguments(.pumpError(.commandRefused)) = error { do { let status = try session.getCurrentPumpStatus() - if case .resumed = self.state.suspendState, status.suspended { - self.state.suspendState = .suspended(Date()) - } + self.setState({ (state) in + if case .resumed = state.suspendState, status.suspended { + state.suspendState = .suspended(Date()) + } + }) self.recents.latestPumpStatus = status } catch { self.log.error("Post-basal suspend state fetch failed: %{public}@", String(describing: error)) @@ -1032,7 +1099,7 @@ extension MinimedPumpManager: PumpManager { } public func createBolusProgressReporter(reportingOn dispatchQueue: DispatchQueue) -> DoseProgressReporter? { - if let bolus = self.state.unfinalizedBolus, !bolus.finished { + if let bolus = self.state.unfinalizedBolus, !bolus.isFinished { return MinimedDoseProgressEstimator(dose: DoseEntry(bolus), pumpModel: state.pumpModel, reportingQueue: dispatchQueue) } return nil @@ -1041,7 +1108,9 @@ extension MinimedPumpManager: PumpManager { extension MinimedPumpManager: PumpOpsDelegate { public func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) { - self.state.pumpState = state + setState { (pumpManagerState) in + pumpManagerState.pumpState = state + } } } diff --git a/MinimedKit/PumpManager/MinimedPumpManagerState.swift b/MinimedKit/PumpManager/MinimedPumpManagerState.swift index 45eea791a..04faa8971 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerState.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerState.swift @@ -25,7 +25,6 @@ extension ReconciledDoseMapping: RawRepresentable { let uuid = UUID(uuidString: uuidString), let eventRawString = rawValue["eventRaw"] as? String, let eventRaw = Data(hexadecimalString: eventRawString) else - { return nil } diff --git a/MinimedKit/PumpManager/UnfinalizedDose.swift b/MinimedKit/PumpManager/UnfinalizedDose.swift index c3879cc0c..2188d82f6 100644 --- a/MinimedKit/PumpManager/UnfinalizedDose.swift +++ b/MinimedKit/PumpManager/UnfinalizedDose.swift @@ -26,7 +26,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti let startTime: Date var duration: TimeInterval var isReconciledWithHistory: Bool - var uniqueId: UUID + var uuid: UUID var finishTime: Date { get { @@ -42,7 +42,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti return min(elapsed / duration, 1) } - public var finished: Bool { + public var isFinished: Bool { return progress >= 1 } @@ -56,7 +56,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti } public var finalizedUnits: Double? { - guard finished else { + guard isFinished else { return nil } return units @@ -69,7 +69,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.duration = duration self.programmedUnits = nil self.isReconciledWithHistory = isReconciledWithHistory - self.uniqueId = UUID() + self.uuid = UUID() } init(tempBasalRate: Double, startTime: Date, duration: TimeInterval, isReconciledWithHistory: Bool = false) { @@ -79,7 +79,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.duration = duration self.programmedUnits = nil self.isReconciledWithHistory = isReconciledWithHistory - self.uniqueId = UUID() + self.uuid = UUID() } init(suspendStartTime: Date, isReconciledWithHistory: Bool = false) { @@ -88,7 +88,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.startTime = suspendStartTime self.duration = 0 self.isReconciledWithHistory = isReconciledWithHistory - self.uniqueId = UUID() + self.uuid = UUID() } init(resumeStartTime: Date, isReconciledWithHistory: Bool = false) { @@ -97,23 +97,24 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.startTime = resumeStartTime self.duration = 0 self.isReconciledWithHistory = isReconciledWithHistory - self.uniqueId = UUID() + self.uuid = UUID() } - public mutating func cancel(at date: Date) { + public mutating func cancel(at date: Date, pumpModel: PumpModel) { guard date < finishTime else { return } - - programmedUnits = units + + let programmedUnits = units + self.programmedUnits = programmedUnits let newDuration = date.timeIntervalSince(startTime) switch doseType { case .bolus: - units = rate * newDuration.hours + (units,_) = pumpModel.estimateBolusProgress(elapsed: newDuration, programmedUnits: programmedUnits) case .tempBasal: programmedTempRate = rate - units = floor(rate * newDuration.hours * 20) / 20 + (units,_) = pumpModel.estimateTempBasalProgress(unitsPerHour: rate, duration: duration, elapsed: newDuration) default: break } @@ -156,14 +157,14 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti self.programmedTempRate = scheduledTempRate } - if let uuidString = rawValue["uniqueId"] as? String { + if let uuidString = rawValue["uuid"] as? String { if let uuid = UUID(uuidString: uuidString) { - self.uniqueId = uuid + self.uuid = uuid } else { return nil } } else { - self.uniqueId = UUID() + self.uuid = UUID() } self.isReconciledWithHistory = rawValue["isReconciledWithHistory"] as? Bool ?? false @@ -176,7 +177,7 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti "startTime": startTime, "duration": duration, "isReconciledWithHistory": isReconciledWithHistory, - "uniqueId": uniqueId.uuidString, + "uuid": uuid.uuidString, ] if let scheduledUnits = programmedUnits { @@ -191,17 +192,23 @@ public struct UnfinalizedDose: RawRepresentable, Equatable, CustomStringConverti } } +extension UnfinalizedDose { + var newPumpEvent: NewPumpEvent { + return NewPumpEvent(self) + } +} + extension NewPumpEvent { init(_ dose: UnfinalizedDose) { let title = String(describing: dose) let entry = DoseEntry(dose) - let raw = dose.uniqueId.asRaw - self.init(date: dose.startTime, dose: entry, isMutable: true, raw: raw, title: title) + let raw = dose.uuid.asRaw + self.init(date: dose.startTime, dose: entry, isMutable: !dose.isFinished, raw: raw, title: title) } - func replacingRawAndDate(newRaw: Data, newDate: Date) -> NewPumpEvent { - let newDose = dose?.replacingStartDate(newDate) - return NewPumpEvent(date: newDate, dose: newDose, isMutable: isMutable, raw: newRaw, title: title, type: type) + func replacingAttributes(raw newRaw: Data, date newDate: Date, duration newDuration: TimeInterval, mutable: Bool) -> NewPumpEvent { + let newDose = dose?.replacingAttributes(startDate: newDate, duration: newDuration) + return NewPumpEvent(date: newDate, dose: newDose, isMutable: mutable, raw: newRaw, title: title, type: type) } } @@ -219,7 +226,7 @@ extension DoseEntry { } } - func replacingStartDate(_ newStartDate: Date) -> DoseEntry { + func replacingAttributes(startDate newStartDate: Date, duration newDuration: TimeInterval) -> DoseEntry { let value: Double switch unit { case .units: @@ -227,7 +234,7 @@ extension DoseEntry { case .unitsPerHour: value = unitsPerHour } - let newEndDate = max(newStartDate, endDate) + let newEndDate = newStartDate.addingTimeInterval(newDuration) return DoseEntry(type: type, startDate: newStartDate, endDate: newEndDate, value: value, unit: unit, deliveredUnits: deliveredUnits, description: description, syncIdentifier: syncIdentifier) } } diff --git a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift index 43ae01317..c066ed05b 100644 --- a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift @@ -20,9 +20,9 @@ public class TempBasalNightscoutTreatment: NightscoutTreatment { let amount: Double? let absolute: Double? let temp: RateType - let duration: Int + let duration: TimeInterval - public init(timestamp: Date, enteredBy: String, temp: RateType, rate: Double, absolute: Double?, duration: Int, amount: Double? = nil, id: String? = nil) { + public init(timestamp: Date, enteredBy: String, temp: RateType, rate: Double, absolute: Double?, duration: TimeInterval, amount: Double? = nil, id: String? = nil) { self.rate = rate self.absolute = absolute self.temp = temp @@ -38,7 +38,7 @@ public class TempBasalNightscoutTreatment: NightscoutTreatment { rval["temp"] = temp.rawValue rval["rate"] = rate rval["absolute"] = absolute - rval["duration"] = duration + rval["duration"] = duration.minutes rval["amount"] = amount return rval } From 9bc0d1a3b58484e133885854374368a2a597f1ca Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 9 Sep 2019 09:22:41 -0500 Subject: [PATCH 42/71] Make quick attempt via getStatus to reconcile uncertain bolus (#544) * Fetch status after uncertain bolus, and allow subsequent status requests * Update podState correctly when resolving bolus uncertainty --- OmniKit/PumpManager/OmnipodPumpManager.swift | 29 ++++++++++++-------- OmniKit/PumpManager/PodCommsSession.swift | 25 +++++++++++++---- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 4ead99318..e479cec40 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -661,7 +661,7 @@ extension OmnipodPumpManager { // MARK: - Pump Commands private func getPodStatus(storeDosesOnSuccess: Bool, completion: ((_ result: PumpManagerResult) -> Void)? = nil) { - guard state.podState?.unfinalizedBolus?.isFinished != false else { + guard state.podState?.unfinalizedBolus?.scheduledCertainty == .uncertain || state.podState?.unfinalizedBolus?.isFinished != false else { self.log.info("Skipping status request due to unfinalized bolus in progress.") completion?(.failure(PodCommsError.unfinalizedBolus)) return @@ -1182,6 +1182,15 @@ extension OmnipodPumpManager: PumpManager { completion(.failure(SetBolusError.certain(error))) return } + + defer { + self.setState({ (state) in + state.bolusEngageState = .stable + }) + } + self.setState({ (state) in + state.bolusEngageState = .engaging + }) var podStatus: StatusResponse @@ -1208,16 +1217,6 @@ extension OmnipodPumpManager: PumpManager { return } - // TODO: Move this to the top, since Loop is expecting a status change to cancel its loading indicator? - defer { - self.setState({ (state) in - state.bolusEngageState = .stable - }) - } - self.setState({ (state) in - state.bolusEngageState = .engaging - }) - let date = Date() let endDate = date.addingTimeInterval(enactUnits / Pod.bolusDeliveryRate) let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: enactUnits, unit: .units) @@ -1466,6 +1465,14 @@ extension OmnipodPumpManager: MessageLogger { extension OmnipodPumpManager: PodCommsDelegate { func podComms(_ podComms: PodComms, didChange podState: PodState) { setState { (state) in + // Check for any updates to bolus certainty, and log them + if let bolus = state.podState?.unfinalizedBolus, bolus.scheduledCertainty == .uncertain, !bolus.isFinished { + if podState.unfinalizedBolus?.scheduledCertainty == .some(.certain) { + self.log.debug("Resolved bolus uncertainty: did bolus") + } else if podState.unfinalizedBolus == nil { + self.log.debug("Resolved bolus uncertainty: did not bolus") + } + } state.podState = podState } } diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 6498f5068..a15813998 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -410,19 +410,33 @@ public class PodCommsSession { return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) } - // 17 0d 00 0064 0001 86a0000000000000 + // Between bluetooth and the radio and firmware, about 1.2s on average passes before we start tracking + let commsOffset = TimeInterval(seconds: -1.5) + let bolusExtraCommand = BolusExtraCommand(units: units, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) do { - // Between bluetooth and the radio and firmware, about 1.2s on average passes before we start tracking - let commsOffset = TimeInterval(seconds: -1.5) let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date().addingTimeInterval(commsOffset), scheduledCertainty: .certain) return DeliveryCommandResult.success(statusResponse: statusResponse) } catch PodCommsError.nonceResyncFailed { return DeliveryCommandResult.certainFailure(error: PodCommsError.nonceResyncFailed) } catch let error { - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), scheduledCertainty: .uncertain) - return DeliveryCommandResult.uncertainFailure(error: error as? PodCommsError ?? PodCommsError.commsError(error: error)) + self.log.debug("Uncertain result bolusing") + // Attempt to verify bolus + let podCommsError = error as? PodCommsError ?? PodCommsError.commsError(error: error) + guard let status = try? getStatus() else { + self.log.debug("Status check failed; could not resolve bolus uncertainty") + podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), scheduledCertainty: .uncertain) + return DeliveryCommandResult.uncertainFailure(error: podCommsError) + } + if status.deliveryStatus.bolusing { + self.log.debug("getStatus resolved bolus uncertainty (succeeded)") + podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date().addingTimeInterval(commsOffset), scheduledCertainty: .certain) + return DeliveryCommandResult.success(statusResponse: status) + } else { + self.log.debug("getStatus resolved bolus uncertainty (failed)") + return DeliveryCommandResult.certainFailure(error: podCommsError) + } } } @@ -559,6 +573,7 @@ public class PodCommsSession { return statusResponse } + @discardableResult public func getStatus() throws -> StatusResponse { if useCancelNoneForStatus { return try cancelNone() // functional replacement for getStatus() From 5b517f3f0dfc251d95296c1dbbe7af4f675be9eb Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 9 Sep 2019 12:18:42 -0500 Subject: [PATCH 43/71] UnfinalizedDose should remain mutable until reconciled (#546) --- MinimedKit/PumpManager/UnfinalizedDose.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinimedKit/PumpManager/UnfinalizedDose.swift b/MinimedKit/PumpManager/UnfinalizedDose.swift index 2188d82f6..e342cdf5c 100644 --- a/MinimedKit/PumpManager/UnfinalizedDose.swift +++ b/MinimedKit/PumpManager/UnfinalizedDose.swift @@ -203,7 +203,7 @@ extension NewPumpEvent { let title = String(describing: dose) let entry = DoseEntry(dose) let raw = dose.uuid.asRaw - self.init(date: dose.startTime, dose: entry, isMutable: !dose.isFinished, raw: raw, title: title) + self.init(date: dose.startTime, dose: entry, isMutable: !dose.isFinished || !dose.isReconciledWithHistory, raw: raw, title: title) } func replacingAttributes(raw newRaw: Data, date newDate: Date, duration newDuration: TimeInterval, mutable: Bool) -> NewPumpEvent { From f171e97de62ed382180c2d82aa6d24607aec964d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 9 Sep 2019 19:40:32 -0500 Subject: [PATCH 44/71] Fix incorrect use of floor() where round() should be used. (#547) --- MinimedKit/Models/PumpModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 4ee8fa835..1f6674276 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -161,7 +161,7 @@ public enum PumpModel: String { } public func estimateTempBasalProgress(unitsPerHour: Double, duration: TimeInterval, elapsed: TimeInterval) -> (deliveredUnits: Double, progress: Double) { - let roundedVolume = floor(unitsPerHour * elapsed.hours * Double(pulsesPerUnit)) / Double(pulsesPerUnit) + let roundedVolume = round(unitsPerHour * elapsed.hours * Double(pulsesPerUnit)) / Double(pulsesPerUnit) return (deliveredUnits: roundedVolume, progress: min(elapsed / duration, 1)) } From 4e37d82e45f87d239bbd160ceacb5eca50d0336e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 13 Sep 2019 17:33:51 -0500 Subject: [PATCH 45/71] Updates for Xcode 11 (#539) * Updates for Xcode 11 * Use systemBackground color for RileyLink image * Dark Mode fixes * More dark mode updates * Use xcode11 branches * Bump carthage revs --- .travis.yml | 4 +- Cartfile.resolved | 2 +- .../Base.lproj/MinimedPumpManager.storyboard | 81 +++++++------ .../MinimedPumpIDSetupViewController.swift | 2 +- ...inimedPumpManagerSetupViewController.swift | 6 +- .../PodLarge.imageset/Contents.json | 6 +- .../PodLarge.imageset/PodLarge@1x.png | Bin 0 -> 31370 bytes .../PodLarge.imageset/PodLarge@2x.png | Bin 0 -> 105837 bytes .../PodLarge.imageset/PodLarge@3x.png | Bin 0 -> 217886 bytes .../PodLarge.imageset/pod_1x-1.png | Bin 67130 -> 0 bytes .../PodLarge.imageset/pod_1x-2.png | Bin 19537 -> 0 bytes .../PodLarge.imageset/pod_1x.png | Bin 138788 -> 0 bytes OmniKitUI/OmnipodPumpManager.storyboard | 108 ++++++++++-------- .../PumpManager/OmnipodHUDProvider.swift | 12 +- .../InsertCannulaSetupViewController.swift | 2 +- ...mnipodPumpManagerSetupViewController.swift | 6 +- .../OmnipodSettingsViewController.swift | 12 +- .../PodReplacementNavigationController.swift | 7 +- .../PodSetupCompleteViewController.swift | 1 - .../ExpirationReminderDateTableViewCell.swift | 27 ++++- .../ExpirationReminderDateTableViewCell.xib | 31 +++-- .../pod_life/pod_life.imageset/Contents.json | 4 +- .../pod_life/pod_life.imageset/Pod Life.pdf | Bin 0 -> 12468 bytes .../pod_life/pod_life.imageset/PodLifeBG.pdf | Bin 3551 -> 0 bytes OmniKitUI/Views/OmnipodReservoirView.xib | 10 +- OmniKitUI/Views/PodLifeHUDView.swift | 24 +++- OmniKitUI/Views/PodLifeHUDView.xib | 19 ++- RileyLink.xcodeproj/project.pbxproj | 4 + .../View Controllers/MainViewController.swift | 4 +- .../RileyLinkDevicesHeaderView.swift | 2 +- .../RileyLinkManagerSetupViewController.swift | 7 +- .../RileyLinkSetupTableViewController.swift | 8 +- RileyLinkKitUI/SetupImageTableViewCell.xib | 13 +-- RileyLinkKitUI/UIActivityIndicatorView.swift | 19 +++ 34 files changed, 259 insertions(+), 162 deletions(-) create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@1x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@2x.png create mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@3x.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-2.png delete mode 100644 OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png create mode 100644 OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Pod Life.pdf delete mode 100644 OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/PodLifeBG.pdf create mode 100644 RileyLinkKitUI/UIActivityIndicatorView.swift diff --git a/.travis.yml b/.travis.yml index 8090dcef2..d80d63f88 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: objective-c -osx_image: xcode10.2 +osx_image: xcode11 before_script: - carthage bootstrap script: - - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone SE' test | xcpretty + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone 6' test | xcpretty diff --git a/Cartfile.resolved b/Cartfile.resolved index c3055a000..b3090c05f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "b47d60e6ed202ca30e4adc63c6d237ac100e72b4" +github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" diff --git a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard index 497d769ce..62b2593aa 100644 --- a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard +++ b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -16,7 +14,7 @@ - + @@ -38,12 +36,12 @@ - + - + @@ -51,8 +49,10 @@ + + @@ -61,13 +61,14 @@ + - + @@ -76,7 +77,7 @@ @@ -96,6 +97,7 @@ + @@ -106,7 +108,7 @@ - + @@ -114,7 +116,6 @@ - @@ -134,13 +135,14 @@ + - + @@ -148,7 +150,6 @@ - @@ -157,9 +158,10 @@ + - + @@ -174,6 +176,7 @@ + @@ -220,12 +223,12 @@ - + - + @@ -240,13 +243,14 @@ + - + @@ -263,7 +267,7 @@ - + @@ -280,7 +284,7 @@ - + @@ -297,7 +301,7 @@ - + @@ -314,7 +318,7 @@ - + @@ -335,7 +339,7 @@ - + @@ -343,7 +347,6 @@ - @@ -352,9 +355,10 @@ + - + @@ -369,6 +373,7 @@ + @@ -395,12 +400,12 @@ - + - + @@ -415,9 +420,10 @@ + - + @@ -433,9 +439,10 @@ + - + @@ -450,6 +457,7 @@ + @@ -474,10 +482,10 @@ - + - + @@ -492,6 +500,7 @@ + @@ -515,12 +524,12 @@ - + - + @@ -538,9 +547,10 @@ + - + @@ -555,6 +565,7 @@ + @@ -595,7 +606,7 @@ - + diff --git a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift index d27e924d9..b7effb9a5 100644 --- a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift @@ -34,7 +34,7 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { private var pumpRegionCode: RegionCode? { didSet { regionAndColorPickerCell.regionLabel.text = pumpRegionCode?.region.description - regionAndColorPickerCell.regionLabel.textColor = .darkText + regionAndColorPickerCell.regionLabel.textColor = nil updateStateForSettings() } diff --git a/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift index edfe67fdf..af9f6886b 100644 --- a/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpManagerSetupViewController.swift @@ -23,7 +23,11 @@ public class MinimedPumpManagerSetupViewController: RileyLinkManagerSetupViewCon override public func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .white + if #available(iOSApplicationExtension 13.0, *) { + view.backgroundColor = .systemBackground + } else { + view.backgroundColor = .white + } navigationBar.shadowImage = UIImage() if let pumpIDSetupVC = topViewController as? MinimedPumpIDSetupViewController, let rileyLinkPumpManager = rileyLinkPumpManager { diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json index c1722d577..1249161af 100644 --- a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json +++ b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "pod_1x-2.png", + "filename" : "PodLarge@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "pod_1x-1.png", + "filename" : "PodLarge@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "pod_1x.png", + "filename" : "PodLarge@3x.png", "scale" : "3x" } ], diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@1x.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..5ef79805723165d00d74147873111848fcc9c9ac GIT binary patch literal 31370 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@2x.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..92e6ef21e07b77e791e11cb38a4aef44581dbc8a GIT binary patch literal 105837 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Ð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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png deleted file mode 100644 index b89405d93442b07eb1116c7463790e9583af9470..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67130 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 z

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 Qj{pDw07*qoM6N<$g7mTL7ytkO diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png deleted file mode 100644 index 086e4a4c9bb964e24d12d0380be767ae4e32c86c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138788 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 z 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 diff --git a/OmniKitUI/OmnipodPumpManager.storyboard b/OmniKitUI/OmnipodPumpManager.storyboard index a0c36d97b..f606b65ad 100644 --- a/OmniKitUI/OmnipodPumpManager.storyboard +++ b/OmniKitUI/OmnipodPumpManager.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -48,7 +46,7 @@ - + @@ -68,23 +66,24 @@ - + - + - + - + + @@ -93,13 +92,14 @@ + - + @@ -113,6 +113,7 @@ + @@ -120,7 +121,7 @@ - + @@ -128,9 +129,10 @@ - + + @@ -141,7 +143,7 @@ - + @@ -155,6 +157,7 @@ + @@ -182,23 +185,24 @@ - + - + - + - + + @@ -213,7 +217,7 @@ - + @@ -227,6 +231,7 @@ + @@ -234,7 +239,7 @@ - + @@ -242,9 +247,10 @@ - + + @@ -255,7 +261,7 @@ - + @@ -269,6 +275,7 @@ + @@ -296,23 +303,24 @@ - + - + - + - + + @@ -323,7 +331,7 @@ - + @@ -336,6 +344,7 @@ + @@ -343,24 +352,25 @@ - + - + + - + @@ -372,17 +382,16 @@ - + @@ -399,14 +408,12 @@ - - - + @@ -448,10 +455,10 @@ - + - + @@ -466,6 +473,7 @@ + @@ -536,23 +544,24 @@ - + - + - + - + + @@ -567,7 +576,7 @@ - + @@ -581,6 +590,7 @@ + @@ -588,7 +598,7 @@ - + @@ -596,9 +606,10 @@ - + + @@ -609,7 +620,7 @@ - + @@ -623,6 +634,7 @@ + @@ -648,7 +660,7 @@ - + diff --git a/OmniKitUI/PumpManager/OmnipodHUDProvider.swift b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift index 41a6881ba..7cffde018 100644 --- a/OmniKitUI/PumpManager/OmnipodHUDProvider.swift +++ b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift @@ -159,7 +159,7 @@ internal class OmnipodHUDProvider: NSObject, HUDProvider, PodStateObserver { rawValue["alerts"] = podState.activeAlerts.values.map { $0.rawValue } } - if let lastInsulinMeasurements = podState?.lastInsulinMeasurements, lastInsulinMeasurements.reservoirVolume != nil { + if let lastInsulinMeasurements = podState?.lastInsulinMeasurements { rawValue["reservoirVolume"] = lastInsulinMeasurements.reservoirVolume rawValue["validTime"] = lastInsulinMeasurements.validTime } @@ -175,15 +175,13 @@ internal class OmnipodHUDProvider: NSObject, HUDProvider, PodStateObserver { return [] } - let reservoirView: OmnipodReservoirView? - let alerts = rawAlerts.compactMap { PodAlert.init(rawValue: $0) } let reservoirVolume = rawValue["reservoirVolume"] as? Double let validTime = rawValue["validTime"] as? Date + let reservoirView = OmnipodReservoirView.instantiate() if let validTime = validTime { - reservoirView = OmnipodReservoirView.instantiate() let reservoirLevel = reservoirVolume?.asReservoirPercentage() var reservoirAlertState: ReservoirAlertState = .ok for alert in alerts { @@ -191,15 +189,13 @@ internal class OmnipodHUDProvider: NSObject, HUDProvider, PodStateObserver { reservoirAlertState = .lowReservoir } } - reservoirView!.update(volume: reservoirVolume, at: validTime, level: reservoirLevel, reservoirAlertState: reservoirAlertState) - } else { - reservoirView = nil + reservoirView.update(volume: reservoirVolume, at: validTime, level: reservoirLevel, reservoirAlertState: reservoirAlertState) } let podLifeHUDView = PodLifeHUDView.instantiate() podLifeHUDView.setPodLifeCycle(startTime: podActivatedAt, lifetime: lifetime) - return [reservoirView, podLifeHUDView].compactMap({ $0 }) + return [reservoirView, podLifeHUDView] } func podStateDidUpdate(_ podState: PodState?) { diff --git a/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift b/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift index e5aea5951..628b881a9 100644 --- a/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift +++ b/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift @@ -148,7 +148,7 @@ class InsertCannulaSetupViewController: SetupTableViewController { present(confirmVC, animated: true) {} } - func insertCannula() { + private func insertCannula() { pumpManager.insertCannula() { (result) in DispatchQueue.main.async { switch(result) { diff --git a/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift b/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift index 4a6001172..76f3617e2 100644 --- a/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift @@ -26,7 +26,11 @@ public class OmnipodPumpManagerSetupViewController: RileyLinkManagerSetupViewCon override public func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .white + if #available(iOSApplicationExtension 13.0, *) { + view.backgroundColor = .systemBackground + } else { + view.backgroundColor = .white + } navigationBar.shadowImage = UIImage() if let omnipodPairingViewController = topViewController as? PairPodSetupViewController, let rileyLinkPumpManager = rileyLinkPumpManager { diff --git a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift index 3f5701044..fef475268 100644 --- a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -92,7 +92,12 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { imageView.contentMode = .center imageView.frame.size.height += 18 // feels right tableView.tableHeaderView = imageView - tableView.tableHeaderView?.backgroundColor = UIColor.white + + if #available(iOSApplicationExtension 13.0, *) { + tableView.tableHeaderView?.backgroundColor = .systemBackground + } else { + tableView.tableHeaderView?.backgroundColor = UIColor.white + } let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTapped(_:))) self.navigationItem.setRightBarButton(button, animated: false) @@ -345,7 +350,6 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { cell.datePicker.minimumDate = podState.expiresAt?.addingTimeInterval(-Pod.expirationReminderAlertMaxTimeBeforeExpiration) cell.datePicker.minuteInterval = 1 cell.delegate = self - print("cell selection style: \(cell.selectionStyle)") } return cell case .timeZoneOffset: @@ -628,8 +632,8 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { extension OmnipodSettingsViewController: CompletionDelegate { func completionNotifyingDidComplete(_ object: CompletionNotifying) { - if let vc = object as? UIViewController { - vc.dismiss(animated: false, completion: nil) + if let vc = object as? UIViewController, vc === presentedViewController { + dismiss(animated: true, completion: nil) } } } diff --git a/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift b/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift index b1b46ea02..19d6fc25a 100644 --- a/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift +++ b/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift @@ -36,6 +36,11 @@ class PodReplacementNavigationController: UINavigationController, UINavigationCo override func viewDidLoad() { super.viewDidLoad() + + if #available(iOSApplicationExtension 13.0, *) { + // Prevent interactive dismissal + isModalInPresentation = true + } delegate = self } @@ -68,6 +73,6 @@ class PodReplacementNavigationController: UINavigationController, UINavigationCo extension PodReplacementNavigationController: SetupTableViewControllerDelegate { func setupTableViewControllerCancelButtonPressed(_ viewController: SetupTableViewController) { - self.dismiss(animated: true, completion: nil) + completionDelegate?.completionNotifyingDidComplete(self) } } diff --git a/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift index 06dff65ef..c5303901a 100644 --- a/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift +++ b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift @@ -48,7 +48,6 @@ class PodSetupCompleteViewController: SetupTableViewController { } override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { - print("willSelectRowAt") tableView.beginUpdates() return indexPath } diff --git a/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift index b34f3d055..e6a666f8e 100644 --- a/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift +++ b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift @@ -13,9 +13,32 @@ public class ExpirationReminderDateTableViewCell: DatePickerTableViewCell { public weak var delegate: DatePickerTableViewCellDelegate? - @IBOutlet public weak var titleLabel: UILabel! + @IBOutlet public weak var titleLabel: UILabel! { + didSet { + // Setting this color in code because the nib isn't being applied correctly + if #available(iOSApplicationExtension 13.0, *) { + titleLabel?.textColor = .label + } + } + } + + @IBOutlet public weak var dateLabel: UILabel! { + didSet { + // Setting this color in code because the nib isn't being applied correctly + if #available(iOSApplicationExtension 13.0, *) { + dateLabel?.textColor = .secondaryLabel + } - @IBOutlet public weak var dateLabel: UILabel! + switch effectiveUserInterfaceLayoutDirection { + case .leftToRight: + dateLabel?.textAlignment = .right + case .rightToLeft: + dateLabel?.textAlignment = .left + @unknown default: + dateLabel?.textAlignment = .right + } + } + } var maximumDate: Date? { set { diff --git a/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib index 27f62e7fc..9379825d0 100644 --- a/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib +++ b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,26 +13,27 @@ - + - + - - @@ -51,9 +50,6 @@ - - - @@ -75,6 +71,7 @@ + diff --git a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json index dd4164b76..bd5c9bd61 100644 --- a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json +++ b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "PodLifeBG.pdf" + "filename" : "Pod Life.pdf" } ], "info" : { @@ -10,7 +10,7 @@ "author" : "xcode" }, "properties" : { - "template-rendering-intent" : "original", + "template-rendering-intent" : "template", "preserves-vector-representation" : true } } \ No newline at end of file diff --git a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Pod Life.pdf b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Pod Life.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c2ce517159ab2e340bc274affd13ae67db9a2b6f GIT binary patch literal 12468 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 literal 0 HcmV?d00001 diff --git a/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/PodLifeBG.pdf b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/PodLifeBG.pdf deleted file mode 100644 index 51ca20cba7af7787b60954859830c99546a75f9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3551 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 WRG(l7B~?Tqfe;ym!L0LiLj4cte>ivm diff --git a/OmniKitUI/Views/OmnipodReservoirView.xib b/OmniKitUI/Views/OmnipodReservoirView.xib index cfc1d18d6..8d8443097 100644 --- a/OmniKitUI/Views/OmnipodReservoirView.xib +++ b/OmniKitUI/Views/OmnipodReservoirView.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -18,7 +16,6 @@ @@ -40,7 +37,6 @@ - diff --git a/OmniKitUI/Views/PodLifeHUDView.swift b/OmniKitUI/Views/PodLifeHUDView.swift index 01bf0a80f..25a298ea8 100644 --- a/OmniKitUI/Views/PodLifeHUDView.swift +++ b/OmniKitUI/Views/PodLifeHUDView.swift @@ -22,7 +22,14 @@ public class PodLifeHUDView: BaseHUDView, NibLoadable { return 12 } - @IBOutlet private weak var timeLabel: UILabel! + @IBOutlet private weak var timeLabel: UILabel! { + didSet { + // Setting this color in code because the nib isn't being applied correctly + if #available(iOSApplicationExtension 13.0, *) { + timeLabel.textColor = .label + } + } + } @IBOutlet private weak var progressRing: RingProgressView! @IBOutlet private weak var alertLabel: UILabel! { @@ -33,6 +40,15 @@ public class PodLifeHUDView: BaseHUDView, NibLoadable { alertLabel.clipsToBounds = true } } + @IBOutlet private weak var backgroundRing: UIImageView! { + didSet { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + backgroundRing.tintColor = .systemGray5 + } else { + backgroundRing.tintColor = UIColor(red: 198 / 255, green: 199 / 255, blue: 201 / 255, alpha: 1) + } + } + } private var startTime: Date? private var lifetime: TimeInterval? @@ -119,10 +135,10 @@ public class PodLifeHUDView: BaseHUDView, NibLoadable { progressRing.shadowOpacity = 0 } else if progress < 1.0 { self.endColor = stateColors?.warning - progressRing.shadowOpacity = 1 + progressRing.shadowOpacity = 0.5 } else { self.endColor = stateColors?.error - progressRing.shadowOpacity = 1 + progressRing.shadowOpacity = 0.8 } let remaining = (lifetime - age) @@ -130,7 +146,7 @@ public class PodLifeHUDView: BaseHUDView, NibLoadable { // Update time label and caption if alertState == .fault { timeLabel.isHidden = true - caption.text = "Fault" + caption.text = LocalizedString("Fault", comment: "Pod life HUD view label") } else if remaining > .hours(24) { timeLabel.isHidden = true caption.text = LocalizedString("Pod Age", comment: "Label describing pod age view") diff --git a/OmniKitUI/Views/PodLifeHUDView.xib b/OmniKitUI/Views/PodLifeHUDView.xib index 111a4f3ea..701140eff 100644 --- a/OmniKitUI/Views/PodLifeHUDView.xib +++ b/OmniKitUI/Views/PodLifeHUDView.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -18,11 +16,10 @@ - + @@ -37,7 +34,6 @@ - @@ -83,7 +79,7 @@ @@ -92,9 +88,9 @@ - + - + @@ -104,6 +100,7 @@ + diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 4f0e01046..4443a53cc 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -186,6 +186,7 @@ 43EBE4541EAD23EC0073A0B5 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; 43EBE4551EAD24410073A0B5 /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */; }; 43F348061D596270009933DC /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F348051D596270009933DC /* HKUnit.swift */; }; + 43F89CA122BDFB8D006BB54E /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F89CA022BDFB8D006BB54E /* UIActivityIndicatorView.swift */; }; 43FF221C1CB9B9DE00024F30 /* NSDateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */; }; 492526711E4521FB00ACBA5F /* NoteNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492526701E4521FB00ACBA5F /* NoteNightscoutTreatment.swift */; }; 541688DB1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541688DA1DB820BF005B1891 /* ReadCurrentGlucosePageMessageBodyTests.swift */; }; @@ -1019,6 +1020,7 @@ 43DFB61420D3791A008A7BAE /* ChangeMaxBasalRateMessageBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeMaxBasalRateMessageBody.swift; sourceTree = ""; }; 43EBE4501EAD238C0073A0B5 /* TimeInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; 43F348051D596270009933DC /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; + 43F89CA022BDFB8D006BB54E /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; 43FB610A20DDF55E002B996B /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43FB610B20DDF55F002B996B /* LoopKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43FF221B1CB9B9DE00024F30 /* NSDateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateComponents.swift; sourceTree = ""; }; @@ -1909,6 +1911,7 @@ 43709AED20E008F300F941B3 /* SetupImageTableViewCell.swift */, 43709AEF20E0120F00F941B3 /* SetupImageTableViewCell.xib */, C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */, + 43F89CA022BDFB8D006BB54E /* UIActivityIndicatorView.swift */, ); path = RileyLinkKitUI; sourceTree = ""; @@ -3631,6 +3634,7 @@ 435D26B620DA0BCC00891C17 /* RileyLinkDevicesTableViewDataSource.swift in Sources */, 43709AE820DF22E700F941B3 /* UIColor.swift in Sources */, 43D5E79A1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewCell.swift in Sources */, + 43F89CA122BDFB8D006BB54E /* UIActivityIndicatorView.swift in Sources */, 43D5E79F1FAF7C98004ACDB7 /* IdentifiableClass.swift in Sources */, 435D26B420DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift in Sources */, 43709ABF20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift in Sources */, diff --git a/RileyLink/View Controllers/MainViewController.swift b/RileyLink/View Controllers/MainViewController.swift index 1bae1e6a4..2c30b3262 100644 --- a/RileyLink/View Controllers/MainViewController.swift +++ b/RileyLink/View Controllers/MainViewController.swift @@ -208,8 +208,8 @@ class MainViewController: RileyLinkSettingsViewController { extension MainViewController: CompletionDelegate { func completionNotifyingDidComplete(_ object: CompletionNotifying) { - if let vc = object as? UIViewController { - vc.dismiss(animated: true, completion: nil) + if let vc = object as? UIViewController, presentedViewController === vc { + dismiss(animated: true, completion: nil) } } } diff --git a/RileyLinkKitUI/RileyLinkDevicesHeaderView.swift b/RileyLinkKitUI/RileyLinkDevicesHeaderView.swift index 9e5487770..17e61b9bd 100644 --- a/RileyLinkKitUI/RileyLinkDevicesHeaderView.swift +++ b/RileyLinkKitUI/RileyLinkDevicesHeaderView.swift @@ -21,7 +21,7 @@ public class RileyLinkDevicesHeaderView: UITableViewHeaderFooterView, Identifiab setup() } - public let spinner = UIActivityIndicatorView(style: .gray) + public let spinner = UIActivityIndicatorView(style: .default) private func setup() { contentView.addSubview(spinner) diff --git a/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift b/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift index 5bf384b58..e6102afd9 100644 --- a/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift +++ b/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift @@ -27,7 +27,12 @@ open class RileyLinkManagerSetupViewController: UINavigationController, PumpMana open override func viewDidLoad() { super.viewDidLoad() - + + if #available(iOSApplicationExtension 13.0, *) { + // Prevent interactive dismissal + isModalInPresentation = true + } + delegate = self } diff --git a/RileyLinkKitUI/RileyLinkSetupTableViewController.swift b/RileyLinkKitUI/RileyLinkSetupTableViewController.swift index 0adc2db1a..75a52c1f2 100644 --- a/RileyLinkKitUI/RileyLinkSetupTableViewController.swift +++ b/RileyLinkKitUI/RileyLinkSetupTableViewController.swift @@ -94,7 +94,9 @@ public class RileyLinkSetupTableViewController: SetupTableViewController { let bundle = Bundle(for: type(of: self)) cell.mainImageView?.image = UIImage(named: "RileyLink", in: bundle, compatibleWith: cell.traitCollection) cell.mainImageView?.tintColor = UIColor(named: "RileyLink Tint", in: bundle, compatibleWith: cell.traitCollection) - + if #available(iOSApplicationExtension 13.0, *) { + cell.backgroundColor = .systemBackground + } return cell case .description: var cell = tableView.dequeueReusableCell(withIdentifier: "DescriptionCell") @@ -103,6 +105,10 @@ public class RileyLinkSetupTableViewController: SetupTableViewController { cell?.selectionStyle = .none cell?.textLabel?.text = LocalizedString("RileyLink allows for communication with the pump over Bluetooth Low Energy.", comment: "RileyLink setup description") cell?.textLabel?.numberOfLines = 0 + + if #available(iOSApplicationExtension 13.0, *) { + cell?.backgroundColor = .systemBackground + } } return cell! } diff --git a/RileyLinkKitUI/SetupImageTableViewCell.xib b/RileyLinkKitUI/SetupImageTableViewCell.xib index 85621a7c4..6fff1b9aa 100644 --- a/RileyLinkKitUI/SetupImageTableViewCell.xib +++ b/RileyLinkKitUI/SetupImageTableViewCell.xib @@ -1,12 +1,9 @@ - - - - + + - - + @@ -16,7 +13,7 @@ - + @@ -31,9 +28,11 @@ + + diff --git a/RileyLinkKitUI/UIActivityIndicatorView.swift b/RileyLinkKitUI/UIActivityIndicatorView.swift new file mode 100644 index 000000000..d97a80735 --- /dev/null +++ b/RileyLinkKitUI/UIActivityIndicatorView.swift @@ -0,0 +1,19 @@ +// +// UIActivityIndicatorView.swift +// LoopKitUI +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + + +extension UIActivityIndicatorView.Style { + static var `default`: UIActivityIndicatorView.Style { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + return .medium + } else { + return .gray + } + } +} From 5e50be7a481f356479e7e8456090fafe4f29c962 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 23 Sep 2019 17:48:24 -0500 Subject: [PATCH 46/71] Bolus tracking fixes (#551) --- MinimedKit/PumpManager/DoseStore.swift | 10 +-- .../PumpManager/MinimedPumpManager.swift | 89 ++++++++----------- 2 files changed, 39 insertions(+), 60 deletions(-) diff --git a/MinimedKit/PumpManager/DoseStore.swift b/MinimedKit/PumpManager/DoseStore.swift index f3d853ebb..42faf2c38 100644 --- a/MinimedKit/PumpManager/DoseStore.swift +++ b/MinimedKit/PumpManager/DoseStore.swift @@ -23,17 +23,9 @@ extension Collection where Element == TimestampedHistoryEvent { var dose: DoseEntry? var eventType: LoopKit.PumpEventType? - switch event.pumpEvent { case let bolus as BolusNormalPumpEvent: - // For entries in-progress, use the programmed amount - let units: Double - if event.isMutable(atDate: now, forPump: model) { - units = bolus.programmed - } else { - units = bolus.amount - } - dose = DoseEntry(type: .bolus, startDate: event.date, endDate: event.date.addingTimeInterval(bolus.duration), value: units, unit: .units) + dose = DoseEntry(type: .bolus, startDate: event.date, endDate: event.date.addingTimeInterval(bolus.duration), value: bolus.programmed, unit: .units, deliveredUnits: bolus.amount) case is SuspendPumpEvent: dose = DoseEntry(suspendDate: event.date) case is ResumePumpEvent: diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 066dd31b5..e5848a804 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -448,8 +448,13 @@ extension MinimedPumpManager { var mergedPumpEvents: [NewPumpEvent] = [] self.lockedState.mutate { (state) in + var reconcilableEvents = events.filter { !state.reconciliationMappings.keys.contains($0.raw) } + // Pending doses and reconciliation mappings will not be kept past this threshold + let expirationCutoff = Date().addingTimeInterval(.hours(-12)) + + // Pending doses can be matched to history events if start time difference is smaller than this let matchingTimeWindow = TimeInterval(minutes: 1) func addReconciliationMapping(startTime: Date, uuid: UUID, eventRaw: Data, index: Int) -> Void { @@ -459,49 +464,47 @@ extension MinimedPumpManager { } // Reconcile any pending doses - state.pendingDoses = state.pendingDoses.map { (dose) -> UnfinalizedDose in + var allPending = (state.pendingDoses + [state.unfinalizedTempBasal, state.unfinalizedBolus]).compactMap({ $0 }) + allPending = allPending.map { (dose) -> UnfinalizedDose in if let index = reconcilableEvents.firstMatchingIndex(for: dose, within: matchingTimeWindow) { let historyEvent = reconcilableEvents[index] self.log.debug("Matched pending dose %@ to history record %@", String(describing: dose), String(describing: historyEvent)) addReconciliationMapping(startTime: dose.startTime, uuid: dose.uuid, eventRaw: historyEvent.raw, index: index) var reconciledDose = dose reconciledDose.isReconciledWithHistory = true + reconcilableEvents.remove(at: index) return reconciledDose } return dose } - // Reconcile current bolus - if let bolus = state.unfinalizedBolus, let index = reconcilableEvents.firstMatchingIndex(for: bolus, within: matchingTimeWindow) { - let matchingBolus = reconcilableEvents[index] - self.log.debug("Matched unfinalized bolus %@ to history record %@", String(describing: bolus), String(describing: matchingBolus)) - state.unfinalizedBolus?.isReconciledWithHistory = true - addReconciliationMapping(startTime: bolus.startTime, uuid: bolus.uuid, eventRaw: matchingBolus.raw, index: index) - } - - // Reconcile current temp basal - if let tempBasal = state.unfinalizedTempBasal, let index = reconcilableEvents.firstMatchingIndex(for: tempBasal, within: matchingTimeWindow) { - let matchedTempBasal = reconcilableEvents[index] - self.log.debug("Matched unfinalized temp basal %@ to history record %@", String(describing: tempBasal), String(describing: matchedTempBasal)) - - // Eventual TODO: We are keeping original command time here, instead of pump time, for NS natural key restriction, but eventually, we - // would like to update the dose time to be reflective of the pump's time (i.e. use the matchedTempBasal.date) - state.unfinalizedTempBasal?.isReconciledWithHistory = true - addReconciliationMapping(startTime: tempBasal.startTime, uuid: tempBasal.uuid, eventRaw: matchedTempBasal.raw, index: index) + state.unfinalizedBolus = nil + state.unfinalizedTempBasal = nil + state.pendingDoses = allPending.filter { (dose) -> Bool in + if !dose.isFinished { + switch dose.doseType { + case .bolus: + state.unfinalizedBolus = dose + return false + case .tempBasal: + state.unfinalizedTempBasal = dose + return false + default: + break + } + } + return dose.startTime >= expirationCutoff } - - // Remove old reconciliation mappings - let expirationCutoff = Date().addingTimeInterval(.hours(-12)) - let recentReconciliationMappings = state.reconciliationMappings.filter { (key, value) -> Bool in + + state.reconciliationMappings = state.reconciliationMappings.filter { (key, value) -> Bool in return value.startTime >= expirationCutoff } - state.reconciliationMappings = recentReconciliationMappings - // Update any history events to be tracked with uuids from appropriate pending doses. - let allPending = (state.pendingDoses + [state.unfinalizedTempBasal, state.unfinalizedBolus]).compactMap({ $0 }) - var pendingDosesByUUID = Dictionary(uniqueKeysWithValues: allPending.map({ ($0.uuid, $0) })) + // Update pump event data (sync id, start time, duration) using reconciled pending doses + let reconciledPendingDoses = (state.pendingDoses + [state.unfinalizedTempBasal, state.unfinalizedBolus]).compactMap { $0 }.filter { $0.isReconciledWithHistory } + var pendingDosesByUUID = Dictionary(uniqueKeysWithValues: reconciledPendingDoses.map({ ($0.uuid, $0) })) let reconciledHistoryEvents = events.map({ (event) -> NewPumpEvent in - guard let mapping = recentReconciliationMappings[event.raw], let pendingDose = pendingDosesByUUID[mapping.uuid] else { + guard let mapping = state.reconciliationMappings[event.raw], let pendingDose = pendingDosesByUUID[mapping.uuid] else { return event } pendingDosesByUUID.removeValue(forKey: mapping.uuid) @@ -510,17 +513,12 @@ extension MinimedPumpManager { }) state.lastReconciliation = Date() - - mergedPumpEvents = reconciledHistoryEvents + pendingDosesByUUID.values.map { NewPumpEvent($0) } + + mergedPumpEvents = reconciledHistoryEvents + allPending.filter { !$0.isReconciledWithHistory }.map { NewPumpEvent($0) } } return mergedPumpEvents } - private var pendingDosesForStorage: [NewPumpEvent] { - // Must be called from the sessionQueue - return (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent }) - } - /// Polls the pump for new history events and passes them to the loop manager /// /// - Parameters: @@ -541,8 +539,9 @@ extension MinimedPumpManager { preconditionFailure("pumpManagerDelegate cannot be nil") } - let (historyEvents, model) = try session.getHistoryEvents(since: startDate) - + // Include events up to a minute before startDate, since pump event time and pending event time might be off + let (historyEvents, model) = try session.getHistoryEvents(since: startDate.addingTimeInterval(.minutes(-1))) + // Reconcile history with pending doses let newPumpEvents = historyEvents.pumpEvents(from: model) let reconciledEvents = self.reconcilePendingDosesWith(newPumpEvents) @@ -556,19 +555,6 @@ extension MinimedPumpManager { // Called on an unknown queue by the delegate if error == nil { self.recents.lastAddedPumpEvents = Date() - - self.setState({ (state) in - // Remove any pending doses that have been reconciled and are finished - if let bolus = state.unfinalizedBolus, bolus.isReconciledWithHistory, bolus.isFinished { - state.unfinalizedBolus = nil - } - if let tempBasal = state.unfinalizedTempBasal, tempBasal.isReconciledWithHistory, tempBasal.isFinished { - state.unfinalizedTempBasal = nil - } - state.pendingDoses.removeAll(where: { (dose) -> Bool in - return dose.isReconciledWithHistory && dose.isFinished - }) - }) } completion(error) }) @@ -584,8 +570,8 @@ extension MinimedPumpManager { private func storePendingPumpEvents(_ completion: @escaping (_ error: Error?) -> Void) { // Must be called from the sessionQueue - let events = pendingDosesForStorage - + let events = (self.state.pendingDoses + [self.state.unfinalizedBolus, self.state.unfinalizedTempBasal]).compactMap({ $0?.newPumpEvent }) + log.debug("Storing pending pump events: %{public}@", String(describing: events)) self.pumpDelegate.notify({ (delegate) in @@ -902,6 +888,7 @@ extension MinimedPumpManager: PumpManager { self.setState({ (state) in state.pendingDoses.append(unfinalizedBolus) + state.unfinalizedBolus = nil }) } From 4bd84e45f24ae97f50f701443f056439615f5080 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 27 Sep 2019 08:39:26 -0500 Subject: [PATCH 47/71] Mark basal as resumed when cancelling temp (#552) --- OmniKit/PumpManager/PodCommsSession.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index a15813998..b884c782b 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -482,6 +482,7 @@ public class PodCommsSession { finishTime > now { podState.unfinalizedTempBasal?.cancel(at: now) + podState.suspendState = .resumed(now) canceledDose = podState.unfinalizedTempBasal log.info("Interrupted temp basal: %@", String(describing: canceledDose)) } From f901d56a41c4afc79fcf7fcca7413ffd0b77d873 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Mon, 30 Sep 2019 21:03:12 -0700 Subject: [PATCH 48/71] Adjust activatedAt (expiresAt) on responses to account for clock drift (#554) * Adjust activatedAt (and thus expiresAt) on Pod responses to account for clock drift Round alert configuration times to the nearest minute * Fixed and improved logic & comments for when to rewrite activatedAt --- .../MessageBlocks/ConfigureAlertsCommand.swift | 5 +++-- OmniKit/PumpManager/PodState.swift | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift b/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift index 8950a2337..e9cf6d904 100644 --- a/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift @@ -116,8 +116,9 @@ extension AlertConfiguration { case .unitsRemaining(let volume): let ticks = UInt16(volume / Pod.pulseSize / 2) data.appendBigEndian(ticks) - case .timeUntilAlert(let duration): - let minutes = UInt16(duration.minutes) + case .timeUntilAlert(let secondsUntilAlert): + // round the time to alert to the nearest minute + let minutes = UInt16((secondsUntilAlert + 30).minutes) data.appendBigEndian(minutes) } data.append(beepRepeat.rawValue) diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index 1b764b8f9..bcfa0a6cc 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -42,7 +42,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public let address: UInt32 fileprivate var nonceState: NonceState - public var activatedAt: Date? + public var activatedAt: Date? // set based on StatusResponse timeActive and can change due to Pod clock drift and/or a system time change public var expiresAt: Date? { return activatedAt?.addingTimeInterval(Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) @@ -144,11 +144,19 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } public mutating func updateFromStatusResponse(_ response: StatusResponse) { + let now = Date() + let activatedAtComputed = now - response.timeActive if activatedAt == nil { - self.activatedAt = Date() - response.timeActive + self.activatedAt = activatedAtComputed + } else if let currActivatedAt = self.activatedAt, + (activatedAtComputed < currActivatedAt || activatedAtComputed > currActivatedAt + TimeInterval(minutes: 1)) { + // The computed activatedAt time is earlier than or more than a minute later than the current activatedAt time, + // so use the computed activatedAt time instead to handle Pod clock drift and/or system time changes issues. + // The more than a minute later test prevents oscillation of activatedAt based on the timing of the responses. + self.activatedAt = activatedAtComputed } updateDeliveryStatus(deliveryStatus: response.deliveryStatus) - lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: response, validTime: Date()) + lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: response, validTime: now) activeAlertSlots = response.alerts } From 4b756c126317320209474f34e909ecf254d8a5b9 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 2 Oct 2019 06:45:53 -0700 Subject: [PATCH 49/71] Make expiresAt persistent & adjustable based on Pod time and activatedAt fixed again (#555) * Adjust activatedAt (and thus expiresAt) on Pod responses to account for clock drift Round alert configuration times to the nearest minute * Fixed and improved logic & comments for when to rewrite activatedAt * Make activatedAt fixed again and expiresAt persistent & changeable * Set expiresAt appropriately when initializing from persistent storage --- OmniKit/PumpManager/OmnipodPumpManager.swift | 1 + OmniKit/PumpManager/PodState.swift | 30 +++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index e479cec40..28e25a374 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -465,6 +465,7 @@ extension OmnipodPumpManager { var podState = PodState(address: address, piVersion: "jumpstarted", pmVersion: "jumpstarted", lot: lot, tid: tid) podState.setupProgress = .podConfigured podState.activatedAt = start + podState.expiresAt = start + .hours(72) let fault = mockFault ? try? PodInfoFaultEvent(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) : nil podState.fault = fault diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index bcfa0a6cc..46fdcfc68 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -42,11 +42,9 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public let address: UInt32 fileprivate var nonceState: NonceState - public var activatedAt: Date? // set based on StatusResponse timeActive and can change due to Pod clock drift and/or a system time change - public var expiresAt: Date? { - return activatedAt?.addingTimeInterval(Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) - } + public var activatedAt: Date? + public var expiresAt: Date? // set based on StatusResponse timeActive and can change with Pod clock drift and/or system time change public let piVersion: String public let pmVersion: String @@ -148,12 +146,15 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl let activatedAtComputed = now - response.timeActive if activatedAt == nil { self.activatedAt = activatedAtComputed - } else if let currActivatedAt = self.activatedAt, - (activatedAtComputed < currActivatedAt || activatedAtComputed > currActivatedAt + TimeInterval(minutes: 1)) { - // The computed activatedAt time is earlier than or more than a minute later than the current activatedAt time, - // so use the computed activatedAt time instead to handle Pod clock drift and/or system time changes issues. - // The more than a minute later test prevents oscillation of activatedAt based on the timing of the responses. - self.activatedAt = activatedAtComputed + } + let expiresAtComputed = activatedAtComputed + (Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) + if expiresAt == nil { + self.expiresAt = expiresAtComputed + } else if expiresAtComputed < self.expiresAt! || expiresAtComputed > (self.expiresAt! + TimeInterval(minutes: 1)) { + // The computed expiresAt time is earlier than or more than a minute later than the current expiresAt time, + // so use the computed expiresAt time instead to handle Pod clock drift and/or system time changes issues. + // The more than a minute later test prevents oscillation of expiresAt based on the timing of the responses. + self.expiresAt = expiresAtComputed } updateDeliveryStatus(deliveryStatus: response.deliveryStatus) lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: response, validTime: now) @@ -254,6 +255,11 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if let activatedAt = rawValue["activatedAt"] as? Date { self.activatedAt = activatedAt + if let expiresAt = rawValue["expiresAt"] as? Date { + self.expiresAt = expiresAt + } else { + self.expiresAt = activatedAt + (Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) + } } if let suspended = rawValue["suspended"] as? Bool { @@ -398,6 +404,10 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl rawValue["activatedAt"] = activatedAt } + if let expiresAt = expiresAt { + rawValue["expiresAt"] = expiresAt + } + if configuredAlerts.count > 0 { let rawConfiguredAlerts = Dictionary(uniqueKeysWithValues: configuredAlerts.map { slot, alarm in (String(describing: slot.rawValue), alarm.rawValue) }) From 08f6371deea9f8ae3cf926d2942aea8c4fd0d336 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 13 Oct 2019 09:22:07 -0500 Subject: [PATCH 50/71] Remote Overrides support (#553) * Upload device id and bundle identifier * Bulk deletion of treatments using client ids * Fix basal rate bug * Bump carthage revs * Use separate representation for indefinite duration treatments * Call completion handler on no items to delete * Fix typo --- Cartfile | 2 +- Cartfile.resolved | 2 +- NightscoutUploadKit/NightscoutProfile.swift | 19 ++- NightscoutUploadKit/NightscoutUploader.swift | 135 ++++++++++++++---- .../Treatments/OverrideTreatment.swift | 53 +++++++ .../PodDoseProgressEstimator.swift | 2 +- RileyLink.xcodeproj/project.pbxproj | 6 + 7 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 NightscoutUploadKit/Treatments/OverrideTreatment.swift diff --git a/Cartfile b/Cartfile index df64400f1..2a09863f3 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "dev" +github "LoopKit/LoopKit" "remote-overrides" github "LoopKit/MKRingProgressView" "appex-safe" diff --git a/Cartfile.resolved b/Cartfile.resolved index b3090c05f..0ea1dc190 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2" +github "LoopKit/LoopKit" "9e480868330d66f4b781b9c1a4e6c434de070760" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" diff --git a/NightscoutUploadKit/NightscoutProfile.swift b/NightscoutUploadKit/NightscoutProfile.swift index 5ad6debe7..19d284792 100644 --- a/NightscoutUploadKit/NightscoutProfile.swift +++ b/NightscoutUploadKit/NightscoutProfile.swift @@ -114,7 +114,7 @@ public struct TemporaryScheduleOverride { let duration: TimeInterval let name: String? - public init(targetRange: ClosedRange?, insulinNeedsScaleFactor: Double?, symbol: String?, duration: TimeInterval, name: String?) { + public init(duration: TimeInterval, targetRange: ClosedRange?, insulinNeedsScaleFactor: Double?, symbol: String?, name: String?) { self.targetRange = targetRange self.insulinNeedsScaleFactor = insulinNeedsScaleFactor self.symbol = symbol @@ -142,7 +142,7 @@ public struct TemporaryScheduleOverride { if let name = name { rval["name"] = name } - + return rval } } @@ -155,8 +155,11 @@ public struct LoopSettings { let preMealTargetRange: ClosedRange? let maximumBasalRatePerHour: Double? let maximumBolus: Double? + let deviceToken: Data? + let bundleIdentifier: String? - public init(dosingEnabled: Bool, overridePresets: [TemporaryScheduleOverride], scheduleOverride: TemporaryScheduleOverride?, minimumBGGuard: Double?, preMealTargetRange: ClosedRange?, maximumBasalRatePerHour: Double?, maximumBolus: Double?) { + public init(dosingEnabled: Bool, overridePresets: [TemporaryScheduleOverride], scheduleOverride: TemporaryScheduleOverride?, minimumBGGuard: Double?, preMealTargetRange: ClosedRange?, maximumBasalRatePerHour: Double?, maximumBolus: Double?, + deviceToken: Data?, bundleIdentifier: String?) { self.dosingEnabled = dosingEnabled self.overridePresets = overridePresets self.scheduleOverride = scheduleOverride @@ -164,6 +167,8 @@ public struct LoopSettings { self.preMealTargetRange = preMealTargetRange self.maximumBasalRatePerHour = maximumBasalRatePerHour self.maximumBolus = maximumBolus + self.deviceToken = deviceToken + self.bundleIdentifier = bundleIdentifier } public var dictionaryRepresentation: [String: Any] { @@ -192,6 +197,14 @@ public struct LoopSettings { if let maximumBolus = maximumBolus { rval["maximumBolus"] = maximumBolus } + + if let deviceToken = deviceToken { + rval["deviceToken"] = deviceToken.hexadecimalString + } + + if let bundleIdentifier = bundleIdentifier { + rval["bundleIdentifier"] = bundleIdentifier + } return rval } diff --git a/NightscoutUploadKit/NightscoutUploader.swift b/NightscoutUploadKit/NightscoutUploader.swift index 6c1d6cfbf..463395a08 100644 --- a/NightscoutUploadKit/NightscoutUploader.swift +++ b/NightscoutUploadKit/NightscoutUploader.swift @@ -13,13 +13,16 @@ public enum UploadError: Error { case missingTimezone case invalidResponse(reason: String) case unauthorized + case missingConfiguration } -private let defaultNightscoutEntriesPath = "/api/v1/entries" -private let defaultNightscoutTreatmentPath = "/api/v1/treatments" -private let defaultNightscoutDeviceStatusPath = "/api/v1/devicestatus" -private let defaultNightscoutAuthTestPath = "/api/v1/experiments/test" -private let defaultNightscoutProfilePath = "/api/v1/profile" +private enum Endpoint: String { + case entries = "/api/v1/entries" + case treatments = "/api/v1/treatments" + case deviceStatus = "/api/v1/devicestatus" + case authTest = "/api/v1/experiments/test" + case profile = "/api/v1/profile" +} public class NightscoutUploader { @@ -46,6 +49,19 @@ public class NightscoutUploader { self.siteURL = siteURL self.apiSecret = APISecret } + + private func url(with path: String, queryItems: [URLQueryItem]? = nil) -> URL? { + var components = URLComponents() + components.scheme = siteURL.scheme + components.host = siteURL.host + components.queryItems = queryItems + components.path = path + return components.url + } + + private func url(for endpoint: Endpoint, queryItems: [URLQueryItem]? = nil) -> URL? { + return url(with: endpoint.rawValue, queryItems: queryItems) + } /// Attempts to upload nightscout treatment objects. /// This method will not retry if the network task failed. @@ -53,7 +69,11 @@ public class NightscoutUploader { /// - parameter treatments: An array of nightscout treatments. /// - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the upload. public func upload(_ treatments: [NightscoutTreatment], completionHandler: @escaping (Either<[String],Error>) -> Void) { - postToNS(treatments.map { $0.dictionaryRepresentation }, endpoint: defaultNightscoutTreatmentPath, completion: completionHandler) + guard let url = url(for: .treatments) else { + completionHandler(.failure(UploadError.missingConfiguration)) + return + } + postToNS(treatments.map { $0.dictionaryRepresentation }, url: url, completion: completionHandler) } /// Attempts to modify nightscout treatments. This method will not retry if the network task failed. @@ -61,6 +81,10 @@ public class NightscoutUploader { /// - parameter treatments: An array of nightscout treatments. The id attribute must be set, identifying the treatment to update. Treatments without id will be ignored. /// - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the modify. public func modifyTreatments(_ treatments:[NightscoutTreatment], completionHandler: @escaping (Error?) -> Void) { + guard let url = url(for: .treatments) else { + completionHandler(UploadError.missingConfiguration) + return + } dataAccessQueue.async { let modifyGroup = DispatchGroup() var errors = [Error]() @@ -70,7 +94,7 @@ public class NightscoutUploader { continue } modifyGroup.enter() - self.putToNS( treatment.dictionaryRepresentation, endpoint: defaultNightscoutTreatmentPath ) { (error) in + self.putToNS( treatment.dictionaryRepresentation, url: url ) { (error) in if let error = error { errors.append(error) } @@ -89,6 +113,7 @@ public class NightscoutUploader { /// - parameter id: An array of nightscout treatment ids /// - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the deletion. public func deleteTreatmentsById(_ ids:[String], completionHandler: @escaping (Error?) -> Void) { + dataAccessQueue.async { let deleteGroup = DispatchGroup() var errors = [Error]() @@ -98,7 +123,7 @@ public class NightscoutUploader { continue } deleteGroup.enter() - self.deleteFromNS(id, endpoint: defaultNightscoutTreatmentPath) { (error) in + self.deleteFromNS(id, endpoint: .treatments) { (error) in if let error = error { errors.append(error) } @@ -110,6 +135,37 @@ public class NightscoutUploader { completionHandler(errors.first) } } + + /// Attempts to delete treatments from nightscout by client identifier. This method will not retry if the network task failed. + /// + /// - parameter id: An array of client assigned uuid strings + /// - parameter completionHandler: A closure to execute when the task completes. It has a single argument for any error that might have occurred during the deletion. + public func deleteTreatmentsByClientId(_ ids:[String], completionHandler: @escaping (Error?) -> Void) { + guard ids.count > 0 else { + // Running this query with no ids ends up in deleting the entire treatments db. Likely not intended. :) + completionHandler(nil) + return + } + + let queryItems = ids.map { URLQueryItem(name: "find[_id][$in][]", value: $0) } + + guard let url = url(for: .treatments, queryItems: queryItems) else { + completionHandler(UploadError.missingConfiguration) + return + } + + dataAccessQueue.async { + self.callNS(nil, url: url, method: "DELETE") { (result) in + switch result { + case .success( _): + completionHandler(nil) + case .failure(let error): + completionHandler(error) + } + } + } + } + public func uploadDeviceStatus(_ status: DeviceStatus) { deviceStatuses.append(status.dictionaryRepresentation) @@ -132,13 +188,23 @@ public class NightscoutUploader { // MARK: - Profiles public func uploadProfile(profileSet: ProfileSet, completion: @escaping (Either<[String],Error>) -> Void) { - postToNS([profileSet.dictionaryRepresentation], endpoint:defaultNightscoutProfilePath, completion: completion) + guard let url = url(for: .profile) else { + completion(.failure(UploadError.missingConfiguration)) + return + } + + postToNS([profileSet.dictionaryRepresentation], url: url, completion: completion) } public func updateProfile(profileSet: ProfileSet, id: String, completion: @escaping (Error?) -> Void) { + guard let url = url(for: .profile) else { + completion(UploadError.missingConfiguration) + return + } + var rep = profileSet.dictionaryRepresentation rep["_id"] = id - putToNS(rep, endpoint: defaultNightscoutProfilePath, completion: completion) + putToNS(rep, url: url, completion: completion) } // MARK: - Uploading @@ -149,9 +215,14 @@ public class NightscoutUploader { flushTreatments() } - func deleteFromNS(_ id: String, endpoint:String, completion: @escaping (Error?) -> Void) { - let resource = "\(endpoint)/\(id)" - callNS(nil, endpoint: resource, method: "DELETE") { (result) in + fileprivate func deleteFromNS(_ id: String, endpoint: Endpoint, completion: @escaping (Error?) -> Void) { + let resource = "\(endpoint.rawValue)/\(id)" + guard let url = url(with: resource) else { + completion(UploadError.missingConfiguration) + return + } + + callNS(nil, url: url, method: "DELETE") { (result) in switch result { case .success( _): completion(nil) @@ -159,11 +230,10 @@ public class NightscoutUploader { completion(error) } } - } - func putToNS(_ json: Any, endpoint:String, completion: @escaping (Error?) -> Void) { - callNS(json, endpoint: endpoint, method: "PUT") { (result) in + func putToNS(_ json: Any, url:URL, completion: @escaping (Error?) -> Void) { + callNS(json, url: url, method: "PUT") { (result) in switch result { case .success( _): completion(nil) @@ -173,13 +243,13 @@ public class NightscoutUploader { } } - func postToNS(_ json: [Any], endpoint:String, completion: @escaping (Either<[String],Error>) -> Void) { + func postToNS(_ json: [Any], url:URL, completion: @escaping (Either<[String],Error>) -> Void) { if json.count == 0 { completion(.success([])) return } - callNS(json, endpoint: endpoint, method: "POST") { (result) in + callNS(json, url: url, method: "POST") { (result) in switch result { case .success(let postResponse): guard let insertedEntries = postResponse as? [[String: Any]], insertedEntries.count == json.count else { @@ -207,9 +277,8 @@ public class NightscoutUploader { } } - func callNS(_ json: Any?, endpoint:String, method:String, completion: @escaping (Either) -> Void) { - let uploadURL = siteURL.appendingPathComponent(endpoint) - var request = URLRequest(url: uploadURL) + func callNS(_ json: Any?, url:URL, method:String, completion: @escaping (Either) -> Void) { + var request = URLRequest(url: url) request.httpMethod = method request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("application/json", forHTTPHeaderField: "Accept") @@ -290,9 +359,13 @@ public class NightscoutUploader { } func flushDeviceStatuses() { + guard let url = url(for: .deviceStatus) else { + return + } + let inFlight = deviceStatuses deviceStatuses = [] - postToNS(inFlight as [Any], endpoint: defaultNightscoutDeviceStatusPath) { (result) in + postToNS(inFlight as [Any], url: url) { (result) in switch result { case .failure(let error): self.errorHandler?(error, "Uploading device status") @@ -305,9 +378,13 @@ public class NightscoutUploader { } func flushEntries() { + guard let url = url(for: .entries) else { + return + } + let inFlight = entries entries = [] - postToNS(inFlight.map({$0.dictionaryRepresentation}), endpoint: defaultNightscoutEntriesPath) { (result) in + postToNS(inFlight.map({$0.dictionaryRepresentation}), url: url) { (result) in switch result { case .failure(let error): self.errorHandler?(error, "Uploading nightscout entries") @@ -320,9 +397,13 @@ public class NightscoutUploader { } func flushTreatments() { + guard let url = url(for: .treatments) else { + return + } + let inFlight = treatmentsQueue treatmentsQueue = [] - postToNS(inFlight.map({$0.dictionaryRepresentation}), endpoint: defaultNightscoutTreatmentPath) { (result) in + postToNS(inFlight.map({$0.dictionaryRepresentation}), url: url) { (result) in switch result { case .failure(let error): self.errorHandler?(error, "Uploading nightscout treatment records") @@ -335,8 +416,10 @@ public class NightscoutUploader { } public func checkAuth(_ completion: @escaping (Error?) -> Void) { - - let testURL = siteURL.appendingPathComponent(defaultNightscoutAuthTestPath) + guard let testURL = url(for: .authTest) else { + completion(UploadError.missingConfiguration) + return + } var request = URLRequest(url: testURL) diff --git a/NightscoutUploadKit/Treatments/OverrideTreatment.swift b/NightscoutUploadKit/Treatments/OverrideTreatment.swift new file mode 100644 index 000000000..a1fa97bbf --- /dev/null +++ b/NightscoutUploadKit/Treatments/OverrideTreatment.swift @@ -0,0 +1,53 @@ +// +// OverrideTreatment.swift +// NightscoutUploadKit +// +// Created by Pete Schwamb on 9/28/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation + + +public class OverrideTreatment: NightscoutTreatment { + + public enum Duration { + case finite(TimeInterval) + case indefinite + } + + let correctionRange: ClosedRange? // mg/dL + let insulinNeedsScaleFactor: Double? + let duration: Duration + let reason: String + let remoteAddress: String? + + public init(startDate: Date, enteredBy: String, reason: String, duration: Duration, correctionRange: ClosedRange?, insulinNeedsScaleFactor: Double?, remoteAddress: String? = nil, id: String? = nil) { + self.reason = reason + self.duration = duration + self.correctionRange = correctionRange + self.insulinNeedsScaleFactor = insulinNeedsScaleFactor + self.remoteAddress = remoteAddress + super.init(timestamp: startDate, enteredBy: enteredBy, id: id, eventType: "Temporary Override") + } + + override public var dictionaryRepresentation: [String: Any] { + var rval = super.dictionaryRepresentation + + switch duration { + case .finite(let timeInterval): + rval["duration"] = timeInterval.minutes + case .indefinite: + rval["durationType"] = "indefinite" + } + rval["reason"] = reason + rval["insulinNeedsScaleFactor"] = insulinNeedsScaleFactor + rval["remoteAddress"] = remoteAddress + + if let correctionRange = correctionRange { + rval["correctionRange"] = [correctionRange.lowerBound, correctionRange.upperBound] + } + + return rval + } +} diff --git a/OmniKit/PumpManager/PodDoseProgressEstimator.swift b/OmniKit/PumpManager/PodDoseProgressEstimator.swift index f264aae3e..f62d076a8 100644 --- a/OmniKit/PumpManager/PodDoseProgressEstimator.swift +++ b/OmniKit/PumpManager/PodDoseProgressEstimator.swift @@ -36,7 +36,7 @@ class PodDoseProgressEstimator: DoseProgressTimerEstimator { case .bolus: timeBetweenPulses = Pod.pulseSize / Pod.bolusDeliveryRate case .basal, .tempBasal: - timeBetweenPulses = Pod.pulseSize / dose.unitsPerHour + timeBetweenPulses = Pod.pulseSize / (dose.unitsPerHour / TimeInterval(hours: 1)) default: fatalError("Can only estimate progress on basal rates or boluses.") } diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index 4443a53cc..e7f86d10f 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -278,6 +278,7 @@ C12198A91C8F2AF200BC374C /* DailyTotal523PumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12198A81C8F2AF200BC374C /* DailyTotal523PumpEvent.swift */; }; C12198AD1C8F332500BC374C /* TimestampedPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12198AC1C8F332500BC374C /* TimestampedPumpEvent.swift */; }; C12198B31C8F730700BC374C /* BolusWizardEstimatePumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12198B21C8F730700BC374C /* BolusWizardEstimatePumpEvent.swift */; }; + C121FE05233FA20E00630EB5 /* OverrideTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C121FE04233FA20E00630EB5 /* OverrideTreatment.swift */; }; C125728B211F7E6C0061BA2F /* UnknownPumpEvent57.swift in Sources */ = {isa = PBXBuildFile; fileRef = C125728A211F7E6C0061BA2F /* UnknownPumpEvent57.swift */; }; C125728C2121D4D60061BA2F /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */; }; C125728D2121D56D0061BA2F /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C659181E16BA9D0025CC58 /* CaseCountable.swift */; }; @@ -314,6 +315,7 @@ C136AA732311899D008A320D /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431CE7941F9B0DAE00255374 /* OSLog.swift */; }; C136AA752311CFC3008A320D /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C13F0436230B1DE6001413FF /* MKRingProgressView.framework */; }; C136AA76231234E1008A320D /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43FB610B20DDF55F002B996B /* LoopKit.framework */; }; + C13B5EAE2331CD7900AA5599 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EAD6BA1C826B92006DBA60 /* Data.swift */; }; C13BD63E21402A88006D7F19 /* PodInsulinMeasurements.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD63D21402A88006D7F19 /* PodInsulinMeasurements.swift */; }; C13BD643214033E5006D7F19 /* UnfinalizedDose.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13BD642214033E5006D7F19 /* UnfinalizedDose.swift */; }; C13D155A1DAACE8400ADC044 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13D15591DAACE8400ADC044 /* Either.swift */; }; @@ -1252,6 +1254,7 @@ C12198A81C8F2AF200BC374C /* DailyTotal523PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DailyTotal523PumpEvent.swift; sourceTree = ""; }; C12198AC1C8F332500BC374C /* TimestampedPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TimestampedPumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; C12198B21C8F730700BC374C /* BolusWizardEstimatePumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BolusWizardEstimatePumpEvent.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + C121FE04233FA20E00630EB5 /* OverrideTreatment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideTreatment.swift; sourceTree = ""; }; C125728A211F7E6C0061BA2F /* UnknownPumpEvent57.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnknownPumpEvent57.swift; sourceTree = ""; }; C125728E2121DB7C0061BA2F /* PumpManagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerState.swift; sourceTree = ""; }; C12572912121EEEE0061BA2F /* SettingsImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsImageTableViewCell.swift; sourceTree = ""; }; @@ -2434,6 +2437,7 @@ C1AF21EF1D4901220088C41D /* TempBasalNightscoutTreatment.swift */, 492526701E4521FB00ACBA5F /* NoteNightscoutTreatment.swift */, C1D00EA01E8986F900B733B7 /* PumpResumeTreatment.swift */, + C121FE04233FA20E00630EB5 /* OverrideTreatment.swift */, ); path = Treatments; sourceTree = ""; @@ -3962,6 +3966,7 @@ C1E5BEAD1D5E26F200BD4390 /* RileyLinkStatus.swift in Sources */, C1AF21F31D4901220088C41D /* MealBolusNightscoutTreatment.swift in Sources */, C1A492691D4A66C0008964FF /* LoopEnacted.swift in Sources */, + C13B5EAE2331CD7900AA5599 /* Data.swift in Sources */, C1A492651D4A5DEB008964FF /* BatteryStatus.swift in Sources */, C1A492631D4A5A19008964FF /* IOBStatus.swift in Sources */, C184875E20BCDB0000ABE9E7 /* ForecastError.swift in Sources */, @@ -3980,6 +3985,7 @@ 43B0ADC91D1268B300AAD278 /* TimeFormat.swift in Sources */, C1AF21E21D4838C90088C41D /* DeviceStatus.swift in Sources */, C1B383281CD0668600CE7782 /* NightscoutUploader.swift in Sources */, + C121FE05233FA20E00630EB5 /* OverrideTreatment.swift in Sources */, 546145C11DCEB47600DC6DEB /* NightscoutEntry.swift in Sources */, C1AF21F41D4901220088C41D /* TempBasalNightscoutTreatment.swift in Sources */, ); From 51d6a20cc63c0c0b2071a4a78b4065e9a11ba81f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 13 Oct 2019 10:43:07 -0500 Subject: [PATCH 51/71] Use dev LoopKit --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index 2a09863f3..df64400f1 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "remote-overrides" +github "LoopKit/LoopKit" "dev" github "LoopKit/MKRingProgressView" "appex-safe" diff --git a/Cartfile.resolved b/Cartfile.resolved index 0ea1dc190..afbbe87d9 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "9e480868330d66f4b781b9c1a4e6c434de070760" +github "LoopKit/LoopKit" "a08405ec7a4e38fa96e39ae079114db30f393a2a" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" From 88530730f45c6a846f9bb9b2d87893b1350993e2 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 26 Oct 2019 07:32:05 -0600 Subject: [PATCH 52/71] Revert x15 patch (#560) * Revert x15 patch * Travis build with iPhone 8 target --- .travis.yml | 2 +- MinimedKit/Models/PumpModel.swift | 5 ----- RileyLinkKit/PumpOpsSession.swift | 16 +--------------- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index d80d63f88..e5e1d2a04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ before_script: - carthage bootstrap script: - - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone 6' test | xcpretty + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone 8' test | xcpretty diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 1f6674276..b18debdcb 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -62,11 +62,6 @@ public enum PumpModel: String { return generation >= 23 } - // On x15 models, a bolus in progress error is returned when bolusing, even though the bolus succeeds - public var returnsErrorOnBolus: Bool { - return generation == 15 - } - /// Newer models allow higher precision delivery, and have bit packing to accomodate this. public var insulinBitPackingScale: Int { return (generation >= 23) ? 40 : 10 diff --git a/RileyLinkKit/PumpOpsSession.swift b/RileyLinkKit/PumpOpsSession.swift index f71e39f75..89baed2f6 100644 --- a/RileyLinkKit/PumpOpsSession.swift +++ b/RileyLinkKit/PumpOpsSession.swift @@ -594,21 +594,7 @@ extension PumpOpsSession { do { let message = PumpMessage(settings: settings, type: .bolus, body: BolusCarelinkMessageBody(units: units, insulinBitPackingScale: pumpModel.insulinBitPackingScale)) - if pumpModel.returnsErrorOnBolus { - // TODO: This isn't working as expected; this logic was probably intended to be in the catch block below - let error: PumpErrorMessageBody = try runCommandWithArguments(message, responseType: .errorResponse) - - switch error.errorCode { - case .known(let errorCode): - if errorCode != .bolusInProgress { - throw PumpOpsError.pumpError(errorCode) - } - case .unknown(let unknownErrorCode): - throw PumpOpsError.unknownPumpErrorCode(unknownErrorCode) - } - } else { - let _: PumpAckMessageBody = try runCommandWithArguments(message) - } + let _: PumpAckMessageBody = try runCommandWithArguments(message) } catch let error as PumpOpsError { throw SetBolusError.certain(error) } catch let error as PumpCommandError { From 92314a08f26e94e271961a629e4d7b898e4e2e4d Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 30 Oct 2019 17:00:48 -0700 Subject: [PATCH 53/71] Various internal improvements for better Omnipod support (#557) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Various internal improvements for better Omnipod support + Change Play Test Beeps to not emit a second beep type (it plays well after the UI has reported Succeeded) + Change the PodAlert enum description string to add “ (inactive)” for inactive configured alerts + Add a new Pod.cannulaInsertionUnits constant of 0.5 + Add a new Pod.nominalPodLife constant (i.e., 72 hours) + Updated comments for the BeepType & BeepConfigType enums + Update the MessageLog description string to include a blank line before and after + Rename the OmnipodPumpManager hasSetupCompletePod var to hasSetupPod + Move readFlashLog() from PodCommsSession to OmnipodPumpManager + Call the setTime(), setBasalSchedule(), resumeBasal(), setTempBasal() functions with all parameters from OmnipodPumpManager + Mark the PodCommsSession’s configureAlerts() and cancelNone() functions with @discardableResult + Change beepConfig() to catch errors and never throw + Don't do the unneeded configuration for the autoOffAlarm alert + Updated PodCommsSession functions to be able to set individual optional alerts values + Only save the first fault status returned in podState + Updated and improved comments and Xcode logging * Remove 5 currently never called utility routines to simplify the PR --- OmniKit/Model/AlertSlot.swift | 15 ++- OmniKit/Model/BeepType.swift | 12 ++- OmniKit/Model/Pod.swift | 8 +- OmniKit/PumpManager/MessageLog.swift | 3 +- OmniKit/PumpManager/OmnipodPumpManager.swift | 95 +++++++++++++------ .../PumpManager/OmnipodPumpManagerState.swift | 2 +- OmniKit/PumpManager/PodCommsSession.swift | 71 ++++++-------- OmniKit/PumpManager/PodState.swift | 4 +- 8 files changed, 125 insertions(+), 85 deletions(-) diff --git a/OmniKit/Model/AlertSlot.swift b/OmniKit/Model/AlertSlot.swift index 744f42fd8..521f85abd 100644 --- a/OmniKit/Model/AlertSlot.swift +++ b/OmniKit/Model/AlertSlot.swift @@ -74,22 +74,27 @@ public enum PodAlert: CustomStringConvertible, RawRepresentable, Equatable { case autoOffAlarm(active: Bool, countdownDuration: TimeInterval) public var description: String { + var alertName: String switch self { case .waitingForPairingReminder: return LocalizedString("Waiting for pairing reminder", comment: "Description waiting for pairing reminder") case .finishSetupReminder: return LocalizedString("Finish setup ", comment: "Description for finish setup") case .expirationAlert: - return LocalizedString("Expiration alert", comment: "Description for expiration alert") + alertName = LocalizedString("Expiration alert", comment: "Description for expiration alert") case .expirationAdvisoryAlarm: - return LocalizedString("Pod expiration advisory alarm", comment: "Description for expiration advisory alarm") + alertName = LocalizedString("Pod expiration advisory alarm", comment: "Description for expiration advisory alarm") case .shutdownImminentAlarm: - return LocalizedString("Shutdown imminent alarm", comment: "Description for shutdown imminent alarm") + alertName = LocalizedString("Shutdown imminent alarm", comment: "Description for shutdown imminent alarm") case .lowReservoirAlarm: - return LocalizedString("Low reservoir advisory alarm", comment: "Description for low reservoir alarm") + alertName = LocalizedString("Low reservoir advisory alarm", comment: "Description for low reservoir alarm") case .autoOffAlarm: - return LocalizedString("Auto-off alarm", comment: "Description for auto-off alarm") + alertName = LocalizedString("Auto-off alarm", comment: "Description for auto-off alarm") } + if self.configuration.active == false { + alertName += LocalizedString(" (inactive)", comment: "Description for an inactive alert modifier") + } + return alertName } public var configuration: AlertConfiguration { diff --git a/OmniKit/Model/BeepType.swift b/OmniKit/Model/BeepType.swift index bcc6c4b01..4f14889a9 100644 --- a/OmniKit/Model/BeepType.swift +++ b/OmniKit/Model/BeepType.swift @@ -9,6 +9,8 @@ import Foundation // BeepType is used for the $19 Configure Alerts and $1F Cancel Commands +// Values 1 thru 8 are exactly the same as in BeepConfigType below +// N.B. for BeepType, noBeep is 0x0, while for BeepConfigType it is 0xF public enum BeepType: UInt8 { case noBeep = 0x0 case beepBeepBeepBeep = 0x1 @@ -19,12 +21,14 @@ public enum BeepType: UInt8 { case beeeeeep = 0x6 case bipBipBipbipBipBip = 0x7 case beeepBeeep = 0x8 + // values greater than 0x8 for $19 and $1F commands can fault pod! } -// BeepConfigType is used only for the $1E Beep Config Command. -// Values 1 thru 8 are exactly the same as in BeepType +// BeepConfigType is used for the $1E Beep Config Command. +// Values 1 thru 8 are exactly the same as in BeepType above +// N.B. for BeepConfigType, noBeep is 0xF, while for BeepType it is 0x0 public enum BeepConfigType: UInt8 { - // 0x0 always returns an error response for Beep Config (use 0xF for no beep) + // 0 always returns an error response for Beep Config case beepBeepBeepBeep = 0x1 case bipBeepBipBeepBipBeepBipBeep = 0x2 case bipBip = 0x3 @@ -37,6 +41,6 @@ public enum BeepConfigType: UInt8 { case beepBeep = 0xB case beeep = 0xC case bipBeeeeep = 0xD - case fiveSecondBeep = 0xE // can only be used if Pod is currently suspended + case fiveSecondBeep = 0xE // can only be used if Pod is currently suspended! case noBeep = 0xF } diff --git a/OmniKit/Model/Pod.swift b/OmniKit/Model/Pod.swift index 8c430e84f..f61d8d6bd 100644 --- a/OmniKit/Model/Pod.swift +++ b/OmniKit/Model/Pod.swift @@ -30,6 +30,9 @@ public struct Pod { // Total pod service time. A fault is triggered if this time is reached before pod deactivation. public static let serviceDuration = TimeInterval(hours: 80) + // Nomimal pod life (72 hours) + public static let nominalPodLife = Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow + // Maximum reservoir level reading public static let maximumReservoirReading: Double = 50 @@ -45,9 +48,12 @@ public struct Pod { // Minimum duration of a single basal schedule entry public static let minimumBasalScheduleEntryDuration = TimeInterval.minutes(30) - // Amount of insulin delivered for priming + // Amount of insulin delivered with 1 second between pulses for priming public static let primeUnits = 2.6 + // Amount of insulin delivered with 1 second between pulses for cannula insertion + public static let cannulaInsertionUnits = 0.5 + // Default and limits for expiration reminder alerts public static let expirationReminderAlertDefaultTimeBeforeExpiration = TimeInterval.hours(2) public static let expirationReminderAlertMinTimeBeforeExpiration = TimeInterval.hours(1) diff --git a/OmniKit/PumpManager/MessageLog.swift b/OmniKit/PumpManager/MessageLog.swift index 1b3feb841..1dfa0bcec 100644 --- a/OmniKit/PumpManager/MessageLog.swift +++ b/OmniKit/PumpManager/MessageLog.swift @@ -57,10 +57,11 @@ public struct MessageLog: CustomStringConvertible, Equatable { var entries = [MessageLogEntry]() public var description: String { - var lines = ["### MessageLog"] + var lines = ["\n### MessageLog"] for entry in entries { lines.append("* " + entry.description) } + lines.append("") return lines.joined(separator: "\n") } diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 28e25a374..613f90416 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -13,6 +13,8 @@ import RileyLinkBLEKit import UserNotifications import os.log + + public enum ReservoirAlertState { case ok case lowReservoir @@ -345,8 +347,8 @@ extension OmnipodPumpManager { } // Thread-safe - public var hasSetupCompletePod: Bool { - return state.hasSetupCompletePod + public var hasSetupPod: Bool { + return state.hasSetupPod } // Thread-safe @@ -742,7 +744,8 @@ extension OmnipodPumpManager { switch result { case .success(let session): do { - let _ = try session.setTime(timeZone: timeZone, basalSchedule: self.state.basalSchedule, date: Date()) + let beep = false + let _ = try session.setTime(timeZone: timeZone, basalSchedule: self.state.basalSchedule, date: Date(), acknowledgementBeep: beep, completionBeep: beep) self.setState { (state) in state.timeZone = timeZone } @@ -798,7 +801,8 @@ extension OmnipodPumpManager { case .success: break } - let _ = try session.setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset) + let beep = false + let _ = try session.setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) self.setState { (state) in state.basalSchedule = schedule @@ -870,8 +874,8 @@ extension OmnipodPumpManager { } public func testingCommands(completion: @escaping (Error?) -> Void) { - // use hasSetupCompletePod instead of hasActivePod so we don't fail on a faulted Pod - guard self.hasSetupCompletePod else { + // use hasSetupPod so that the user can see any fault info + guard self.hasSetupPod else { completion(OmnipodPumpManagerError.noPodPaired) return } @@ -897,20 +901,49 @@ extension OmnipodPumpManager { completion(OmnipodPumpManagerError.noPodPaired) return } + guard self.state.podState?.unfinalizedBolus?.isFinished != false else { + self.log.info("Skipping Play Test Beeps due to bolus still in progress.") + completion(PodCommsError.unfinalizedBolus) + return + } let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice self.podComms.runSession(withName: "Play Test Beeps", using: rileyLinkSelector) { (result) in + switch result { + case .success(let session): + session.beepConfig(beepConfigType: .bipBeepBipBeepBipBeepBipBeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: self.bolusBeeps) + // Don't bother emitting another beep sequence to approximate the PDM "Check alarms" function as the Pod beeping is + // asynchronous to the UI which will have already printed Succeeded before the first beep sequence is done playing + completion(nil) + case .failure(let error): + completion(error) + } + } + } + + public func readFlashLog(completion: @escaping (Error?) -> Void) { + // use hasSetupPod to be able to read the flash log from a faulted Pod + guard self.hasSetupPod else { + completion(OmnipodPumpManagerError.noPodPaired) + return + } + if self.state.podState?.fault == nil && self.state.podState?.unfinalizedBolus?.isFinished == false { + self.log.info("Skipping Read Flash Log due to bolus still in progress.") + completion(PodCommsError.unfinalizedBolus) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Read Flash Log", using: rileyLinkSelector) { (result) in switch result { case .success(let session): do { - guard self.state.podState?.unfinalizedBolus?.isFinished != false else { - self.log.info("Unfinalized bolus, skipping play test beeps") - throw PodCommsError.unfinalizedBolus - } - - try session.beepConfig(beepConfigType: .bipBeepBipBeepBipBeepBipBeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: self.bolusBeeps) - // .fiveSecondBeep could be used for a PDM style "Check alarms", but this only works if the pod is suspended! - try session.beepConfig(beepConfigType: .beeeeeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: self.bolusBeeps) + // read up to the most recent 50 entries from flash log + try session.readFlashLogsRequest(podInfoResponseSubType: .flashLogRecent) + + // read up to the previous 50 entries from flash log + try session.readFlashLogsRequest(podInfoResponseSubType: .dumpOlderFlashlog) + completion(nil) } catch let error { completion(error) @@ -922,26 +955,26 @@ extension OmnipodPumpManager { } public func setBolusBeeps(enabled: Bool, completion: @escaping (Error?) -> Void) { - self.log.info("Set Bolus Beeps to %s", enabled ? "true" : "false") - + self.bolusBeeps = enabled // set here to allow changes on a faulted Pod + self.log.default("Set Bolus Beeps to %s", String(describing: enabled)) guard self.hasActivePod else { completion(nil) return } let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice - let name: String = enabled ? "Enable Confirmation Beeps" : "Disable Confirmation Beeps" + let name: String = enabled ? "Enable Bolus Beeps" : "Disable Bolus Beeps" self.podComms.runSession(withName: name, using: rileyLinkSelector) { (result) in switch result { case .success(let session): - do { - let beepConfigType: BeepConfigType = enabled ? .bipBip : .noBeep - try session.beepConfig(beepConfigType: beepConfigType, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: enabled) - self.bolusBeeps = enabled - completion(nil) - } catch let error { - completion(error) - } + let beepConfigType: BeepConfigType = enabled ? .bipBip : .noBeep + let basalCompletionBeep = false + let tempBasalCompletionBeep = false + let bolusCompletionBeep = enabled + + // enable/disable Pod completion beeps for any in-progress insulin delivery + session.beepConfig(beepConfigType: beepConfigType, basalCompletionBeep: basalCompletionBeep, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) + completion(nil) case .failure(let error): completion(error) } @@ -1104,7 +1137,8 @@ extension OmnipodPumpManager: PumpManager { do { let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) - let _ = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset) + let beep = false + let _ = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } @@ -1206,7 +1240,8 @@ extension OmnipodPumpManager: PumpManager { if podStatus.deliveryStatus == .suspended { do { let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) - podStatus = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset) + let beep = false + podStatus = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) } catch let error { completion(.failure(SetBolusError.certain(error as? PodCommsError ?? PodCommsError.commsError(error: error)))) return @@ -1223,7 +1258,8 @@ extension OmnipodPumpManager: PumpManager { let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: enactUnits, unit: .units) willRequest(dose) - let result = session.bolus(units: enactUnits, acknowledgementBeep: self.bolusBeeps, completionBeep: self.bolusBeeps) + let beep = self.bolusBeeps + let result = session.bolus(units: enactUnits, acknowledgementBeep: beep, completionBeep: beep) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } @@ -1374,7 +1410,8 @@ extension OmnipodPumpManager: PumpManager { state.tempBasalEngageState = .engaging }) - let result = session.setTempBasal(rate: rate, duration: duration) + let beep = false + let result = session.setTempBasal(rate: rate, duration: duration, acknowledgementBeep: beep, completionBeep: beep) let basalStart = Date() let dose = DoseEntry(type: .tempBasal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: rate, unit: .unitsPerHour) session.dosesForStorage() { (doses) -> Bool in diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift index 4ad25f630..62299dbc9 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -165,7 +165,7 @@ extension OmnipodPumpManagerState { return podState?.isActive == true } - var hasSetupCompletePod: Bool { + var hasSetupPod: Bool { return podState?.isSetupComplete == true } diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index b884c782b..e117d40dd 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -143,7 +143,6 @@ public protocol PodCommsSessionDelegate: class { public class PodCommsSession { private let useCancelNoneForStatus: Bool = false // whether to always use a cancel none to get status - private let podLowReservoirLevel: Double = 20 // default pod low reservoir alert value public let log = OSLog(category: "PodCommsSession") @@ -165,7 +164,9 @@ public class PodCommsSession { } private func handlePodFault(fault: PodInfoFaultEvent) { - self.podState.fault = fault + if self.podState.fault == nil { + self.podState.fault = fault // save the first fault returned + } log.error("Pod Fault: %@", String(describing: fault)) if fault.deliveryStatus == .suspended { let now = Date() @@ -263,7 +264,7 @@ public class PodCommsSession { // The following will set Tab5[$16] to 0 during pairing, which disables $6x faults. let _: StatusResponse = try send([FaultConfigCommand(nonce: podState.currentNonce, tab5Sub16: 0, tab5Sub17: 0)]) let finishSetupReminder = PodAlert.finishSetupReminder - let _ = try configureAlerts([finishSetupReminder]) + try configureAlerts([finishSetupReminder]) } else { // We started prime, but didn't get confirmation somehow, so check status let status: StatusResponse = try send([GetStatusCommand()]) @@ -274,7 +275,7 @@ public class PodCommsSession { } } - // Mark 2.6U delivery for prime + // Mark 2.6U delivery with 1 second between pulses for prime let primeFinishTime = Date() + primeDuration podState.primeFinishTime = primeFinishTime @@ -308,6 +309,7 @@ public class PodCommsSession { podState.finalizedDoses.append(UnfinalizedDose(resumeStartTime: Date(), scheduledCertainty: .certain)) } + @discardableResult private func configureAlerts(_ alerts: [PodAlert]) throws -> StatusResponse { let configurations = alerts.map { $0.configuration } let configureAlerts = ConfigureAlertsCommand(nonce: podState.currentNonce, configurations: configurations) @@ -319,15 +321,18 @@ public class PodCommsSession { return status } - // emits the specified beep type and sets the completion beep flags based on the specified confirmationBeep value - public func beepConfig(beepConfigType: BeepConfigType, basalCompletionBeep: Bool, tempBasalCompletionBeep: Bool, bolusCompletionBeep: Bool) throws { + // emits the specified beep type and sets the completion beep flags, doesn't throw + public func beepConfig(beepConfigType: BeepConfigType, basalCompletionBeep: Bool, tempBasalCompletionBeep: Bool, bolusCompletionBeep: Bool) { guard self.podState.fault == nil else { - return // skip if already faulted to avoid a Beep Config Command error response + log.info("Skip beep config with faulted pod") + return } let beepConfigCommand = BeepConfigCommand(beepConfigType: beepConfigType, basalCompletionBeep: basalCompletionBeep, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) - let statusResponse: StatusResponse = try send([beepConfigCommand]) - podState.updateFromStatusResponse(statusResponse) + do { + let statusResponse: StatusResponse = try send([beepConfigCommand]) + podState.updateFromStatusResponse(statusResponse) + } catch {} // never critical } public func insertCannula() throws -> TimeInterval { @@ -351,24 +356,22 @@ public class PodCommsSession { } } else { // Configure all the non-optional Pod Alarms - let endOfServiceTime = activatedAt + Pod.serviceDuration - let timeUntilExpirationAdvisory = (endOfServiceTime - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow).timeIntervalSinceNow + let expirationTime = activatedAt + Pod.nominalPodLife + let timeUntilExpirationAdvisory = expirationTime.timeIntervalSinceNow let expirationAdvisoryAlarm = PodAlert.expirationAdvisoryAlarm(alarmTime: timeUntilExpirationAdvisory, duration: Pod.expirationAdvisoryWindow) + let endOfServiceTime = activatedAt + Pod.serviceDuration let shutdownImminentAlarm = PodAlert.shutdownImminentAlarm((endOfServiceTime - Pod.endOfServiceImminentWindow).timeIntervalSinceNow) - let autoOffAlarm = PodAlert.autoOffAlarm(active: false, countdownDuration: 0) // Turn Auto-off feature off - let _ = try configureAlerts([expirationAdvisoryAlarm, shutdownImminentAlarm, autoOffAlarm]) + try configureAlerts([expirationAdvisoryAlarm, shutdownImminentAlarm]) } - // Insert Cannula - // 1a0e7e30bf16020065010050000a000a - let insertionBolusAmount = 0.5 + // Mark 0.5U delivery with 1 second between pulses for cannula insertion + let timeBetweenPulses = TimeInterval(seconds: 1) - let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: insertionBolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: Pod.cannulaInsertionUnits, timeBetweenPulses: timeBetweenPulses) let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) - // 17 0d 00 0064 0001 86a0000000000000 podState.setupProgress = .startingInsertCannula - let bolusExtraCommand = BolusExtraCommand(units: insertionBolusAmount, timeBetweenPulses: timeBetweenPulses) + let bolusExtraCommand = BolusExtraCommand(units: Pod.cannulaInsertionUnits, timeBetweenPulses: timeBetweenPulses) let status2: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.updateFromStatusResponse(status2) @@ -510,11 +513,10 @@ public class PodCommsSession { } public func testingCommands() throws { - // try readFlashLogs() - let _ = try cancelNone() // a functional replacement for getStatus() which also verifies & advances nonce + try cancelNone() // reads status & verifies nonce by doing a cancel none } - public func setTime(timeZone: TimeZone, basalSchedule: BasalSchedule, date: Date) throws -> StatusResponse { + public func setTime(timeZone: TimeZone, basalSchedule: BasalSchedule, date: Date, acknowledgementBeep: Bool, completionBeep: Bool) throws -> StatusResponse { let result = cancelDelivery(deliveryType: .all, beepType: .noBeep) switch result { case .certainFailure(let error): @@ -523,7 +525,7 @@ public class PodCommsSession { throw error case .success: let scheduleOffset = timeZone.scheduleOffset(forDate: date) - let status = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset) + let status = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) return status } } @@ -557,10 +559,11 @@ public class PodCommsSession { return status } + // use cancelDelivery with .none to get status as well as to validate & advance the nonce + @discardableResult public func cancelNone() throws -> StatusResponse { var statusResponse: StatusResponse - // use cancelDelivery .none to get status AND validate & advance the nonce let cancelResult: CancelDeliveryResult = cancelDelivery(deliveryType: .none, beepType: .noBeep) switch cancelResult { case .certainFailure(let error): @@ -584,14 +587,13 @@ public class PodCommsSession { return statusResponse } - private func readFlashLogsRequest(podInfoResponseSubType: PodInfoResponseSubType) throws { - + public func readFlashLogsRequest(podInfoResponseSubType: PodInfoResponseSubType) throws { let blocksToSend = [GetStatusCommand(podInfoType: podInfoResponseSubType)] let message = Message(address: podState.address, messageBlocks: blocksToSend, sequenceNum: transport.messageNumber) let messageResponse = try transport.sendMessage(message) if let podInfoResponseMessageBlock = messageResponse.messageBlocks[0] as? PodInfoResponse { - log.info("Pod flash log: %@", String(describing: podInfoResponseMessageBlock)) + log.default("Pod flash log: %@", String(describing: podInfoResponseMessageBlock)) } else if let fault = messageResponse.fault { handlePodFault(fault: fault) throw PodCommsError.podFault(fault: fault) @@ -601,21 +603,6 @@ public class PodCommsSession { } } - public func readFlashLogs() throws { - if self.podState.fault == nil { - let _ = try cancelNone() - guard podState.unfinalizedBolus?.isFinished != false else { - log.info("Unfinalized bolus, skipping read flash logs") - throw PodCommsError.unfinalizedBolus - } - } - - // read up to the most recent 50 entries from flash log - try readFlashLogsRequest(podInfoResponseSubType: .flashLogRecent) - // read up to the previous 50 entries from flash log - try readFlashLogsRequest(podInfoResponseSubType: .dumpOlderFlashlog) - } - public func deactivatePod() throws { if podState.fault == nil && !podState.isSuspended { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index 46fdcfc68..a5654eba4 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -147,7 +147,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if activatedAt == nil { self.activatedAt = activatedAtComputed } - let expiresAtComputed = activatedAtComputed + (Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) + let expiresAtComputed = activatedAtComputed + Pod.nominalPodLife if expiresAt == nil { self.expiresAt = expiresAtComputed } else if expiresAtComputed < self.expiresAt! || expiresAtComputed > (self.expiresAt! + TimeInterval(minutes: 1)) { @@ -258,7 +258,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl if let expiresAt = rawValue["expiresAt"] as? Date { self.expiresAt = expiresAt } else { - self.expiresAt = activatedAt + (Pod.serviceDuration - Pod.endOfServiceImminentWindow - Pod.expirationAdvisoryWindow) + self.expiresAt = activatedAt + Pod.nominalPodLife } } From 9495dd910e0e433198eff9c5decb37c8ea7a0a78 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 7 Nov 2019 00:27:20 -0600 Subject: [PATCH 54/71] Add workaround for core foundation timezone bug (#562) --- MinimedKit/Messages/ChangeTimeCarelinkMessageBody.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MinimedKit/Messages/ChangeTimeCarelinkMessageBody.swift b/MinimedKit/Messages/ChangeTimeCarelinkMessageBody.swift index e266d35b6..f5b522ca9 100644 --- a/MinimedKit/Messages/ChangeTimeCarelinkMessageBody.swift +++ b/MinimedKit/Messages/ChangeTimeCarelinkMessageBody.swift @@ -12,8 +12,13 @@ import Foundation public class ChangeTimeCarelinkMessageBody: CarelinkLongMessageBody { public convenience init?(dateComponents: DateComponents) { + + var calendar = Calendar(identifier: .gregorian) + if let timeZone = dateComponents.timeZone { + calendar.timeZone = timeZone + } - guard dateComponents.isValidDate(in: Calendar(identifier: Calendar.Identifier.gregorian)) else { + guard dateComponents.isValidDate(in: calendar) else { return nil } From 3082367b3484586cd3a70a7134edef3ea35c6b9b Mon Sep 17 00:00:00 2001 From: Alin Artiom Kenibasov Date: Wed, 13 Nov 2019 23:36:24 +0100 Subject: [PATCH 55/71] Fixed building from directories containing space (#559) --- RileyLink.xcodeproj/project.pbxproj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index e7f86d10f..3698098b3 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -3402,7 +3402,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; A9B839DC2280A178004E745E /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3422,7 +3422,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n\n"; }; C136AA4323116F4D008A320D /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3445,7 +3445,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; C136AA72231188FC008A320D /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3468,7 +3468,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; C136AA7723123A5D008A320D /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3487,7 +3487,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; C136AA7823123B8C008A320D /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3507,7 +3507,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; C14A538023123CF500C86755 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3526,7 +3526,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; /* End PBXShellScriptBuildPhase section */ From 8c12092b606b238c90f034203f4b588dd457525a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 24 Nov 2019 21:28:10 -0600 Subject: [PATCH 56/71] Correct suspendState after suspending during temp-basal (#568) --- OmniKit/PumpManager/PodCommsSession.swift | 4 +++- OmniKitUI/PumpManager/OmnipodHUDProvider.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index e117d40dd..1c6b8304c 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -485,7 +485,9 @@ public class PodCommsSession { finishTime > now { podState.unfinalizedTempBasal?.cancel(at: now) - podState.suspendState = .resumed(now) + if !deliveryType.contains(.basal) { + podState.suspendState = .resumed(now) + } canceledDose = podState.unfinalizedTempBasal log.info("Interrupted temp basal: %@", String(describing: canceledDose)) } diff --git a/OmniKitUI/PumpManager/OmnipodHUDProvider.swift b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift index 7cffde018..2079180d5 100644 --- a/OmniKitUI/PumpManager/OmnipodHUDProvider.swift +++ b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift @@ -105,7 +105,7 @@ internal class OmnipodHUDProvider: NSObject, HUDProvider, PodStateObserver { let lifetime = expiresAt.timeIntervalSince(activatedAt) podLifeView.setPodLifeCycle(startTime: activatedAt, lifetime: lifetime) } else { - podLifeView.setPodLifeCycle(startTime: Date(), lifetime: 0) + podLifeView.setPodLifeCycle(startTime: Date(), lifetime: Pod.nominalPodLife) } } From 26931d3545da011a5bd4df4d6b2b0462b0cce71a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 25 Nov 2019 21:32:37 -0600 Subject: [PATCH 57/71] Detect remote flag for commands (#564) * Detect remote flag for commands * Fix tests * Handle remote trigger flag for BolusNormal on x22 pumps --- Cartfile.resolved | 2 +- .../PumpEvents/BolusNormalPumpEvent.swift | 39 ++++++++----------- MinimedKit/PumpEvents/ResumePumpEvent.swift | 4 ++ MinimedKit/PumpEvents/SuspendPumpEvent.swift | 6 ++- .../PumpEvents/TempBasalPumpEvent.swift | 4 ++ .../PumpManager/MinimedPumpManager.swift | 5 +++ .../PumpEvents/ResumePumpEventTests.swift | 24 ++++++++++++ MinimedKitTests/PumpOpsSynchronousTests.swift | 17 +++----- RileyLink.xcodeproj/project.pbxproj | 4 ++ 9 files changed, 69 insertions(+), 36 deletions(-) create mode 100644 MinimedKitTests/PumpEvents/ResumePumpEventTests.swift diff --git a/Cartfile.resolved b/Cartfile.resolved index afbbe87d9..57fe30949 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "a08405ec7a4e38fa96e39ae079114db30f393a2a" +github "LoopKit/LoopKit" "a931da1ca106697b791d1508bad357c47f73dec0" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" diff --git a/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift b/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift index 4c98bba58..c5965c168 100644 --- a/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift +++ b/MinimedKit/PumpEvents/BolusNormalPumpEvent.swift @@ -24,6 +24,20 @@ public struct BolusNormalPumpEvent: TimestampedPumpEvent { public let unabsorbedInsulinTotal: Double public let type: BolusType public let duration: TimeInterval + public let wasRemotelyTriggered: Bool + + public init(length: Int, rawData: Data, timestamp: DateComponents, unabsorbedInsulinRecord: UnabsorbedInsulinPumpEvent?, amount: Double, programmed: Double, unabsorbedInsulinTotal: Double, type: BolusType, duration: TimeInterval, wasRemotelyTriggered: Bool) { + self.length = length + self.rawData = rawData + self.timestamp = timestamp + self.unabsorbedInsulinRecord = unabsorbedInsulinRecord + self.amount = amount + self.programmed = programmed + self.unabsorbedInsulinTotal = unabsorbedInsulinTotal + self.type = type + self.duration = duration + self.wasRemotelyTriggered = wasRemotelyTriggered + } /* It takes a MM pump about 40s to deliver 1 Unit while bolusing @@ -40,28 +54,7 @@ public struct BolusNormalPumpEvent: TimestampedPumpEvent { } } - public init(length: Int, rawData: Data, timestamp: DateComponents, unabsorbedInsulinRecord: UnabsorbedInsulinPumpEvent?, amount: Double, programmed: Double, unabsorbedInsulinTotal: Double, type: BolusType, duration: TimeInterval) { - self.length = length - self.rawData = rawData - self.timestamp = timestamp - self.unabsorbedInsulinRecord = unabsorbedInsulinRecord - self.amount = amount - self.programmed = programmed - self.unabsorbedInsulinTotal = unabsorbedInsulinTotal - self.type = type - self.duration = duration - } - public init?(availableData: Data, pumpModel: PumpModel) { - let length: Int - let rawData: Data - let timestamp: DateComponents - var unabsorbedInsulinRecord: UnabsorbedInsulinPumpEvent? - let amount: Double - let programmed: Double - let unabsorbedInsulinTotal: Double - let type: BolusType - let duration: TimeInterval func doubleValueFromData(at index: Int) -> Double { return Double(availableData[index]) @@ -81,12 +74,14 @@ public struct BolusNormalPumpEvent: TimestampedPumpEvent { if pumpModel.larger { timestamp = DateComponents(pumpEventData: availableData, offset: 8) + wasRemotelyTriggered = availableData[11] & 0b01000000 != 0 programmed = decodeInsulin(from: availableData.subdata(in: 1..<3)) amount = decodeInsulin(from: availableData.subdata(in: 3..<5)) unabsorbedInsulinTotal = decodeInsulin(from: availableData.subdata(in: 5..<7)) duration = TimeInterval(minutes: 30 * doubleValueFromData(at: 7)) } else { timestamp = DateComponents(pumpEventData: availableData, offset: 4) + wasRemotelyTriggered = availableData[7] & 0b01000000 != 0 programmed = decodeInsulin(from: availableData.subdata(in: 1..<2)) amount = decodeInsulin(from: availableData.subdata(in: 2..<3)) duration = TimeInterval(minutes: 30 * doubleValueFromData(at: 3)) @@ -94,7 +89,6 @@ public struct BolusNormalPumpEvent: TimestampedPumpEvent { } type = duration > 0 ? .square : .normal - self.init(length: length, rawData: rawData, timestamp: timestamp, unabsorbedInsulinRecord: unabsorbedInsulinRecord, amount:amount, programmed: programmed, unabsorbedInsulinTotal: unabsorbedInsulinTotal, type: type, duration: duration) } public var dictionaryRepresentation: [String: Any] { @@ -103,6 +97,7 @@ public struct BolusNormalPumpEvent: TimestampedPumpEvent { "amount": amount, "programmed": programmed, "type": type.rawValue, + "wasRemotelyTriggered": wasRemotelyTriggered, ] if let unabsorbedInsulinRecord = unabsorbedInsulinRecord { diff --git a/MinimedKit/PumpEvents/ResumePumpEvent.swift b/MinimedKit/PumpEvents/ResumePumpEvent.swift index 1959d53c1..638e19052 100644 --- a/MinimedKit/PumpEvents/ResumePumpEvent.swift +++ b/MinimedKit/PumpEvents/ResumePumpEvent.swift @@ -12,6 +12,7 @@ public struct ResumePumpEvent: TimestampedPumpEvent { public let length: Int public let rawData: Data public let timestamp: DateComponents + public let wasRemotelyTriggered: Bool public init?(availableData: Data, pumpModel: PumpModel) { length = 7 @@ -23,11 +24,14 @@ public struct ResumePumpEvent: TimestampedPumpEvent { rawData = availableData.subdata(in: 0.. BolusNormalPumpEvent { - //2016-08-01 05:00:16 +000 - let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2016, month: 8, day: 1, hour: 5, minute: 0, second: 16) - let data = Data(hexadecimalString: "01009009600058008a344b1010")! - return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(minutes: 120)) - } - func createSquareBolusEvent2010() -> BolusNormalPumpEvent { //2010-08-01 05:00:16 +000 let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2010, month: 8, day: 1, hour: 5, minute: 0, second: 16) let data = Data(hexadecimalString: "01009000900058008a344b1010")! - return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(minutes: 120)) + return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(minutes: 120), wasRemotelyTriggered: false) } func createSquareBolusEvent(dateComponents: DateComponents) -> BolusNormalPumpEvent { let data = Data(hexadecimalString: randomDataString(length: squareBolusDataLength))! - return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(hours: 8)) + return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(hours: 8), wasRemotelyTriggered: false) } func createBolusEvent2011() -> BolusNormalPumpEvent { //2010-08-01 05:00:11 +000 let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2011, month: 8, day: 1, hour: 5, minute: 0, second: 16) let data = Data(hexadecimalString: "01009000900058008a344b10FF")! - return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .normal, duration: TimeInterval(minutes: 120)) + return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .normal, duration: TimeInterval(minutes: 120), wasRemotelyTriggered: false) } func createTempEventBasal2016() -> TempBasalPumpEvent { @@ -296,7 +289,7 @@ class PumpOpsSynchronousTests: XCTestCase { let timeInterval: TimeInterval = TimeInterval(minutes: 2) let data = Data(hexadecimalString:"338c4055145d2000")! - return BolusNormalPumpEvent(length: 13, rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 2.0, programmed: 1.0, unabsorbedInsulinTotal: 0.0, type: .normal, duration: timeInterval) + return BolusNormalPumpEvent(length: 13, rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 2.0, programmed: 1.0, unabsorbedInsulinTotal: 0.0, type: .normal, duration: timeInterval, wasRemotelyTriggered: false) } func createNonDelayedEvent2009() -> BolusReminderPumpEvent { @@ -311,7 +304,7 @@ class PumpOpsSynchronousTests: XCTestCase { // from comment at https://gist.github.com/szhernovoy/276e69eb90a0de84dd90 func randomDataString(length:Int) -> String { let charSet = "abcdef0123456789" - var c = charSet.map { String($0) } + let c = charSet.map { String($0) } var s:String = "" for _ in 0.. Date: Fri, 6 Dec 2019 19:34:17 -0800 Subject: [PATCH 58/71] Calculate insertion wait time from Pod cannulaInsertionUnits constant (#567) * Calculate insertion wait time from Pod cannulaInsertionUnits constant * New primeDeliveryRate, secondsPerPrimePulse & secondsPerBolusPulse constants --- .../MessageBlocks/BolusExtraCommand.swift | 2 +- OmniKit/Model/Pod.swift | 15 ++++++++++++--- OmniKit/PumpManager/PodCommsSession.swift | 12 ++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift index 8d6c1beb0..dfd965870 100644 --- a/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/BolusExtraCommand.swift @@ -64,7 +64,7 @@ public struct BolusExtraCommand : MessageBlock { squareWaveDuration = timeBetweenExtendedPulses * Double(pulseCountX10) / 10 } - public init(units: Double, timeBetweenPulses: TimeInterval = 2, squareWaveUnits: Double = 0.0, squareWaveDuration: TimeInterval = 0, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) { + public init(units: Double, timeBetweenPulses: TimeInterval = Pod.secondsPerBolusPulse, squareWaveUnits: Double = 0.0, squareWaveDuration: TimeInterval = 0, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) { self.acknowledgementBeep = acknowledgementBeep self.completionBeep = completionBeep self.programReminderInterval = programReminderInterval diff --git a/OmniKit/Model/Pod.swift b/OmniKit/Model/Pod.swift index f61d8d6bd..24c8ff874 100644 --- a/OmniKit/Model/Pod.swift +++ b/OmniKit/Model/Pod.swift @@ -13,10 +13,19 @@ public struct Pod { public static let pulseSize: Double = 0.05 // Number of pulses required to deliver one unit of insulin - public static let pulsesPerUnit: Double = 20 + public static let pulsesPerUnit: Double = 1 / Pod.pulseSize - // Units per second - public static let bolusDeliveryRate: Double = 0.025 + // Seconds per pulse for boluses + public static let secondsPerBolusPulse: Double = 2 + + // Units per second for boluses + public static let bolusDeliveryRate: Double = Pod.pulseSize / Pod.secondsPerBolusPulse + + // Seconds per pulse for priming/cannula insertion + public static let secondsPerPrimePulse: Double = 1 + + // Units per second for priming/cannula insertion + public static let primeDeliveryRate: Double = Pod.pulseSize / Pod.secondsPerPrimePulse // User configured time before expiration advisory (PDM allows 1-24 hours) public static let expirationAlertWindow = TimeInterval(hours: 2) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 1c6b8304c..09c69b7d7 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -256,7 +256,7 @@ public class PodCommsSession { public func prime() throws -> TimeInterval { //4c00 00c8 0102 - let primeDuration = TimeInterval(seconds: 55) + let primeDuration = TimeInterval(seconds: 55) // a bit more than (Pod.primeUnits / Pod.primeDeliveryRate) // Skip following alerts if we've already done them before if podState.setupProgress != .startingPrime { @@ -281,7 +281,7 @@ public class PodCommsSession { podState.primeFinishTime = primeFinishTime podState.setupProgress = .startingPrime - let timeBetweenPulses = TimeInterval(seconds: 1) + let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerPrimePulse) let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: Pod.primeUnits, timeBetweenPulses: timeBetweenPulses) let scheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) let bolusExtraCommand = BolusExtraCommand(units: Pod.primeUnits, timeBetweenPulses: timeBetweenPulses) @@ -336,7 +336,7 @@ public class PodCommsSession { } public func insertCannula() throws -> TimeInterval { - let insertionWait: TimeInterval = .seconds(10) + let insertionWait: TimeInterval = .seconds(Pod.cannulaInsertionUnits / Pod.primeDeliveryRate) guard let activatedAt = podState.activatedAt else { throw PodCommsError.noPodPaired @@ -366,7 +366,7 @@ public class PodCommsSession { // Mark 0.5U delivery with 1 second between pulses for cannula insertion - let timeBetweenPulses = TimeInterval(seconds: 1) + let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerPrimePulse) let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: Pod.cannulaInsertionUnits, timeBetweenPulses: timeBetweenPulses) let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) @@ -405,7 +405,7 @@ public class PodCommsSession { public func bolus(units: Double, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { - let timeBetweenPulses = TimeInterval(seconds: 2) + let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerBolusPulse) let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: units, timeBetweenPulses: timeBetweenPulses) let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) @@ -416,7 +416,7 @@ public class PodCommsSession { // Between bluetooth and the radio and firmware, about 1.2s on average passes before we start tracking let commsOffset = TimeInterval(seconds: -1.5) - let bolusExtraCommand = BolusExtraCommand(units: units, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) + let bolusExtraCommand = BolusExtraCommand(units: units, timeBetweenPulses: timeBetweenPulses, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) do { let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date().addingTimeInterval(commsOffset), scheduledCertainty: .certain) From f47c390c710dbfa9f8c85981f0940989b5e58297 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Mon, 16 Dec 2019 20:39:49 -0800 Subject: [PATCH 59/71] Have the Loop Insulin Delivered start at 0 U despite variable priming amounts reported from Pod (#563) * Exclude the cannula insertion bolus amount from the calculated insulin delivered * Add a new persistent setupUnitsDelivered PodState variable to save setup amount counted by Pod as being actually delivered so that the user reported Insulin Delivered starts at 0 U. --- OmniKit/PumpManager/PodCommsSession.swift | 22 ++++++++++++++----- .../PumpManager/PodInsulinMeasurements.swift | 11 +++++++--- OmniKit/PumpManager/PodState.swift | 14 +++++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 09c69b7d7..197c9d970 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -335,6 +335,14 @@ public class PodCommsSession { } catch {} // never critical } + private func markSetupProgressCompleted(statusResponse: StatusResponse) { + if (podState.setupProgress != .completed) { + podState.setupProgress = .completed + podState.setupUnitsDelivered = statusResponse.insulin // stash the current insulin delivered value as the baseline + log.info("Total setup units delivered: %@", String(describing: statusResponse.insulin)) + } + } + public func insertCannula() throws -> TimeInterval { let insertionWait: TimeInterval = .seconds(Pod.cannulaInsertionUnits / Pod.primeDeliveryRate) @@ -345,15 +353,17 @@ public class PodCommsSession { if podState.setupProgress == .startingInsertCannula || podState.setupProgress == .cannulaInserting { // We started cannula insertion, but didn't get confirmation somehow, so check status let status: StatusResponse = try send([GetStatusCommand()]) - podState.updateFromStatusResponse(status) if status.podProgressStatus == .cannulaInserting { podState.setupProgress = .cannulaInserting - return insertionWait// Not sure when it started, wait full time to be sure + podState.updateFromStatusResponse(status) + return insertionWait // Not sure when it started, wait full time to be sure } if status.podProgressStatus.readyForDelivery { - podState.setupProgress = .completed + markSetupProgressCompleted(statusResponse: status) + podState.updateFromStatusResponse(status) return TimeInterval(0) // Already done; no need to wait } + podState.updateFromStatusResponse(status) } else { // Configure all the non-optional Pod Alarms let expirationTime = activatedAt + Pod.nominalPodLife @@ -382,13 +392,13 @@ public class PodCommsSession { public func checkInsertionCompleted() throws { if podState.setupProgress == .cannulaInserting { let response: StatusResponse = try send([GetStatusCommand()]) - podState.updateFromStatusResponse(response) if response.podProgressStatus.readyForDelivery { - podState.setupProgress = .completed + markSetupProgressCompleted(statusResponse: response) } + podState.updateFromStatusResponse(response) } } - + // Throws SetBolusError public enum DeliveryCommandResult { case success(statusResponse: StatusResponse) diff --git a/OmniKit/PumpManager/PodInsulinMeasurements.swift b/OmniKit/PumpManager/PodInsulinMeasurements.swift index 5bc071904..d67906007 100644 --- a/OmniKit/PumpManager/PodInsulinMeasurements.swift +++ b/OmniKit/PumpManager/PodInsulinMeasurements.swift @@ -15,11 +15,16 @@ public struct PodInsulinMeasurements: RawRepresentable, Equatable { public let delivered: Double public let reservoirVolume: Double? - public init(statusResponse: StatusResponse, validTime: Date) { + public init(statusResponse: StatusResponse, validTime: Date, setupUnitsDelivered: Double?) { self.validTime = validTime - self.delivered = statusResponse.insulin - Pod.primeUnits self.reservoirVolume = statusResponse.reservoirLevel - } + if let setupUnitsDelivered = setupUnitsDelivered { + self.delivered = statusResponse.insulin - setupUnitsDelivered + } else { + // subtract off the fixed setup command values as we don't have an actual value (yet) + self.delivered = max(statusResponse.insulin - Pod.primeUnits - Pod.cannulaInsertionUnits, 0) + } + } // RawRepresentable public init?(rawValue: RawValue) { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index a5654eba4..e66ae4ce2 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -46,6 +46,8 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public var activatedAt: Date? public var expiresAt: Date? // set based on StatusResponse timeActive and can change with Pod clock drift and/or system time change + public var setupUnitsDelivered: Double? + public let piVersion: String public let pmVersion: String public let lot: UInt32 @@ -157,7 +159,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl self.expiresAt = expiresAtComputed } updateDeliveryStatus(deliveryStatus: response.deliveryStatus) - lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: response, validTime: now) + lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: response, validTime: now, setupUnitsDelivered: setupUnitsDelivered) activeAlertSlots = response.alerts } @@ -262,6 +264,10 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl } } + if let setupUnitsDelivered = rawValue["setupUnitsDelivered"] as? Double { + self.setupUnitsDelivered = setupUnitsDelivered + } + if let suspended = rawValue["suspended"] as? Bool { // Migrate old value if suspended { @@ -408,6 +414,11 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl rawValue["expiresAt"] = expiresAt } + if let setupUnitsDelivered = setupUnitsDelivered { + rawValue["setupUnitsDelivered"] = setupUnitsDelivered + } + + if configuredAlerts.count > 0 { let rawConfiguredAlerts = Dictionary(uniqueKeysWithValues: configuredAlerts.map { slot, alarm in (String(describing: slot.rawValue), alarm.rawValue) }) @@ -425,6 +436,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl "* address: \(String(format: "%04X", address))", "* activatedAt: \(String(reflecting: activatedAt))", "* expiresAt: \(String(reflecting: expiresAt))", + "* setupUnitsDelivered: \(String(reflecting: setupUnitsDelivered))", "* piVersion: \(piVersion)", "* pmVersion: \(pmVersion)", "* lot: \(lot)", From 1be064c82df90351621299720d7106876cf9f5d3 Mon Sep 17 00:00:00 2001 From: achkars <34329790+achkars@users.noreply.github.com> Date: Sun, 22 Dec 2019 15:22:05 -0500 Subject: [PATCH 60/71] Add .canada PumpRegion (#573) * Update MinimedPumpIDSetupViewController.swift * Update PumpRegion.swift * Update PumpOpsSession.swift Add .canada PumpRegion * Update PumpOpsSession.swift --- MinimedKit/Models/PumpRegion.swift | 3 +++ MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift | 4 +++- RileyLinkKit/PumpOpsSession.swift | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/MinimedKit/Models/PumpRegion.swift b/MinimedKit/Models/PumpRegion.swift index 1822e2220..9d4147b7d 100644 --- a/MinimedKit/Models/PumpRegion.swift +++ b/MinimedKit/Models/PumpRegion.swift @@ -11,6 +11,7 @@ import Foundation public enum PumpRegion: Int, CustomStringConvertible { case northAmerica = 0 case worldWide + case canada public var description: String { switch self { @@ -18,6 +19,8 @@ public enum PumpRegion: Int, CustomStringConvertible { return LocalizedString("World-Wide", comment: "Describing the worldwide pump region") case .northAmerica: return LocalizedString("North America", comment: "Describing the North America pump region") + case .canada: + return LocalizedString("Canada", comment: "Describing the Canada pump region ") } } } diff --git a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift index b7effb9a5..6ea567863 100644 --- a/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift +++ b/MinimedKitUI/Setup/MinimedPumpIDSetupViewController.swift @@ -23,8 +23,10 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { var region: PumpRegion { switch self { - case .northAmerica, .canada: + case .northAmerica: return .northAmerica + case .canada: + return .canada case .worldWide: return .worldWide } diff --git a/RileyLinkKit/PumpOpsSession.swift b/RileyLinkKit/PumpOpsSession.swift index 89baed2f6..dd699efde 100644 --- a/RileyLinkKit/PumpOpsSession.swift +++ b/RileyLinkKit/PumpOpsSession.swift @@ -788,7 +788,7 @@ private extension PumpRegion { switch self { case .worldWide: scanFrequencies = [868.25, 868.30, 868.35, 868.40, 868.45, 868.50, 868.55, 868.60, 868.65] - case .northAmerica: + case .northAmerica, .canada: scanFrequencies = [916.45, 916.50, 916.55, 916.60, 916.65, 916.70, 916.75, 916.80] } @@ -878,7 +878,7 @@ extension PumpOpsSession { try session.updateRegister(.mdmcfg1, value: 0x62) try session.updateRegister(.mdmcfg0, value: 0x1A) try session.updateRegister(.deviatn, value: 0x13) - case .northAmerica: + case .northAmerica, .canada: //try session.updateRegister(.mdmcfg4, value: 0x99) try setRXFilterMode(.narrow) //try session.updateRegister(.mdmcfg3, value: 0x66) From 4b099a74b19015bd3f6570a04dc88a4e91aa42ba Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Mon, 23 Dec 2019 13:31:04 -0800 Subject: [PATCH 61/71] Record all non-garbage Pod responses (#571) * Record pod responses before trying to parse to ensure they get logged Part of fix for https://github.com/ps2/rileylink_ios/issues/569 * Improved logic to avoid logging partial messages and to mark failed responses * Don't do any logging for a garbage message --- OmniKit/MessageTransport/MessageTransport.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/OmniKit/MessageTransport/MessageTransport.swift b/OmniKit/MessageTransport/MessageTransport.swift index 816e172fd..6364835db 100644 --- a/OmniKit/MessageTransport/MessageTransport.swift +++ b/OmniKit/MessageTransport/MessageTransport.swift @@ -241,7 +241,7 @@ class PodMessageTransport: MessageTransport { } // Assemble fragmented message from multiple packets - let response = try { () throws -> Message in + let response = try { () throws -> Message in var responseData = responsePacket.data while true { do { @@ -258,9 +258,17 @@ class PodMessageTransport: MessageTransport { throw PodCommsError.unexpectedPacketType(packetType: conPacket.packetType) } responseData += conPacket.data + } catch MessageError.invalidCrc { + // throw the error without any logging for a garbage message + throw MessageError.invalidCrc + } catch let error { + // log any other non-garbage messages that generate errors + log.debug("Error Recv(Hex): %@", responseData.hexadecimalString) + messageLogger?.didReceive(responseData) + throw error } } - }() + }() ackUntilQuiet() From 4012c8362e456003d20535747a7734f66530e71d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 23 Dec 2019 15:43:20 -0600 Subject: [PATCH 62/71] Attempt resolution of uncertain bolus while canceling (#574) * Attempt resolution of uncertain bolus while canceling * Add packet logging for sysdiagnose --- .../MessageTransport/MessageTransport.swift | 19 +++++++++---------- OmniKit/PumpManager/OmnipodPumpManager.swift | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/OmniKit/MessageTransport/MessageTransport.swift b/OmniKit/MessageTransport/MessageTransport.swift index 6364835db..0511cb396 100644 --- a/OmniKit/MessageTransport/MessageTransport.swift +++ b/OmniKit/MessageTransport/MessageTransport.swift @@ -171,21 +171,22 @@ class PodMessageTransport: MessageTransport { do { candidatePacket = try Packet(rfPacket: rfPacket) + log.default("Received packet (%d): %@", rfPacket.rssi, rfPacket.data.hexadecimalString) } catch PacketError.insufficientData { - log.debug("Insufficient packet data: %@", rfPacket.data.hexadecimalString) + log.default("Insufficient packet data: %@", rfPacket.data.hexadecimalString) continue } catch let error { - log.debug("Packet error: %@", String(describing: error)) + log.default("Packet error: %@", String(describing: error)) continue } guard candidatePacket.address == packet.address else { - log.debug("Address %@ does not match %@", String(describing: candidatePacket.address), String(describing: packet.address)) + log.default("Address %@ does not match %@", String(describing: candidatePacket.address), String(describing: packet.address)) continue } guard candidatePacket.sequenceNum == ((packet.sequenceNum + 1) & 0b11111) else { - log.debug("Sequence %@ does not match %@", String(describing: candidatePacket.sequenceNum), String(describing: ((packet.sequenceNum + 1) & 0b11111))) + log.default("Sequence %@ does not match %@", String(describing: candidatePacket.sequenceNum), String(describing: ((packet.sequenceNum + 1) & 0b11111))) continue } @@ -236,7 +237,7 @@ class PodMessageTransport: MessageTransport { guard responsePacket.packetType != .ack else { messageLogger?.didReceive(responsePacket.data) - log.debug("Pod responded with ack instead of response: %@", String(describing: responsePacket)) + log.default("Pod responded with ack instead of response: %@", String(describing: responsePacket)) throw PodCommsError.podAckedInsteadOfReturningResponse } @@ -246,7 +247,6 @@ class PodMessageTransport: MessageTransport { while true { do { let msg = try Message(encodedData: responseData) - log.debug("Recv(Hex): %@", responseData.hexadecimalString) messageLogger?.didReceive(responseData) return msg } catch MessageError.notEnoughData { @@ -254,7 +254,7 @@ class PodMessageTransport: MessageTransport { let conPacket = try self.exchangePackets(packet: makeAckPacket(), repeatCount: 3, preambleExtension:TimeInterval(milliseconds: 40)) guard conPacket.packetType == .con else { - log.debug("Expected CON packet, received; %@", String(describing: conPacket)) + log.default("Expected CON packet, received; %@", String(describing: conPacket)) throw PodCommsError.unexpectedPacketType(packetType: conPacket.packetType) } responseData += conPacket.data @@ -273,7 +273,7 @@ class PodMessageTransport: MessageTransport { ackUntilQuiet() guard response.messageBlocks.count > 0 else { - log.debug("Empty response") + log.default("Empty response") throw PodCommsError.emptyResponse } @@ -281,8 +281,7 @@ class PodMessageTransport: MessageTransport { incrementMessageNumber() } - log.debug("Recv: %@", String(describing: response)) - return response + return response } catch let error { log.error("Error during communication with POD: %@", String(describing: error)) throw error diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 613f90416..9fee9bf90 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -1302,6 +1302,15 @@ extension OmnipodPumpManager: PumpManager { self.setState({ (state) in state.bolusEngageState = .disengaging }) + + if let bolus = self.state.podState?.unfinalizedBolus, !bolus.isFinished, bolus.scheduledCertainty == .uncertain { + let status = try session.getStatus() + + if !status.deliveryStatus.bolusing { + completion(.success(nil)) + return + } + } // when cancelling a bolus give a type 6 beeeeeep to match PDM if doing bolus confirmation beeps let beeptype: BeepType = self.bolusBeeps ? .beeeeeep : .noBeep @@ -1488,12 +1497,14 @@ extension OmnipodPumpManager: PumpManager { extension OmnipodPumpManager: MessageLogger { func didSend(_ message: Data) { + log.default("didSend: %{public}@", message.hexadecimalString) setState { (state) in state.messageLog.record(MessageLogEntry(messageDirection: .send, timestamp: Date(), data: message)) } } func didReceive(_ message: Data) { + log.default("didReceive: %{public}@", message.hexadecimalString) setState { (state) in state.messageLog.record(MessageLogEntry(messageDirection: .receive, timestamp: Date(), data: message)) } @@ -1506,9 +1517,9 @@ extension OmnipodPumpManager: PodCommsDelegate { // Check for any updates to bolus certainty, and log them if let bolus = state.podState?.unfinalizedBolus, bolus.scheduledCertainty == .uncertain, !bolus.isFinished { if podState.unfinalizedBolus?.scheduledCertainty == .some(.certain) { - self.log.debug("Resolved bolus uncertainty: did bolus") + self.log.default("Resolved bolus uncertainty: did bolus") } else if podState.unfinalizedBolus == nil { - self.log.debug("Resolved bolus uncertainty: did not bolus") + self.log.default("Resolved bolus uncertainty: did not bolus") } } state.podState = podState From d6023f9f190217925dd247761ca50e1ffe261404 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 23 Dec 2019 23:32:33 -0600 Subject: [PATCH 63/71] Bump carthage revs --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 57fe30949..5c609e184 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "a931da1ca106697b791d1508bad357c47f73dec0" +github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" From 871fa6b64219d5fe2fd85ce43bf8e99f9f60e361 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Wed, 25 Dec 2019 21:52:54 -0800 Subject: [PATCH 64/71] Miscellanous PodCommsSessions fixes (#575) * Allow a nonce command to appear anywhere in a message * Pass on optional programReminderInterval argument to the underlying insulin Extra functions --- OmniKit/PumpManager/PodCommsSession.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/OmniKit/PumpManager/PodCommsSession.swift b/OmniKit/PumpManager/PodCommsSession.swift index 197c9d970..2168ad579 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -200,17 +200,18 @@ public class PodCommsSession { var sentNonce: UInt32? - while (triesRemaining > 0) { triesRemaining -= 1 - if let nonceBlock = messageBlocks[0] as? NonceResyncableMessageBlock { - sentNonce = nonceBlock.nonce + for command in blocksToSend { + if let nonceBlock = command as? NonceResyncableMessageBlock { + sentNonce = nonceBlock.nonce + break // N.B. all nonce commands in single message should have the same value + } } let message = Message(address: podState.address, messageBlocks: blocksToSend, sequenceNum: messageNumber, expectFollowOnMessage: expectFollowOnMessage) - let response = try transport.sendMessage(message) // Simulate fault @@ -229,10 +230,11 @@ public class PodCommsSession { errorResponse.errorReponseType == .badNonce { podState.resyncNonce(syncWord: errorResponse.nonceSearchKey, sentNonce: sentNonce, messageSequenceNum: message.sequenceNum) - log.info("resyncNonce(syncWord: %02X, sentNonce: %04X, messageSequenceNum: %d) -> %04X", errorResponse.nonceSearchKey, sentNonce, message.sequenceNum, podState.currentNonce) + log.info("resyncNonce(syncWord: 0x%02x, sentNonce: 0x%04x, messageSequenceNum: %d) -> 0x%04x", errorResponse.nonceSearchKey, sentNonce, message.sequenceNum, podState.currentNonce) blocksToSend = blocksToSend.map({ (block) -> MessageBlock in if var resyncableBlock = block as? NonceResyncableMessageBlock { + log.info("Replaced old nonce 0x%04x with resync nonce 0x%04x", resyncableBlock.nonce, podState.currentNonce) resyncableBlock.nonce = podState.currentNonce return resyncableBlock } else { @@ -426,7 +428,7 @@ public class PodCommsSession { // Between bluetooth and the radio and firmware, about 1.2s on average passes before we start tracking let commsOffset = TimeInterval(seconds: -1.5) - let bolusExtraCommand = BolusExtraCommand(units: units, timeBetweenPulses: timeBetweenPulses, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) + let bolusExtraCommand = BolusExtraCommand(units: units, timeBetweenPulses: timeBetweenPulses, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) do { let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date().addingTimeInterval(commsOffset), scheduledCertainty: .certain) @@ -456,7 +458,7 @@ public class PodCommsSession { public func setTempBasal(rate: Double, duration: TimeInterval, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { let tempBasalCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, tempBasalRate: rate, duration: duration) - let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep) + let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) guard podState.unfinalizedBolus?.isFinished != false else { return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) @@ -545,7 +547,7 @@ public class PodCommsSession { public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) throws -> StatusResponse { let basalScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, basalSchedule: schedule, scheduleOffset: scheduleOffset) - let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: 0) + let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) do { let status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) From 4850a9ecf656a80f241c2fcc81f228c46e400fb4 Mon Sep 17 00:00:00 2001 From: katie disimone Date: Sat, 28 Dec 2019 08:11:30 -0800 Subject: [PATCH 65/71] Translations update (#561) * Deleting unused infoplist.strings * Adding OmniKit and OmniKitUI localizable.strings and touchups on existing genstrings * Adding new translations for old languages * Initial add of Japanese, Vietnamese, Swedish, Portuguese (BRA), Danish, and Finnish * Adding available new language translations (mostly Vietnamese, Swedish, Finnish, and Portuguese) * Localizing OmniPodKitUI's OmnipodPumpManager.storyboard * omnipodPumpManager storyboard translation additions for French, Finnish, Portuguese, Dutch * typo fix * Touchups for spanish * adding Romanian * All languages * typo fixes * typo fixes * last of typo fixes * completing portuguese, japanese, and danish * swedish touchups * romanian and finnish --- Crypto/de.lproj/InfoPlist.strings | 3 - Crypto/es.lproj/InfoPlist.strings | 3 - Crypto/fr.lproj/InfoPlist.strings | 3 - Crypto/it.lproj/InfoPlist.strings | 3 - Crypto/nb.lproj/InfoPlist.strings | 3 - Crypto/nl.lproj/InfoPlist.strings | 3 - Crypto/pl.lproj/InfoPlist.strings | 3 - Crypto/ru.lproj/InfoPlist.strings | 3 - Crypto/zh-Hans.lproj/InfoPlist.strings | 3 - MinimedKit/Base.lproj/Localizable.strings | 1 - MinimedKit/da.lproj/Localizable.strings | 99 +++ MinimedKit/de.lproj/InfoPlist.strings | 3 - MinimedKit/es.lproj/InfoPlist.strings | 3 - MinimedKit/fi.lproj/Localizable.strings | 98 +++ MinimedKit/fr.lproj/InfoPlist.strings | 3 - MinimedKit/it.lproj/InfoPlist.strings | 3 - MinimedKit/ja.lproj/Localizable.strings | 98 +++ MinimedKit/nb.lproj/InfoPlist.strings | 3 - MinimedKit/nl.lproj/InfoPlist.strings | 3 - MinimedKit/pl.lproj/InfoPlist.strings | 3 - MinimedKit/pt-BR.lproj/Localizable.strings | 98 +++ MinimedKit/ro.lproj/Localizable.strings | 98 +++ MinimedKit/ru.lproj/InfoPlist.strings | 3 - MinimedKit/sv.lproj/Localizable.strings | 98 +++ MinimedKit/vi.lproj/Localizable.strings | 98 +++ MinimedKit/zh-Hans.lproj/InfoPlist.strings | 3 - MinimedKitTests/de.lproj/InfoPlist.strings | 3 - MinimedKitTests/es.lproj/InfoPlist.strings | 3 - MinimedKitTests/fr.lproj/InfoPlist.strings | 3 - MinimedKitTests/it.lproj/InfoPlist.strings | 3 - MinimedKitTests/nb.lproj/InfoPlist.strings | 3 - MinimedKitTests/nl.lproj/InfoPlist.strings | 3 - MinimedKitTests/pl.lproj/InfoPlist.strings | 3 - MinimedKitTests/ru.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/InfoPlist.strings | 3 - MinimedKitUI/Base.lproj/Localizable.strings | 20 +- MinimedKitUI/da.lproj/Localizable.strings | 209 +++++++ .../da.lproj/MinimedPumpManager.strings | 75 +++ MinimedKitUI/de.lproj/InfoPlist.strings | 3 - MinimedKitUI/de.lproj/Localizable.strings | 20 +- .../de.lproj/MinimedPumpManager.strings | 37 +- .../en.lproj/MinimedPumpManager.strings | 74 +++ MinimedKitUI/es.lproj/InfoPlist.strings | 3 - MinimedKitUI/es.lproj/Localizable.strings | 20 +- .../es.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/fi.lproj/Localizable.strings | 209 +++++++ .../fi.lproj/MinimedPumpManager.strings | 74 +++ MinimedKitUI/fr.lproj/InfoPlist.strings | 3 - MinimedKitUI/fr.lproj/Localizable.strings | 20 +- .../fr.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/it.lproj/InfoPlist.strings | 3 - MinimedKitUI/it.lproj/Localizable.strings | 20 +- .../it.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/ja.lproj/Localizable.strings | 209 +++++++ .../ja.lproj/MinimedPumpManager.strings | 74 +++ MinimedKitUI/nb.lproj/InfoPlist.strings | 3 - MinimedKitUI/nb.lproj/Localizable.strings | 22 +- .../nb.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/nl.lproj/InfoPlist.strings | 3 - MinimedKitUI/nl.lproj/Localizable.strings | 20 +- .../nl.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/pl.lproj/InfoPlist.strings | 3 - MinimedKitUI/pl.lproj/Localizable.strings | 20 +- .../pl.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/pt-BR.lproj/Localizable.strings | 210 +++++++ .../pt-BR.lproj/MinimedPumpManager.strings | 74 +++ MinimedKitUI/ro.lproj/Localizable.strings | 210 +++++++ .../ro.lproj/MinimedPumpManager.strings | 74 +++ MinimedKitUI/ru.lproj/InfoPlist.strings | 3 - MinimedKitUI/ru.lproj/Localizable.strings | 20 +- .../ru.lproj/MinimedPumpManager.strings | 3 + MinimedKitUI/sv.lproj/Localizable.strings | 209 +++++++ .../sv.lproj/MinimedPumpManager.strings | 75 +++ MinimedKitUI/vi.lproj/Localizable.strings | 209 +++++++ .../vi.lproj/MinimedPumpManager.strings | 74 +++ MinimedKitUI/zh-Hans.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/Localizable.strings | 20 +- .../zh-Hans.lproj/MinimedPumpManager.strings | 3 + .../de.lproj/InfoPlist.strings | 3 - .../es.lproj/InfoPlist.strings | 3 - .../fr.lproj/InfoPlist.strings | 3 - .../it.lproj/InfoPlist.strings | 3 - .../nb.lproj/InfoPlist.strings | 3 - .../nl.lproj/InfoPlist.strings | 3 - .../pl.lproj/InfoPlist.strings | 3 - .../ru.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/InfoPlist.strings | 3 - .../de.lproj/InfoPlist.strings | 3 - .../es.lproj/InfoPlist.strings | 3 - .../fr.lproj/InfoPlist.strings | 3 - .../it.lproj/InfoPlist.strings | 3 - .../nb.lproj/InfoPlist.strings | 3 - .../nl.lproj/InfoPlist.strings | 3 - .../pl.lproj/InfoPlist.strings | 3 - .../ru.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/InfoPlist.strings | 3 - OmniKit/da.lproj/Localizable.strings | 178 ++++++ OmniKit/de.lproj/Localizable.strings | 178 ++++++ OmniKit/en.lproj/Localizable.strings | 178 ++++++ OmniKit/es.lproj/Localizable.strings | 178 ++++++ OmniKit/fi.lproj/Localizable.strings | 178 ++++++ OmniKit/fr.lproj/Localizable.strings | 178 ++++++ OmniKit/it.lproj/Localizable.strings | 178 ++++++ OmniKit/ja.lproj/Localizable.strings | 178 ++++++ OmniKit/nb.lproj/Localizable.strings | 178 ++++++ OmniKit/nl.lproj/Localizable.strings | 178 ++++++ OmniKit/pl.lproj/Localizable.strings | 178 ++++++ OmniKit/pt-BR.lproj/Localizable.strings | 178 ++++++ OmniKit/ro.lproj/Localizable.strings | 178 ++++++ OmniKit/ru.lproj/Localizable.strings | 178 ++++++ OmniKit/sv.lproj/Localizable.strings | 178 ++++++ OmniKit/vi.lproj/Localizable.strings | 178 ++++++ OmniKit/zh-Hans.lproj/Localizable.strings | 178 ++++++ .../OmnipodPumpManager.storyboard | 0 OmniKitUI/da.lproj/Localizable.strings | 231 +++++++ OmniKitUI/da.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/de.lproj/Localizable.strings | 231 +++++++ OmniKitUI/de.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/en.lproj/Localizable.strings | 231 +++++++ OmniKitUI/en.lproj/OmnipodPumpManager.strings | 69 +++ OmniKitUI/es.lproj/Localizable.strings | 231 +++++++ OmniKitUI/es.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/fi.lproj/Localizable.strings | 231 +++++++ OmniKitUI/fi.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/fr.lproj/Localizable.strings | 231 +++++++ OmniKitUI/fr.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/it.lproj/Localizable.strings | 232 +++++++ OmniKitUI/it.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/ja.lproj/Localizable.strings | 231 +++++++ OmniKitUI/ja.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/nb.lproj/Localizable.strings | 232 +++++++ OmniKitUI/nb.lproj/OmnipodPumpManager.strings | 69 +++ OmniKitUI/nl.lproj/Localizable.strings | 232 +++++++ OmniKitUI/nl.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/pl.lproj/Localizable.strings | 231 +++++++ OmniKitUI/pl.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/pt-BR.lproj/Localizable.strings | 231 +++++++ .../pt-BR.lproj/OmnipodPumpManager.strings | 70 +++ OmniKitUI/ro.lproj/Localizable.strings | 231 +++++++ OmniKitUI/ro.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/ru.lproj/Localizable.strings | 231 +++++++ OmniKitUI/ru.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/sv.lproj/Localizable.strings | 231 +++++++ OmniKitUI/sv.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/vi.lproj/Localizable.strings | 231 +++++++ OmniKitUI/vi.lproj/OmnipodPumpManager.strings | 68 ++ OmniKitUI/zh-Hans.lproj/Localizable.strings | 231 +++++++ .../zh-Hans.lproj/OmnipodPumpManager.strings | 68 ++ RileyLink.xcodeproj/project.pbxproj | 579 +++++++----------- RileyLink/Base.lproj/Localizable.strings | 20 +- RileyLink/Models/NightscoutService.swift | 2 +- RileyLink/da.lproj/Localizable.strings | 51 ++ RileyLink/de.lproj/InfoPlist.strings | 0 RileyLink/de.lproj/Localizable.strings | 21 +- RileyLink/de.lproj/LoopKit.strings | 3 - RileyLink/en.lproj/InfoPlist.strings | 2 - RileyLink/en.lproj/Localizable.strings | 51 ++ RileyLink/es.lproj/InfoPlist.strings | 6 - RileyLink/es.lproj/Localizable.strings | 22 +- RileyLink/es.lproj/LoopKit.strings | 3 - RileyLink/fi.lproj/Localizable.strings | 51 ++ RileyLink/fr.lproj/InfoPlist.strings | 0 RileyLink/fr.lproj/Localizable.strings | 20 +- RileyLink/fr.lproj/LoopKit.strings | 3 - RileyLink/it.lproj/InfoPlist.strings | 0 RileyLink/it.lproj/Localizable.strings | 22 +- RileyLink/it.lproj/LoopKit.strings | 3 - RileyLink/ja.lproj/Localizable.strings | 51 ++ RileyLink/nb.lproj/InfoPlist.strings | 0 RileyLink/nb.lproj/Localizable.strings | 21 +- RileyLink/nb.lproj/LoopKit.strings | 3 - RileyLink/nl.lproj/InfoPlist.strings | 6 - RileyLink/nl.lproj/Localizable.strings | 21 +- RileyLink/nl.lproj/LoopKit.strings | 3 - RileyLink/pl.lproj/InfoPlist.strings | 2 - RileyLink/pl.lproj/Localizable.strings | 21 +- RileyLink/pt-BR.lproj/Localizable.strings | 51 ++ RileyLink/ro.lproj/Localizable.strings | 55 ++ RileyLink/ru.lproj/InfoPlist.strings | 6 - RileyLink/ru.lproj/Localizable.strings | 20 +- RileyLink/ru.lproj/LoopKit.strings | 3 - RileyLink/sv.lproj/Localizable.strings | 51 ++ RileyLink/vi.lproj/Localizable.strings | 51 ++ RileyLink/zh-Hans.lproj/InfoPlist.strings | 6 - RileyLink/zh-Hans.lproj/Localizable.strings | 22 +- RileyLink/zh-Hans.lproj/LoopKit.strings | 3 - RileyLinkBLEKit/RileyLinkDeviceError.swift | 2 +- RileyLinkBLEKit/da.lproj/Localizable.strings | 29 + RileyLinkBLEKit/de.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/es.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/fi.lproj/Localizable.strings | 29 + RileyLinkBLEKit/fr.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/it.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/ja.lproj/Localizable.strings | 29 + RileyLinkBLEKit/nb.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/nl.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/pl.lproj/InfoPlist.strings | 3 - .../pt-BR.lproj/Localizable.strings | 29 + RileyLinkBLEKit/ro.lproj/Localizable.strings | 29 + RileyLinkBLEKit/ru.lproj/InfoPlist.strings | 3 - RileyLinkBLEKit/sv.lproj/Localizable.strings | 29 + RileyLinkBLEKit/vi.lproj/Localizable.strings | 29 + .../zh-Hans.lproj/InfoPlist.strings | 3 - .../de.lproj/InfoPlist.strings | 3 - .../es.lproj/InfoPlist.strings | 3 - .../fr.lproj/InfoPlist.strings | 3 - .../it.lproj/InfoPlist.strings | 3 - .../nb.lproj/InfoPlist.strings | 3 - .../nl.lproj/InfoPlist.strings | 3 - .../pl.lproj/InfoPlist.strings | 3 - .../ru.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/de.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/es.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/fr.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/it.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/nb.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/nl.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/pl.lproj/InfoPlist.strings | 3 - RileyLinkKitTests/ru.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/Base.lproj/Localizable.strings | 19 +- RileyLinkKitUI/da.lproj/Localizable.strings | 41 ++ RileyLinkKitUI/de.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/de.lproj/Localizable.strings | 18 +- RileyLinkKitUI/es.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/es.lproj/Localizable.strings | 18 +- RileyLinkKitUI/fi.lproj/Localizable.strings | 42 ++ RileyLinkKitUI/fr.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/fr.lproj/Localizable.strings | 18 +- RileyLinkKitUI/it.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/it.lproj/Localizable.strings | 18 +- RileyLinkKitUI/ja.lproj/Localizable.strings | 41 ++ RileyLinkKitUI/nb.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/nb.lproj/Localizable.strings | 20 +- RileyLinkKitUI/nl.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/nl.lproj/Localizable.strings | 18 +- RileyLinkKitUI/pl.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/pl.lproj/Localizable.strings | 19 +- .../pt-BR.lproj/Localizable.strings | 42 ++ RileyLinkKitUI/ro.lproj/Localizable.strings | 42 ++ RileyLinkKitUI/ru.lproj/InfoPlist.strings | 3 - RileyLinkKitUI/ru.lproj/Localizable.strings | 18 +- RileyLinkKitUI/sv.lproj/Localizable.strings | 41 ++ RileyLinkKitUI/vi.lproj/Localizable.strings | 41 ++ .../zh-Hans.lproj/InfoPlist.strings | 3 - .../zh-Hans.lproj/Localizable.strings | 18 +- RileyLinkTests/de.lproj/InfoPlist.strings | 2 - RileyLinkTests/en.lproj/InfoPlist.strings | 2 - RileyLinkTests/es.lproj/InfoPlist.strings | 2 - RileyLinkTests/fr.lproj/InfoPlist.strings | 2 - RileyLinkTests/it.lproj/InfoPlist.strings | 2 - RileyLinkTests/nb.lproj/InfoPlist.strings | 2 - RileyLinkTests/nl.lproj/InfoPlist.strings | 2 - RileyLinkTests/pl.lproj/InfoPlist.strings | 2 - RileyLinkTests/ru.lproj/InfoPlist.strings | 2 - .../zh-Hans.lproj/InfoPlist.strings | 2 - 257 files changed, 12336 insertions(+), 1014 deletions(-) delete mode 100644 Crypto/de.lproj/InfoPlist.strings delete mode 100644 Crypto/es.lproj/InfoPlist.strings delete mode 100644 Crypto/fr.lproj/InfoPlist.strings delete mode 100644 Crypto/it.lproj/InfoPlist.strings delete mode 100644 Crypto/nb.lproj/InfoPlist.strings delete mode 100644 Crypto/nl.lproj/InfoPlist.strings delete mode 100644 Crypto/pl.lproj/InfoPlist.strings delete mode 100644 Crypto/ru.lproj/InfoPlist.strings delete mode 100644 Crypto/zh-Hans.lproj/InfoPlist.strings create mode 100644 MinimedKit/da.lproj/Localizable.strings delete mode 100644 MinimedKit/de.lproj/InfoPlist.strings delete mode 100644 MinimedKit/es.lproj/InfoPlist.strings create mode 100644 MinimedKit/fi.lproj/Localizable.strings delete mode 100644 MinimedKit/fr.lproj/InfoPlist.strings delete mode 100644 MinimedKit/it.lproj/InfoPlist.strings create mode 100644 MinimedKit/ja.lproj/Localizable.strings delete mode 100644 MinimedKit/nb.lproj/InfoPlist.strings delete mode 100644 MinimedKit/nl.lproj/InfoPlist.strings delete mode 100644 MinimedKit/pl.lproj/InfoPlist.strings create mode 100644 MinimedKit/pt-BR.lproj/Localizable.strings create mode 100644 MinimedKit/ro.lproj/Localizable.strings delete mode 100644 MinimedKit/ru.lproj/InfoPlist.strings create mode 100644 MinimedKit/sv.lproj/Localizable.strings create mode 100644 MinimedKit/vi.lproj/Localizable.strings delete mode 100644 MinimedKit/zh-Hans.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/de.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/es.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/fr.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/it.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/nb.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/nl.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/pl.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/ru.lproj/InfoPlist.strings delete mode 100644 MinimedKitTests/zh-Hans.lproj/InfoPlist.strings create mode 100644 MinimedKitUI/da.lproj/Localizable.strings create mode 100644 MinimedKitUI/da.lproj/MinimedPumpManager.strings delete mode 100644 MinimedKitUI/de.lproj/InfoPlist.strings create mode 100644 MinimedKitUI/en.lproj/MinimedPumpManager.strings delete mode 100644 MinimedKitUI/es.lproj/InfoPlist.strings create mode 100644 MinimedKitUI/fi.lproj/Localizable.strings create mode 100644 MinimedKitUI/fi.lproj/MinimedPumpManager.strings delete mode 100644 MinimedKitUI/fr.lproj/InfoPlist.strings delete mode 100644 MinimedKitUI/it.lproj/InfoPlist.strings create mode 100644 MinimedKitUI/ja.lproj/Localizable.strings create mode 100644 MinimedKitUI/ja.lproj/MinimedPumpManager.strings delete mode 100644 MinimedKitUI/nb.lproj/InfoPlist.strings delete mode 100644 MinimedKitUI/nl.lproj/InfoPlist.strings delete mode 100644 MinimedKitUI/pl.lproj/InfoPlist.strings create mode 100644 MinimedKitUI/pt-BR.lproj/Localizable.strings create mode 100644 MinimedKitUI/pt-BR.lproj/MinimedPumpManager.strings create mode 100644 MinimedKitUI/ro.lproj/Localizable.strings create mode 100644 MinimedKitUI/ro.lproj/MinimedPumpManager.strings delete mode 100644 MinimedKitUI/ru.lproj/InfoPlist.strings create mode 100644 MinimedKitUI/sv.lproj/Localizable.strings create mode 100644 MinimedKitUI/sv.lproj/MinimedPumpManager.strings create mode 100644 MinimedKitUI/vi.lproj/Localizable.strings create mode 100644 MinimedKitUI/vi.lproj/MinimedPumpManager.strings delete mode 100644 MinimedKitUI/zh-Hans.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/de.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/es.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/fr.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/it.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/nb.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/nl.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/pl.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/ru.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKit/zh-Hans.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/de.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/es.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/fr.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/it.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/nb.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/nl.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/pl.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/ru.lproj/InfoPlist.strings delete mode 100644 NightscoutUploadKitTests/zh-Hans.lproj/InfoPlist.strings create mode 100644 OmniKit/da.lproj/Localizable.strings create mode 100644 OmniKit/de.lproj/Localizable.strings create mode 100644 OmniKit/en.lproj/Localizable.strings create mode 100644 OmniKit/es.lproj/Localizable.strings create mode 100644 OmniKit/fi.lproj/Localizable.strings create mode 100644 OmniKit/fr.lproj/Localizable.strings create mode 100644 OmniKit/it.lproj/Localizable.strings create mode 100644 OmniKit/ja.lproj/Localizable.strings create mode 100644 OmniKit/nb.lproj/Localizable.strings create mode 100644 OmniKit/nl.lproj/Localizable.strings create mode 100644 OmniKit/pl.lproj/Localizable.strings create mode 100644 OmniKit/pt-BR.lproj/Localizable.strings create mode 100644 OmniKit/ro.lproj/Localizable.strings create mode 100644 OmniKit/ru.lproj/Localizable.strings create mode 100644 OmniKit/sv.lproj/Localizable.strings create mode 100644 OmniKit/vi.lproj/Localizable.strings create mode 100644 OmniKit/zh-Hans.lproj/Localizable.strings rename OmniKitUI/{ => Base.lproj}/OmnipodPumpManager.storyboard (100%) create mode 100644 OmniKitUI/da.lproj/Localizable.strings create mode 100644 OmniKitUI/da.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/de.lproj/Localizable.strings create mode 100644 OmniKitUI/de.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/en.lproj/Localizable.strings create mode 100644 OmniKitUI/en.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/es.lproj/Localizable.strings create mode 100644 OmniKitUI/es.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/fi.lproj/Localizable.strings create mode 100644 OmniKitUI/fi.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/fr.lproj/Localizable.strings create mode 100644 OmniKitUI/fr.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/it.lproj/Localizable.strings create mode 100644 OmniKitUI/it.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/ja.lproj/Localizable.strings create mode 100644 OmniKitUI/ja.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/nb.lproj/Localizable.strings create mode 100644 OmniKitUI/nb.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/nl.lproj/Localizable.strings create mode 100644 OmniKitUI/nl.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/pl.lproj/Localizable.strings create mode 100644 OmniKitUI/pl.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/pt-BR.lproj/Localizable.strings create mode 100644 OmniKitUI/pt-BR.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/ro.lproj/Localizable.strings create mode 100644 OmniKitUI/ro.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/ru.lproj/Localizable.strings create mode 100644 OmniKitUI/ru.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/sv.lproj/Localizable.strings create mode 100644 OmniKitUI/sv.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/vi.lproj/Localizable.strings create mode 100644 OmniKitUI/vi.lproj/OmnipodPumpManager.strings create mode 100644 OmniKitUI/zh-Hans.lproj/Localizable.strings create mode 100644 OmniKitUI/zh-Hans.lproj/OmnipodPumpManager.strings create mode 100644 RileyLink/da.lproj/Localizable.strings delete mode 100644 RileyLink/de.lproj/InfoPlist.strings delete mode 100644 RileyLink/de.lproj/LoopKit.strings delete mode 100644 RileyLink/en.lproj/InfoPlist.strings create mode 100644 RileyLink/en.lproj/Localizable.strings delete mode 100644 RileyLink/es.lproj/InfoPlist.strings delete mode 100644 RileyLink/es.lproj/LoopKit.strings create mode 100644 RileyLink/fi.lproj/Localizable.strings delete mode 100644 RileyLink/fr.lproj/InfoPlist.strings delete mode 100644 RileyLink/fr.lproj/LoopKit.strings delete mode 100644 RileyLink/it.lproj/InfoPlist.strings delete mode 100644 RileyLink/it.lproj/LoopKit.strings create mode 100644 RileyLink/ja.lproj/Localizable.strings delete mode 100644 RileyLink/nb.lproj/InfoPlist.strings delete mode 100644 RileyLink/nb.lproj/LoopKit.strings delete mode 100644 RileyLink/nl.lproj/InfoPlist.strings delete mode 100644 RileyLink/nl.lproj/LoopKit.strings delete mode 100644 RileyLink/pl.lproj/InfoPlist.strings create mode 100644 RileyLink/pt-BR.lproj/Localizable.strings create mode 100644 RileyLink/ro.lproj/Localizable.strings delete mode 100644 RileyLink/ru.lproj/InfoPlist.strings delete mode 100644 RileyLink/ru.lproj/LoopKit.strings create mode 100644 RileyLink/sv.lproj/Localizable.strings create mode 100644 RileyLink/vi.lproj/Localizable.strings delete mode 100644 RileyLink/zh-Hans.lproj/InfoPlist.strings delete mode 100644 RileyLink/zh-Hans.lproj/LoopKit.strings create mode 100644 RileyLinkBLEKit/da.lproj/Localizable.strings delete mode 100644 RileyLinkBLEKit/de.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKit/es.lproj/InfoPlist.strings create mode 100644 RileyLinkBLEKit/fi.lproj/Localizable.strings delete mode 100644 RileyLinkBLEKit/fr.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKit/it.lproj/InfoPlist.strings create mode 100644 RileyLinkBLEKit/ja.lproj/Localizable.strings delete mode 100644 RileyLinkBLEKit/nb.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKit/nl.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKit/pl.lproj/InfoPlist.strings create mode 100644 RileyLinkBLEKit/pt-BR.lproj/Localizable.strings create mode 100644 RileyLinkBLEKit/ro.lproj/Localizable.strings delete mode 100644 RileyLinkBLEKit/ru.lproj/InfoPlist.strings create mode 100644 RileyLinkBLEKit/sv.lproj/Localizable.strings create mode 100644 RileyLinkBLEKit/vi.lproj/Localizable.strings delete mode 100644 RileyLinkBLEKit/zh-Hans.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/de.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/es.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/fr.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/it.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/nb.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/nl.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/pl.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/ru.lproj/InfoPlist.strings delete mode 100644 RileyLinkBLEKitTests/zh-Hans.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/de.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/es.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/fr.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/it.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/nb.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/nl.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/pl.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/ru.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitTests/zh-Hans.lproj/InfoPlist.strings create mode 100644 RileyLinkKitUI/da.lproj/Localizable.strings delete mode 100644 RileyLinkKitUI/de.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitUI/es.lproj/InfoPlist.strings create mode 100644 RileyLinkKitUI/fi.lproj/Localizable.strings delete mode 100644 RileyLinkKitUI/fr.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitUI/it.lproj/InfoPlist.strings create mode 100644 RileyLinkKitUI/ja.lproj/Localizable.strings delete mode 100644 RileyLinkKitUI/nb.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitUI/nl.lproj/InfoPlist.strings delete mode 100644 RileyLinkKitUI/pl.lproj/InfoPlist.strings create mode 100644 RileyLinkKitUI/pt-BR.lproj/Localizable.strings create mode 100644 RileyLinkKitUI/ro.lproj/Localizable.strings delete mode 100644 RileyLinkKitUI/ru.lproj/InfoPlist.strings create mode 100644 RileyLinkKitUI/sv.lproj/Localizable.strings create mode 100644 RileyLinkKitUI/vi.lproj/Localizable.strings delete mode 100644 RileyLinkKitUI/zh-Hans.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/de.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/en.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/es.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/fr.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/it.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/nb.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/nl.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/pl.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/ru.lproj/InfoPlist.strings delete mode 100644 RileyLinkTests/zh-Hans.lproj/InfoPlist.strings diff --git a/Crypto/de.lproj/InfoPlist.strings b/Crypto/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/es.lproj/InfoPlist.strings b/Crypto/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/fr.lproj/InfoPlist.strings b/Crypto/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/it.lproj/InfoPlist.strings b/Crypto/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/nb.lproj/InfoPlist.strings b/Crypto/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/nl.lproj/InfoPlist.strings b/Crypto/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/pl.lproj/InfoPlist.strings b/Crypto/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/ru.lproj/InfoPlist.strings b/Crypto/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Crypto/zh-Hans.lproj/InfoPlist.strings b/Crypto/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/Crypto/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/Base.lproj/Localizable.strings b/MinimedKit/Base.lproj/Localizable.strings index 8748566d5..afe05abcf 100644 --- a/MinimedKit/Base.lproj/Localizable.strings +++ b/MinimedKit/Base.lproj/Localizable.strings @@ -96,4 +96,3 @@ /* Describing the worldwide pump region */ "World-Wide" = "World-Wide"; - diff --git a/MinimedKit/da.lproj/Localizable.strings b/MinimedKit/da.lproj/Localizable.strings new file mode 100644 index 000000000..1872ee8c3 --- /dev/null +++ b/MinimedKit/da.lproj/Localizable.strings @@ -0,0 +1,99 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "En bolus er allerede i gang"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "AlarmUrPåmindelse"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "AlarmSensor"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "Alkaline"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "Basal Profil %1$@: %2$@ E/time"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "Bolus i gang"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Tjek at pumpen ikke er suspenderet eller under klargøring, eller har en procent midlertidig basal type"; + +/* Pump error code returned when command refused */ +"Command refused" = "Kommando nægtet"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "Kommunikation med en anden pumpe opdaget."; + +/* Error description */ +"Decoding Error" = "Undersøger fejl"; + +/* Error description */ +"Device Error" = "Enheds Fejl"; + +/* Describing the pump history insulin data source */ +"Event History" = "Hændelses log"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "Fejlagtigt svar ved %1$@: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "Lithium"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "Sørg for at din RileyLink er i nærheden, og tændt"; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "Max indstilling overskredet"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 Serier"; + +/* Describing the North America pump region */ +"North America" = "Nord Amerika"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "Pumpe svarede ikke"; + +/* Error description */ +"Pump Error" = "Pumpe Fejl"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "Pumpe er afbrudt"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = "Pumpe reagerede uventet"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "PumpMessage(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "Reservoir"; + +/* Error description */ +"RileyLink radio tune failed" = "RileyLink radio indstilling fejlede"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "Midlertidig Basal: %1$.3f E/time"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "Midlertidig Basal: %1$d min"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "Midlertidig Basal: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "Ukendt pumpe fejlkode: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "Unknown pumpe model %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "Ukendt svar under %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "Verden (World-Wide)"; + diff --git a/MinimedKit/de.lproj/InfoPlist.strings b/MinimedKit/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/es.lproj/InfoPlist.strings b/MinimedKit/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/fi.lproj/Localizable.strings b/MinimedKit/fi.lproj/Localizable.strings new file mode 100644 index 000000000..c45e19155 --- /dev/null +++ b/MinimedKit/fi.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "Bolus on jo käynnissä"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "HerätysKelloMuistutus"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "HälytysSensori"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "Alkaliini"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "Basaaliprofiili %1$@: %2$@ U/h"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "Bolus käynnissä"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Tarkista, että pumppu ei ole pysäytetty, vakiotäytöllä tai basaalityypiksi ei ole valittu prosentteja"; + +/* Pump error code returned when command refused */ +"Command refused" = "Komento hylättiin"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "Toisen pumpun viestintää havaittu."; + +/* Error description */ +"Decoding Error" = "Dekoodausvirhe"; + +/* Error description */ +"Device Error" = "Laitevirhe"; + +/* Describing the pump history insulin data source */ +"Event History" = "Tapahtumahistoria"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "Virheellinen vastaus %1$@: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "Litium"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "Varmista, että RileyLink on riittävän lähellä ja se on kytketty päälle"; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "Maksimiasetus ylitetty"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 -sarja"; + +/* Describing the North America pump region */ +"North America" = "Pohjois-Amerikka"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "Pumppu ei vastannut"; + +/* Error description */ +"Pump Error" = "Pumppuvirhe"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "Pumppu on pysäytetty"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = "Pumppu vastasi epätavallisesti"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "PumppuViesti(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "Säiliö"; + +/* Error description */ +"RileyLink radio tune failed" = "RileyLink-radion viritys epäonnistui"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "Tilapäinen basaali: %1$.3f U/h"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "Tilapäinen basaalil: %1$d min"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "Tilapäinen basaali: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "Tuntematon pumpun virhekoodi: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "Tuntematon pumppumalli: %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "Tuntematon vastaus %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "Maailmanlaajuinen"; diff --git a/MinimedKit/fr.lproj/InfoPlist.strings b/MinimedKit/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/it.lproj/InfoPlist.strings b/MinimedKit/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/ja.lproj/Localizable.strings b/MinimedKit/ja.lproj/Localizable.strings new file mode 100644 index 000000000..25ea53322 --- /dev/null +++ b/MinimedKit/ja.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "ボーラス注入中"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "アラームリマインダー"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "アラームセンサー"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "アルカリ"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "ベーサルプロファイル %1$@: %2$@ U/時"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "ボーラス注入中"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "ポンプが停止、プライミング中、または基礎レートが一時的に変更になっていないことを確認してください"; + +/* Pump error code returned when command refused */ +"Command refused" = "コマンド拒否"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "他のポンプが検出"; + +/* Error description */ +"Decoding Error" = "デコーディングエラー"; + +/* Error description */ +"Device Error" = "デバイスエラー"; + +/* Describing the pump history insulin data source */ +"Event History" = "イベント履歴"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "%1$@ で反応が無効: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "リチウム"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "RileyLink が近くにあり電源が入っているか確認してください"; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "設定の制限を超えています"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 シリーズ"; + +/* Describing the North America pump region */ +"North America" = "North America"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "ポンプが反応しません"; + +/* Error description */ +"Pump Error" = "ポンプエラー"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "ポンプが停止しています"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = "ポンプが不意に反応しました"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "ポンプメッセージ(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "リザーバ"; + +/* Error description */ +"RileyLink radio tune failed" = "RileyLink 通信に失敗しました"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "一時基礎レート: %1$.3f U/hour"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "一時基礎レート: %1$d 分"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "一時基礎レート: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "ポンプエラーコード 不明: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "ポンプモデル 不明: %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "反応が不明 %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "World-Wide"; diff --git a/MinimedKit/nb.lproj/InfoPlist.strings b/MinimedKit/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/nl.lproj/InfoPlist.strings b/MinimedKit/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/pl.lproj/InfoPlist.strings b/MinimedKit/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/pt-BR.lproj/Localizable.strings b/MinimedKit/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..69bb5df7b --- /dev/null +++ b/MinimedKit/pt-BR.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "Um bolus está em andamento"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "LembreteDeAlarme"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "AlarmeDeSensor"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "Alcalina"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "Perfil Basal %1$@: %2$@ U/hora"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "Bolus em andamento"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Verifique se a bomba não está suspensa ou em preparação ou tem uma basal temporária percentual"; + +/* Pump error code returned when command refused */ +"Command refused" = "Comando recusado"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "Comunicação com outra bomba detectada."; + +/* Error description */ +"Decoding Error" = "Erro de Decodificação"; + +/* Error description */ +"Device Error" = "Erro no Dispositivo"; + +/* Describing the pump history insulin data source */ +"Event History" = "Histórico de Eventos"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "Resposta inválida durante %1$@: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "Lítio"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "Verifique se o seu RileyLink está próximo e ligado"; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "Configuração máxima excedida"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 Series"; + +/* Describing the North America pump region */ +"North America" = "América do Norte"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "A bomba não respondeu"; + +/* Error description */ +"Pump Error" = "Erro na Bomba"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "Bomba suspensa"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = " Bomba respondeu inesperadamente"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "MensagemDaBomba(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "Reservatório"; + +/* Error description */ +"RileyLink radio tune failed" = "A sintonia do rádio RileyLink falhou"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "Basal Temporária: %1$.3f U/hora"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "Basal Temporária: %1$d min"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "Basal Temporária: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "Código de erro da bomba desconhecido: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "Modelo de bomba desconhecido: %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "Resposta desconhecida durante %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "Mundial"; diff --git a/MinimedKit/ro.lproj/Localizable.strings b/MinimedKit/ro.lproj/Localizable.strings new file mode 100644 index 000000000..6a0cdc73d --- /dev/null +++ b/MinimedKit/ro.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "Există deja un bolus în curs de administrare"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "AlarmClockReminder"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "AlarmSensor"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "Alcalină"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "Profil bazal %1$@: %2$@ U/oră"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "Bolus în curs de administrare"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Verificați că pompa nu este suspendată sau în curs de amorsare sau ca nu folosește un tip procentual de bazală temporară"; + +/* Pump error code returned when command refused */ +"Command refused" = "Comandă refuzată"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "S-a detectat o comunicare cu altă pompă."; + +/* Error description */ +"Decoding Error" = "Eroare la decodare"; + +/* Error description */ +"Device Error" = "Eroare dispozitiv"; + +/* Describing the pump history insulin data source */ +"Event History" = "Istoric evenimente"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "Răspuns invalid în timpul %1$@: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "Litiu"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "Asigurați-vă că RileyLink este pornit și situat în apropiere"; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "Setare maximă depășită"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 Series"; + +/* Describing the North America pump region */ +"North America" = "North America"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "Pumpa nu a răspuns"; + +/* Error description */ +"Pump Error" = "Eroare pompă"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "Pompa este suspendată"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = "Pompa a răspuns în mod neașteptat"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "PumpMessage(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "Rezervor"; + +/* Error description */ +"RileyLink radio tune failed" = "Eșec RileyLink la reglarea radio"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "Bazală temporară: %1$.3f U/oră"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "Bazală temporară: %1$d min"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "Bazală temporară: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "Cod de eroare pompă necunoscut: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "Model de pompă necunoscut: %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "Răspuns nerecunoscut în timpul %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "Global"; diff --git a/MinimedKit/ru.lproj/InfoPlist.strings b/MinimedKit/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKit/sv.lproj/Localizable.strings b/MinimedKit/sv.lproj/Localizable.strings new file mode 100644 index 000000000..b8ddca1e9 --- /dev/null +++ b/MinimedKit/sv.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "En bolus pågår redan"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "AlarmClockReminder"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "AlarmSensor"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "Alkaline"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "Basalprofil %1$@: %2$@ E/timme"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "Bolus pågår"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Kontrollera att pumpen inte är pausad eller håller på att fyllas eller har en temporär basal."; + +/* Pump error code returned when command refused */ +"Command refused" = "Kommando avvisat"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "Kommunikation med annan pump upptäckt."; + +/* Error description */ +"Decoding Error" = "Avkodingsfel"; + +/* Error description */ +"Device Error" = "Enhetsfel"; + +/* Describing the pump history insulin data source */ +"Event History" = "Händelsehistorik"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "Ogiltigt svar under %1$@: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "Lithium"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "Säkerställ att din RileyLink är nära och påslagen."; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "Maxinställning överskriden"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 Series"; + +/* Describing the North America pump region */ +"North America" = "North America"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "Pump svarade inte"; + +/* Error description */ +"Pump Error" = "Pumpfel"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "Pump är pausad"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = "Pump svarade oväntat"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "Pumpmeddelande(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "Reservoar"; + +/* Error description */ +"RileyLink radio tune failed" = "RileyLink radiosignal misslyckad"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "Temporär basal: %1$.3f E/timme"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "Temporär basal: %1$d min"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "Temporär basal: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "Okänt pumpfel: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "Okänd pumpmodell: %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "Okänt svar under %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "World-Wide"; diff --git a/MinimedKit/vi.lproj/Localizable.strings b/MinimedKit/vi.lproj/Localizable.strings new file mode 100644 index 000000000..c480a9596 --- /dev/null +++ b/MinimedKit/vi.lproj/Localizable.strings @@ -0,0 +1,98 @@ +/* Communications error for a bolus currently running */ +"A bolus is already in progress" = "Liều bolus đang được thực hiện"; + +/* The description of AlarmClockReminderPumpEvent */ +"AlarmClockReminder" = "AlarmClockReminder"; + +/* The description of AlarmSensorPumpEvent */ +"AlarmSensor" = "AlarmSensor"; + +/* Describing the battery chemistry as Alkaline */ +"Alkaline" = "Alkaline"; + +/* The format string description of a BasalProfileStartPumpEvent. (1: The index of the profile)(2: The basal rate) */ +"Basal Profile %1$@: %2$@ U/hour" = "Hồ sơ Basal %1$@: %2$@ U/giờ"; + +/* Pump error code when bolus is in progress */ +"Bolus in progress" = "Liều bolus đang thực hiện"; + +/* Suggestions for diagnosing a command refused pump error */ +"Check that the pump is not suspended or priming, or has a percent temp basal type" = "Kiểm tra và đảm bảo bơm không tạm ngưng hoặc đang bơm hoặc đang thực hiện liều basal tạm thời"; + +/* Pump error code returned when command refused */ +"Command refused" = "Lệnh bị từ chối"; + +/* No comment provided by engineer. */ +"Comms with another pump detected" = "Comms cho bơm khác được phát hiện."; + +/* Error description */ +"Decoding Error" = "Đang giải mã bị lỗi"; + +/* Error description */ +"Device Error" = "Thiết bị lỗi"; + +/* Describing the pump history insulin data source */ +"Event History" = "Lược sử tác vụ trước đó"; + +/* Format string for failure reason. (1: The operation being performed) (2: The response data) */ +"Invalid response during %1$@: %2$@" = "Phản ứng không phù hợp trong khoảng %1$@: %2$@"; + +/* Describing the battery chemistry as Lithium */ +"Lithium" = "Lithium"; + +/* Recovery suggestion */ +"Make sure your RileyLink is nearby and powered on" = "Đảm bảo RileyLink bên cạnh và đã được bật"; + +/* Pump error code describing max setting exceeded */ +"Max setting exceeded" = "Cài đặt tối đa vượt giới hạn"; + +/* Pump title (1: model number) */ +"Minimed %@" = "Minimed %@"; + +/* Generic title of the minimed pump manager */ +"Minimed 500/700 Series" = "Minimed 500/700 Series"; + +/* Describing the North America pump region */ +"North America" = "North America"; + +/* No comment provided by engineer. */ +"Pump did not respond" = "Bơm không phản hồi"; + +/* Error description */ +"Pump Error" = "Bơm lỗi"; + +/* No comment provided by engineer. */ +"Pump is suspended" = "Bơm đang được tạm ngưng"; + +/* No comment provided by engineer. */ +"Pump responded unexpectedly" = "Bơm phản ứng bất ngờ"; + +/* The format string describing a pump message. (1: The packet type)(2: The message type)(3: The message address)(4: The message data */ +"PumpMessage(%1$@, %2$@, %3$@, %4$@)" = "PumpMessage(%1$@, %2$@, %3$@, %4$@)"; + +/* Describing the reservoir insulin data source */ +"Reservoir" = "Ngăn chứa insulin"; + +/* Error description */ +"RileyLink radio tune failed" = "RileyLink radio thất bại"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in minutes) */ +"Temporary Basal: %1$.3f U/hour" = "Liều Basal tạm thời: %1$.3f U/giờ"; + +/* The format string description of a TempBasalDurationPumpEvent. (1: The duration of the temp basal in minutes) */ +"Temporary Basal: %1$d min" = "Liều Basal tạm thời: %1$d phút"; + +/* The format string description of a TempBasalPumpEvent. (1: The rate of the temp basal in percent) */ +"Temporary Basal: %1$d%%" = "Liều Basal tạm thời: %1$d%%"; + +/* The format string description of an unknown pump error code. (1: The specific error code raw value) */ +"Unknown pump error code: %1$@" = "Không xác định lỗi của bơm: %1$@"; + +/* No comment provided by engineer. */ +"Unknown pump model: %@" = "Không xác định mẫu bơm: %@"; + +/* Format string for an unknown response. (1: The operation being performed) (2: The response data) */ +"Unknown response during %1$@: %2$@" = "Phản hồi không xác định trong %1$@: %2$@"; + +/* Describing the worldwide pump region */ +"World-Wide" = "World-Wide"; diff --git a/MinimedKit/zh-Hans.lproj/InfoPlist.strings b/MinimedKit/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKit/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/de.lproj/InfoPlist.strings b/MinimedKitTests/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/es.lproj/InfoPlist.strings b/MinimedKitTests/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/fr.lproj/InfoPlist.strings b/MinimedKitTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/it.lproj/InfoPlist.strings b/MinimedKitTests/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/nb.lproj/InfoPlist.strings b/MinimedKitTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/nl.lproj/InfoPlist.strings b/MinimedKitTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/pl.lproj/InfoPlist.strings b/MinimedKitTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/ru.lproj/InfoPlist.strings b/MinimedKitTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitTests/zh-Hans.lproj/InfoPlist.strings b/MinimedKitTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/Base.lproj/Localizable.strings b/MinimedKitUI/Base.lproj/Localizable.strings index c25a160ce..8fced6f56 100644 --- a/MinimedKitUI/Base.lproj/Localizable.strings +++ b/MinimedKitUI/Base.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Enabled Diagnostic LEDs"; +/* The alert title for a resume error */ +"Error Resuming" = "Error Resuming"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Error Suspending"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Fetch Enlite Glucose"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Get Pump Model"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "On Idle"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Preferred Data Source"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Reading pump status…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Retry"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Unknown"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/da.lproj/Localizable.strings b/MinimedKitUI/da.lproj/Localizable.strings new file mode 100644 index 000000000..fddb0b07b --- /dev/null +++ b/MinimedKitUI/da.lproj/Localizable.strings @@ -0,0 +1,209 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "%1$@ planlagt basal \n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "%1$@ Udestående enheder insulin\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "Alkaline og Lithium batterier henfalder med forskellige satser. Alkaline har tendens til at have et lineært fald over tid, hvor Lithium batteri celler typisk opretholder deres spænding indtil halvejen i deres levetid. Ved normalt brug i en ikke-MySentry kompatibel Minimed (x22/x15) insulin pumpe, som kører Loop vil alkaline batterier holde cirka 4 til 5 dage. Lithium batterier holder mellem 1 til 2 uger. Denne indstilling bruger forskellige batteri henfalds satser for hver kemiske batteritype og alarmere brugeren når et batteri er cirka 8 til 10 timer fra at løbe tør."; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "Er du sikker på at du vil fjerne denne pumpe?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "Vågen indtil"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basal rater"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "Batteri: %1$@ volt\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "Bedste frekvens"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "Giver bolus: %1$@\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Afbryd"; + +/* The title of the command to change pump time */ +"Change Time" = "Ændre klokkeslæt"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Ændre tidszone"; + +/* Progress message for changing pump time. */ +"Changing time…" = "Ændre tid..."; + +/* The title of the section describing commands */ +"Commands" = "Kommandoer"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* Button title to connect to pump during setup */ +"Connect" = "Forbind"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Status på forbindelse"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "Fjern pumpe"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Mængdebegrænsning"; + +/* The title of the section describing the device */ +"Device" = "Enhed"; + +/* The title of the command to discover commands */ +"Discover Commands" = "Opdag kommandoer"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "Opdager kommandoer..."; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "Slå diagnoserings LED’er til"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "Diagnoserings LED’er aktiveret"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fejl i genoptagelse"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fejl i suspendering"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Hent seneste glukose"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "Hent seneste historie"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "Henter glukose..."; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "Henter histoik..."; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "Henter pumpe model..."; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware version"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "Hent pumpe model"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "Hent statistik"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Levering af insulin kan blive bestemt fra pumpen ved enten at tolke på begivenheds historikken eller ved at sammenligne volumen af reservoiret over tid. Aflæsning af begivenheds historik kan give mere præcise status grafer og uploading af up-to-date behandlings data til Nightscout, ved bekostning af hurtigere dræning af pumpe batteriet, risiko for højere fejlrate i radiokommunikation i forhold til aflæsning af volumen af reservoiret. Hvis den valgte kilde ikke kan anvendes, af en eller anden grund, så vil systemet falde tilbage på den anden mulighed."; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "Senest vågen"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "Aflytning slået fra"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "MySentry parring"; + +/* The title of the cell showing device name */ +"Name" = "Navn"; + +/* Message display when no response from tuning pump */ +"No response" = "Intet svar"; + +/* The title of the cell showing the last idle */ +"On Idle" = "Slumre"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" ="On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "Foretrukne data kilde"; + +/* The title of the section describing the pump */ +"Pump" = "Pumpe"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "Pumpe batteritype"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "Pumpe ID"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "Pumpe model"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "Pumpe indstillinger"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "Læs basal plan"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "Læs pumpe status"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "Læser basal plan..."; + +/* Progress message for reading pump status */ +"Reading pump status…" = "Læser pumpe status..."; + +/* The title of the cell showing the pump region */ +"Region" = "Region"; + +/* Button title to retry sentry setup */ +"Retry" = "Prøv igen"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "RileyLink Statistik"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "Gem på pumpen"; + +/* The title of the command to send a button press */ +"Send Button Press" = "Send knappe tryk"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "Sender knappe tryk "; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Signal styrke"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Lykkedes"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "Suspenderet: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "Forsøg"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "Indstil radio frekvens"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "Indstiller radio frekvens..."; + +/* The detail text for an unknown pump model */ +"Unknown" = "Ukendt"; + +/* The title of the cell showing uptime */ +"Uptime" = "Oppetid"; diff --git a/MinimedKitUI/da.lproj/MinimedPumpManager.strings b/MinimedKitUI/da.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..ae6ed4fe8 --- /dev/null +++ b/MinimedKitUI/da.lproj/MinimedPumpManager.strings @@ -0,0 +1,75 @@ + +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "RileyLink Indstilling"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Find Enhed"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Andre Enheder"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Juster ikke uret i pumpens menu."; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Pumpe Ur"; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Indtast det 6-cifrede pumpe ID"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Gennemgå pumpens indstillinger nedenfor. De kan altid ændres i Loop’s indstillinger."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "Hvis du rejser til en anden tidszone i længere tid, kan du ændre pumpens tidszone i Loop’s Indstillinger."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop vil holde din pumpes ur synkroniseret med din telefon, i den tidszone du er i nu."; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Indstilling Komplet"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Pumpe Indstilling"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Pumpe Indstilling"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Hoved Menu"; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Tilbehør"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Tilslut Enheder"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "Pumpens ID er den 6-cifrede nummeriske del af serienummeret (mærket SN eller S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "Pumpe ID"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Pumpen er klar til brug."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Pumpe Indstillinger"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "Pumpe region og farve er angivet som de sidste 3 bogstaver i modelnummeret (mærket REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Region og Farve"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Pumpe Udsendelser"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "Tændt"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Angiv pumpe region"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop vil lytte efter status beskeder fra pumpen. Følg nedenstående skridt på pumpen, for at aktivere disse beskeder:"; diff --git a/MinimedKitUI/de.lproj/InfoPlist.strings b/MinimedKitUI/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/de.lproj/Localizable.strings b/MinimedKitUI/de.lproj/Localizable.strings index 314abef9d..00b5271ba 100644 --- a/MinimedKitUI/de.lproj/Localizable.strings +++ b/MinimedKitUI/de.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Diagnostische LEDs aktiviert"; +/* The alert title for a resume error */ +"Error Resuming" = "Fehler beim Fortsetzen"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fehler beim Unterbrechen"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Enlite-Glukosewert einlesen"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Pumpmodell erhalten"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "im Leerlauf"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Bevorzugte Datenquelle"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Pumpenstand lesen…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Wiederholen"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Unbekannt"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/de.lproj/MinimedPumpManager.strings b/MinimedKitUI/de.lproj/MinimedPumpManager.strings index 012177982..6aa18e7ef 100644 --- a/MinimedKitUI/de.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/de.lproj/MinimedPumpManager.strings @@ -1,5 +1,5 @@ /* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ -"0MV-2k-Dty.title" = "RileyLink Setup"; +"0MV-2k-Dty.title" = "RileyLink-Einstellungen"; /* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ "1fp-45-qWK.text" = "Gerät finden"; @@ -11,22 +11,22 @@ "Bdb-j4-WcR.text" = "Ändern Sie nicht die Zeit in Ihrem Pumpenmenü."; /* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ -"c7t-pZ-WqY.text" = "Utilities"; +"c7t-pZ-WqY.text" = "Dienstprogramme"; /* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ -"erq-yb-anx.text" = "Connect Devices"; +"erq-yb-anx.text" = "Geräte verbinden"; /* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ "Fps-h3-V4K.title" = "Uhrzeit der Pumpe"; /* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ -"fVG-pl-jT9.footerTitle" = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; +"fVG-pl-jT9.footerTitle" = "Die Pumpen-ID ist der 6-stellige numerische Teil der Seriennummer (gekennzeichnet als SN oder S / N)."; /* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ -"fVG-pl-jT9.headerTitle" = "Pump ID"; +"fVG-pl-jT9.headerTitle" = "Pumpen-ID"; /* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ -"g1m-3k-XI3.text" = "Your pump is ready for use."; +"g1m-3k-XI3.text" = "Ihre Pumpe ist betriebsbereit."; /* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ "HeG-VF-L5P.placeholder" = "Geben Sie die 6-stellige Pumpen-ID ein"; @@ -38,35 +38,38 @@ "HuY-fE-vM8.text" = "Wenn Sie für längere Zeit in eine andere Zeitzone verreisen, kann die Zeitzone der Pumpe jederzeit über das Einstellungsmenü von Loop geändert werden."; /* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ -"IQ5-53-x9s.text" = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; +"IQ5-53-x9s.text" = "Loop synchronisiert die Uhrzeit Ihrer Pumpe mit der Uhrzeit Ihres Smartphones in Ihrer aktuellen Zeitzone."; /* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ -"iQZ-kT-QUm.title" = "Pump Settings"; +"iQZ-kT-QUm.title" = "Einstellungen der Pumpe"; /* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ -"lGI-LD-xR7.footerTitle" = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; +"lGI-LD-xR7.footerTitle" = "Die Region und die Farbe der Pumpe sind mit den letzten 3 Buchstaben in der Modellnummer bezeichnet (REF-Nummer in der Kennzeichnung der Pumpe)."; /* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ -"lGI-LD-xR7.headerTitle" = "Region and Color"; +"lGI-LD-xR7.headerTitle" = "Region und Farbe"; /* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ -"Nwf-TJ-KmJ.title" = "Setup Complete"; +"Nwf-TJ-KmJ.title" = "Setup erfolgreich"; /* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ -"oBL-lh-SHI.title" = "Pump Broadcasts"; +"oBL-lh-SHI.title" = "Datenübertragung der Pumpe"; /* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ -"ojQ-ob-gBx.text" = "On"; +"ojQ-ob-gBx.text" = "An"; /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ -"OZk-Db-KCs.title" = "Pump Setup"; +"OZk-Db-KCs.title" = "Einstellungen der Pumpe"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Einstellungen der Pumpe"; /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ -"tGa-FP-JqD.text" = "Enter the pump region"; +"tGa-FP-JqD.text" = "Geben Sie die Region der Pumpe ein"; /* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ -"yLn-Ya-p1R.text" = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; +"yLn-Ya-p1R.text" = "Loop wird die Statusberichte Ihrer Pumpe überwachen. Führen Sie die nachfolgenden Schritte in Ihrer Pumpe aus, um diese Statusberichte einzuschalten:"; /* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ -"ZnF-zy-5gR.headerTitle" = "Main Menu"; +"ZnF-zy-5gR.headerTitle" = "Hauptmenü"; diff --git a/MinimedKitUI/en.lproj/MinimedPumpManager.strings b/MinimedKitUI/en.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..e05f87473 --- /dev/null +++ b/MinimedKitUI/en.lproj/MinimedPumpManager.strings @@ -0,0 +1,74 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "RileyLink Setup"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Find Device"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Other Devices"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Do not change the time using your pumpʼs menu."; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Pump Clock"; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Enter the 6-digit pump ID"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Setup Complete"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Pump Setup"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Pump Setup"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Main Menu"; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Utilities"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Connect Devices"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "Pump ID"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Your pump is ready for use."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Pump Settings"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Region and Color"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Pump Broadcasts"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "On"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Enter the pump region"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; diff --git a/MinimedKitUI/es.lproj/InfoPlist.strings b/MinimedKitUI/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/es.lproj/Localizable.strings b/MinimedKitUI/es.lproj/Localizable.strings index 3c551fa97..dee2730c6 100644 --- a/MinimedKitUI/es.lproj/Localizable.strings +++ b/MinimedKitUI/es.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Diagnóstico LEDs Habilitado"; +/* The alert title for a resume error */ +"Error Resuming" = "Error de reanudación"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Error de suspensión"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Obtener Enlite Glucose"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware"; + /* The title of the command to get pump model */ "Get Pump Model" = "Obtener modelo de Microinfusora"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "En Inactivo"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Fuente de Datos Preferida"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Obteniendo estada de microinfusadora…"; +/* The title of the cell showing the pump region */ +"Region" = "Región"; + /* Button title to retry sentry setup */ "Retry" = "Reintentar"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Desconocido"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/es.lproj/MinimedPumpManager.strings b/MinimedKitUI/es.lproj/MinimedPumpManager.strings index a062b7ae8..f6dcf8d72 100644 --- a/MinimedKitUI/es.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/es.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Configuración de Microinfusora"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Configuración de Microinfusora"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "Ingresa la región de la Microinfusora"; diff --git a/MinimedKitUI/fi.lproj/Localizable.strings b/MinimedKitUI/fi.lproj/Localizable.strings new file mode 100644 index 000000000..043337222 --- /dev/null +++ b/MinimedKitUI/fi.lproj/Localizable.strings @@ -0,0 +1,209 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "%1$@ basaaliohjelman kirjaukset\n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "%1$@ insuliiniyksiköitä jäljellä\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "Alkali- ja litiumparistot tyhjenevät eri nopeudella. Alkaliparistojen varaus vähenee yleensä lineaarisesti ajan kuluessa, kun taas litiumparistot ylläpitävät yleensä varaustaan korkeana kunnes käyttöaika on noin puolessa välissä. Normaalilla käytöllä Minimed (x22/x15) insuliinipumppu toimii Loop-käytössä alkaliparistolla noin 4–5 päivää. Litiumparistot kestävät 1–2 viikkoa. Tämä valinta näyttää pariston varauksen vähenemisnopeuden sen perusteella kumpi paristotyyppi on valittu ja varoittaa käyttäjää, kun pariston käyttöaika on loppumassa noin 8–10 tunnin kuluessa"; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "Haluatko varmasti poistaa tämän pumpun?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "Hereillä asti"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basaalimäärät"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "Paristo: %1$@ volttia\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "Paras taajuus"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "Annostellaan bolus: %1$@\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Kumoa"; + +/* The title of the command to change pump time */ +"Change Time" = "Muuta aika"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Muuta aikavyöhyke"; + +/* Progress message for changing pump time. */ +"Changing time…" = "Muutetaan aikaa…"; + +/* The title of the section describing commands */ +"Commands" = "Komennot"; + +/* The title of the configuration section in settings */ +"Configuration" = "Määritykset"; + +/* Button title to connect to pump during setup */ +"Connect" = "Yhdistä"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Yhdistämisen tila"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "Poista pumppu"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Annostelurajat"; + +/* The title of the section describing the device */ +"Device" = "Laite"; + +/* The title of the command to discover commands */ +"Discover Commands" = "Hae komennot"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "Haetaan komentoja…"; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "Ota käyttöön diagnostiset LED-valot"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "Diagnostiset LED-valot otettu käyttöön"; + +/* The alert title for a resume error */ +"Error Resuming" = "Virhe jatkamisessa"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Virhe keskeytyksessä"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Hae Enliten glukoosiarvo"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "Hae viimeaikaiset tapahtumat"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "Haetaan glukoosiarvo…"; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "Haetaan viimeaikaisia tapahtumia…"; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "Haetaan pumpun mallia…"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Laiteohjelmisto"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Laiteohjelmiston versio"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "Hae pumpun malli"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "Hae tilastotiedot…"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Pumpun annostelemat insuliinimäärät on mahdollista määrittää joko tulkitsemalla tapahtumahistoriaa tai arvioimalla pumppusäiliön insuliinimäärää ajan kuluessa. Tapahtumahistorian lukeminen antaa tarkemman kuvan tilanteesta ja mahdollistaa ajantasaisen tiedonsiirron Nightscoutiin, mutta toisaalta tämä kuluttaa pumpun paristoa nopeammin ja lisää radiovirheiden todennäköisyyttä siihen verrattuna, että luettaisiin ainoastaan pumppusäiliössä olevan insuliinin määrää. Jos valittua lähdettä ei voida mistä tahansa syystä käyttää, järjestelmä pyrkii käyttämään toista vaihtoehtoa."; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "Viimeksi hereillä"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "Kuuntelu pois päältä"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "MySentry-paritus"; + +/* The title of the cell showing device name */ +"Name" = "Nimi"; + +/* Message display when no response from tuning pump */ +"No response" = "Ei vastausta"; + +/* The title of the cell showing the last idle */ +"On Idle" = "Valmiustilassa"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Mene pumppusi Etsi laite -näytölle ja valitse \"Etsi laite\".\n\n Päävalikko >\nTyökalut >\nYhdistä laitteet >\nMuut laitteet >\nPäällä >\nEtsi laite"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "Ensisijainen tietolähde"; + +/* The title of the section describing the pump */ +"Pump" = "Pumppu"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "Pumpun paristotyyppi"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "Pumpun tunniste"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "Pumpun malli"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "Pumpun asetukset"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "Lue basaaliohjelma"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "Lue pumpun tila"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "Luetaan basaaliohjelmaa…"; + +/* Progress message for reading pump status */ +"Reading pump status…" = "Luetaan pumpun tilaa…"; + +/* The title of the cell showing the pump region */ +"Region" = "Alue"; + +/* Button title to retry sentry setup */ +"Retry" = "Yritä uudelleen"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "RileyLinkin tiedot"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "Tallenna pumppuun…"; + +/* The title of the command to send a button press */ +"Send Button Press" = "Lähetä napin painallus"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "Lähetetään napin painallusta…"; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Signaalin vahvuus"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Onnistui"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "Annostelu keskeytetty: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "Yritykset"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "Säädä radiotaajuus"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "Säädetään radiota…"; + +/* The detail text for an unknown pump model */ +"Unknown" = "Tuntematon"; + +/* The title of the cell showing uptime */ +"Uptime" = "Toiminta-aika"; diff --git a/MinimedKitUI/fi.lproj/MinimedPumpManager.strings b/MinimedKitUI/fi.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..d86802aab --- /dev/null +++ b/MinimedKitUI/fi.lproj/MinimedPumpManager.strings @@ -0,0 +1,74 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "RileyLink-asennus"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Etsi laite"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Muut laitteet"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Älä muuta kellonaikaa käyttämällä pumpun omaa valikkoa."; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Apuohjelmat"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Yhdistetyt laitteet"; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Pumpun kello"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "Pumpun tunniste on 6-numeroinen osa sarjanumeroa (alkaa SN tai S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "Pumpun tunniste"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Pumppusi on käyttövalmis."; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Syötä 6-numeroinen pumpun tunniste"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Tarkista pumpun asetukset alta. Voit muokata näitä asetuksia milloin tahansa Loopin Asetukset-näkymässä."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "Jos matkustat toiselle aikavyöhykkeelle pidemmäksi aikaa, voit muuttaa pumpun aikavyöhykkeen milloin tahansa Loopin Asetukset-näkymässä."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop synkronoi pumpun kellon puhelimesi kanssa samalle aikavyöhykkeelle sijaintisi perusteella."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Pumpun asetukset"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "Pumpun alue ja väri on merkitty kolmella viimeisellä kirjaimella mallinumeron lopussa (nimetty REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Alue ja väri"; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Asennus valmis"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Pumpun lähetykset"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "Päällä"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Pumpun asennus"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Syötä pumpun alue"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Pumpun asennus"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop kuuntelee pumpun lähettämiä tilaviestejä. Seuraa alla mainittuja ohjeita ottaaksesi nämä viestit käyttöön pumpussasi:"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Päävalikko"; diff --git a/MinimedKitUI/fr.lproj/InfoPlist.strings b/MinimedKitUI/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/fr.lproj/Localizable.strings b/MinimedKitUI/fr.lproj/Localizable.strings index 1d6470edc..2607615b0 100644 --- a/MinimedKitUI/fr.lproj/Localizable.strings +++ b/MinimedKitUI/fr.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Activation des LEDs de diagnostique"; +/* The alert title for a resume error */ +"Error Resuming" = "Erreur lors de la reprise"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Erreur lors de la suspension"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Obtenir glycémie Enlite"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Obtenir le modèle de pompe"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "Au Repos"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Source de données préférée"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Lecture de l’état de la pompe…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Nouvel essai"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Inconnu"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/fr.lproj/MinimedPumpManager.strings b/MinimedKitUI/fr.lproj/MinimedPumpManager.strings index 2198f6209..e64637ae4 100644 --- a/MinimedKitUI/fr.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/fr.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Configuration de la Pompe"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Configuration de la Pompe"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "Entrez la région des pompes"; diff --git a/MinimedKitUI/it.lproj/InfoPlist.strings b/MinimedKitUI/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/it.lproj/Localizable.strings b/MinimedKitUI/it.lproj/Localizable.strings index 5a0e351ae..b37cede4d 100644 --- a/MinimedKitUI/it.lproj/Localizable.strings +++ b/MinimedKitUI/it.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "LED Diagnostici Abilitati"; +/* The alert title for a resume error */ +"Error Resuming" = "Errore durante la ripresa"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Errore durante la sospensione"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Sincronizzare Glicemie Enlite"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Ottieni Modello del Microinfusore"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "Inattivo"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Fonte Dati"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Lettura stato microinfusore…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Riprova"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Sconosciuto"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/it.lproj/MinimedPumpManager.strings b/MinimedKitUI/it.lproj/MinimedPumpManager.strings index 7d2e31d96..b79698fd9 100644 --- a/MinimedKitUI/it.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/it.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Impostazione Microinfusore"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Impostazione Microinfusore"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "Inserisci la provenienza del microinfusore"; diff --git a/MinimedKitUI/ja.lproj/Localizable.strings b/MinimedKitUI/ja.lproj/Localizable.strings new file mode 100644 index 000000000..e9ecb269a --- /dev/null +++ b/MinimedKitUI/ja.lproj/Localizable.strings @@ -0,0 +1,209 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "基礎パターン入力 %1$@回\\n\n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "インスリン 残り%1$@U\\n\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "アルカリ電池とリチウム電池では消耗速度が異なります。アルカリは徐々に電力が落ちるのに対し、リチウムは電池生命の前半は電圧を保持します。MySentryに互換性のないMinimed (x22/x15)でループを使用する場合は、アルカリ電池は4~5日、リチウム電池は1~2週間使用できることが多いです。それぞれの電池の種類の電圧消耗速度を設定して、電池生命が約8~10時間になるとユーザに知らせます。"; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "このポンプを削除しますか?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "無線終了"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "基礎レート"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "電池: %1$@ ボルト\\n\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "最良周波数"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "ボーラス注入中: %1$@\\n\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "キャンセル"; + +/* The title of the command to change pump time */ +"Change Time" = "時刻を変更"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "時間帯を変更"; + +/* Progress message for changing pump time. */ +"Changing time…" = "時刻を変更中 ..."; + +/* The title of the section describing commands */ +"Commands" = "コマンド"; + +/* The title of the configuration section in settings */ +"Configuration" = "コンフィグレーション"; + +/* Button title to connect to pump during setup */ +"Connect" = "接続"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "接続状態"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "ポンプを削除"; + +/* Title text for delivery limits */ +"Delivery Limits" = "注入限度"; + +/* The title of the section describing the device */ +"Device" = "デバイス"; + +/* The title of the command to discover commands */ +"Discover Commands" = "コマンドを見つける"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "コマンドを見つけています…"; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "診断 LED を有効にする"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "診断 LED を無効にする"; + +/* The alert title for a resume error */ +"Error Resuming" = "再開エラー"; + +/* The alert title for a suspend error */ +"Error Suspending" = "一時停止エラー"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Enliteのグルコースを取得"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "直近履歴を取得"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "グルコースを取得しています..."; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "履歴を取得しています..."; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "ポンプモデルを取得しています ..."; + +/* The title of the cell showing firmware version */ +"Firmware" = "ファームウェア"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "ファームウェアバージョン"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "ポンプモデルを取得"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "統計を取得…"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "インスリン注入は、イベント履歴を解釈する、またはリザーバの残量を計ることにより決定されます。イベント履歴を読み取ることにより、ステータスグラフがより正確になり、Nightscoutに最新のトリートメントデータをアップロードできます。リザーバの残量のみを計るよりも、ポンプの電池寿命が短くなり、無線周波数のエラーが増える可能性があります。選択されているデータソースが何らかの事情により使えない場合は、システムはもう片方のソースを使用します。"; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "受信"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "受信オフ"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "MySentryをペアリング"; + +/* The title of the cell showing device name */ +"Name" = "機器名"; + +/* Message display when no response from tuning pump */ +"No response" = "反応なし"; + +/* The title of the cell showing the last idle */ +"On Idle" = "アイドル"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "ポンプの Find Device 画面で「Find Device」を選択します。\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "推奨データソース"; + +/* The title of the section describing the pump */ +"Pump" = "ポンプ"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "ポンプの電池の種類"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "ポンプID"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "ポンプモデル"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "ポンプ設定"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "基礎パターンを読む"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "ポンプの状態を読む"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "基礎パターンを読んでいます ..."; + +/* Progress message for reading pump status */ +"Reading pump status…" = "ポンプの状態を読んでいます ..."; + +/* The title of the cell showing the pump region */ +"Region" = "リージョン"; + +/* Button title to retry sentry setup */ +"Retry" = "やり直す"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "RileyLink 統計"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "ポンプに保存…"; + +/* The title of the command to send a button press */ +"Send Button Press" = "ボタン押しを送信"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "ボタン押しを送信しています ..."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "シグナル強度"; + +/* A message indicating a command succeeded */ +"Succeeded" = "成功しました"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "一時停止: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "トライ"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "無線周波数を合わせる"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "無線を合わせています ..."; + +/* The detail text for an unknown pump model */ +"Unknown" = "不明"; + +/* The title of the cell showing uptime */ +"Uptime" = "アップタイム"; diff --git a/MinimedKitUI/ja.lproj/MinimedPumpManager.strings b/MinimedKitUI/ja.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..1f2d01469 --- /dev/null +++ b/MinimedKitUI/ja.lproj/MinimedPumpManager.strings @@ -0,0 +1,74 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "RileyLink 設定"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "デバイスを探す"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "他のデバイス"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "ポンプのメニュー機能を使って日付時刻を変更しないでください。"; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "ユーティリティ"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "デバイスを接続"; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "ポンプ時刻"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "ポンプIDはシリアル番号の 6桁の数字の部分です。(SNやS/Nで表示)"; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "ポンプID"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "ポンプを使えます"; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "6桁のポンプIDを入力"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "ポンプの設定を確認してください。この設定はループの設定画面でいつでも変更できます。"; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "時差がある海外へ旅行に行く時は、ループの設定画面からいつでもポンプの日付時刻を変更出来ます。."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "ループはiPhoneと同期して、あなたが今いる場所のタイムゾーンにポンプの日付時刻を同期し続けます。"; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "ポンプ設定"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "ポンプのリージョンと色は型番 (REFで表示)の最終の3文字で表されています。"; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "リージョンと色"; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "設定完了"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "ポンプのブロードキャスト"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "オン"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "ポンプ設定"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "ポンプリージョンを入力"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "ポンプ設定"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "ループがポンプが発するステータスメッセージを聞き取ります。ポンプがメッセージを発せられるように次のステップにしたがって設定してください。"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "メインメニュー"; diff --git a/MinimedKitUI/nb.lproj/InfoPlist.strings b/MinimedKitUI/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/nb.lproj/Localizable.strings b/MinimedKitUI/nb.lproj/Localizable.strings index 4fd791035..1f967ac9e 100644 --- a/MinimedKitUI/nb.lproj/Localizable.strings +++ b/MinimedKitUI/nb.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Tilgjengeligjort diagnostiske LEDs"; +/* The alert title for a resume error */ +"Error Resuming" = "Gjenoppta feilet"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Pause feilet"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Hent enlite blodsukker"; @@ -96,7 +99,10 @@ "Fetching pump model…" = "Henter pumpemodell…"; /* The title of the cell showing firmware version */ -"Firmware" = "Firmware"; +"Firmware" = "Fastvare"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Fastvareversjon"; /* The title of the command to get pump model */ "Get Pump Model" = "Hent pumpemodell"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "På Vent"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Foretrukken datakilde"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Leser pumpestatus…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Prøv igjen"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Ukjent"; +/* The title of the cell showing uptime */ +"Uptime" = "Oppetid"; diff --git a/MinimedKitUI/nb.lproj/MinimedPumpManager.strings b/MinimedKitUI/nb.lproj/MinimedPumpManager.strings index c6864d270..bf8209f76 100644 --- a/MinimedKitUI/nb.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/nb.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Pumpeinnstillinger"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Pumpeinnstillinger"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "Angi pumperegionen"; diff --git a/MinimedKitUI/nl.lproj/InfoPlist.strings b/MinimedKitUI/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/nl.lproj/Localizable.strings b/MinimedKitUI/nl.lproj/Localizable.strings index e92dc7055..2b193323c 100644 --- a/MinimedKitUI/nl.lproj/Localizable.strings +++ b/MinimedKitUI/nl.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Diagnostische LEDs aangezet"; +/* The alert title for a resume error */ +"Error Resuming" = "Fout bij hervatten"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fout bij onderbreken"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Ophalen enlite glucose"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Lees pompmodel"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "Inactief"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Voorkeur databron"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Lees pomp gegevens…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Opnieuw proberen"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Onbekend"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/nl.lproj/MinimedPumpManager.strings b/MinimedKitUI/nl.lproj/MinimedPumpManager.strings index a92b773b8..aa8cf5d30 100644 --- a/MinimedKitUI/nl.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/nl.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Pomp configuratie"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Pomp configuratie"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "Voer regio van de pomp in"; diff --git a/MinimedKitUI/pl.lproj/InfoPlist.strings b/MinimedKitUI/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/pl.lproj/Localizable.strings b/MinimedKitUI/pl.lproj/Localizable.strings index d73dccc73..ca57badb7 100644 --- a/MinimedKitUI/pl.lproj/Localizable.strings +++ b/MinimedKitUI/pl.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "Diagnostyczne LEDy włączone"; +/* The alert title for a resume error */ +"Error Resuming" = "Error Resuming"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Error Suspending"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Pobierz glukozę z Enlite"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Oprogramowanie"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Pobierz model pompy"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "Wstrzymany"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Preferowane źródło danych"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Sprawdzanie statusu pompy…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Spróbuj ponownie"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Nieznany"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/pl.lproj/MinimedPumpManager.strings b/MinimedKitUI/pl.lproj/MinimedPumpManager.strings index 4cff197ca..fda0a282b 100644 --- a/MinimedKitUI/pl.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/pl.lproj/MinimedPumpManager.strings @@ -32,6 +32,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Konfiguracja pompy"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Konfiguracja pompy"; + /* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ "ZnF-zy-5gR.headerTitle" = "Menu główne"; diff --git a/MinimedKitUI/pt-BR.lproj/Localizable.strings b/MinimedKitUI/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..3d321e121 --- /dev/null +++ b/MinimedKitUI/pt-BR.lproj/Localizable.strings @@ -0,0 +1,210 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "%1$@ entradas de basal programadas\n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "%1$@ Unidades de insulina restantes\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "Baterias alcalinas e de lítio descarregam de formas diferentes. Baterias alcalinas tendem a perder voltagem linearmente, enquanto baterias de célula de lítio tendem a manter a voltagem até metade de sua vida útil. Em condições normais de uso do Loop em uma bomba Minimed que não utiliza o MySentry (x22/x15), baterias alcalinas devem durar entre 4 e 5 dias. Baterias de lítio duram entre 1 e 2 semanas. Essa seleção vai utilizar diferentes taxas de descarregamento para cada tipo de bateria e alertar o usuário quando a bateria estiver entre 8 e 10 horas de falhar."; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "Tem certeza que quer deletar essa bomba?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "Ligado até"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Taxas de Basal"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "Bateria: %1$@ volts\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "Melhor Frequência"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "Injetando Bolus: %1$@\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Cancelar"; + +/* The title of the command to change pump time */ +"Change Time" = "Mudar Horário"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Mudar Fuso Horário"; + +/* Progress message for changing pump time. */ +"Changing time…" = "Mudando o Horário…"; + +/* The title of the section describing commands */ +"Commands" = "Comandos"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuração"; + +/* Button title to connect to pump during setup */ +"Connect" = "Conectar"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Estado de Conexão"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "Deletar Bomba"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limites de Liberação"; + +/* The title of the section describing the device */ +"Device" = "Dispositivo"; + +/* The title of the command to discover commands */ +"Discover Commands" = "Descobrir Comandos"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "Descobrindo comandos..."; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "Habilitar LEDs de Diagnóstico"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "LEDs de Diagnóstico Habilitados"; + +/* The alert title for a resume error */ +"Error Resuming" = "Erro ao Retomar"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Erro ao Suspender"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Buscar Glicose do Enlite"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "Buscar Histórico Recente"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "Buscando glicose…"; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "`Buscando histórico…"; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "Buscando modelo da bomba..."; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Versão do Firmware"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "Extrair Modelo da Bomba"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "Extrair Estatísticas…"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "A quantidade de insulina utilizada pode ser determinada tanto interpretando o histórico de eventos quanto medindo a diferença no volume do reservatório. Ler a partir do histórico de eventos permite um gráfico de estado mais preciso e o envio de dados de tratamento mais atualizados para o Nightscout, mas com um maior gasto de bateria e com uma maior possibilidade de erros de transmissão, quando comparado com a leitura apenas do volume do reservatório. Se a fonte selecionada não puder ser utilizada por algum motivo, o sistema tentará a outra opção."; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "Ultima Conexão"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "Escutar Desligado"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "Pair MySentry"; + +/* The title of the cell showing device name */ +"Name" = "Nome"; + +/* Message display when no response from tuning pump */ +"No response" = "Sem Resposta"; + +/* The title of the cell showing the last idle */ +"On Idle" = "Tempo Ocioso"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Na sua Bomba, vá para a tela de encontrar dispositivos (Find Device) e selecione \"Encontrar Dispositivo\" (Find Device).\n\nMenu Principal >\nUtilitários (Utilities) >\nConectar Dispositivos (Connect Devices)\nOutros Dispositivos (Other Devices) >\nLigar (On) >\nEncontrar Dispositivo (Find Device)"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "Fonte de Dados Preferido"; + +/* The title of the section describing the pump */ +"Pump" = "Bomba"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "Tipo de Bateria da Bomba"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "ID da Bomba"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "Modelo da Bomba"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "Configurações da Bomba"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "Ler Programação de Basal"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "Ler Estado da Bomba"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "Lendo programação de basal…"; + +/* Progress message for reading pump status */ +"Reading pump status…" = "Lendo estado da bomba…"; + +/* The title of the cell showing the pump region */ +"Region" = "Região"; + +/* Button title to retry sentry setup */ +"Retry" = "Tentar de Novo"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "Estatísticas do RileyLink"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "Salvar na Bomba…"; + +/* The title of the command to send a button press */ +"Send Button Press" = "Send Button Press"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "Enviando aperto do botão"; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Força do Sinal"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Sucesso"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "Suspenso: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "Tentativas"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "Sintonizar frequência"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "Sintonizando frequência…"; + +/* The detail text for an unknown pump model */ +"Unknown" = "Modelo Desconhecido"; + +/* The title of the cell showing uptime */ +"Uptime" = "Tempo Ligado"; + diff --git a/MinimedKitUI/pt-BR.lproj/MinimedPumpManager.strings b/MinimedKitUI/pt-BR.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..ac1b01e0c --- /dev/null +++ b/MinimedKitUI/pt-BR.lproj/MinimedPumpManager.strings @@ -0,0 +1,74 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "Configuração RileyLink"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Localizar Dispositivo"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Outro Dispositivo"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Não altere a hora utilizando o menu da sua bomba."; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Utilidades"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Conectar Dispositivos"; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Hora da Bomba"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "O ID da bomba é a parte numérica de 6 dígitos do número de série (etiquetado como SN ou S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "ID da Bomba"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Sua bomba está pronta para uso."; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Digite o ID da bomba de 6 dígitos"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Revise as configurações da sua bomba abaixo. Você pode alterar essas configurações a qualquer momento na tela Configurações do Loop."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "Se você viajar para um fuso horário diferente por um longo período de tempo, poderá alterar o fuso horário da bomba a qualquer momento na tela Configurações do Loop."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop manterá o relógio da sua bomba sincronizado com o seu telefone no fuso horário em que você está agora."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Configurações da Bomba"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "A região e a cor da bomba são indicadas como as três últimas letras do número do modelo (etiquetado como REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Região e Cor"; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Instalação Concluída"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Transmissões da Bomba"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "Ligado"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Configuração da Bomba"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Entre com a região da bomba"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Configuração da Bomba"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop receberá as mensagens de status enviadas por sua bomba. Siga as etapas abaixo na sua bomba para ativar estas mensagens:"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Título Principal"; diff --git a/MinimedKitUI/ro.lproj/Localizable.strings b/MinimedKitUI/ro.lproj/Localizable.strings new file mode 100644 index 000000000..b1f5791e7 --- /dev/null +++ b/MinimedKitUI/ro.lproj/Localizable.strings @@ -0,0 +1,210 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "%1$@ intervale de insulină bazală\n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "%1$@ Unități de insulină rămase\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "Baterii alcaline și cele cu litiu se descarcă diferit. La cele alcaline, tensiunea scade proporțional cu descărcare, pe când cele cu litiu mențin tensiunea normală pană aproape de descărcare completă. În condiții normale pe pompele Minimed care nu sunt compatibile cu MySentry (x22/x15), rulând Loop, bateriile alcaline durează 4-5 zile. Bateriile cu litiu durează aproximativ 1-2 săptămâni. Prin această opțiune se alege modul de calcul al descărcării bateriilor în funcție de tipul lor, astfel încât utilizatorul să fie anunțat cu 8-10 ore înainte de descărcare completă."; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "Sigur vreți să ștergeți această pompă?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "Activ până la"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Rate bazale"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "Baterie: %1$@ volți\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "Frecvența optimă"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "Se livrează: %1$@\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Anulare"; + +/* The title of the command to change pump time */ +"Change Time" = "Ajustarea ceasului"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Schimbarea fusului orar"; + +/* Progress message for changing pump time. */ +"Changing time…" = "Se ajusteaza ceasul…"; + +/* The title of the section describing commands */ +"Commands" = "Comenzi"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configurare"; + +/* Button title to connect to pump during setup */ +"Connect" = "Conectare"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Starea conexiunii"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "Elimină pompa"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limitarea livrării"; + +/* The title of the section describing the device */ +"Device" = "Dispozitiv"; + +/* The title of the command to discover commands */ +"Discover Commands" = "Descoperă comenzi"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "Descoperirea comenzilor…"; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "Aprinde LED-urile de diagnoză"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "LED-urile de diagnoza pornite"; + +/* The alert title for a resume error */ +"Error Resuming" = "Eroare la reluare livrării"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Eroare la suspendarea livrării"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Obține datelor de la Enlite"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "Obține istoricul recent"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "Obținerea glicemiei…"; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "Obținerea istoricului…"; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "Obținerea modelului pompei…"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Versiunea Firmware"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "Obține modelul pompei"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "Obținerea statisticelor…"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Livrarea insulinei poate fi determinată din interpretarea istoricului citit din pompă sau analizând nivelul rezervorului în timp. Citirea istoricului de evenimente permite o construcție mai exacta a graficului și uploadarea datelor actualizate în Nightscout cu prețul descărcării mai rapide a bateriei pompei si a probabilității mai mari de erori de transmisie radio, comparând cu citire numai a nivelului rezervorului. Dacă dintr-o cauză sau alta sistemul nu va reuși să folosească opțiunea aleasă, se va încerca revenirea la cealaltă posibilitate."; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "Ultima activitate"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "Ascultare oprită"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "Conectare MySentry"; + +/* The title of the cell showing device name */ +"Name" = "Denumire"; + +/* Message display when no response from tuning pump */ +"No response" = "Nici un raspuns"; + +/* The title of the cell showing the last idle */ +"On Idle" = "în așteptare"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "Pe pompa, mergeți la ecranul \"Find Device\" și selectați \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "Sursa de date preferată"; + +/* The title of the section describing the pump */ +"Pump" = "Pompa"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "Tipul bateriei pompei"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "ID-ul pompei"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "Modelul pompei"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "Setările pompei"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "Citirea programului bazalelor"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "Citirea stării pompei"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "Se citește programul bazalelor…"; + +/* Progress message for reading pump status */ +"Reading pump status…" = "Se citește starea pompei…"; + +/* The title of the cell showing the pump region */ +"Region" = "Regiune"; + +/* Button title to retry sentry setup */ +"Retry" = "Reîncearcă"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "Statisticile RileyLink-ului"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "Se salvează în pompă…"; + +/* The title of the command to send a button press */ +"Send Button Press" = "Trimite apăsarea butonului"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "Se trimite apăsarea butonului…"; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Puterea semnalului"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Succeeded"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "Suspendat: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "Încercări"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "Ajustarea frecvenței radio"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "Se ajustează frecvența…"; + +/* The detail text for an unknown pump model */ +"Unknown" = "Necunoscut"; + +/* The title of the cell showing uptime */ +"Uptime" = "Timp în funcțiune"; + diff --git a/MinimedKitUI/ro.lproj/MinimedPumpManager.strings b/MinimedKitUI/ro.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..9824c27b2 --- /dev/null +++ b/MinimedKitUI/ro.lproj/MinimedPumpManager.strings @@ -0,0 +1,74 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "Setare RileyLink"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Caută dispozitiv"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Alte dispozitive"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Nu modificați timpul folosind meniul pompei."; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Utilitare"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Dispozitive conectate"; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Ceas pompă"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "ID-ul de pompă este porțiunea de 6 cifre din numărul serial (afișat ca SN sau S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "ID pompă"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Pompa este gata de utilizare."; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Introduceți ID-ul pompei format din 6 cifre"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Verificați setările pompei de mai jos. Puteți modifica ulterior aceste setări din ecranul de setări Loop."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "În cazul în care călătoriți într-un alt fus orar pentru o perioadă extinsă, puteți modifica fusul orar al pompei din ecranul de setări Loop."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop va menține sincronizat ceasul pompei cu cel al telefonului, în fusul orar în care vă aflați acum."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Setări pompă"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "Regiunea pompei și culoarea sunt indicate prin ultimele 3 litere ale numărului de model (afișat ca REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Regiune și culoare"; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Setup complet"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Transmisii pompă"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "Activ"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Setare pompă"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Introduceți regiunea pompei"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Setare pompă"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop va recepționa mesajele de status transmise de pompă. Urmații pașii de mai jos pentru a activa aceste mesaje:"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Meniu principal"; diff --git a/MinimedKitUI/ru.lproj/InfoPlist.strings b/MinimedKitUI/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/ru.lproj/Localizable.strings b/MinimedKitUI/ru.lproj/Localizable.strings index fb8581cea..d5eff8b69 100644 --- a/MinimedKitUI/ru.lproj/Localizable.strings +++ b/MinimedKitUI/ru.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "LED лампочки диагностики включены"; +/* The alert title for a resume error */ +"Error Resuming" = "Ошибка при возобновлении"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Ошибка при остановке"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "Получить данные гликемии с Enlite"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "Прошивка"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "Получить модель помпы"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "Бездействие"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "Предпочтительный источник данных"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "Чтение статуса помпы…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "Повторить попытку"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "Неизвестно"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/ru.lproj/MinimedPumpManager.strings b/MinimedKitUI/ru.lproj/MinimedPumpManager.strings index 026a5f1fb..da9394a8d 100644 --- a/MinimedKitUI/ru.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/ru.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "Настройки помпы"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Настройки помпы"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "Введите регион помпы"; diff --git a/MinimedKitUI/sv.lproj/Localizable.strings b/MinimedKitUI/sv.lproj/Localizable.strings new file mode 100644 index 000000000..3ffc5f67b --- /dev/null +++ b/MinimedKitUI/sv.lproj/Localizable.strings @@ -0,0 +1,209 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "%1$@ basalscheman\n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "%1$@ Enheter insulin återstår\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure."; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "Säkert att du vill radera den här pumpen?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "Vaken tills"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basaldoser"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "Batteri: %1$@ volt\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "Besta frekvensen"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "Ger bolus: %1$@\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Avbryt"; + +/* The title of the command to change pump time */ +"Change Time" = "Ändra tid"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Ändra tidszon"; + +/* Progress message for changing pump time. */ +"Changing time…" = "Ändrar tid…"; + +/* The title of the section describing commands */ +"Commands" = "Kommandon"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* Button title to connect to pump during setup */ +"Connect" = "Anslut"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Anslutningsstatus"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "Radera pump"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Maxdoser"; + +/* The title of the section describing the device */ +"Device" = "Enhet"; + +/* The title of the command to discover commands */ +"Discover Commands" = "Upptäck kommandon"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "Upptäcker kommandon…"; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "Slå på diagnostiska LED"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "Diagnostiska LED på"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fel vid återgång"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fel vid försök till paus"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Hämta Enlite Glukos"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "Hämta senaste historik"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "Hämtar glukos…"; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "Hämtar historik…"; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "Hämtar pumpmodell…"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "Hämtar Pumpmodell"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "Hämtar statistik…"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option."; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "Senast vaken"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "Avlyssning av"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "MySentry Pair"; + +/* The title of the cell showing device name */ +"Name" = "Namn"; + +/* Message display when no response from tuning pump */ +"No response" = "Inget svar"; + +/* The title of the cell showing the last idle */ +"On Idle" = "On Idle"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "Föredragen datakälla"; + +/* The title of the section describing the pump */ +"Pump" = "Pump"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "Typ av pumpbatteri"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "Pump-ID"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "Pumpmodell"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "Pumpinställningar"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "Läs Basalscheman"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "Läs pumpstatus"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "Läser basalscheman…"; + +/* Progress message for reading pump status */ +"Reading pump status…" = "Läser pumpstatus…"; + +/* The title of the cell showing the pump region */ +"Region" = "Region"; + +/* Button title to retry sentry setup */ +"Retry" = "Försök igen"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "RileyLink statistik"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "Spara till pump…"; + +/* The title of the command to send a button press */ +"Send Button Press" = "Skicka knapptryckning"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "Skicka knapptryckning…"; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Signalstyrka"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Lyckades"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "Pausad: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "Försök"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "Radiofrekvens"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "Justera radio…"; + +/* The detail text for an unknown pump model */ +"Unknown" = "Okänd"; + +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/sv.lproj/MinimedPumpManager.strings b/MinimedKitUI/sv.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..0184b8eb4 --- /dev/null +++ b/MinimedKitUI/sv.lproj/MinimedPumpManager.strings @@ -0,0 +1,75 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "RileyLink inställning"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Hitta enhet"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Annan enhet"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Ädra inte tiden i din pumps menyer."; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Tillbehör"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Anslut eheter"; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Pumpklocka"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "Ditt pump-ID är den 6-siffriga nemeriska delen av serienumret (markerat med SN eller S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "Pump-ID"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Din pump är redo att användas."; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Ange ditt 6-siffriga pump-ID"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Se över dina pumpinställingar nedan. Du kan närsomhelst ändra dessa i Loops inställningar."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "Om du reser till annan tidszon under en längre tid, kan du ändra tidszonen närsomhelst i Loops iställningar."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop kommer att synkronisera pumpens klocka med din telefon i tidszonen du befinner dig nu."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Pumpinställningar"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "Pumpregion och färg är märkta som de 3 sista siffrorna i modellnumret (markerad som REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Region och färg"; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Inställning klar"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Pumpsändningar"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "På"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Pumpinställning"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Ange pumpregion"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Pumpinställning"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop kommer att lyssna på statusmeddelanden skickade av din pump. Följ de här stegen nedan på din pump för att aktivera dessa meddelanden:"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Huvudmeny"; + diff --git a/MinimedKitUI/vi.lproj/Localizable.strings b/MinimedKitUI/vi.lproj/Localizable.strings new file mode 100644 index 000000000..92ede5373 --- /dev/null +++ b/MinimedKitUI/vi.lproj/Localizable.strings @@ -0,0 +1,209 @@ +/* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ +"%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; + +/* The format string describing number of basal schedule entries: (1: number of entries) */ +"%1$@ basal schedule entries\n" = "%1$@ Lịch biểu liều nền\n"; + +/* The format string describing units of insulin remaining: (1: number of units) */ +"%1$@ Units of insulin remaining\n" = "%1$@ Số unit insulin còn lại\n"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Instructions on selecting battery chemistry type */ +"Alkaline and Lithium batteries decay at differing rates. Alkaline tend to have a linear voltage drop over time whereas lithium cell batteries tend to maintain voltage until halfway through their lifespan. Under normal usage in a Non-MySentry compatible Minimed (x22/x15) insulin pump running Loop, Alkaline batteries last approximately 4 to 5 days. Lithium batteries last between 1-2 weeks. This selection will use different battery voltage decay rates for each of the battery chemistry types and alert the user when a battery is approximately 8 to 10 hours from failure." = "Pin kềm và pin lithium phân rã ở các mức độ khác nhau. Pin kềm có xu hướng giảm điện áp tuyến tính theo thời gian trong khi pin lithium có xu hướng duy trì điện áp cho đến khi hết nửa vòng đời. Trong điều kiện sử dụng bình thường trên bơm Minimed (loại X22 hay X15) khi chạy Loop, pin kiềm có thể dùng được trong khoảng 4 đến 5 ngày trong khi pin lithium dùng dc 2 tuần. Việc lựa chọn này được sử dụng theo các mức phân rã điện áp khác nhau cho mỗi loại pin hóa học và sẽ có cảnh báo đối với người dùng khi pin hỏng đạt khoảng từ 8-10 giờ."; + +/* Confirmation message for deleting a pump */ +"Are you sure you want to delete this pump?" = "Bạn có chắc muốn xóa bơm này không?"; + +/* The title of the cell describing an awake radio */ +"Awake Until" = "Giữ liên lạc cho đến khi"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Tỷ lệ liều nền"; + +/* The format string describing pump battery voltage: (1: battery voltage) */ +"Battery: %1$@ volts\n" = "Pin: %1$@ volts\n"; + +/* The label indicating the best radio frequency */ +"Best Frequency" = "Tần số tối ưu"; + +/* The format string describing pump bolusing state: (1: bolusing) */ +"Bolusing: %1$@\n" = "Đang tiêm liều bolus: %1$@\n"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Hủy bỏ"; + +/* The title of the command to change pump time */ +"Change Time" = "Thay đổi thời gian"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Đổi múi giờ"; + +/* Progress message for changing pump time. */ +"Changing time…" = "Đang thay đổi thời gian…"; + +/* The title of the section describing commands */ +"Commands" = "Các câu lệnh"; + +/* The title of the configuration section in settings */ +"Configuration" = "Cấu hình"; + +/* Button title to connect to pump during setup */ +"Connect" = "Kết nối"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Tình trạng Kết nối"; + +/* Button title to delete pump + Title text for the button to remove a pump from Loop */ +"Delete Pump" = "Xóa bơm"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Giới hạn tiêm"; + +/* The title of the section describing the device */ +"Device" = "Thiết bị"; + +/* The title of the command to discover commands */ +"Discover Commands" = "Phát hiện các dòng lệnh"; + +/* Progress message for discovering commands. */ +"Discovering commands…" = "Đang phát hiện các dòng lệnh…"; + +/* The title of the command to enable diagnostic LEDs */ +"Enable Diagnostic LEDs" = "Cho phép chuẩn đoán LEDs"; + +/* Progress message for enabling diagnostic LEDs */ +"Enabled Diagnostic LEDs" = "Cho phép chuẩn đoán LEDs"; + +/* The alert title for a resume error */ +"Error Resuming" = "Lỗi khi thực hiện Tiếp tục lại"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Lỗi khi thực hiện Tạm ngưng"; + +/* The title of the command to fetch recent glucose */ +"Fetch Enlite Glucose" = "Lấy dữ liệu đường huyết"; + +/* The title of the command to fetch recent history */ +"Fetch Recent History" = "Lấy dữ liệu gần đây"; + +/* Progress message for fetching pump glucose. */ +"Fetching glucose…" = "Đang lấy dữ liệu đường huyết…"; + +/* Progress message for fetching pump history. */ +"Fetching history…" = "Đang lấy thông tin…"; + +/* Progress message for fetching pump model. */ +"Fetching pump model…" = "Đang lấy model bơm…"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Chương trình cơ sở"; + +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Chương trình cơ sở"; + +/* The title of the command to get pump model */ +"Get Pump Model" = "Lấy model của bơm"; + +/* Progress message for getting statistics. */ +"Get Statistics…" = "Lấy các thống kê…"; + +/* Instructions on selecting an insulin data source */ +"Insulin delivery can be determined from the pump by either interpreting the event history or comparing the reservoir volume over time. Reading event history allows for a more accurate status graph and uploading up-to-date treatment data to Nightscout, at the cost of faster pump battery drain and the possibility of a higher radio error rate compared to reading only reservoir volume. If the selected source cannot be used for any reason, the system will attempt to fall back to the other option." = "Việc tiêm insulin có thể được quyết định từ bơm bằng cách kết hợp giải thuật dữ liệu của người sử dụng và so sánh với khối lượngngăn chứa insulin theo thời gian. Việc đọc các dữ liệu cũ sẽ đảm bảo biểu đồ đường huyết luôn được tính chính xác và tải dữ liệu điều trị cập nhật lên Nightscout, nhưng lại tăng việc tiêu hao pin cũng như lỗi giao tiếp tần số radio cao hơn việc chỉ đọc mỗi dữ liệu ngăn chứa insulin. Trong trường hợp nguồn dữ liệu không được lựa chọn vì bất kỳ lý do gì thì phần mềm sẽ quay sang lựa chọn khác."; + +/* The title of the cell describing an awake radio */ +"Last Awake" = "Lần giao tiếp cuối cùng"; + +/* The title of the cell describing no radio awake data */ +"Listening Off" = "Đang lắng nghe"; + +/* The title of the command to pair with mysentry */ +"MySentry Pair" = "Ghép đôi MySentry"; + +/* The title of the cell showing device name */ +"Name" = "Tên"; + +/* Message display when no response from tuning pump */ +"No response" = "Không có phản hồi nào"; + +/* The title of the cell showing the last idle */ +"On Idle" = "Đang chờ"; + +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + +/* The title text for the preferred insulin data source config */ +"Preferred Data Source" = "Nguồn dữ liệu được ưa thích"; + +/* The title of the section describing the pump */ +"Pump" = "Bơm"; + +/* The title text for the battery type value */ +"Pump Battery Type" = "Loại pin của bơm"; + +/* The title of the cell showing pump ID + The title text for the pump ID config value */ +"Pump ID" = "Số ID của bơm"; + +/* The title of the cell showing the pump model number */ +"Pump Model" = "Model của bơm"; + +/* Title of the pump settings view controller */ +"Pump Settings" = "Cấu hình của bơm"; + +/* The title of the command to read basal schedule */ +"Read Basal Schedule" = "Đọc lịch biểu tiêm liều nền"; + +/* The title of the command to read pump status */ +"Read Pump Status" = "Đọc tình trạng bơm"; + +/* Progress message for reading basal schedule */ +"Reading basal schedule…" = "Đang đọc lịch biểu liều nền…"; + +/* Progress message for reading pump status */ +"Reading pump status…" = "Đang đọc tình trạng bơm…"; + +/* The title of the cell showing the pump region */ +"Region" = "Region"; + +/* Button title to retry sentry setup */ +"Retry" = "Thử lại"; + +/* The title of the command to fetch RileyLink statistics */ +"RileyLink Statistics" = "Các thống kê của RileyLink"; + +/* Title of button to save basal profile to pump + Title of button to save delivery limit settings to pump */ +"Save to Pump…" = "Lưu vào bơm…"; + +/* The title of the command to send a button press */ +"Send Button Press" = "Gửi nút bấm"; + +/* Progress message for sending button press to pump. */ +"Sending button press…" = "Đang gửi nút bấm…"; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Cường độ tín hiệu"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Đã thành công"; + +/* The format string describing pump suspended state: (1: suspended) */ +"Suspended: %1$@\n" = "Đã tạm ngưng: %1$@\n"; + +/* The label indicating the results of each frequency trial */ +"Trials" = "Các thử nghiệm"; + +/* The title of the command to re-tune the radio */ +"Tune Radio Frequency" = "Tần số Radio"; + +/* Progress message for tuning radio */ +"Tuning radio…" = "Chuyển tần số radio…"; + +/* The detail text for an unknown pump model */ +"Unknown" = "Không nhận ra"; + +/* The title of the cell showing uptime */ +"Uptime" = "Thời gian hoạt động"; diff --git a/MinimedKitUI/vi.lproj/MinimedPumpManager.strings b/MinimedKitUI/vi.lproj/MinimedPumpManager.strings new file mode 100644 index 000000000..cf2f6a812 --- /dev/null +++ b/MinimedKitUI/vi.lproj/MinimedPumpManager.strings @@ -0,0 +1,74 @@ +/* Class = "UITableViewController"; title = "RileyLink Setup"; ObjectID = "0MV-2k-Dty"; */ +"0MV-2k-Dty.title" = "Cài đặt RileyLink"; + +/* Class = "UILabel"; text = "Find Device"; ObjectID = "1fp-45-qWK"; */ +"1fp-45-qWK.text" = "Tìm kiếm thiết bị"; + +/* Class = "UILabel"; text = "Other Devices"; ObjectID = "A6i-Cb-baR"; */ +"A6i-Cb-baR.text" = "Các thiết bị khác"; + +/* Class = "UILabel"; text = "Do not change the time using your pumpʼs menu."; ObjectID = "Bdb-j4-WcR"; */ +"Bdb-j4-WcR.text" = "Không thay đổi thời gian sử dụng trên menu bơm của bạn."; + +/* Class = "UILabel"; text = "Utilities"; ObjectID = "c7t-pZ-WqY"; */ +"c7t-pZ-WqY.text" = "Các tiện ích"; + +/* Class = "UILabel"; text = "Connect Devices"; ObjectID = "erq-yb-anx"; */ +"erq-yb-anx.text" = "Kết nối thiết bị"; + +/* Class = "UITableViewController"; title = "Pump Clock"; ObjectID = "Fps-h3-V4K"; */ +"Fps-h3-V4K.title" = "Đồng hồ của bơm"; + +/* Class = "UITableViewSection"; footerTitle = "The pump ID is the 6-digit numerical portion of the serial number (labeled as SN or S/N)."; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.footerTitle" = "Số ID của bơm là phần số củ dãy số seri (được ký hiệu là SN hay S/N)."; + +/* Class = "UITableViewSection"; headerTitle = "Pump ID"; ObjectID = "fVG-pl-jT9"; */ +"fVG-pl-jT9.headerTitle" = "Số ID của bơm"; + +/* Class = "UILabel"; text = "Your pump is ready for use."; ObjectID = "g1m-3k-XI3"; */ +"g1m-3k-XI3.text" = "Bơm của bạn đã sẵn sàng."; + +/* Class = "UITextField"; placeholder = "Enter the 6-digit pump ID"; ObjectID = "HeG-VF-L5P"; */ +"HeG-VF-L5P.placeholder" = "Nhập 6 số ID của bơm"; + +/* Class = "UILabel"; text = "Review your pump settings below. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "HfQ-fG-8vO"; */ +"HfQ-fG-8vO.text" = "Xem lại cấu hình bơm của bạn bên dưới. Bạn có thể thay đổi cấu hình bất kỳ lúc nào."; + +/* Class = "UILabel"; text = "If you travel to a different time zone for an extended period of time, you can change the pumpʼs time zone at any time in Loopʼs Settings screen."; ObjectID = "HuY-fE-vM8"; */ +"HuY-fE-vM8.text" = "Trường hợp bạn du lịch đến vùng khác múi giờ trong thời gian dài, bạn có thể thay dổi múi giờ của bơm bất kỳ lúc nào trong phần cấu hình của bơm."; + +/* Class = "UILabel"; text = "Loop will keep your pumpʼs clock synchronized with your phone in the time zone youʼre in now."; ObjectID = "IQ5-53-x9s"; */ +"IQ5-53-x9s.text" = "Loop sẽ giữ đồng hồ của bơm đồng hóa với điện thoại trong múi giờ mà bạn đang hiện diện."; + +/* Class = "UITableViewController"; title = "Pump Settings"; ObjectID = "iQZ-kT-QUm"; */ +"iQZ-kT-QUm.title" = "Cấu hình cho bơm"; + +/* Class = "UITableViewSection"; footerTitle = "The pump region and color are denoted as the last 3 letters of the the model number (labeled as REF)."; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.footerTitle" = "Khu vực và màu sắc của bơm được thể hiện qua 3 chữ cuối của chủng loại bơm (thể hiện là REF)."; + +/* Class = "UITableViewSection"; headerTitle = "Region and Color"; ObjectID = "lGI-LD-xR7"; */ +"lGI-LD-xR7.headerTitle" = "Vùng và Màu sắc"; + +/* Class = "UITableViewController"; title = "Setup Complete"; ObjectID = "Nwf-TJ-KmJ"; */ +"Nwf-TJ-KmJ.title" = "Cấu hình hoàn thành"; + +/* Class = "UITableViewController"; title = "Pump Broadcasts"; ObjectID = "oBL-lh-SHI"; */ +"oBL-lh-SHI.title" = "Pump Broadcasts"; + +/* Class = "UILabel"; text = "On"; ObjectID = "ojQ-ob-gBx"; */ +"ojQ-ob-gBx.text" = "On"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ +"OZk-Db-KCs.title" = "Cấu hình bơm"; + +/* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ +"tGa-FP-JqD.text" = "Nhập khu vực của bơm"; + +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "Cấu hình cho bơm"; + +/* Class = "UILabel"; text = "Loop will listen for status messages sent by your pump. Follow the steps below on your pump to enable these messages:"; ObjectID = "yLn-Ya-p1R"; */ +"yLn-Ya-p1R.text" = "Loop sẽ lắng nghe các thông điệp được gửi từ bơm của bạn. Làm theo các bước dưới đây để thực hiện các thông điệp này:"; + +/* Class = "UITableViewSection"; headerTitle = "Main Menu"; ObjectID = "ZnF-zy-5gR"; */ +"ZnF-zy-5gR.headerTitle" = "Menu chính"; diff --git a/MinimedKitUI/zh-Hans.lproj/InfoPlist.strings b/MinimedKitUI/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/MinimedKitUI/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/MinimedKitUI/zh-Hans.lproj/Localizable.strings b/MinimedKitUI/zh-Hans.lproj/Localizable.strings index 453262040..31c7addd1 100644 --- a/MinimedKitUI/zh-Hans.lproj/Localizable.strings +++ b/MinimedKitUI/zh-Hans.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The format string for displaying a frequency tune trial. Extra spaces added for emphesis: (1: frequency in MHz)(2: success count)(3: total count)(4: average RSSI) */ "%1$@ %2$@/%3$@ %4$@" = "%1$@ %2$@/%3$@ %4$@"; @@ -80,6 +77,12 @@ /* Progress message for enabling diagnostic LEDs */ "Enabled Diagnostic LEDs" = "正在打开状态LED指示灯"; +/* The alert title for a resume error */ +"Error Resuming" = "无法恢复"; + +/* The alert title for a suspend error */ +"Error Suspending" = "无法暂停"; + /* The title of the command to fetch recent glucose */ "Fetch Enlite Glucose" = "获取Enlite葡萄糖"; @@ -98,6 +101,9 @@ /* The title of the cell showing firmware version */ "Firmware" = "固件"; +/* The title of the cell showing the pump firmware version */ +"Firmware Version" = "Firmware Version"; + /* The title of the command to get pump model */ "Get Pump Model" = "获取胰岛素泵型号"; @@ -125,6 +131,9 @@ /* The title of the cell showing the last idle */ "On Idle" = "空闲"; +/* Pump find device instruction */ +"On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device" = "On your pump, go to the Find Device screen and select \"Find Device\".\n\nMain Menu >\nUtilities >\nConnect Devices >\nOther Devices >\nOn >\nFind Device"; + /* The title text for the preferred insulin data source config */ "Preferred Data Source" = "首选数据源"; @@ -156,6 +165,9 @@ /* Progress message for reading pump status */ "Reading pump status…" = "正在读取胰岛素泵状态…"; +/* The title of the cell showing the pump region */ +"Region" = "Region"; + /* Button title to retry sentry setup */ "Retry" = "重试"; @@ -193,3 +205,5 @@ /* The detail text for an unknown pump model */ "Unknown" = "未知"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/MinimedKitUI/zh-Hans.lproj/MinimedPumpManager.strings b/MinimedKitUI/zh-Hans.lproj/MinimedPumpManager.strings index e2a66c57d..c6f20400a 100644 --- a/MinimedKitUI/zh-Hans.lproj/MinimedPumpManager.strings +++ b/MinimedKitUI/zh-Hans.lproj/MinimedPumpManager.strings @@ -61,6 +61,9 @@ /* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "OZk-Db-KCs"; */ "OZk-Db-KCs.title" = "泵设置"; +/* Class = "UINavigationItem"; title = "Pump Setup"; ObjectID = "V47-Nq-7ew"; */ +"V47-Nq-7ew.title" = "泵设置"; + /* Class = "UILabel"; text = "Enter the pump region"; ObjectID = "tGa-FP-JqD"; */ "tGa-FP-JqD.text" = "输入泵区域"; diff --git a/NightscoutUploadKit/de.lproj/InfoPlist.strings b/NightscoutUploadKit/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/es.lproj/InfoPlist.strings b/NightscoutUploadKit/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/fr.lproj/InfoPlist.strings b/NightscoutUploadKit/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/it.lproj/InfoPlist.strings b/NightscoutUploadKit/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/nb.lproj/InfoPlist.strings b/NightscoutUploadKit/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/nl.lproj/InfoPlist.strings b/NightscoutUploadKit/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/pl.lproj/InfoPlist.strings b/NightscoutUploadKit/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/ru.lproj/InfoPlist.strings b/NightscoutUploadKit/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKit/zh-Hans.lproj/InfoPlist.strings b/NightscoutUploadKit/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKit/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/de.lproj/InfoPlist.strings b/NightscoutUploadKitTests/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/es.lproj/InfoPlist.strings b/NightscoutUploadKitTests/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/fr.lproj/InfoPlist.strings b/NightscoutUploadKitTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/it.lproj/InfoPlist.strings b/NightscoutUploadKitTests/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/nb.lproj/InfoPlist.strings b/NightscoutUploadKitTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/nl.lproj/InfoPlist.strings b/NightscoutUploadKitTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/pl.lproj/InfoPlist.strings b/NightscoutUploadKitTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/ru.lproj/InfoPlist.strings b/NightscoutUploadKitTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/NightscoutUploadKitTests/zh-Hans.lproj/InfoPlist.strings b/NightscoutUploadKitTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/NightscoutUploadKitTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/OmniKit/da.lproj/Localizable.strings b/OmniKit/da.lproj/Localizable.strings new file mode 100644 index 000000000..2111cb5d4 --- /dev/null +++ b/OmniKit/da.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Auto-sluk alarm"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Under 50 enheder"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus i gang"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@E %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Indgiver Bolus"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Indgiver Bolus med midlertidig basal"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Indfører Kanyle"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Sikker"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Deaktiveret"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Tomt reservoir"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Tom svar fra pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Fejl hændelse logget, lukker ned"; + +/* Description for expiration alert */ +"Expiration alert" = "Udløbs advarsel"; + +/* Description for finish setup */ +"Finish setup " = "Afslut indstilling"; + +/* Pod inititialized */ +"Initialized" = "Initialiseret"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Intern pod fejl %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "AfbrudtBolus: %1$@ E (%2$@ E planlagt) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Lavt reservoir alarm"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Sørg for at din RileyLink er i nærheden og tændt"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Ingen alarmer"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Ingen pod parret"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Intet svar fra pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Ingen RileyLink til stede"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Blokkering opdaget"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Parret"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Venligst flyt pod’en tættere på din RileyLink og prøv igen"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Venligst par med en ny pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod allerede parret"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod allerede klargjort"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Pod udløbs alarm"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Pod udløbs påmindelse"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod udløbet"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Pod Fejl: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod er ikke klar til kanyle indførsel."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod er ikke klar til ‘klargøring’."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod er pauset"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Pod indstillings vindue udløbet"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Klargør"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Klar til basal programmering"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Klar til kanyle indførsel"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Fortsæt: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Planlagt Basal"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Afbryd forestående alarm"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Pause: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Pauset"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Reservoir påfyldning komplet"; + +/* Pod power to motor activated */ +"Tank power activated" = "Reservoir strøm aktiveret"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Midlertidig basal allerede i gang"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Midlertidig basal i gang"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "MidlertidigBasal: %1$@ E/time %2$@ %3$@ %4$@ E %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Tid til udskiftning af pod! Din pod udløber om %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Usikker"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Uventet svar fra pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Ukendt pod fejl %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Vent indtil eksisterende bolus er færdig, eller annuller bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Vent indtil den eksisterende midlertidige basal er færdig, eller pause for at annullere"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Venter på parrings påmindelse"; diff --git a/OmniKit/de.lproj/Localizable.strings b/OmniKit/de.lproj/Localizable.strings new file mode 100644 index 000000000..9fa01cdc5 --- /dev/null +++ b/OmniKit/de.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Auto-Off Alarm"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Weniger als 50 Einheiten"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolusabgabe läuft "; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@IE %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Bolusabgabe"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Abgabe mit temporärer Basalrate"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Einsetzen der Kanüle"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Sicher"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Deaktiviert"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Reservoir leer"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Rückmeldung leer von Pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Fehlerereignis protokolliert, fahre herunter"; + +/* Description for expiration alert */ +"Expiration alert" = "Ablaufalarm"; + +/* Description for finish setup */ +"Finish setup " = "Setup beenden"; + +/* Pod inititialized */ +"Initialized" = "Initialisiert"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Interner Podfehler %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "Abgebrochener Bolus: %1$@ IE (%2$@ IE geplant) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Hinweisalarm für fast leeres Reservoir"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Stellen Sie sicher, dass sich Ihr RileyLink in der Nähe befindet und eingeschaltet ist"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Keine Alarme"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Kein Pod gekoppelt"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Keine Rückmeldung von Pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Kein RileyLink verfügbar"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Verstopfung erkannt"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Gekoppelt"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Bitte bringen Sie Ihren Pod näher an den RileyLink und versuchen Sie es erneut"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Bitte koppeln Sie einen neuen Pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod bereits gekoppelt"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod bereits gefüllt"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Ablaufalarm des Pods"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Hinweis zum Ablaufen des Pods"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod abgelaufen"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Podfehler: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Der Pod ist nicht bereit zum Einsetzen der Kanüle."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Der Pod ist nicht bereit zum Befüllen"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod ist unterbrochen"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Das Zeitfenster für das Pod-Setup ist abgelaufen"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Befüllen"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Bereit für die Programmierung der Basalrate"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Bereit zum Einführen der Kanüle"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Fortsetzen: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Geplante Basalrate"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Alarm für die bevorstehende Pod-Abschaltung"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Unterbrochen: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Unterbrochen"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Befüllen des Pods erfolgreich"; + +/* Pod power to motor activated */ +"Tank power activated" = "Energieversorgung für den Podmotor aktiviert"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Eine temporäre Basalrate läuft bereits."; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Temporäre Basalrate läuft."; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "TempBasal: %1$@ U/h %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Zeit, Ihren Pod zu ersetzen! Ihr Pod läuft ab in %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Unsicher"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Unerwartete Antwort vom Pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Unbekannter Podfehler %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Warten Sie, bis der aktuelle Bolus abgegeben wurde, oder brechen Sie den Bolus ab."; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Warten Sie, bis die aktuelle temporäre Basalrate beendet wurde, oder unterbrechen Sie diese."; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Warte auf die Erinnerung zum Koppeln"; diff --git a/OmniKit/en.lproj/Localizable.strings b/OmniKit/en.lproj/Localizable.strings new file mode 100644 index 000000000..acdec0259 --- /dev/null +++ b/OmniKit/en.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Auto-off alarm"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Below 50 units"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus in progress"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Bolusing"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Bolusing with temp basal"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Cannula inserting"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Certain"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Deactivated"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Empty reservoir"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Empty response from pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Error event logged, shutting down"; + +/* Description for expiration alert */ +"Expiration alert" = "Expiration alert"; + +/* Description for finish setup */ +"Finish setup " = "Finish setup "; + +/* Pod inititialized */ +"Initialized" = "Initialized"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Internal pod fault %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Low reservoir advisory alarm"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Make sure your RileyLink is nearby and powered on"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "No alerts"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "No pod paired"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "No response from pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "No RileyLink available"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Occlusion detected"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Paired"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Please bring your pod closer to the RileyLink and try again"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Please pair a new pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod already paired"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod already primed"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Pod expiration advisory alarm"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Pod Expiration Notice"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod expired"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Pod Fault: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod is not in a state ready for cannula insertion."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod is not in a state ready for priming."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod is suspended"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Pod setup window expired"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Priming"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Ready for basal programming"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Ready to insert cannula"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Resume: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Scheduled Basal"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Shutdown imminent alarm"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Suspend: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Suspended"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Tank fill completed"; + +/* Pod power to motor activated */ +"Tank power activated" = "Tank power activated"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Temp basal in progress"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Temp basal running"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Time to replace your pod! Your pod will expire in %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Uncertain"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Unexpected response from pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Unknown pod fault %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Wait for existing bolus to finish, or cancel bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Wait for existing temp basal to finish, or suspend to cancel"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Waiting for pairing reminder"; diff --git a/OmniKit/es.lproj/Localizable.strings b/OmniKit/es.lproj/Localizable.strings new file mode 100644 index 000000000..31189b907 --- /dev/null +++ b/OmniKit/es.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Alarma de apagado automático"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Menos de 50 unidades"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolo en progreso"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolo: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Poniendo bolo"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Poniendo bolo con basal temporal"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Cánula insertándose"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Programación acertada"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Pod desactivado"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Reservorio vacío"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Respuesta vacía del pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Evento de error de registro, apagándose"; + +/* Description for expiration alert */ +"Expiration alert" = "Alerta de caducidad"; + +/* Description for finish setup */ +"Finish setup " = "Fin de la configuración"; + +/* Pod inititialized */ +"Initialized" = " iniciado"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Error pod interno %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "BoloInterrumpido: %1$@ U (%2$@ U planeadas) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Alarma de aviso de depósito bajo"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Asegúrese de que su RileyLink está cerca y encendido"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "No hay alertas"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "No hay pod emparejado"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Sin respuesta del pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "No hay RileyLink disponible"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Oclusion detectada"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Emparejado"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Por favor, acerque su pod al RileyLink e inténtelo de nuevo"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Por favor, empareje un nuevo pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Ya hay un pod emparejado"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "El pod ya está purgado"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Alarma de aviso de caducidad de un pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Aviso de vencimiento de un pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod caducado"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Error de pod: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "El Pod no está listo para insertar la cánula"; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "El Pod no está listo para purgar"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "El Pod está suspendido"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "La pantalla de configuración del pod ha caducado"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Purgando"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Listo para programar basales"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Listo para insertar la cánula"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Reanudar: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Basal programada"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Apagar la alarma inminente"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Suspender: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Suspendido"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Completado el llenado del depósito"; + +/* Pod power to motor activated */ +"Tank power activated" = "Depósito encendido"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Basal temporal en progreso"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Basal temporal funcionando"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "BasalTemporal: %1$@ U/hora %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Es hora de reemplazar el pod! El pod expira en %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Incierto"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Respuesta inesperada del pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Fallo de pod desconocido %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Espere a que termine el bolo o cancele el bolo"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Espere a que termine la basal temporal existente o suspénda para cancelar"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Esperando el recordatorio de emperejamiento"; diff --git a/OmniKit/fi.lproj/Localizable.strings b/OmniKit/fi.lproj/Localizable.strings new file mode 100644 index 000000000..cb64d3836 --- /dev/null +++ b/OmniKit/fi.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Autom. pois -varoitus"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Alle 50 yksikköä"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus vireillä"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Annostellaan bolus"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Annostellaan bolus ja tilap. basaali"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Asetetaan kanyyli"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Varma"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Deaktivoitu"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Tyhjä säiliö"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Tyhjä vastaus pumpulta"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Virhetapahtuma, suljetaan"; + +/* Description for expiration alert */ +"Expiration alert" = "Pumppu vanhenee -varoitus"; + +/* Description for finish setup */ +"Finish setup " = "Lopeta asennus"; + +/* Pod inititialized */ +"Initialized" = "Aloitettu"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Sisäinen pumpun vika %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "KeskeytettyBolus: %1$@ U (%2$@ U suunniteltu) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Säiliö lähes tyhjä -tiedotehälytys"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Varmista, että RileyLink on riittävän lähellä ja kytketty päälle"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Ei hälytyksiä"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Ei yhdistettyä pumppua"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Ei vastausta pumpusta"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Ei RileyLinkiä lähistöllä"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normaali"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Tukos havaittu"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Yhdistetty"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Vie pumppu ja RileyLink lähemmäksi toisiaan ja yritä uudelleen"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Yhdistä uusi pumppu"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pumppu on jo yhdistetty"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pumppu on jo täytetty"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Pumppu vanhenemassa -tiedotehälytys"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Pumppu vanhenemassa -ilmoitus"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pumppu vanhentunut"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Pumppuvirhe: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pumppu ei ole valmis kanyylin asettamiseen."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pumppu ei ole valmis täyttöön."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pumppu on pysäytetty"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Pumpun asennusaika umpeutui"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Alustetaan"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Valmis basaalin ohjelmointiin"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Valmis kanyylin asetukseen"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Jatka: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Suunniteltu basaali"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Mykistä hälytys"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Pysäytä: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Pysäytetty"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Säiliön täyttö valmis"; + +/* Pod power to motor activated */ +"Tank power activated" = "Säiliön virta aktivoitu"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Tilapäinen basaali meneillään"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Tilapäinen basaali käynnissä"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "TilapBasaali: %1$@ U/h %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Aika vaihtaa pumppu! Pumppu vanhenee %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Epävarma"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Odottamaton vastaus pumpusta"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Tuntematon pumppuvirhe %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Odota, että meneillään oleva bolus päättyy tai kumoa bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Odota, että meneillään oleva tilapäinen basaali päättyy tai pysäytä pumppu kumotaksesi"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Odotetaan yhdistämismuistutusta"; diff --git a/OmniKit/fr.lproj/Localizable.strings b/OmniKit/fr.lproj/Localizable.strings new file mode 100644 index 000000000..56e9b7844 --- /dev/null +++ b/OmniKit/fr.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Alarme d’arrêt automatique"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "en dessous de 50 unités"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus en cours"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Bolus en cours"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Bolus en cours avec basale temporaire"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Canule en cours d'insertion"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Certain"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Désactivé"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Réservoir vide"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Réponse vide du pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Événement d’erreur envoyé, arrêt en cours"; + +/* Description for expiration alert */ +"Expiration alert" = "Alerte d'expiration"; + +/* Description for finish setup */ +"Finish setup " = "Installation terminée"; + +/* Pod inititialized */ +"Initialized" = "Initialisé"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "code d'erreur du pod %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "Bolus interrompu: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Alarme de réservoir bas"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Assurez vous que votre RileyLink est à proximité et allumé"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Pas d'alarme"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Pas de pod appairé"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Pas de réponse du pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Aucun RileyLink disponible"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Occlusion détectée"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Appairé"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Merci de rapprocher votre pod du RileyLink et d'essayer à nouveau"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Merci d'appairer un nouveau pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod déjà appairé"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod déjà amorcé"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Alarme d'expiration du pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Notification de l'expiration du pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod expiré"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Erreur du pod: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Le pod n’est pas dans un état prêt pour l’insertion de la canule"; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Le pod n’est pas dans un état prêt pour l’amorçage"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Le pod est suspendu"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "La fenêtre de mise en place du pod a expiré"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Amorçage"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Prêt pour la programmation de la basale"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Prêt à insérer la canule"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Reprise : %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Basale programmée"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Alarme imminente d’arrêt"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Suspension : %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Suspendue"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Remplissage du réservoir terminé"; + +/* Pod power to motor activated */ +"Tank power activated" = "Charge du réservoir activée"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Basale temporaire en cours"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Basale temporaire active"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "Basale temporaire: %1$@ U/heure %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "C’est le moment de remplacer votre pod ! Votre pod va expirer dans %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Incertain"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Reponse inattendue du pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Erreur de pod inconnue %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Attendez que le bolus en cours se termine, ou annulez le bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Attendez que la basale se termine pour quitter, ou mettez en suspens pour annuler"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "En attente d'appairage"; diff --git a/OmniKit/it.lproj/Localizable.strings b/OmniKit/it.lproj/Localizable.strings new file mode 100644 index 000000000..ffc8290a9 --- /dev/null +++ b/OmniKit/it.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Allarme spegnimento automatico"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Inferiore a 50 unità"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolo in corso"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolo: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Somministrazione in bolo in corso"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Somministrazione in bolo con velocità basale temporanea"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Inserimento cannula in corso"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Certa"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Disattivato"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Serbatoio vuoto"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Risposta senza contenuto da Pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "È stato rilevato un errore. Spegnimento in corso"; + +/* Description for expiration alert */ +"Expiration alert" = "Avviso di scadenza"; + +/* Description for finish setup */ +"Finish setup " = "Termina configurazione"; + +/* Pod inititialized */ +"Initialized" = "Inizializzato"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Errore interno Pod %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "BoloInterrotto: %1$@ U (%2$@ U previste) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Allarme di avviso livello serbatoio basso"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Assicurati che RileyLink si trovi nelle vicinanze e sia acceso"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Nessun avviso"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Nessun Pod abbinato"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Nessuna risposta da Pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Nessun RileyLink disponibile"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normale"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Occlusione rilevata"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Abbinato"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Avvicina Pod a RileyLink e riprova"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Abbina nuovo Pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod già abbinato"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod già caricato"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Allarme di avviso scadenza Pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Avviso di scadenza Pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod scaduto"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Errore Pod: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod non è pronto per l’inserimento della cannula."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod non è pronto per il caricamento"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod sospeso"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Finestra di configurazione Pod scaduta"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Caricamento in corso"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Pronto per la programmazione basale"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Pronto per l’inserimento della cannula"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Riprendi: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Basale programmato"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Allarme di spegnimento imminente"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Sospeso: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Sospeso"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Riempimento del serbatoio completato"; + +/* Pod power to motor activated */ +"Tank power activated" = "Alimentazione del serbatoio attivata"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Velocità basale temporanea in corso"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Velocità basale temporanea in esecuzione"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "VelocitàBasaleTemporanea: %1$@ U/ora %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "È ora di sostituire Pod! Pod scadrà tra %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Non certa"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Risposta inaspettata da Pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Errore Pod sconosciuto %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Attendi il termine del bolo esistente oppure annulla bolo"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Attendi il termine della velocità basale temporanea esistente oppure sospendi per annullare"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "In attesa del promemoria di abbinamento"; diff --git a/OmniKit/ja.lproj/Localizable.strings b/OmniKit/ja.lproj/Localizable.strings new file mode 100644 index 000000000..0acc66274 --- /dev/null +++ b/OmniKit/ja.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "アラーム自動オフ"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "50Uより下"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "ボーラスが進行中"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"ボーラス: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "ボーラス注入中"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "一時基礎とボーラス"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "カニューレ挿入中"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "確実"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "停止されました"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "リザーバが空です"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "ポッドからの反応 - 空"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "エラーイベントを保存、終了します"; + +/* Description for expiration alert */ +"Expiration alert" = "期限切れアラート"; + +/* Description for finish setup */ +"Finish setup " = "設定終了"; + +/* Pod initialized */ +"Initialized" = "初期化完了"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "インターナルポッドエラー %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "ボーラス中断: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "リザーバ残量低下アラーム"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "RileyLink が近くにあり電源が入っているか確認"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "アラートなし"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "ポッドペアリングできません"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "ポンド反応なし"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "RileyLinkがありません"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "通常"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "閉塞があります"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "ペアリングされました"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "ポッドを RileyLink に近づけてやり直してください"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "新しいポッドをペアリングしてください"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "既にペアリングされています"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "プライミングされています"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "ポッド期限アラーム"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "ポッド期限注意"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "ポッド期限切れ"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "ポッドエラー: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "ポッドがカニューレを挿入できる状態ではありません。"; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "ポッドがプライミングできる状態ではありません。"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "ポッドが一時停止"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "ポッドの設定期限切れ"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "プライミング"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "基礎レート設定できます"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "カニューレを挿入できます"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "再開: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "定期基礎"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "今から鳴るアラームを切る"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "一時停止: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "一時停止"; + +/* Pod tank fill completed */ +"Tank fill completed" = "タンクが満たされました"; + +/* Pod power to motor activated */ +"Tank power activated" = "タンクがオンになりました"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "一時基礎進行中"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "一時基礎注入中"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "一時基礎: %1$@ U/時 %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "ポッドの交換時です。 %1$@でポッドの期限が切れます。"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "不明"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "ポッドから予期せぬ反応"; +  +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "不明なポッドエラー %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "進行中のボーラスが完了するのを待つか、ボーラスをキャンセルしてください。"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "進行中の一時基礎が完了するのを待つか、一時基礎を停止してください"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "ペアリングリマインダー待機中"; diff --git a/OmniKit/nb.lproj/Localizable.strings b/OmniKit/nb.lproj/Localizable.strings new file mode 100644 index 000000000..96540c1f4 --- /dev/null +++ b/OmniKit/nb.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Alarm for automatisk av"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Under 50 enheter"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus pågår"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@E %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Gir bolus"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Gir bolus med temp-basal"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Setter inn kanyle"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Sikker"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Deaktivert"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Tomt reservoar"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Tomt svar fra pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Feil logget, avslutter"; + +/* Description for expiration alert */ +"Expiration alert" = "Utløpsalarm"; + +/* Description for finish setup */ +"Finish setup " = "Ferdigstill oppsett"; + +/* Pod inititialized */ +"Initialized" = "Klargjort"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Intern pod-feil %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "InterruptedBolus: %1$@ E (%2$@ E planlagt) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Anbefalt alarm for lavt reservoar"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Pass på at din RileyLink er slått på og er i nærheten"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Ingen alarmer"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Ingen sammenkoblet pod"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Ingen svar fra pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Ingen RileyLink tilgjengelig"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Tilstoppelse oppdaget"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Sammenkoblbet"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Prøv å sette pod og RileyLink nærmere hverandre og prøv så igjen"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Vennligst koble til ny pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod er allerede sammenkoblet"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod er allerede fyllt"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Anbefalt utløpsalarm for pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Utløpsmelding for pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Utløpt pod"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Pod-feil: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod er ikke klar for å sette inn kanyle."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod er ikke klar for fylling"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod er suspendert"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Tidsvindu for oppsett av pod er utløpt"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Fyller"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Klar for basal-programmering"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Klar for å sette inn kanyle"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Fortsett: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Planlagt basal"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Avslutt forestående alarm"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Pause: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Pauset"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Tankpåfylling komplett"; + +/* Pod power to motor activated */ +"Tank power activated" = "Tank-motor aktivert"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Temp-basal pågår"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Temp-basal kjører"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "Temp-basal: %1$@ E/time %2$@ %3$@ %4$@ E %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Det er på tide å bytte pod! Pod utløper om %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Usikker"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Uventet svar fra pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Ukjent pod-feil %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Vent til at eksisterende bolus skal bli ferdig, eller kanseler bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Venter på at eksisterende temp-basal skal bli ferdig, eller at pause skal avsluttes"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Venter på påminnelse for sammenkobling"; diff --git a/OmniKit/nl.lproj/Localizable.strings b/OmniKit/nl.lproj/Localizable.strings new file mode 100644 index 000000000..7c8466bcc --- /dev/null +++ b/OmniKit/nl.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Auto-uit alarm"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Minder dan 50 eenheden"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus bezig"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Bolussen"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Bolus met tijdelijk basaal"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Canule wordt geplaatst"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Zeker"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Gedeactiveerd"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Reservoir leeg"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Lege/geen reactie van pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Fout geconstateerd, sluit af"; + +/* Description for expiration alert */ +"Expiration alert" = "Alarm vervaltijd"; + +/* Description for finish setup */ +"Finish setup " = "Voltooi installatie"; + +/* Pod inititialized */ +"Initialized" = "Geinitialiseerd"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Interne pod fout %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "OnderbrokenBolus: %1$@ U (%2$@ Eenheden gepland) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Reservoir bijna leeg alarm"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Zorg ervoor dat je RileyLink dichtbij is en aanstaat"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Geen foutmeldingen"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Geen pod verbonden"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Geen reactie van pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Geen RileyLink aanwezig"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normaal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Afsluiting gedetecteerd"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Verbonden"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Breng de pod dichter bij de RileyLink en probeer opnieuw"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Verbind een nieuwe pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod al verbonden"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod is al voorbereid"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Pod vervaltijd advies alarm"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Pod vervaltijd aankondiging"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod verlopen"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = " Pod fout: %1S@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod is niet gereed voor canule plaatsing."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod is niet gereed voor het voorvullen."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod is onderbroken"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Insteltijd van de Pod is verlopen"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Voorvullen"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Klaar voor programmeren basaal"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Gereed for canule plaatsing"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Hervat: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Gepland basaal"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Sluit af wegens dreigend alarm"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Onderbreek: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Onderbroken"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Opslag vullen compleet"; + +/* Pod power to motor activated */ +"Tank power activated" = "Opslag power geactiveerd"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Tijdelijk basaal wordt uitgevoerd"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Tijdelijk basaal werkend"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "Tijdelijk basaal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Tijd om je pod te vervangen! Vervang je pod in %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Onzeker"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Onverwachte reactie van pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Onbekende pod fout %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Wacht op huidige bolus of maak bolus ongedaan"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Wacht op huidig tijdelijk basaal of onderbreek om te annuleren"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Wacht op herinnering om te verbinden"; diff --git a/OmniKit/pl.lproj/Localizable.strings b/OmniKit/pl.lproj/Localizable.strings new file mode 100644 index 000000000..16d8cd157 --- /dev/null +++ b/OmniKit/pl.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Alarm automatycznego wyłączenia"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Poniżej 50 jedn."; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus w toku"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Podawanie bolusa"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Podawanie bolusa z dawką podstawową"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Wprowadzanie kaniuli"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Pewna"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Deaktywowany"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Pusty zbiornik"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Pusta odpowiedź POD"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Zarejestrowano zdarzenie błędu, wyłączanie"; + +/* Description for expiration alert */ +"Expiration alert" = "Alert o upływie terminu ważności"; + +/* Description for finish setup */ +"Finish setup " = "Zakończ konfigurację"; + +/* Pod inititialized */ +"Initialized" = "Zainicjalizowany"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Wewnętrzny błąd PODa %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "Przerwany bolus: %1$@ jedn. (%2$@ jedn. zaplanowanych) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Alarm informujący o niskim poziomie w zbiorniku"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Upewnij się, że RileyLink jest w pobliżu i jest włączony"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Brak alertów"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Brak sparowanego PODa"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Brak odpowiedzi z PODa"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Brak dostępnego RileyLink"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normalny"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Wykryto niedrożność"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Sparowany"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Umieść PODa bliżej RileyLink i spróbuj ponownie"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Sparuj nowego PODa"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "POD już sparowany"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "POD już napełniony"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Alarm informujący o upływie terminu ważności PODa"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Powiadomienie o upływie terminu ważności PODa"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Termin ważności PODa upłynął"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Usterka PODa: 1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "POD nie znajduje się w stanie gotowości do wprowadzenia kaniuli."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "POD nie znajduje się w stanie gotowości do napełniania."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "POD wstrzymany"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Upłynął termin ważności okna konfiguracji PODa"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Napełnianie"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Gotowy do zaprogramowania dawki podstawowej"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Gotowy do wprowadzenia kaniuli"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Wznów: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Zaplanowana dawka podstawowa"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Alarm o nadchodzącym wyłączeniu"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Wstrzymaj: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Wstrzymany"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Napełnianie zbiornika zakończone"; + +/* Pod power to motor activated */ +"Tank power activated" = "Zasilanie zbiornika włączone"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Tymczasowa dawka podstawowa w toku"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Tymczasowa dawka podstawowa działa"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "Tymczasowa dawka podstawowa: %1$@ jedn./godz. %2$@ %3$@ %4$@ jedn. %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Pora wymienić PODa! Termin ważności PODa upłynie za %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Niepewna"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Nieoczekiwana odpowiedź PODa"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Nieznany błąd PODa %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Poczekaj na zakończenie istniejącego bolusa lub anuluj bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Poczekaj na zakończenie istniejącej dawki podstawowej lub wstrzymaj, aby anulować"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Poczekaj na przypomnienie o parowaniu"; diff --git a/OmniKit/pt-BR.lproj/Localizable.strings b/OmniKit/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..58abd5fa1 --- /dev/null +++ b/OmniKit/pt-BR.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Alarme de desligamento automático"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Abaixo de 50 unidades"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus em andamento"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Entregando Bolus"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Aplicando bolus com basal temp"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Inserindo Cânula"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Certo"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Desativado"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Reservatório vazio"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Resposta vazia do pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Evento de erro registrado, desligando"; + +/* Description for expiration alert */ +"Expiration alert" = "Alerta de expiração"; + +/* Description for finish setup */ +"Finish setup " = "Concluir configuração"; + +/* Pod inititialized */ +"Initialized" = "Inicializado"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Falha interna do pod %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "BolusInterrompido: %1$@ U (%2$@ U agendado) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Alarme de baixo reservatório"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Verifique se o seu RileyLink está próximo e ligado"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Sem alertas"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Nenhum pod emparelhado"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Nenhuma resposta do pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Nenhum RileyLink disponível"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Oclusão detectada"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Emparelhado"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Traga seu pod para mais perto do RileyLink e tente novamente"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Emparelhe um novo pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod já emparelhado"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod já preparado"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Alarme de expiração do pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Aviso de Expiração do Pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod expirado"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Falha no Pod: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "O Pod não está pronto para a inserção da cânula."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "O Pod não está em um estado pronto para preparação."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "O pod está suspenso"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "A janela de configuração do pod expirou"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Preparando"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Pronto para programação basal"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Pronto para inserir a cânula"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Retomar: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Basal Agendado"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Alarme de desligamento iminente"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Suspender: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Suspenço"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Enchimento do tanque concluído"; + +/* Pod power to motor activated */ +"Tank power activated" = "Alimentação do tanque ativada"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Basal temporária em andamento"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Executando basal temporária"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "BasalTemp: %1$@ U/hora %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Hora de substituir o seu pod! Seu pod expirará em %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Incerto"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Resposta inesperada do pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Falha desconhecida do pod %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Aguarde a conclusão do bolus existente ou cancele-o"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Aguarde até que a basal temp existente termine ou suspenda para cancelar"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Aguardando lembrete de emparelhamento"; diff --git a/OmniKit/ro.lproj/Localizable.strings b/OmniKit/ro.lproj/Localizable.strings new file mode 100644 index 000000000..ba582291f --- /dev/null +++ b/OmniKit/ro.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Alarmă Auto-off"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Sub 50 de unități"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus în derulare"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Bolus în derulare"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Bolus în derulare cu bazală temporară"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Se inserează canula"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Doză confirmată"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Dezactivat"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Rezervor gol"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Răspuns gol primit de la Pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Eroare înregistrată în jurnal, se oprește"; + +/* Description for expiration alert */ +"Expiration alert" = "Alertă expirare"; + +/* Description for finish setup */ +"Finish setup " = "Finalizare setare"; + +/* Pod inititialized */ +"Initialized" = "Inițializat"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Defecțiune Pod internă %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "BolusÎntrerupt: %1$@ U (%2$@ U planificat) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Alarmă nivel scăzut rezervor"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Asigurați-vă că RileyLink este pornit și situat în apropriere"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Nicio alertă"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Niciun Pod asociat"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Nu s-a primit răspuns de la Pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Nu există un RileyLink disponibil"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Ocluziune detectată"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Asociat"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Apropiați Pod-ul de RileyLink și încercați din nou"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Asociați un Pod nou"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod deja asociat"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod deja amorsat"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Alarmă expirare Pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Notificare expirare Pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod expirat"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Defecțiune Pod: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod-ul nu este pregătit pentru inserarea canulei."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod nu este pregătit pentru amorsare."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod-ul este suspendat"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Intervalul de timp în care se poate seta Pod-ul a expirat"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Se amorsează"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Pregătit pentru programarea bazalelor"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Pregătit pentru inserarea canulei"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Reluare: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Bazală planificată"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Alarmă de oprire iminentă"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Suspendare: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Suspendat"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Umplere rezervor completă"; + +/* Pod power to motor activated */ +"Tank power activated" = "Putere motor rezervor activată"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Bazală temporară în curs de rulare"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Bazala temporară este în curs de rulare"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "BasalăTemporară: %1$@ U/oră %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "E timpul să înlocuiți Pod-ul! Acesta va expira în %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Doză neconfirmată"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Răspuns neașteptat de la Pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Defecțiune Pod neidentificată %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Așteptați finalizarea bolusului curent sau opriți bolusul"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Așteptați finalizarea bazalei temporare curente sau suspendați pentru oprirea ei"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Se așteaptă reamintirea de asociere"; diff --git a/OmniKit/ru.lproj/Localizable.strings b/OmniKit/ru.lproj/Localizable.strings new file mode 100644 index 000000000..5030de588 --- /dev/null +++ b/OmniKit/ru.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Авто отключение сигнала"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Менее 50 единиц"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Подача болюса"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Болюс: %1$@ед %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Подается болюс"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Подача болюса при врем базале"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Установка катетера"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Определенно"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Не активен"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Картридж пуст"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Пустой ответ от пода"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Ошибка внесена, отключение"; + +/* Description for expiration alert */ +"Expiration alert" = "Оповещение об истечении срока"; + +/* Description for finish setup */ +"Finish setup " = "Завершение настройки"; + +/* Pod inititialized */ +"Initialized" = "Активирован"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Внутренняя ошибка пода %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "Болюс прерван%1$@ ед (%2$@ ед намечено) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Оповещение о малом запасе инсулина в картридже"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Убедитесь, что RileyLink поблизости и включен"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Активных оповещений нет"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Нет сопряжения с подом"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Нет ответа от пода"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Нет доступного RileyLink"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Норма"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Обнаружена закупорка"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Омнипод"; + +/* Pod status after pairing */ +"Paired" = "Сопряжен"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Поднесите Omnipod ближе к RileyLink и попробуйте снова"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Начните сопряжение с новым Omnipod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Omnipod уже сопряжен"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Omnipod уже заполнен"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Оповещение об окончании гарантийного срока Omnipod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Сообщение об окончании гарантийного срока Omnipod "; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Срок гарантии Omnipod истек"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Отказ Omnipod %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Omnipod не готов к установке катетера"; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Omnipod не готов к первичному заполнению"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Omnipod остановлен"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Время для окна настроек Omnipod истекло"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Заполнение"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Готов к программированию базала"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Готов к установке катетера"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Возобновление: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Основной базал"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Оповещение о неизбежном выключении"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Приостановка: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Остановлено"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Заполнение танка Omnipod завершено"; + +/* Pod power to motor activated */ +"Tank power activated" = "Питание танка активировано"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Работает временный базал"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Подается временный базал"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "Врем базал: %1$@ ед/ч %2$@ %3$@ %4$@ ед %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Пора заменить Pod - Срок годности истекает через %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Не подтверждено"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Неожиданный отклик Omnipod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Неизвестная неполадка Omnipod"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Дождитесь окончания подачи болюса или отмените его"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Дождитесь окончания текущего врем базала или отмените"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Напоминание об ожидании сопряжения"; diff --git a/OmniKit/sv.lproj/Localizable.strings b/OmniKit/sv.lproj/Localizable.strings new file mode 100644 index 000000000..b6b7363bb --- /dev/null +++ b/OmniKit/sv.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Auto-av larm"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Under 50 enheter"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Bolus pågår"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@E %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Ger bolus"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Ger bolus med temporär basal"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Kanyl förs in"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Säker"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Inaktiverad"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Podfel, tom reservoarl"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Inget svar från pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Felhändelse loggad, stänger ned"; + +/* Description for expiration alert */ +"Expiration alert" = "Larm om utgångsdatum"; + +/* Description for finish setup */ +"Finish setup " = "Inställning färdig"; + +/* Pod inititialized */ +"Initialized" = "Pod initialiserad"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Internt podfel %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "AvbrutenBolus: %1$@ E (%2$@ E schemalagd) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Larm vid låg reservoarvolym"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Säkerställ att din RileyLink är nära och påslagen"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Inga larm"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Ingen parkopplad"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Inget svar från pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Ingen RileyLink tillgänglig"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Normal"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Ocklusion upptäckt"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Parkopplad"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "För din pod närmare din RileyLink och försök igen"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Var god parkoppla ny pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod redan parkopplad"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod redan fylld"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Larm för utgångsdatum för pod"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Notis om utgångsdatum för pod"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod har utgått"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Podfel: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod är inte klar för att föra in kanyl"; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod är inte klar för att fyllas"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod är pausad"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Tid för podinställning är överskriden"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Pod fylls på"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Klar för programmering av basal"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Klar att föra in kanyl"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Återuppta: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Schemalagd basal"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Larm för omedelbar avstängning"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Pausa: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Pausad"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Pod har fyllts klart"; + +/* Pod power to motor activated */ +"Tank power activated" = "Ström till motor för behållare aktiverad"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Temp basal pågår redan"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Temp basal pågår"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "TempBasal: %1$@ E/timme %2$@ %3$@ %4$@ E %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Byt din pod! Din pod går ut om %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Osäker"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Oväntat svar från din pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Okänt podfel %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Vänta på att pågående bolus är färdig, eller avbryt bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Vänta på nuvarande temporära basal, eller pausa för att avbryta"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Väntar på påminnelse för parkoppling"; diff --git a/OmniKit/vi.lproj/Localizable.strings b/OmniKit/vi.lproj/Localizable.strings new file mode 100644 index 000000000..3fe7113b3 --- /dev/null +++ b/OmniKit/vi.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "Auto-off alarm"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "Dưới 50 units"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "Liều Bolus đang được thực hiện"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "Bolus: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "Đang tiến hành bolus"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "Đang thực hiện liều basal tạm thời"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "Đang gắn Cannula"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Chắc chắn"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "Đã hủy kích hoạt"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "Ngăn chứa insulin rỗng"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Không có phản hồi từ pod"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Lỗi đăng nhập, đang tắt"; + +/* Description for expiration alert */ +"Expiration alert" = "Thông báo hết hạn"; + +/* Description for finish setup */ +"Finish setup " = "Hoàn tất cấu hình"; + +/* Pod inititialized */ +"Initialized" = "Đã được khởi tạo"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Lỗi bên trong pod %1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "Báo động ngăn chứa insulin thấp"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "Đảm bảo RileyLink bên cạnh và đã được bật"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "Không có cảnh báo nào"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "Không có pod nào được kết nối"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Không có tín hiệu phản hồi từ pod"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "Không tìm thấy RileyLink"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "Bình thường"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "Occlusion detected"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "Đã được ghép đôi"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "Đề nghị để pod gần với Rileylink và thử lại lần nữa"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "Đề nghị ghép đôi pod mới"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod đã được ghép đôi"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod đã được mồi"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Cảnh báo pod hết hạn"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Thông báo pod hết hạn"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod đã hết hạn"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Pod lỗi: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod không sẵn sàng để gắn cannula."; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod không sẵn sàng để mồi."; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod bị tạm ngưng"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Cửa sổ cấu hình pod hết hạn"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "Đang mồi"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "Sẵn sàng cho việc tính toán liều basal"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Sẵn sàng cho việc gắn cannula"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "Tái lập: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "Đã lên chương trình cho liều Basal"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "Tắt báo động sắp xảy ra"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "Tạm ngưng: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "Đã tạm ngưng"; + +/* Pod tank fill completed */ +"Tank fill completed" = "Hoàn tất nạp"; + +/* Pod power to motor activated */ +"Tank power activated" = "Pod được kích hoạt"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "Liều basal tạm thời đang tiến hành"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "Liều basal tạm thời đang thực hiện"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "TempBasal: %1$@ U/giờ %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Thời gian thay pod của bạn! Pod của bạn sẽ hết hạn trong %1$@"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "Không chắc chắn"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Phản hồi bất thường từ pod"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Lỗi không xác định của pod %1$03d"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "Chờ đợi liệu bolus hiện tại hoàn tất hoặc hủy liều bolus"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "Chờ đợi liều basal tạm thời hoàn tất hoặc chọn ngưng để hủy"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "Đang chờ đợi câu thông báo ghép đôi"; diff --git a/OmniKit/zh-Hans.lproj/Localizable.strings b/OmniKit/zh-Hans.lproj/Localizable.strings new file mode 100644 index 000000000..93875ca23 --- /dev/null +++ b/OmniKit/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,178 @@ +/* Description for auto-off alarm */ +"Auto-off alarm" = "自动关闭提醒"; + +/* Pod state when running below fifty units */ +"Below 50 units" = "胰岛素已低于50U"; + +/* Error message shown when operation could not be completed due to existing bolus in progress */ +"Bolus in progress" = "大剂量输注中"; + +/* The format string describing a bolus. (1: The amount delivered)(2: Start time of the dose)(3: duration)(4: scheduled certainty) */ +"Bolus: %1$@U %2$@ %3$@ %4$@" = "大剂量: %1$@U %2$@ %3$@ %4$@"; + +/* Delivery status when bolusing */ +"Bolusing" = "注射中"; + +/* Delivery status when bolusing and temp basal is running */ +"Bolusing with temp basal" = "正在运行临时基础并输注大剂量"; + +/* Pod state when inserting cannula */ +"Cannula inserting" = "植入管路"; + +/* String describing a dose that was certainly scheduled */ +"Certain" = "Certain"; + +/* Pod state when pod has been deactivated */ +"Deactivated" = "已解除"; + +/* Description for Empty reservoir pod fault */ +"Empty reservoir" = "胰岛素储量为零"; + +/* Error message shown when empty response from pod was received */ +"Empty response from pod" = "Pod无响应"; + +/* Pod state error event logged shutting down */ +"Error event logged, shutting down" = "Pod错误已记录"; + +/* Description for expiration alert */ +"Expiration alert" = "到期提醒"; + +/* Description for finish setup */ +"Finish setup " = "完成设置"; + +/* Pod inititialized */ +"Initialized" = "初始化"; + +/* The format string for Internal pod fault (1: The fault code value) */ +"Internal pod fault %1$03d" = "Pod内部错误%1$03d"; + +/* The format string describing a bolus that was interrupted. (1: The amount delivered)(2: The amount scheduled)(3: Start time of the dose)(4: duration)(5: scheduled certainty) */ +"InterruptedBolus: %1$@ U (%2$@ U scheduled) %3$@ %4$@ %5$@" = "大剂量输注终端: %1$@ U (%2$@ U 已输注) %3$@ %4$@ %5$@"; + +/* Description for low reservoir alarm */ +"Low reservoir advisory alarm" = "储药量低"; + +/* Recovery suggestion when no RileyLink is available */ +"Make sure your RileyLink is nearby and powered on" = "确保Rileylink与Pod保持比较近的距离"; + +/* Pod alert state when no alerts are active */ +"No alerts" = "运行正常"; + +/* Error message shown when no pod is paired */ +"No pod paired" = "未配对Pod"; + +/* Error message shown when no response from pod was received */ +"No response from pod" = "Pod无响应"; + +/* Error message shown when no response from pod was received */ +"No RileyLink available" = "没有发现Rileylink"; + +/* Delivery status when basal is running + Pod state when running above fifty units */ +"Normal" = "正常"; + +/* Description for Occlusion detected pod fault */ +"Occlusion detected" = "堵管"; + +/* Generic title of the omnipod pump manager */ +"Omnipod" = "Omnipod"; + +/* Pod status after pairing */ +"Paired" = "已配对"; + +/* Recovery suggestion when no response is received from pod */ +"Please bring your pod closer to the RileyLink and try again" = "请确保Rileylink与Pod保持近距离并重试"; + +/* Recover suggestion shown when no pod is paired */ +"Please pair a new pod" = "请配对一个新的Pod"; + +/* Error message shown when user cannot pair because pod is already paired */ +"Pod already paired" = "Pod已配对"; + +/* Error message shown when prime is attempted, but pod is already primed */ +"Pod already primed" = "Pod充盈已完成"; + +/* Description for expiration advisory alarm */ +"Pod expiration advisory alarm" = "Pod到期提醒"; + +/* The title for pod expiration notification */ +"Pod Expiration Notice" = "Pod到期通知"; + +/* Description for Pod expired pod fault */ +"Pod expired" = "Pod已到期"; + +/* Format string for pod fault code */ +"Pod Fault: %1$@" = "Pod错误: %1$@"; + +/* Error message when cannula insertion fails because the pod is in an unexpected state */ +"Pod is not in a state ready for cannula insertion." = "Pod无法植入"; + +/* Error message when prime fails because the pod is in an unexpected state */ +"Pod is not in a state ready for priming." = "Pod无法充盈"; + +/* Error message action could not be performed because pod is suspended */ +"Pod is suspended" = "Pod已暂停"; + +/* Pod state when prime or cannula insertion has not completed in the time allotted */ +"Pod setup window expired" = "Pod设置超时"; + +/* Delivery status when pod is priming + Pod status when priming */ +"Priming" = "充盈中"; + +/* Pod state when ready for basal programming */ +"Ready for basal programming" = "基础率同步已就绪"; + +/* Pod state when ready for cannula insertion */ +"Ready to insert cannula" = "Pod可以进行植入操作"; + +/* The format string describing a resume. (1: Time)(2: Scheduled certainty */ +"Resume: %1$@ %2$@" = "恢复输注: %1$@ %2$@"; + +/* Delivery status when basal is running */ +"Scheduled Basal" = "预设基础率"; + +/* Description for shutdown imminent alarm */ +"Shutdown imminent alarm" = "关闭提醒"; + +/* The format string describing a suspend. (1: Time)(2: Scheduled certainty */ +"Suspend: %1$@ %2$@" = "暂停输注: %1$@ %2$@"; + +/* Delivery status when insulin delivery is suspended */ +"Suspended" = "已暂停"; + +/* Pod tank fill completed */ +"Tank fill completed" = "已向Pod注入胰岛素"; + +/* Pod power to motor activated */ +"Tank power activated" = "Pod已开启"; + +/* Error message shown when temp basal could not be set due to existing temp basal in progress */ +"Temp basal in progress" = "正在设置临时基础率"; + +/* Delivery status when temp basal is running */ +"Temp basal running" = "临时基础率正在运行"; + +/* The format string describing a temp basal. (1: The rate)(2: Start time)(3: duration)(4: volume)(5: scheduled certainty */ +"TempBasal: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@" = "临时基础率: %1$@ U/hour %2$@ %3$@ %4$@ U %5$@"; + +/* The format string for pod expiration notification body (1: time until expiration) */ +"Time to replace your pod! Your pod will expire in %1$@" = "Pod将在%1$@后到期,请准备更换Pod"; + +/* String describing a dose that was possibly scheduled */ +"Uncertain" = "未知"; + +/* Error message shown when empty response from pod was received */ +"Unexpected response from pod" = "Pod未知响应"; + +/* The format string for Unknown pod fault (1: The fault code value) */ +"Unknown pod fault %1$03d" = "Pod未知错误"; + +/* Recovery suggestion when operation could not be completed due to existing bolus in progress */ +"Wait for existing bolus to finish, or cancel bolus" = "请等待大剂量输注完成,或取消大剂量输注"; + +/* Recovery suggestion when operation could not be completed due to existing temp basal in progress */ +"Wait for existing temp basal to finish, or suspend to cancel" = "请等待临时基础率结束,或暂停以取消临时基础率"; + +/* Description waiting for pairing reminder */ +"Waiting for pairing reminder" = "等待配对提醒"; diff --git a/OmniKitUI/OmnipodPumpManager.storyboard b/OmniKitUI/Base.lproj/OmnipodPumpManager.storyboard similarity index 100% rename from OmniKitUI/OmnipodPumpManager.storyboard rename to OmniKitUI/Base.lproj/OmnipodPumpManager.storyboard diff --git a/OmniKitUI/da.lproj/Localizable.strings b/OmniKitUI/da.lproj/Localizable.strings new file mode 100644 index 000000000..d812c6535 --- /dev/null +++ b/OmniKitUI/da.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ tilbageværende enheder ved %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Insulin indgivelse er stoppet. Venligst deaktiver og fjern pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ E"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ E (Færdig)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ E af %2$@ E (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ E/time"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ E"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@E"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Aktiv Tid"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmer"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Er du sikker på at du vil slukke for denne pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Er du sikker på at du vil stoppe med at bruge Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Tildelt Adresse"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basal Indgivelse"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basal Rater"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolus Indgivelse"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Annuller"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Skift Tidszone"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Skifter tiden…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Fortsæt"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Deaktiver"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Deaktiver Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Slet Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Indgivelsesgrænser"; + +/* The title of the device information section in settings */ +"Device Information" = "Enheds Information"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Deaktiver bolus bip"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Aktiver bolus bip"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Fejl under deaktivering af bolus bip"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Fejl under aktivering af bolus bip"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fejl under genoptagelse"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fejl under udsættelse"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Udløbs Påmindelse"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Udløbet"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Udløber"; + +/* Pod life HUD view label */ +"Fault" = "Fejl"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Afslut pod indstilling"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Flere end %1$@ enheder tilbage ved %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Ufuldstændigt indstillet pod skal først deaktiveres, før der kan parres med en ny. Venligst deaktiver og fjern pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Ufuldstændigt indstillet pod skal først deaktiveres, før der kan parres med en ny. Venligst deaktiver og fjern pod"; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Indfør kanyle"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Indført Insulin"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "fejlagtig indtastning"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Ingen"; + +/* Button title to pair with pod during setup */ +"Pair" = "Par"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Par ny Pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Parres…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI Version"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Afspil Test Bip"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Afspiller Test Bip…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM Version"; + +/* Label describing pod age view */ +"Pod Age" = "Pod Alder"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pod Indstillinger"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Klargjort"; + +/* The text of the loading label when priming */ +"Priming…" = "Klargør…"; + +/* Label describing time remaining view */ +"Remaining" = "Tilbage"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Udskift Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Udskift Pod Nu"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservoir"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Forsøg Pod Deaktivering igen"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Gem"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Tidsplan"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Gennemført"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Udsat"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Skift fra Omnipod Pumper"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synkroniser med Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Test Kommando"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Tester Kommandoer…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "For mange indtastninger"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Kan ikke deaktivere pod. Venligst fortsæt og par med en ny."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Ukendt"; diff --git a/OmniKitUI/da.lproj/OmnipodPumpManager.strings b/OmniKitUI/da.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..f69568872 --- /dev/null +++ b/OmniKitUI/da.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Fjern POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink Indstillinger"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Pod Indstillinger"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Pumpe Indstilling"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "OBS: Fjern ikke pod’ens beskyttelseskappe endnu."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Klargør Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop vil minde dig om at udskifte pod’en før den udløber. Du kan ændre det til et belejligt tidspunkt for dig."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Venligst deaktiver pod. Når deaktivering er komplet, fjernes den fra kroppen."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Indfør Kanyle"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Klargør placeringen. Fjern pod’ens beskyttelseskappe og tapebagside. Hvis pod er OK, påsættes den."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Pumpe Indstilling"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Pumpe Indstilling"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Din Pod er klar til brug."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Påmindelse"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod Parres"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Pumpe Indstilling"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Gennemgå dine indstillinger nedenfor. De vil blive overført til pod’en under parring. Du kan ændre dem til enhver tid i Loop’s Indstillinger."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Indstilling gennemført"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "OBS: Hvis kanylen stikker ud, tryk annuller."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Påsæt POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Label"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Påfyld en ny pod med insulin. Lyt efter 2 bip fra pod’en under påfyldning. Hold RileyLink’en tæt på pod’en under parring."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Udskift Pod"; diff --git a/OmniKitUI/de.lproj/Localizable.strings b/OmniKitUI/de.lproj/Localizable.strings new file mode 100644 index 000000000..09e1c78f6 --- /dev/null +++ b/OmniKitUI/de.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ Einheiten verbleiben bei %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Die Insulinabgabe wurde gestoppt. Bitte deaktivieren und entfernen Sie den Pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ IE"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ IE (abgegeben)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%@ IE von %@ IE (%@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ IE/Std."; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ IE"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@IE"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Laufzeit"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarme"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Möchten Sie diesen Pod wirklich deaktivieren?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Möchten Sie Omnipod wirklich nicht mehr verwenden?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Zugewiesene Adresse"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basalabgabe"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basalrate"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolusabgabe"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Abbrechen"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Zeitzone ändern"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Zeit ändern"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Fortsetzen"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Deaktivieren"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Pod deaktivieren"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Omnipod löschen"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Abgabebeschränkungen"; + +/* The title of the device information section in settings */ +"Device Information" = "Geräteinformation"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Deaktiviere Bolustöne"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Aktiviere Bolustöne"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Fehler beim Deaktivieren der Bolustöne"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Fehler beim Aktivieren der Bolustöne"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fehler beim Fortfahren"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fehler beim Unterbrechen"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Erinnerung an den Ablauf der Nutzungsdauer"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Abgelaufen"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Läuft ab"; + +/* Pod life HUD view label */ +"Fault" = "Störung"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Pod-Setup beenden"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Mehr als %1$@ verbleibende Einheiten um %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Der unvollständig eingerichtete Pod muss vor der Kopplung eines neuen deaktiviert werden. Bitte deaktivieren und verwerfen Sie den Pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Der unvollständig eingerichtete Pod muss vor der Kopplung mit einem neuen deaktiviert werden. Bitte deaktivieren und entfernen Sie den Pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Kanüle einsetzen"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Abgegebenes Insulin"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Ungültiger Eintrag"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Keiner"; + +/* Button title to pair with pod during setup */ +"Pair" = "Koppeln"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Neuen Pod koppeln"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Koppeln ..."; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI-Version"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Testtöne abspielen"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Testtöne abspielen"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM-Version"; + +/* Label describing pod age view */ +"Pod Age" = "Pod-Alter"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pod-Einstellungen"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Gefüllt"; + +/* The text of the loading label when priming */ +"Priming…" = "Füllen ..."; + +/* Label describing time remaining view */ +"Remaining" = "Verbleibend"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Pod ersetzen"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Pod jetzt ersetzen"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservoir"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Pod-Deaktivierung wiederholen"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Sichern"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Voreingestellt"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Erfolgreich"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Unterbrochen"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Omnipod nicht mehr verwenden"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Mit Pod synchronisieren"; + +/* The title of the command to run the test command */ +"Test Command" = "Befehl testen"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Teste Befehle"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Zu viele Einträge"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Pod kann nicht deaktiviert werden. Bitte fahren Sie fort und koppeln Sie einen neuen."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Unbekannt"; diff --git a/OmniKitUI/de.lproj/OmnipodPumpManager.strings b/OmniKitUI/de.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..57f46dbf6 --- /dev/null +++ b/OmniKitUI/de.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Pod entfernen"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink-Setup"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Pod-Einstellungen"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Pumpen-Setup"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "HINWEIS: Bitte entfernen Sie die Schutzkappe der Nadel noch nicht."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Pod vorbereiten"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop sendet Ihnen eine Benachrichtigung, bevor die Nutzungsdauer des Pods abläuft. Sie können die Zeit entsprechend Ihren Bedürfnissen anpassen."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Bitte deaktivieren Sie den Pod. Sobald der Pod vollständig deaktiviert ist, können Sie ihn vom Körper entfernen."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Kanüle einsetzen"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Bereiten Sie die Einsetzstelle für den neuen Pod vor. Entfernen Sie die Schutzkappe der Nadel und die Folie. Sofern der Pod in Ordnung ist, bringen Sie diesen auf der Einsetzstelle an."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Pumpen-Setup"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Pumpen-Setup"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Ihr Pod kann nun verwendet werden."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Erinnerung"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Kopplung des Pods"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Pumpen-Setup"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Überprüfen Sie Ihre Einstellungen. Diese werden während der Kopplung vom Pod übernommen. Sie können Ihre Einstellungen jederzeit bei den Loop-Einstellungen anpassen."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Setup erfolgreich"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "HINWEIS: Sollte die Kanüle hervorstehen, wählen Sie abbrechen."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Pod anbringen"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Label"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Befüllen Sie den neuen Pod mit Insulin. Achten Sie auf die 2 Piepstöne während des Füllvorgangs. Behalten Sie während der Kopplung den RileyLink in der Nähe des Pods."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Pod ersetzen"; diff --git a/OmniKitUI/en.lproj/Localizable.strings b/OmniKitUI/en.lproj/Localizable.strings new file mode 100644 index 000000000..a6afce86f --- /dev/null +++ b/OmniKitUI/en.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Insulin delivery has stopped. Please deactivate and remove pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (Finished)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U of %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/hour"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Active Time"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarms"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Are you sure you want to shutdown this pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Are you sure you want to stop using Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Assigned Address"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basal Delivery"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basal Rates"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolus Delivery"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Cancel"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Change Time Zone"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Changing time…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuration"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Continue"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Deactivate"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Deactivate Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Delete Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Delivery Limits"; + +/* The title of the device information section in settings */ +"Device Information" = "Device Information"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Disable Bolus Beeps"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Enable Bolus Beeps"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Error disabling bolus beeps"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Error enabling bolus beeps"; + +/* The alert title for a resume error */ +"Error Resuming" = "Error Resuming"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Error Suspending"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Expiration Reminder"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Expired"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Expires"; + +/* Pod life HUD view label */ +"Fault" = "Fault"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Finish pod setup"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Greater than %1$@ units remaining at %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Insert Cannula"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulin Delivered"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Invalid entry"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "None"; + +/* Button title to pair with pod during setup */ +"Pair" = "Pair"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Pair New Pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Pairing…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI Version"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Play Test Beeps"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Play Test Beeps…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM Version"; + +/* Label describing pod age view */ +"Pod Age" = "Pod Age"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pod Settings"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Primed"; + +/* The text of the loading label when priming */ +"Priming…" = "Priming…"; + +/* Label describing time remaining view */ +"Remaining" = "Remaining"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Replace Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Replace Pod Now"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservoir"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Retry Pod Deactivation"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Save"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Schedule"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Succeeded"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Suspended"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Switch from Omnipod Pumps"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Sync With Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Test Command"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Testing Commands…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Too many entries"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Unable to deactivate pod. Please continue and pair a new one."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Unknown"; diff --git a/OmniKitUI/en.lproj/OmnipodPumpManager.strings b/OmniKitUI/en.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..ea0f7bdd0 --- /dev/null +++ b/OmniKitUI/en.lproj/OmnipodPumpManager.strings @@ -0,0 +1,69 @@ + +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Remove POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink Setup"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Pod Settings"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Pump Setup"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "NOTE: Do not remove the pod's needle cap at this time."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Prepare Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Please deactivate the pod. When deactivation is complete, remove pod from body."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Insert Cannula"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Pump Setup"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Pump Setup"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Your Pod is ready for use."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Reminder"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod Pairing"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Pump Setup"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Setup Complete"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "NOTE: If cannula sticks out, press cancel."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Apply POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Label"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Replace Pod"; diff --git a/OmniKitUI/es.lproj/Localizable.strings b/OmniKitUI/es.lproj/Localizable.strings new file mode 100644 index 000000000..553743f51 --- /dev/null +++ b/OmniKitUI/es.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "quedan %1$@ unidades a las %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Administración de insulina ha parado. Por favor, desactive y remueva el pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (Completado)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U de %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/hora"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Tiempo Activo"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmas"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "¿Está seguro que quiere apagar este pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "¿Está seguro que quiere dejar de usar Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Dirección Asignada"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basal Administrada"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Rangos de Basal"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolo Administrado"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Cancelar"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Cambiar Zona Horaria"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Cambiando hora..."; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuración"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Continuar"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Desactivar"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Desactivar Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Eliminar Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Límites de Administración de Insulina"; + +/* The title of the device information section in settings */ +"Device Information" = "Información del Dispositivo"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Desactivar Pitidos del Bolo"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Activar Pitidos del Bolo"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Error desactivando pitidos del bolo"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Error activando pitidos del bolo"; + +/* The alert title for a resume error */ +"Error Resuming" = "Error Reanudando"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Error Suspendiendo"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Recordatorio de Caducidad"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Caducado"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Caduca"; + +/* Pod life HUD view label */ +"Fault" = "Fallo"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Terminar la configuración del pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Quedan más de %1$@ unidades a las %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Pod con configuración incompleta tiene que desactivarse antes de emparejarse con uno nuevo"; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Pod con configuración incompleta tiene que desactivarse antes de emparejar uno nuevo. Por favor desactive y quítese el pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Insertar Cánula"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulina Administrada"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Entrada inválida"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lote"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Ninguno"; + +/* Button title to pair with pod during setup */ +"Pair" = "Emparejar"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Emparejar Nuevo Pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Emparejando..."; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Versión PI del Pod"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Tocar Pitidos de Prueba"; + +/* Progress message for play test beeps. */ +"Playing Test Beeps…" = "Tocado Pitidos de Prueba..."; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Versión de PM"; + +/* Label describing pod age view */ +"Pod Age" = "Edad del Pod"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Configuración del Pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Purgado"; + +/* The text of the loading label when priming */ +"Priming…" = "Purgando..."; + +/* Label describing time remaining view */ +"Remaining" = "Restante"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Cambie el Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Cambie el Pod Ahora"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservorio"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Reintentar Desactivar el Pod"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Guardar"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Programa"; + +/* The title of the status section in settings */ +"Status" = "Estado"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Logrado"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Suspendido"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Cambiar de Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Sincronizar con el Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Comando de Prueba"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Probando Comandos..."; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Demasiadas entradas"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Desactivación del pod falló. Por favor, continúe y empareje uno nuevo"; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Desconocido"; diff --git a/OmniKitUI/es.lproj/OmnipodPumpManager.strings b/OmniKitUI/es.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..f69d61717 --- /dev/null +++ b/OmniKitUI/es.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Quitar Pod"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Configuración del RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Ajustes del Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Configuración de la bomba de insulina"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "NOTA: No retire la tapa de la aguja del Pod en este momento."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Preparar Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop le recordará cambiar el Pod antes de que expire. Puede cambiar la hora a una más conveniente para usted."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Por favor, desactive el Pod en uso. Cuando se haya desactivado por completo, retírelo del cuerpo."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Inserte la cánula"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Prepare la zona de infusión. Retire la tapa de la aguja y el papel blanco adherido a la cinta adhesiva. Si el pod está bien, aplíquelo a la zona de infusión."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Configuración de la bomba de insulina"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Configuración de la bomba de insulina"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Su Pod está listo para usar."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Recordatorio"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Enlazando Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Configuración de la bomba de insulina"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Revise las configuraciones a continuación. Las mismas serán programadas en el Pod durante el enlace. Usted puede cambiar estas configuraciones en cualquier momento en la pantalla de Configuración en Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Configuración exitosa"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "NOTA: Si la cánula se extiende más allá del forro adhesivo, presione Cancelar."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Colocar Pod"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Etiqueta"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Llene el pod con insulina. Espere escuchar 2 pitidos del pod durante el llenado. Mantenga el RileyLink adyacente al pod durante el enlace."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Reemplazar Pod"; diff --git a/OmniKitUI/fi.lproj/Localizable.strings b/OmniKitUI/fi.lproj/Localizable.strings new file mode 100644 index 000000000..6e6e4eb38 --- /dev/null +++ b/OmniKitUI/fi.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Insuliinin annostelu on loppunut. Deaktivoi ja poista pumppu."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (valmis)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U of %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/tunti"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Aktiivinen aika"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Hälytykset"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Haluatko varmasti sammuttaa tämän pumpun?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Haluatko varmasti lopettaa Omnipodin käytön?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Määritetty osoite"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basaali"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basaalitasot"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolus"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Kumoa"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Muuta aikavyöhyke"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Muutetaan aika…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Kokoonpano"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Jatka"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Deaktivoi"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Deaktivoi pumppu"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Poista Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Annostelurajat"; + +/* The title of the device information section in settings */ +"Device Information" = "Laitteen tiedot"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Laita bolusäänet pois"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Laita bolusäänet päälle"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Virhe laitettaessa bolusäänet pois päältä"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Virhe laitettaessa bolusäänet päälle"; + +/* The alert title for a resume error */ +"Error Resuming" = "Virhe jatkamisessa"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Virhe pysäytyksessä"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Vanhenemismuistutus"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Vanhentunut"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Vanhenee"; + +/* Pod life HUD view label */ +"Fault" = "Virhe"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Lopeta pumpun asennus"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Enemmän kuin %1$@ yksikköä jäljellä klo %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Puutteellisesti asennettu pumppu täytyy deaktivoida ennen kuin uusi pumppu voidaan yhdistää. Deaktivoi ja hylkää pumppu."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Puutteellisesti asennettu pumppu täytyy deaktivoida ennen kuin uusi pumppu voidaan yhdistää. Deaktivoi ja poista pumppu."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Aseta kanyyli"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Annosteltu insuliini"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Virheellinen merkintä"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Ei mitään"; + +/* Button title to pair with pod during setup */ +"Pair" = "Yhdistä"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Yhdistä uusi pumppu"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Yhdistetään…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI-versio"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Soita tekstiääni"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Soitetaan testiäänet…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM-versio"; + +/* Label describing pod age view */ +"Pod Age" = "Pumpun ikä"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pumpun asetukset"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Täytetty"; + +/* The text of the loading label when priming */ +"Priming…" = "Täytetään…"; + +/* Label describing time remaining view */ +"Remaining" = "Jäljellä"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Vaihda pumppu"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Vaihda pumppu nyt"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Säiliö"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Deaktivoi pumppu uudelleen"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Tallenna"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Ohjelmoitu"; + +/* The title of the status section in settings */ +"Status" = "Tila"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Onnistui"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Pysäytetty"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Lopeta Omnipodin käyttö"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synkronoi pumpun kanssa"; + +/* The title of the command to run the test command */ +"Test Command" = "Testikomento"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Testataan komentoja…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Liian monta basaalitasoa määritetty"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Pumpun deaktivointi ei onnistunut. Jatka ja yhdistä uusi pumppu."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Tuntematon"; diff --git a/OmniKitUI/fi.lproj/OmnipodPumpManager.strings b/OmniKitUI/fi.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..9752ce840 --- /dev/null +++ b/OmniKitUI/fi.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Poista pumppu"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink-asennus"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Pumpun asetukset"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Pumpun asennus"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "HUOM: Älä poista pumpun neulansuojusta vielä tässä vaiheessa."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Valmistele pumppu"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop muistuttaa sinua, ennen kuin pumpun käyttöaika loppuu. Halutessasi voit muuttaa aikaa sinulle sopivaksi."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Deaktivoi pumppu. Kun deaktivointi on valmis, irrota pumppu kehosta."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Aseta kanyyli"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Valmistele infuusiokohta. Poista pumpun neulansuojus ja suojateippi. Jos pumppu on OK, kiinnitä se iholle."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Pumpun asennus"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Pumpun asennus"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Pumppu on valmis käyttöön."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Muistutus"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Yhdistäminen pumppuun"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Pumpun asennus"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Tarkista, että asetukset ovat oikein. Ne tallennetaan pumppuun yhdistämisen aikana. Voit muokata asetuksia milloin tahansa Loopin Asetukset-näkymässä."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Asennus valmis"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "HUOM: Jos kanyyli ulottuu taustan ulkopuolelle, paina Kumoa."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Aseta pumppu"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Nimiö"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Täytä uusi pumppu insuliinilla. Odota, että kuulet täytön aikana kaksi pumpun piippausta. Pidä RileyLink pumpun vieressä yhdistämisen aikana."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Vaihda pumppu"; diff --git a/OmniKitUI/fr.lproj/Localizable.strings b/OmniKitUI/fr.lproj/Localizable.strings new file mode 100644 index 000000000..58b86cc7d --- /dev/null +++ b/OmniKitUI/fr.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ unités restantes à %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. La distribution d’insuline s’est arrêtée. Veuillez désactiver et remplacer le pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (Terminé)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U of %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/heure"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Heure d’activation"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmes"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Voulez-vous vraiment désactiver ce pod ?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Voulez-vous vraiment arrêter d’utiliser Omnipod ?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Adresse assignée"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Débit basal"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Débits basaux"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Débit de bolus"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Annuler"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Changement de fuseau horaire"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Changement de l’heure..."; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuration"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Continuer"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Désactiver"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Désactiver le pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Supprimer Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limites d’administration"; + +/* The title of the device information section in settings */ +"Device Information" = "Information de l’appareil"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Désactiver les bips de bolus"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Activer les bips de bolus"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Erreur lors de la désactivation des bips de bolus"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Erreur lors de l’activation des bips de bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Erreur lors de la reprise"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Erreur lors de la suspension"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Rappel d’expiration"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Expiré"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Expire"; + +/* Pod life HUD view label */ +"Fault" = "Erreur"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Terminer l’installation du pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Plus de %1$@ unités restantes à %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Le pod mal activé doit être désactivé avant d’en appairer un nouveau. Veuillez le désactiver et le jeter."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Le pod mal activé doit être désactivé avant d’en appairer un nouveau. Veuillez le désactiver et l’enlever."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Insérer la canule"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insuline délivrée"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Saisie invalide"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Aucun"; + +/* Button title to pair with pod during setup */ +"Pair" = "Appairer"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Appairer un nouveau pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Appairage en cours..."; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Version PI"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Jouer des bips de test"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Bips de test en cours"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Version PM"; + +/* Label describing pod age view */ +"Pod Age" = " ge du pod"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Réglages du pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Amorcé"; + +/* The text of the loading label when priming */ +"Priming…" = "Amorçage en cours"; + +/* Label describing time remaining view */ +"Remaining" = "Restant"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Remplacer le pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Remplacer le pod maintenant"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Réservoir"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Réessayez la désactivation du pod"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Sauvegarder"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Programmé"; + +/* The title of the status section in settings */ +"Status" = "Statut"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Réussi"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Suspendu"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Changer de pompes Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synchroniser avec le pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Commande de test"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Commandes de test en cours..."; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Trop d’entrées"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Impossible de désactiver le pod. Veuillez continuer et en appairer un nouveau."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Inconnu"; diff --git a/OmniKitUI/fr.lproj/OmnipodPumpManager.strings b/OmniKitUI/fr.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..0eefbb747 --- /dev/null +++ b/OmniKitUI/fr.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Enlever POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Mise en place du RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Réglages du pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Mise en place de la pompe"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "REMARQUE : n’enlevez pas le capuchon d'aiguille à cette étape."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Préparez le pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop vous rappellera de changer le pod avant son expiration. Vous pouvez changer ce rappel à une heure qui vous conviendrait davantage."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Veuillez désactiver le pod. Quand la désactivation se termine, veuillez retirer le pod de votre peau."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Insérer la canule"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Préparez le site. Enlevez le capuchon d’aiguille et le support adhésif. Si le pod est prêt et en bon état, veuillez le placer sur votre peau."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Mise en place de la pompe"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Mise en place de la pompe"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Votre pod est prêt à être utilisé."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Rappel"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Appairage du pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Mise en place de la pompe"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Vérifiez vos réglages ci-dessous. Ils seront programmés dans le pod pendant l’appairage. Vous pouvez changer ces réglages à chaque moment dans les réglages de Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Mise en place terminée"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "REMARQUE : si la canule ressort, appuyez sur annuler."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Appliquez le POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Label"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Remplissez un nouveau pod avec de l’insuline. Vérifiez de bien entendre les 2 beeps du pod pendant le remplissage. Gardez le RileyLink à proximité du pod pendant l’appairage."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Remplacez le pod"; diff --git a/OmniKitUI/it.lproj/Localizable.strings b/OmniKitUI/it.lproj/Localizable.strings new file mode 100644 index 000000000..ffb1994f8 --- /dev/null +++ b/OmniKitUI/it.lproj/Localizable.strings @@ -0,0 +1,232 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ unità rimanenti alle ore %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. L’iniezione di insulina è stata interrotta. Disattiva e rimuovi Pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (Terminato)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U di %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/ora"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Tempo di attività"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Allarmi"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Sei sicuro di voler spegnere Pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Sei sicuro di voler interrompere l’uso di Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Indirizzo assegnato"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Somministrazione basale"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Tassi basali"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Somministrazione in bolo"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Annulla"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Cambia fuso orario"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Modifica ora in corso"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configurazione"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Continua"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Disattiva"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Disattiva Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Elimina Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limiti di somministrazione"; + +/* The title of the device information section in settings */ +"Device Information" = "Informazioni sul dispositivo"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Disattiva bip bolo"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Attiva bip bolo"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Errore durante la disattivazione dei bip bolo"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Errore durante l’attivazione dei bip bolo"; + +/* The alert title for a resume error */ +"Error Resuming" = "Errore durante la ripresa"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Errore durante l’interruzione"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Promemoria di scadenza"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Scaduto"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Scadenza"; + +/* Pod life HUD view label */ +"Fault" = "Guasto"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Completa configurazione Pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Più di %1$@ unità rimanenti alle ore %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Prima di abbinare un nuovo Pod è necessario disattivare i Pod non completamente configurati. Disattiva e rimuovi Pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Prima di abbinare un nuovo Pod è necessario disattivare i Pod non completamente configurati. Disattiva e rimuovi Pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Inserisci cannula"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulina erogata"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Immissione non valida"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lotto"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Nessuno"; + +/* Button title to pair with pod during setup */ +"Pair" = "Abbina"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Abbina nuovo Pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Abbinamento in corso"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Versione PI"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Emetti bip di prova"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Emissione bip di prova in corso"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Versione PM"; + +/* Label describing pod age view */ +"Pod Age" = "Età Pod"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Impostazioni Pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Caricato"; + +/* The text of the loading label when priming */ +"Priming…" = "Caricamento in corso"; + +/* Label describing time remaining view */ +"Remaining" = "Rimanente"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Sostituisci Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Sostituisci Pod adesso"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Serbatoio"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Riprova a disattivare Pod"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Salva"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Programma"; + +/* The title of the status section in settings */ +"Status" = "Stato"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Effettuato con successo"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Sospeso"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Passa a pompe diverse da Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Sincronizza con Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Comando di prova"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Collaudo dei comandi in corso..."; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Troppe voci"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Impossibile disattivare Pod. Continua e abbina un nuovo Pod."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Sconosciuto"; + diff --git a/OmniKitUI/it.lproj/OmnipodPumpManager.strings b/OmniKitUI/it.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..fd16e902a --- /dev/null +++ b/OmniKitUI/it.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Rimuovi POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Configurazione RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Impostazioni Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Configurazione pompa"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "ATTENZIONE: non rimuovere il tappo dell’ago di Pod in questo momento."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Prepara Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop ti ricorderà di cambiare Pod prima della sua scadenza. Puoi modificare questa data a tuo piacimento."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Disattiva Pod. Al termine della procedura, rimuovi Pod dal corpo."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Inserisci cannula"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Prepara la zona. Rimuovi il tappo dell’ago di Pod e la protezione adesiva. Se Pod è in buono stato, applicalo sulla zona."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Configurazione pompa"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Configurazione pompa"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Pod pronto per l’uso."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Promemoria"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Abbinamento Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Configurazione pompa"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Controlla le impostazioni qui sotto. Queste saranno programmate all’interno di Pod durante l'abbinamento. Puoi modificarle in qualsiasi momento dalla schermata Impostazioni Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Configurazione terminata"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "ATTENZIONE: Se la cannula sporge, fai clic su Annulla."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Applica POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Etichetta"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Riempi un nuovo Pod di insulina. Pod emetterà 2 bip durante il riempimento. Mantieni RileyLink accanto a Pod durante l'abbinamento."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Sostituisci Pod"; diff --git a/OmniKitUI/ja.lproj/Localizable.strings b/OmniKitUI/ja.lproj/Localizable.strings new file mode 100644 index 000000000..62cd8d87d --- /dev/null +++ b/OmniKitUI/ja.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%2$@の時点で %1$@ U 残っています"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@。インスリン注入が止まりました。ポッドを停止して取り外してください。."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (完了)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U of %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/時"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "アクティブ時間"; + +/* The title of the cell showing alarm status */ +"Alarms" = "アラーム"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "ポッドを終了しますか?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Omnipodの使用を終了しますか?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "割り当てアドレス"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "基礎注入"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "基礎レート"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "ボーラス注入"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "キャンセル"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "タイムゾーンを変更"; + +/* Progress message for changing pod time. */ +"Changing time…" = "時間を変えています"; + +/* The title of the configuration section in settings */ +"Configuration" = "コンフィグレーション"; + +/* The title of the continue action in an action sheet */ +"Continue" = "次へ"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "無効にする"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "ポッドを無効にする"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Omnipodを削除"; + +/* Title text for delivery limits */ +"Delivery Limits" = "注入限度"; + +/* The title of the device information section in settings */ +"Device Information" = "デバイス情報"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "ボーラス音をオフにする"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "ボーラス音をオンにする"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "ボーラス音オフエラー"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "ボーラス音オンエラー"; + +/* The alert title for a resume error */ +"Error Resuming" = "再開エラー"; + +/* The alert title for a suspend error */ +"Error Suspending" = "一時中止エラー"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "有効期限リマインダー"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "期限切れ"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "期限"; + +/* Pod life HUD view label */ +"Fault" = "エラー"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "ポッド設定を終了"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "%2$@時点で %1$@U以上残っています"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "正しく設定されていないポッドはペアリングの前に停止してください。ポッドを停止して処分してください。"; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "正しく設定されていないポッドはペアリングの前に停止してください。ポッドを停止して処分してください。"; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "カニューレを挿入"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "インスリン注入済み"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "入力が無効です"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "ロット"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "なし"; + +/* Button title to pair with pod during setup */ +"Pair" = "ペアリング"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "新しいポッドをペアリング"; + +/* The text of the loading label when pairing */ +"Pairing…" = "ペアリングしています"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PIバージョン"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "警告音をテスト"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "警告音をテストしています"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PMバージョン"; + +/* Label describing pod age view */ +"Pod Age" = "ポッド経過時間"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "ポッド設定"; + +/* The text of the loading label when pod is primed */ +"Primed" = "プライミング完了"; + +/* The text of the loading label when priming */ +"Priming..." = "プライミング中"; + +/* Label describing time remaining view */ +"Remaining" = "残り"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "ポッドを交換"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "すぐにポッドを交換してください"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "リザーバ"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "ポッドの停止をやり直す"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "保存"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "パターン"; + +/* The title of the status section in settings */ +"Status" = "ステータス"; + +/* A message indicating a command succeeded */ +"Succeeded" = "成功"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "一時停止"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Omnipodポンプから変更"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "ポッドとシンク"; + +/* The title of the command to run the test command */ +"Test Command" = "テストコマンド"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "コマンドをテストしています"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "入力過多"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "ポッドの停止ができません。新しいポッドをペアリングしてください。"; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "不明"; diff --git a/OmniKitUI/ja.lproj/OmnipodPumpManager.strings b/OmniKitUI/ja.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..22200c3ca --- /dev/null +++ b/OmniKitUI/ja.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "ポッドを削除"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink 設定"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "ポッド設定"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "ポンプ設定"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "注意:ポッドのニードルキャップはまだ外さないでください。"; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "ポッドの準備"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "期限の前にポッドを交換するようループがお知らせします。都合のよい時間に変更できます。"; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "ポッドを停止してください。停止ができたら、装着を外してください。"; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "カニューレを挿入"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "装着部分を準備します。ポッドのニードルキャップと接着カバーを外し、用意ができたら装着部分につけます。"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "ポンプ設定"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "ポンプ設定"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "ポッドが使えます。"; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "リマインダー"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "ポッドをペアリング"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "ポンプ設定"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "下の設定を確認してください。ポッドのペアリング時にプログラムされます。これらの設定はループの設定画面でいつでも変更できます。"; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "設定完了"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "注意:カニューレが飛び出ていたらキャンセルを押してしてください。"; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "ポッドを適用"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "ラベル"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "新しいポッドにインスリンを入れてください。「ピー」という音が2回鳴ります。ペアリングのため、RileyLinkをポッドの横においてください。"; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "ポッドを交換"; diff --git a/OmniKitUI/nb.lproj/Localizable.strings b/OmniKitUI/nb.lproj/Localizable.strings new file mode 100644 index 000000000..6d2b702e2 --- /dev/null +++ b/OmniKitUI/nb.lproj/Localizable.strings @@ -0,0 +1,232 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Insulintilførsel har stoppet. Vennligst deaktiver og fjern pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ E"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ E (Ferdig)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%@ E av %@ E (%@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ E/time"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ E"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@E"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Aktiveringstidspunkt"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmer"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Er du sikker på at du vil avslutte denne poden?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Er du sikker på at du vil slutte å bruke Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Tildelt Adresse"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basalleveranse"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basal-satser"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolus-leveranse"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Avbryt"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Endre tidssone"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Endrer tid..."; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfigurasjon"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Fortsett"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Deaktiver"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Deaktiver Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Slett Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Leveransegrenser"; + +/* The title of the device information section in settings */ +"Device Information" = "Enhetsinformasjon"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Slå av lyd ved bolus"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Slå på lyd ved bolus"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Kunne ikke slå av lyd ved bolus"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Kunne ikke slå på lyd ved bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Kunne ikke fortsette"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Kunne ikke stoppe"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Utløpspåminnelse"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Utløpt"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Utløper"; + +/* Pod life HUD view label */ +"Fault" = "Feil"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Fullfør oppsett for pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Flere enn %1$@ enheter gjenstår klokken %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Ufullstendig oppsatt pod må deaktiveres før sammenkobling med en ny. Vennligst deaktiver og kast pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Ufullstendig oppsatt pod må deaktiveres før sammenkobling med en ny. Vennligst deaktiver og fjern pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Sett inn kanyle"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Levert insulin"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Ugyldig oppføring"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Parti"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Ingen"; + +/* Button title to pair with pod during setup */ +"Pair" = "Koble sammen"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Koble sammen ny pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Kobler sammen..."; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI Versjon"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Spill test-toner"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Spill test-toner..."; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM Versjon"; + +/* Label describing pod age view */ +"Pod Age" = "Pod-alder"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pod-innstillinger"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Fyllt"; + +/* The text of the loading label when priming */ +"Priming…" = "Fyller..."; + +/* Label describing time remaining view */ +"Remaining" = "Gjenstående"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Bytt pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Bytt pod nå"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservoar"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Prøv igjen å deaktivere pod"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Lagre"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Tidsplan"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Suksess"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Suspendert"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Bytt fra Omnipod pumpe"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synkroniser med pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Test kommando"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Tester kommandoer..."; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "For mange oppføringer"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Kunne ikke deaktivere pod. Vennligst fortsett og koble sammen med en ny."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Ukjent"; + diff --git a/OmniKitUI/nb.lproj/OmnipodPumpManager.strings b/OmniKitUI/nb.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..1d2053787 --- /dev/null +++ b/OmniKitUI/nb.lproj/OmnipodPumpManager.strings @@ -0,0 +1,69 @@ + +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Fjern POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink-oppsett"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Pod-innstillinger"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Pumpe-oppsett"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "MERK: Fjern ikke beskyttelsen over kanylen på dette tidspunktet."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Klargjør Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop vil minne deg på å bytte pod før den er oppbrukt. Du kan endre dette tidspunktet til det som passer best for deg."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Vennligst deaktiver pod. Når den er deaktivert, fjern så pod fra kroppen."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Sett inn kanyle"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Klargjør området. Fjern kanylebeskyttelse og beskyttelse for tape. Om pod er ok, sett den på plass."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Pumpeoppsett"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Pumpeoppsett"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Din Pod er nå klar til bruk."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Påminnelse"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod-sammenkobling"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Pumpeoppsett"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Se over innstillinger under. Disse blir programmert inn i pod ved sammenkobling. Du kan når som helst endre disse på skjermbildet for innstillinger i Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Oppsett ferdig."; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "MERK: Hvis kanylen stikker ut, trykk avbryt."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Påfør POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Etikett"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Fyll en ny pod med insulin. Lytt etter 2 pip mens du fyller. Hold RileyLink i nærheten under sammenkobling."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Bytt Pod"; diff --git a/OmniKitUI/nl.lproj/Localizable.strings b/OmniKitUI/nl.lproj/Localizable.strings new file mode 100644 index 000000000..82c4760c8 --- /dev/null +++ b/OmniKitUI/nl.lproj/Localizable.strings @@ -0,0 +1,232 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ eenheden aanwezig op %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Insuline toevoeging gestopt. Deactiveer en vervang pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ E"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ E (afgerond)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ E van %2$@ E (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ E/uur"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ E"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@E"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Activatie tijd"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmen"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Weet je zeker dat je pod wilt afsluiten"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Weet je zeker dat je wilt stoppen met gebruik Omnipod"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Toegewezen adres"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basaal levering"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basaal ratios"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolus levering"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Annuleer"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Pas tijdzone aan"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Vervang tijd…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuratie"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Vervolg"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Deactiveer"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Deactibeer pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Verwijder Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Leveringslimieten"; + +/* The title of the device information section in settings */ +"Device Information" = "Apparaat informatie"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Stop gebruik Bolus Piepjes"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Pas Bolus Piepjes gebruik toe"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Fout in stoppen gebruik bolus piepjes"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Fout in toepassen gebruik bolus piepjes"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fout met hervatten"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fout met onderbreken"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Herinnering over de vervaltijd"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Vervallen"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Vervalt"; + +/* Pod life HUD view label */ +"Fault" = "Fout"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Rond pod setup af"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Meer dan %1$@ eenheden aanwezig op %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Onvolledig verbonden pod moet eerst worden gedeactiveerd voordat er geprobeerd wordt te verbinden met een nieuwe pod. Deactiveer pod en gooi weg"; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Onvolledig verbonden pod moet eerst worden gedeactiveerd voordat er geprobeerd wordt te verbinden met een nieuwe pod. Deactiveer pod en gooi weg"; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Plaats canule"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Geleverde insuline"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Ongeldige mogelijkheid"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Partij"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Geen"; + +/* Button title to pair with pod during setup */ +"Pair" = "Verbind"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Verbind nieuwe pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Verbinden…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI versie"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Speel test piepjes af"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Speelt test piepjes…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM versie"; + +/* Label describing pod age view */ +"Pod Age" = "Pod leeftijd"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pod instellingen"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Klaar gemaakt"; + +/* The text of the loading label when priming */ +"Priming…" = "Klaar maken…"; + +/* Label describing time remaining view */ +"Remaining" = "Resterend"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Vervang pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Vervang pod nu"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservoir"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Probeer pod deactivatie nog een keer"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Sla op"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Plan"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Geslaagd"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Onderbroken"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Switch van Omnipod pompen"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synchroniseer met pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Test commando"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Test commando’s…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Te veel invoer"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Onmogelijk om pod te deactiveren. Ga verder en verbind een nieuwe."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Onbekend"; + diff --git a/OmniKitUI/nl.lproj/OmnipodPumpManager.strings b/OmniKitUI/nl.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..e06cddf13 --- /dev/null +++ b/OmniKitUI/nl.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Verwijder POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "RileyLink Setup"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Pod Instellingen"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Pomp Setup"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "Belangrijk: Verwijder de afdek voor de naald nog niet."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Bereid Pod voor"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop stuurt een herinnering om de pod te vervangen voordat deze vervalt. Je kan deze vervangen wanneer het jou uitkomt."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Deactiveer pod graag. Wanneer deactivatie compleet is, kan de pod verwijderd worden van het lichaam."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Plaats Canule"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Bereid locatie voor. Verwijder de pod naald kapje en haal beschermfolie van de plakker. Als de pod OK is mag deze geplaatst worden."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Pomp Setup"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Pomp Setup"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Je pod is klaar om te gebruiken."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Herinnering"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod verbinden"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Pomp Setup"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Kijk onderstaand instellingen na. Deze worden in de pod geprogrammeerd gedurende verbinden. Je kan de instellingen altijd wijzigen in Loop’s instellingen scherm"; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Setup Compleet"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "Belangrijk: Als de naald uitsteekt, druk annuleer."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Pas pod toe"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Label"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Vul de nieuwe pod met insuline. Luister naar de 2 piepjes van de pod gedurende het vullen. Houdt de RileyLink dicht bij de pod gedurende het verbinden."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Vervang Pod"; diff --git a/OmniKitUI/pl.lproj/Localizable.strings b/OmniKitUI/pl.lproj/Localizable.strings new file mode 100644 index 000000000..3a8ea5b8b --- /dev/null +++ b/OmniKitUI/pl.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ jednostek pozostaje w %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Podawanie insuliny zostało zatrzymane. Deaktywuj i zdejmij PODa"; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ J"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ J (zakończone)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ J z %2$@ J (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ J/godzina"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ J"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@J"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Czas aktywny"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmy"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Czy na pewno chcesz wyłączyć tego PODa?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Czy na pewno chcesz przestać korzystać z Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Przypisany adres"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Podawanie dawki podstawowej"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Dawki podstawowe"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Podawanie bolusa"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Anuluj"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Zmień strefę czasową"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Zmiana czasu…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguracja"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Kontynuuj"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Dezaktywuj"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Dezaktywuj PODa"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Usuń Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limity podawania"; + +/* The title of the device information section in settings */ +"Device Information" = "Informacje o urządzeniu"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Wyłącz dźwięki bolusa"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Włącz dźwięki bolusa"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Błąd wyłączania dźwięków bolusa"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Błąd włączania dźwięków bolusa"; + +/* The alert title for a resume error */ +"Error Resuming" = "Błąd wznawiania"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Błąd wstrzymywania"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Przypomnienie o terminie ważności"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Po terminie"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Upływa termin ważności"; + +/* Pod life HUD view label */ +"Fault" = "Usterka"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Zakończ konfigurację PODa"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Więcej niż %1$@ jedn. pozostaje w %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Niekompletną konfigurację PODa należy deaktywować przed sparowaniem z nowym. Deaktywuj i wyrzuć PODa."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Niekompletną konfigurację PODa należy deaktywować przed sparowaniem z nowym. Deaktywuj i usuń PODa."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Wprowadź kaniulę"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulina podana"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Nieprawidłowy wpis"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Partia"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Brak"; + +/* Button title to pair with pod during setup */ +"Pair" = "Paruj"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Sparuj nowego PODa"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Parowanie…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Wersja PI"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Odtwarzaj dźwięki testu"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Odtwarzaj dźwięki testu…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Wersja PM"; + +/* Label describing pod age view */ +"Pod Age" = "Wiek PODa"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Ustawienia PODa"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Napełniony"; + +/* The text of the loading label when priming */ +"Priming…" = "Napełnianie…"; + +/* Label describing time remaining view */ +"Remaining" = "Pozostało"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Wymień PODa"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Wymień PODa teraz"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Zbiornik"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Ponów deaktywację PODa"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Zapisz"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Harmonogram"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Zakończone powodzeniem"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Wstrzymane"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Przełącz z pomp Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synchronizuj z PODem"; + +/* The title of the command to run the test command */ +"Test Command" = "Polecenie testowe"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Testowanie poleceń…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Zbyt dużo wpisów"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Deaktywacja PODa niemożliwa. Kontynuuj i sparuj nowy."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Nieznana"; diff --git a/OmniKitUI/pl.lproj/OmnipodPumpManager.strings b/OmniKitUI/pl.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..2b7ed6f86 --- /dev/null +++ b/OmniKitUI/pl.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Usuń POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Konfiguracja RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Ustawienia POD"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Konfiguracja pompy"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "UWAGA: nie zdejmuj tym razem nasadki igły POD."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Przygotuj POD"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Algorytm Loop przypomni o zmianie przed upływem terminu ważności POD. Można to zmienić na dogodny.dla siebie czas"; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Deaktywuj POD. Po zakończeniu deaktywacji zdejmij POD z ciała."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Wprowadź kaniulę"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Przygotuj miejsce. Zdejmij nasadkę igły POD i osłonę przylepca. Jeśli POD jest OK, nałóż na miejsce."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Konfiguracja pompy"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = " Konfiguracja pompy"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "POD gotowy do użycia."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Przypomnienie"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Parowanie PODa"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Konfiguracja pompy"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Sprawdź ustawienia poniżej. Zostaną one zaprogramowane w trakcie parowania PODa. Można zmienić te ustawienia w dowolnym momencie na ekranie ustawień algorytmu Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Konfiguracja zakończona"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "UWAGA: jeśli kaniula wystaje, naciśnij „anuluj”."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Nałóż POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Etykieta"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Napełnij nowy POD insuliną. W trakcie napełniania POD powinien wyemitować 2 dźwięki. W trakcie parowania umieść RileyLink w pobliżu PODa."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Wymień POD"; diff --git a/OmniKitUI/pt-BR.lproj/Localizable.strings b/OmniKitUI/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..0e8bdf71e --- /dev/null +++ b/OmniKitUI/pt-BR.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ unidades restantes em %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. A administração de insulina parou. Desative e remova o pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (Completo)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U do %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/hora"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Tempo Ativo"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarmes"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Tem certeza de que deseja encerrar este pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Tem certeza de que deseja parar de usar o Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Endereço Atribuído"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Entrega Basal"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Taxas Basais"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Entrega de Bolus"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Cancelar"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Alterar Fuso Horário"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Alterando a hora..."; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuração"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Continuar"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Desativar"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Desativar Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Remover Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limites de Entrega"; + +/* The title of the device information section in settings */ +"Device Information" = "Informação do Dispositivo"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Desativar Bipes de Bolus"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Ativar bipes de bolus"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Erro ao desativar bipes de bolus"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Erro ao ativar bipes de bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Erro ao Retomar"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Erro ao Suspender"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Lembrete de Expiração"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Expirada"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Expira"; + +/* Pod life HUD view label */ +"Fault" = "Falha"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Concluir configuração do pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Mais de %1$@ unidades restantes em %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Pod de configuração incompleta deve ser desativado antes de emparelhar com um novo. Desative e descarte o pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Pod de configuração incompleta deve ser desativado antes de emparelhar com um novo. Desative e remova o pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Inserir Cânula"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulina Entregue"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Entrada inválida"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Nenhum"; + +/* Button title to pair with pod during setup */ +"Pair" = "Emparelhar"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Emparelhe um Novo Pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Emparelhando..."; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Versão PI"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Tocar bipes de teste"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Tocar bipes de teste…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Versão PM"; + +/* Label describing pod age view */ +"Pod Age" = "Idade do Pod"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Configurações do Pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Preparado"; + +/* The text of the loading label when priming */ +"Priming…" = "Preparando…"; + +/* Label describing time remaining view */ +"Remaining" = "Restante"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Substituir Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Substituir Pod Agora"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservatório"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Retentar Desativação do Pod"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Salvar"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Agendada"; + +/* The title of the status section in settings */ +"Status" = "Estado"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Completo"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Suspenso"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Mudar de Bombas Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Sincronizar com o Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Testar Comando"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Testando Comandos…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Muitas entradas"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Não foi possível desativar o pod. Continue e emparelhe um novo."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Desconhecido"; diff --git a/OmniKitUI/pt-BR.lproj/OmnipodPumpManager.strings b/OmniKitUI/pt-BR.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..63ccfea4b --- /dev/null +++ b/OmniKitUI/pt-BR.lproj/OmnipodPumpManager.strings @@ -0,0 +1,70 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Remover POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Configuração do RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Configuração do Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Configuração da Bomba"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "NOTA: Não remova a tampa da agulha do pod neste momento."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Preparar Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop lembrará que você deve trocar seu pod antes que ele expire. Você pode alterar isso para um horário mais conveniente para você."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Desative o pod. Quando a desativação estiver concluída, remova o pod do corpo."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Inserir Cânula"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Prepare o local. Remova a tampa da agulha do pod e o adesivo. Se o pod estiver OK, aplique no local."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Configuração da Bomba"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Configuração da Bomba"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Seu Pod está pronto para uso."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Lembrete"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Emparelhando Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Configuração da Bomba"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Revise suas configurações abaixo. Eles serão programados no pod durante o emparelhamento. Você pode alterar essas configurações a qualquer momento na tela Configurações do Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Configuração Completa"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "NOTA: Se a cânula desprender, pressione cancelar."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Aplicar POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Rótulo"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Encha um novo pod com insulina. Ouça 2 bipes do pod durante o enchimento. Mantenha o RileyLink próximo ao pod durante o emparelhamento."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Substituir Pod"; + + diff --git a/OmniKitUI/ro.lproj/Localizable.strings b/OmniKitUI/ro.lproj/Localizable.strings new file mode 100644 index 000000000..1626dccdb --- /dev/null +++ b/OmniKitUI/ro.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ unități rămase la %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Administrarea insulinei a fost oprită. Trebuie să dezactivați și să detașati Pod-ul."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (finalizat)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U din %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/oră"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Timp activ"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Alarme"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Sigur doriți să opriți acest Pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Sigur doriți să nu mai folosiți Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Adresă asignată"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Administrare bazală"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Rate bazale"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Administrare bolus"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Renunță"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Schimbare fus orar"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Se modifică ora…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configurare"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Continuă"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Dezactivează"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Dezactivează Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Șterge Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limite administrare"; + +/* The title of the device information section in settings */ +"Device Information" = "Informații dispozitiv"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Dezactivează beep-urile de bolus"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Activează beep-urile de bolus"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Eroare la dezactivarea beep-urilor de bolus"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Eroare la activarea beep-urilor de bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Eroare în timpul reluării"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Eroare în timpul suspendării"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Reamintire expirare"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Expirat"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Expiră"; + +/* Pod life HUD view label */ +"Fault" = "Defecțiune"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Finalizare setare Pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Au rămas mai mult de %1$@ unități la %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Înainte de a asocia un Pod nou, trebuie să dezactivați orice Pod setat incomplet. Dezactivați și aruncați Pod-ul curent."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Înainte de a asocia un Pod nou, trebuie să dezactivați orice Pod setat incomplet. Dezactivați și îndepărtați Pod-ul curent."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Inserează canula"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulină livrată"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Rată invalidă"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Nimic"; + +/* Button title to pair with pod during setup */ +"Pair" = "Asociază"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Asociază un Pod nou"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Se asociază…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Versiune PI"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Redă beep-uri de test"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Beep-uri de test în desfășurare…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Versiune PM"; + +/* Label describing pod age view */ +"Pod Age" = "Vechime Pod"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Setări Pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Amorsat"; + +/* The text of the loading label when priming */ +"Priming…" = "Se amorsează…"; + +/* Label describing time remaining view */ +"Remaining" = "Rămas"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Înlocuiți Pod-ul"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Înlocuiți Pod-ul acum"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Rezervor"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Reîncearcă dezactivarea Pod-ului"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Salvează"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Planificare"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Succes"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Suspendat"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Schimbă de la pompele Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Sincronizează cu Pod-ul"; + +/* The title of the command to run the test command */ +"Test Command" = "Comandă de test"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Se execută comenzile de test…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Prea multe înregistrări"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Nu s-a putut dezactiva Pod-ul. Continuați și asociați unui Pod nou."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Necunoscut"; diff --git a/OmniKitUI/ro.lproj/OmnipodPumpManager.strings b/OmniKitUI/ro.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..b4768a7ec --- /dev/null +++ b/OmniKitUI/ro.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Elimină POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Setare RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Setări Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Setare pompă"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "NOTĂ: Nu îndepărtați capacul acului din Pod la acest moment."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Pregătire Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop vă va reaminti să schimbați Pod-ul înainte de expirare. Puteți modifica aceasta la un moment convenabil pentru dvs."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Dezactivați Pod-ul. Când dezactivarea este completă, îndepărtați Pod-ul de pe corp."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Inserează canula"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Pregătiți suprafața de aplicare. Îndepărtați capacul acului din Pod și folia de protecție a adezivului. Dacă Pod-ul este OK, aplicați-l pe corp"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Setare pompă"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Setare pompă"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Pod-ul este gata de utilizare."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Reamintire"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Asociere Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Setare pompă"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Verificați setările de mai jos. Acestea vor fi salvate în Pod în timpul asocierii. Puteți modifica ulterior aceste setări din ecranul de setări Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Setare completă"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "NOTĂ: În cazul în care observați canula ieșită în afară, apăsați Renunță."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Aplică POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Etichetă"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Umpleți un Pod nou cu insulină. Urmăriți ca Pod-ul să emită 2 beep-uri în timpul umplerii."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Înlocuiește Pod"; diff --git a/OmniKitUI/ru.lproj/Localizable.strings b/OmniKitUI/ru.lproj/Localizable.strings new file mode 100644 index 000000000..18f52f860 --- /dev/null +++ b/OmniKitUI/ru.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ ед остается в %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Подача инсулина остановлена. Остановите и снимите Pod"; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ ед"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ ед (подано)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ ед из %2$@ ед (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ ед/час"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ ед"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@ед"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Активирован в"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Оповещения"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Подтвердите отключение Omnipod"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Подтвердите остановку Omnipod"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Заданный адрес"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Подача базала"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Скорости базала"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Подача болюса"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Отмена"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Изменить часовой пояс"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Выполняется изменение времени"; + +/* The title of the configuration section in settings */ +"Configuration" = "Конфигурация"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Продолжить"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Деактивировать"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Деактивировать Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Удалить Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Лимит подачи"; + +/* The title of the device information section in settings */ +"Device Information" = "Информация об устройстве"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Отключить звуковой сигнал болюса"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Включить звуковой сигнал болюса"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Ошибка отключения звукового сигнала болюса"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Ошибка подключения звукового сигнала болюса"; + +/* The alert title for a resume error */ +"Error Resuming" = "Ошибка возобновления"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Ошибка остановки"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Напоминание об истечении срока"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Срок истек"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Срок истекает"; + +/* Pod life HUD view label */ +"Fault" = "Сбой"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Завершить настройку Omnipod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Более %1$@ ед остается в %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Неполностью настроенный Omnipod должен быть отключен до соединения с новым. Отключите и утилизируйте Pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Неполностью настроенный Omnipod должен быть отключен до соединения с новым. Отключите и удалите Pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Вставьте катетер"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Инсулин подан"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Неверная запись"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "№ партии"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Не подан"; + +/* Button title to pair with pod during setup */ +"Pair" = "Сопряжение"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Установить сопряжение"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Производится Сопряжение"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "Версия PI"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Включить проверку сигналов"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Проверка тест сигналов"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "Версия PM"; + +/* Label describing pod age view */ +"Pod Age" = "Pod проработал"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Настройки Pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Заполнено"; + +/* The text of the loading label when priming */ +"Priming…" = ""; + +/* Label describing time remaining view */ +"Remaining" = "Заполняется"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Заменить Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Немедленно замените Pod"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Картридж"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Повторить деактивацию Pod’a"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Сохранить"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "По графику"; + +/* The title of the status section in settings */ +"Status" = "Состояние"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Успешно"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Остановлен"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Отключиться от помп Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Синхронизировать с Pod'ом"; + +/* The title of the command to run the test command */ +"Test Command" = "Провести тест"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Команды тестов"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Слишком много данных"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Не удалось деактивировать Pod. Продолжить и соединить с новым"; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Неизвестно"; diff --git a/OmniKitUI/ru.lproj/OmnipodPumpManager.strings b/OmniKitUI/ru.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..8c5a4e208 --- /dev/null +++ b/OmniKitUI/ru.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Снимите POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Настройка RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Настройка Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Настройка помпы"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "Примечание: сейчас не снимайте крышку иглы Pod’а"; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Подготовьте Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Алгоритм цикла напомнит о замене Pod’а до истечения срока использования. Можно изменить это время на удобное для вас."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Деактивируйте Pod. По окончании деактивации снимите Pod с тела. "; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Вставить катетер"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Подготовьте место установки. Снимите крышку иголки и защитную пленку липкой поверхности"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Настройка Помпы"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Настройка помпы"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Pod готов к использованию"; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Напоминание"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod Pairing"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Настройка помпы"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Проверьте настройки ниже. Они будут переданы в помпу во время сопряжения. Их можно изменить в дальнейшем на экране настроек программы."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Установка завершена"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "Примечание: Если катетер выступает нажмите отменить."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Установить POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Ярлык"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Заполните новый Pod инсулином. Дождитесь двух гудков Pod’ а во время заполнения."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Смените Pod"; diff --git a/OmniKitUI/sv.lproj/Localizable.strings b/OmniKitUI/sv.lproj/Localizable.strings new file mode 100644 index 000000000..d0b673c3b --- /dev/null +++ b/OmniKitUI/sv.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ enheter återstår kl %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Insulintillförsel har stoppats. Var god inaktivera och byt pod"; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ E"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ E (Klar)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%@ E av %@ E (%@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ E/timme"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ E"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@E"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Aktiv tid"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Larm"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Säkert att du vill stänga av denna pod?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Säkert att du vill sluta använda Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Tilldelad adress"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Basaldos"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Basaldoser"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Bolusdos"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Avbryt"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Ändra tidszon"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Ändra tid..."; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Fortsätt"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Inaktivera"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Inaktivera pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Radera Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Maxdoser"; + +/* The title of the device information section in settings */ +"Device Information" = "Enhetsinformation"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Stäng av bolusljud"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Sätt på bolusljud"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Fel vid avstängning av bolusljud"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Fel vid aktivering av bolusljud"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fel vid återupptagande"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Fel vid försök till paus"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Påminnelse om utgångsdatum"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Utgått"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Utgår"; + +/* Pod life HUD view label */ +"Fault" = "Fel"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Gör färdigt podinställning"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Mer än %1$@ enheter återstår kl %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Ofullständigt inställd pod måste inaktiveras före parkoppling av ny. Var god inaktivera och kasta pod"; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Ofullständigt inställd pod måste inaktiveras före parkoppling av ny. Var god inaktivera och kasta pod"; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "För in kanyl"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulin doserat"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Ogiltig inmatning"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "Ingen"; + +/* Button title to pair with pod during setup */ +"Pair" = "Parkoppla"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Koppla ny pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Parkopplar..."; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI-version"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Spela upp testljud"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Spelar upp testljud..."; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM-version"; + +/* Label describing pod age view */ +"Pod Age" = "Podålder"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Podinställningar"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Pod är fylld"; + +/* The text of the loading label when priming */ +"Priming…" = "Pod fylls..."; + +/* Label describing time remaining view */ +"Remaining" = "Återstår"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Byt pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Byt pod nu"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Reservoar"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Försök inaktivera pod igen"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Spara"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Schema"; + +/* The title of the status section in settings */ +"Status" = "Status"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Lyckades"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Pausad"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Byt från Omnipod-pumpar"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Synkronisera med pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Testkommandon"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Testkommadon körs"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "För många inmatningar"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Inaktivering misslyckades. Var god fortsätt med att parkoppla en ny"; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Okänd"; diff --git a/OmniKitUI/sv.lproj/OmnipodPumpManager.strings b/OmniKitUI/sv.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..15e1be636 --- /dev/null +++ b/OmniKitUI/sv.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Ta bort POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Ställ in RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Podinställningar"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Ställ in pump"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "OBS: Ta inte bort kanylskyddet ännu."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Förbered pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop kommer att påminna dig om att byta pod innan den går ut. Du kan ändra påminnelsetiden till till en som passar dig bättre."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Var god inaktivera pod. När inaktiveringen är klar, ta bort poden från kroppen."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "För in kanyl"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Förbered insticksstället. Ta bort podens kanylskydd och skyddspapper. Om poden är OK, sätt fast den."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Ställ in pump"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Ställ in pump"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Din pod är aktiv."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Påminnelse"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod parkopplas"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Ställ in pump"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Se över dina inställningar nedan. Poden kommer att programmeras med dessa under parkopplingen. Du kan ändra dessa inställningar närsomhelst i menyn Inställningar i Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Inställning färdig"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "OBS: Om kanyl sticker ut, tryck Avbryt."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Sätt fast pod"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Titel"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Fyll din nya pod med insulin. Lyssna efter 2 pip under fyllning. Ha din RileyLink nära poden under parkoppling."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Byt pod"; diff --git a/OmniKitUI/vi.lproj/Localizable.strings b/OmniKitUI/vi.lproj/Localizable.strings new file mode 100644 index 000000000..c145ffcb8 --- /dev/null +++ b/OmniKitUI/vi.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ units vẫn đang còn lúc %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. Việc tiêm Insulin đã dừng. Hủy kích hoạt và thay pod."; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (Đã hoàn tất)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U of %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/giờ"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Thời gian Hoạt động"; + +/* The title of the cell showing alarm status */ +"Alarms" = "Cảnh báo"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "Bạn có chắc muốn tắt pod này?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "Bạn có chắc muốn dừng sử dụng Omnipod?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Địa chỉ được chỉ định"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "Liều tiêm basal"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "Lịch biểu tiêm liều nền"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "Liều tiêm bolus"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Hủy"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "Thay đổi múi giờ"; + +/* Progress message for changing pod time. */ +"Changing time…" = "Đang thay đổi giờ…"; + +/* The title of the configuration section in settings */ +"Configuration" = "Cấu hình"; + +/* The title of the continue action in an action sheet */ +"Continue" = "Tiếp tục"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "Hủy kích hoạt"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "Hủy kích hoạt Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "Xóa Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Giới hạn liều tiêm"; + +/* The title of the device information section in settings */ +"Device Information" = "Thông tin thiết bị"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "Vô hiệu tiếng Beep liều bolus"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "Bật tiếng Beep liều bolus"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "Lỗi trong việc vô hiệu tiếng beep liều bolus"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "Lỗi trong việc bật tiếng beep liều bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Lỗi khi đang tái thực hiện"; + +/* The alert title for a suspend error */ +"Error Suspending" = "Lỗi khi đang tạm ngưng"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Nhắc nhở Hết hạn"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Đã hết hạn"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Hết hạn"; + +/* Pod life HUD view label */ +"Fault" = "Lỗi"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "Hoàn tất thiết lập pod"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = "Nhiều hơn %1$@ units vẫn còn lúc %2$@"; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Thiết lập pod không hoàn thành phải được hủy rước khi tiến hành ghép đôi pod mới. Đề nghị hủy và loại pod."; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Thiết lập pod không hoàn thành phải được hủy rước khi tiến hành ghép đôi pod mới. Đề nghị hủy và thay thế pod."; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "Lắp Cannula"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "Insulin đã được tiêm"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "Lỗi nhập không hợp lệ"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "Lot"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "None"; + +/* Button title to pair with pod during setup */ +"Pair" = "Ghép đôi"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "Ghép đôi pod mới"; + +/* The text of the loading label when pairing */ +"Pairing…" = "Đang ghép đôi…"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI Version"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "Kiểm tra tiếng Beep"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "Kiểm tra tiếng Beep…"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM Version"; + +/* Label describing pod age view */ +"Pod Age" = "Pod Age"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Các thiết lập cho pod"; + +/* The text of the loading label when pod is primed */ +"Primed" = "Đã được mồi"; + +/* The text of the loading label when priming */ +"Priming…" = "Đang mồi…"; + +/* Label describing time remaining view */ +"Remaining" = "Đang còn lại"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "Thay thế Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "Thay thế pod ngay"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "Ngăn chứa insulin"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "Hủy kích hoạt pod lại"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "Lưu"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "Kế hoạch"; + +/* The title of the status section in settings */ +"Status" = "Tình trạng"; + +/* A message indicating a command succeeded */ +"Succeeded" = "Đã thành công"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "Đã tạm ngưng"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "Chuyển đổ từ bơm Omnipod"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "Sync với Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "Thử nghiệm câu lệnh"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "Thử nghiệm câu lệnh…"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Quá nhiều mục"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "Không thể hủy kích hoạt pod. Đề nghị tiếp tục và ghép đôi pod mới."; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "Không xác định"; diff --git a/OmniKitUI/vi.lproj/OmnipodPumpManager.strings b/OmniKitUI/vi.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..f8884d1bf --- /dev/null +++ b/OmniKitUI/vi.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "Thay POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "Cấu hình RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "Cấu hình Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "Cấu hình bơm"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "CHÚ Ý: Không tháo nắp bảo vệ kim của Pod vào lúc này."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "Chuẩn bị Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop sẽ nhắc bạn thay pod trước khi pod hết hạn. Bạn có thể chọn thay vào lúc thích hợp."; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "Hủy kích hoạt pod. Khi việc hủy kích hoạt hoàn tất, gỡ bỏ pod ra khỏi cơ thể."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "Gắn Cannula"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "Chuẩn bị vị trí gắn. Gỡ nắp bảo vệ kim trên pod và keo dán. Nếu pod sẵn sàng thì gắn vào vị trí."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "Cấu hình bơm"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "Cấu hình bơm"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Pod của bạn sẵn sàng sử dụng."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "Nhắc nhớ"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod đang ghép đôi"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "Cấu hình bơm"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "Xem lại cấu hình dưới đây. Những cài đặt này sẽ được lập trình vào pod trong quá trình ghép đôi. Bạn có thể chỉnh sửa cấu hình vào bất kỳ lúc nào trong Loop."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "Cấu hình hoàn thành"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "CHÚ Ý: Nếu cannula nhô ra, hãy nhấn hủy bỏ."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "Gắn POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "Nhãn"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "Nạp insulin vào pod mới. Bạn sẽ nghe 2 tiếng beep trong quá trình nạp. Hãy giữ Rileylink ngay bên cạnh pod trong quá trình ghép đôi."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "Thay Pod"; diff --git a/OmniKitUI/zh-Hans.lproj/Localizable.strings b/OmniKitUI/zh-Hans.lproj/Localizable.strings new file mode 100644 index 000000000..cd794f59c --- /dev/null +++ b/OmniKitUI/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,231 @@ +/* Accessibility format string for (1: localized volume)(2: time) */ +"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; + +/* The format string for displaying an offset from a time zone: (1: GMT)(2: -)(3: 4:00) */ +"%1$@%2$@%3$@" = "%1$@%2$@%3$@"; + +/* Format string providing instructions for replacing pod due to a fault. (1: The fault description) */ +"%1$@. Insulin delivery has stopped. Please deactivate and remove pod." = "%1$@. 胰岛素输注已停止,请移除Pod"; + +/* Format string for delivered insulin. (1: The localized amount) + Format string for insulin remaining in reservoir. (1: The localized amount) */ +"%@ U" = "%@ U"; + +/* Format string for bolus progress when finished. (1: The localized amount) */ +"%@ U (Finished)" = "%@ U (已输注)"; + +/* Format string for bolus progress. (1: The delivered amount) (2: The programmed amount) (3: the percent progress) */ +"%@ U of %@ U (%@)" = "%1$@ U of %2$@ U (%3$@)"; + +/* Format string for temp basal rate. (1: The localized amount) */ +"%@ U/hour" = "%@ U/小时"; + +/* Format string for bolus percent progress. (1: Percent progress) */ +"%@%%" = "%@%%"; + +/* Format string for reservoir reading when above or equal to maximum reading. (1: The localized amount) */ +"%@+ U" = "%@+ U"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for reservoir volume. (1: The localized volume) */ +"%@U" = "%@U"; + +/* The title of the cell showing the pod activated at time */ +"Active Time" = "Pod启动时间"; + +/* The title of the cell showing alarm status */ +"Alarms" = "提醒"; + +/* Confirmation message for shutting down a pod */ +"Are you sure you want to shutdown this pod?" = "确定要停止这个Pod吗?"; + +/* Confirmation message for removing Omnipod PumpManager */ +"Are you sure you want to stop using Omnipod?" = "确定要停止使用这个Pod吗?"; + +/* The title text for the address assigned to the pod */ +"Assigned Address" = "Assigned Address"; + +/* The title of the cell showing pod basal status */ +"Basal Delivery" = "当前基础率"; + +/* The title text for the basal rate schedule */ +"Basal Rates" = "基础率"; + +/* The title of the cell showing pod bolus status */ +"Bolus Delivery" = "当前大剂量"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "取消"; + +/* The title of the command to change pump time zone */ +"Change Time Zone" = "更改时区"; + +/* Progress message for changing pod time. */ +"Changing time…" = "更改时间"; + +/* The title of the configuration section in settings */ +"Configuration" = "配置"; + +/* The title of the continue action in an action sheet */ +"Continue" = "继续"; + +/* Button title to deactivate pod because of fault during setup */ +"Deactivate" = "解除"; + +/* Button title for pod deactivation + Button title to deactivate pod */ +"Deactivate Pod" = "解除Pod"; + +/* Button title to delete Omnipod PumpManager */ +"Delete Omnipod" = "删除Omnipod"; + +/* Title text for delivery limits */ +"Delivery Limits" = "输注限制"; + +/* The title of the device information section in settings */ +"Device Information" = "设备信息"; + +/* Title text for button to disable bolus beeps */ +"Disable Bolus Beeps" = "禁用大剂量输注中提醒"; + +/* Title text for button to enable bolus beeps */ +"Enable Bolus Beeps" = "启动大剂量输注中提醒"; + +/* The alert title for disable bolus beeps error */ +"Error disabling bolus beeps" = "禁用大剂量输注中提醒错误"; + +/* The alert title for enable bolus beeps error */ +"Error enabling bolus beeps" = "启用大剂量输注中提醒错误"; + +/* The alert title for a resume error */ +"Error Resuming" = "恢复输注错误"; + +/* The alert title for a suspend error */ +"Error Suspending" = "暂停输注错误"; + +/* The title of the cell showing the pod expiration reminder date */ +"Expiration Reminder" = "Pod到期提醒"; + +/* The title of the cell showing the pod expiration after expiry */ +"Expired" = "Pod已到期"; + +/* The title of the cell showing the pod expiration */ +"Expires" = "Pod即将到期"; + +/* Pod life HUD view label */ +"Fault" = "错误"; + +/* The title of the command to finish pod setup */ +"Finish pod setup" = "完成设置"; + +/* Accessibility format string for (1: localized volume)(2: time) */ +"Greater than %1$@ units remaining at %2$@" = ""; + +/* Instructions when deactivating pod that has been paired, but not attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and discard pod." = "Pod设置失败,请解除该从身体移除Pod,然后配对新Pod"; + +/* Instructions when deactivating pod that has been paired and possibly attached. */ +"Incompletely set up pod must be deactivated before pairing with a new one. Please deactivate and remove pod." = "Pod设置失败,请解除并从身体移除该Pod,然后配对新Pod"; + +/* Button title to insert cannula during setup */ +"Insert Cannula" = "植入Pod"; + +/* The title of the cell showing delivered insulin */ +"Insulin Delivered" = "已输注胰岛素"; + +/* The error message shown when Loop's basal schedule has an unsupported rate */ +"Invalid entry" = "无效输入"; + +/* The title of the cell showing the pod lot id */ +"Lot" = "批次号"; + +/* The detail text for bolus delivery when no bolus is being delivered */ +"None" = "无"; + +/* Button title to pair with pod during setup */ +"Pair" = "配对"; + +/* The title of the command to pair new pod */ +"Pair New Pod" = "配对新Pod"; + +/* The text of the loading label when pairing */ +"Pairing…" = "配对中"; + +/* The title of the cell showing the pod pi version */ +"PI Version" = "PI版本号"; + +/* The title of the command to play test beeps */ +"Play Test Beeps" = "测试提示音"; + +/* Progress message for play test beeps. */ +"Play Test Beeps…" = "提示音测试中"; + +/* The title of the cell showing the pod pm version */ +"PM Version" = "PM版本号"; + +/* Label describing pod age view */ +"Pod Age" = "Pod使用天数"; + +/* Title of the pod settings view controller */ +"Pod Settings" = "Pod设置"; + +/* The text of the loading label when pod is primed */ +"Primed" = "已充盈"; + +/* The text of the loading label when priming */ +"Priming…" = "正在充盈"; + +/* Label describing time remaining view */ +"Remaining" = "剩余"; + +/* Label indicating pod replacement necessary + The title of the command to replace pod */ +"Replace Pod" = "更换Pod"; + +/* The title of the command to replace pod when there is a pod fault */ +"Replace Pod Now" = "现在更换Pod"; + +/* The title of the cell showing reservoir status */ +"Reservoir" = "胰岛素容量"; + +/* Button title for retrying pod deactivation */ +"Retry Pod Deactivation" = "重新尝试解绑"; + +/* Title of button to save delivery limit settings + Title of button to sync basal profile when no pod paired */ +"Save" = "保存"; + +/* The detail text of the basal row when pod is running scheduled basal */ +"Schedule" = "预设"; + +/* The title of the status section in settings */ +"Status" = "状态"; + +/* A message indicating a command succeeded */ +"Succeeded" = "成功"; + +/* The detail text of the basal row when pod is suspended */ +"Suspended" = "暂停"; + +/* Title text for the button to delete Omnipod PumpManager */ +"Switch from Omnipod Pumps" = "删除Omnipod泵"; + +/* Title of button to sync basal profile from pod */ +"Sync With Pod" = "同步配置到Pod"; + +/* The title of the command to run the test command */ +"Test Command" = "测试命令"; + +/* Progress message for testing commands. */ +"Testing Commands…" = "测试命令进行中"; + +/* The error message shown when Loop's basal schedule has more entries than the pod can support */ +"Too many entries" = "Pod不支持该基础率设置"; + +/* Instructions when pod cannot be deactivated */ +"Unable to deactivate pod. Please continue and pair a new one." = "无法解除Pod,请配对新Pod"; + +/* The detail text for delivered insulin when no measurement is available */ +"Unknown" = "未知"; diff --git a/OmniKitUI/zh-Hans.lproj/OmnipodPumpManager.strings b/OmniKitUI/zh-Hans.lproj/OmnipodPumpManager.strings new file mode 100644 index 000000000..7a47a6a43 --- /dev/null +++ b/OmniKitUI/zh-Hans.lproj/OmnipodPumpManager.strings @@ -0,0 +1,68 @@ +/* Class = "UITableViewSection"; headerTitle = "Remove POD"; ObjectID = "1LF-te-Bdd"; */ +"1LF-te-Bdd.headerTitle" = "移除 POD"; + +/* Class = "UINavigationItem"; title = "RileyLink Setup"; ObjectID = "3HH-eJ-lRh"; */ +"3HH-eJ-lRh.title" = "设置RileyLink"; + +/* Class = "UITableViewController"; title = "Pod Settings"; ObjectID = "6vo-Ov-UpE"; */ +"6vo-Ov-UpE.title" = "设置Pod"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "91O-Un-vKc"; */ +"91O-Un-vKc.title" = "设置泵"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: Do not remove the pod's needle cap at this time."; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.footerTitle" = "注意: 此刻请勿移除针帽."; + +/* Class = "UITableViewSection"; headerTitle = "Prepare Pod"; ObjectID = "EUt-xk-Rmp"; */ +"EUt-xk-Rmp.headerTitle" = "预备Pod"; + +/* Class = "UILabel"; text = "Loop will remind you to change your pod before it expires. You can change this to a time convenient for you."; ObjectID = "Eng-IY-fQ7"; */ +"Eng-IY-fQ7.text" = "Loop会在Pod到期前发出提醒.你也可以自行设定到期提醒时间"; + +/* Class = "UILabel"; text = "Please deactivate the pod. When deactivation is complete, remove pod from body."; ObjectID = "GK7-jb-tyY"; */ +"GK7-jb-tyY.text" = "请先解绑Pod,解绑完成后将Pod从身体上摘除."; + +/* Class = "UINavigationItem"; title = "Insert Cannula"; ObjectID = "HwT-30-f0y"; */ +"HwT-30-f0y.title" = "植入管路"; + +/* Class = "UILabel"; text = "Prepare site. Remove the pod's needle cap and adhesive backing. If pod is OK, apply to site."; ObjectID = "Iuv-5M-bDH"; */ +"Iuv-5M-bDH.text" = "清洁植入部位. 移除Pod针帽并撕下胶布,然后将Pod敷贴在身上."; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "aNg-mm-Uuy"; */ +"aNg-mm-Uuy.title" = "设置泵"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "ack-ra-XH6"; */ +"ack-ra-XH6.title" = "设置泵"; + +/* Class = "UILabel"; text = "Your Pod is ready for use."; ObjectID = "bJ5-iH-fnF"; */ +"bJ5-iH-fnF.text" = "Pod已经可以正常使用."; + +/* Class = "UILabel"; text = "Reminder"; ObjectID = "ePA-6p-q8C"; */ +"ePA-6p-q8C.text" = "提醒"; + +/* Class = "UINavigationItem"; title = "Pod Pairing"; ObjectID = "jVO-Ut-MhL"; */ +"jVO-Ut-MhL.title" = "Pod配对中"; + +/* Class = "UITableViewController"; title = "Pump Setup"; ObjectID = "k1Y-x4-m0a"; */ +"k1Y-x4-m0a.title" = "设置泵"; + +/* Class = "UILabel"; text = "Review your settings below. They will be programmed into the pod during pairing. You can change these settings at any time in Loopʼs Settings screen."; ObjectID = "kLL-SQ-K0a"; */ +"kLL-SQ-K0a.text" = "请查看以下设置,设置将会在配对过程中同步到Pod中. 这些设置也可以在配对完成后在Loop的设置中进行修改."; + +/* Class = "UINavigationItem"; title = "Setup Complete"; ObjectID = "nDb-R5-e02"; */ +"nDb-R5-e02.title" = "设置已完成"; + +/* Class = "UITableViewSection"; footerTitle = "NOTE: If cannula sticks out, press cancel."; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.footerTitle" = "注意: 如果管路已经突出, 点击取消."; + +/* Class = "UITableViewSection"; headerTitle = "Apply POD"; ObjectID = "rcC-ke-lUP"; */ +"rcC-ke-lUP.headerTitle" = "使用POD"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "vEc-Km-ewe"; */ +"vEc-Km-ewe.text" = "标签"; + +/* Class = "UILabel"; text = "Fill a new pod with insulin. Listen for 2 beeps from the pod during filling. Keep RileyLink adjacent to the pod during pairing."; ObjectID = "vmF-Dc-3DS"; */ +"vmF-Dc-3DS.text" = "为新Pod注入胰岛素. 注入过程中会听到两声“滴滴. 配对过程中,请将Pod靠近Rileylink."; + +/* Class = "UINavigationItem"; title = "Replace Pod"; ObjectID = "yy1-xf-HdR"; */ +"yy1-xf-HdR.title" = "更换Pod"; diff --git a/RileyLink.xcodeproj/project.pbxproj b/RileyLink.xcodeproj/project.pbxproj index fd93d0ac4..bdfb3a5af 100644 --- a/RileyLink.xcodeproj/project.pbxproj +++ b/RileyLink.xcodeproj/project.pbxproj @@ -242,18 +242,13 @@ 7D2366F5212527DA0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; 7D2366F6212527DA0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; 7D2366F7212527DA0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D2366EF212527DA0028B67D /* LocalizedString.swift */; }; - 7D23674A21252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674821252A5E0028B67D /* InfoPlist.strings */; }; - 7D23674D21252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674B21252A5E0028B67D /* InfoPlist.strings */; }; - 7D23675021252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23674E21252A5E0028B67D /* InfoPlist.strings */; }; - 7D23675321252A5E0028B67D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23675121252A5E0028B67D /* InfoPlist.strings */; }; 7D23679421252EBC0028B67D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23679221252EBC0028B67D /* Localizable.strings */; }; 7D23679721252EBC0028B67D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D23679521252EBC0028B67D /* Localizable.strings */; }; - 7D70766D1FE092D4004AC8EA /* LoopKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70766F1FE092D4004AC8EA /* LoopKit.strings */; }; - 7D7076771FE092D6004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076791FE092D6004AC8EA /* InfoPlist.strings */; }; - 7D70767C1FE092D6004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70767E1FE092D6004AC8EA /* InfoPlist.strings */; }; 7D70768B1FE09310004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70768D1FE09310004AC8EA /* Localizable.strings */; }; - 7D7076901FE09311004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076921FE09311004AC8EA /* InfoPlist.strings */; }; 7D7076951FE09311004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076971FE09311004AC8EA /* Localizable.strings */; }; + 7D9BEFEF23369861005DCFD6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BEFF123369861005DCFD6 /* Localizable.strings */; }; + 7D9BF00123369910005DCFD6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BF00323369910005DCFD6 /* Localizable.strings */; }; + 7D9BF03D2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */; }; 7DEFE05322ED1C2400FCD378 /* OverrideStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */; }; C104A9C1217E603E006E3C3E /* OmnipodReservoirView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */; }; C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */; }; @@ -298,7 +293,6 @@ C12EA23B198B436800309FA4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23A198B436800309FA4 /* Foundation.framework */; }; C12EA23D198B436800309FA4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23C198B436800309FA4 /* CoreGraphics.framework */; }; C12EA23F198B436800309FA4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C12EA23E198B436800309FA4 /* UIKit.framework */; }; - C12EA245198B436800309FA4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C12EA243198B436800309FA4 /* InfoPlist.strings */; }; C1330F431DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */; }; C133CF931D5943780034B82D /* PredictedBG.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133CF921D5943780034B82D /* PredictedBG.swift */; }; C136AA3A23116E32008A320D /* OmniKitPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = C136AA2C23116E32008A320D /* OmniKitPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -541,7 +535,6 @@ C1FC49EE2135DD56007D0788 /* CommandResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */; }; C1FDFCA91D964A3E00ADBC31 /* BolusReminderPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FDFCA81D964A3E00ADBC31 /* BolusReminderPumpEvent.swift */; }; C1FFAF4D212944F600C50C1D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF4B212944F600C50C1D /* Localizable.strings */; }; - C1FFAF64212B126E00C50C1D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF62212B126E00C50C1D /* InfoPlist.strings */; }; C1FFAF6F212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAF6E212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift */; }; C1FFAF72212FAAEF00C50C1D /* RileyLink.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1FFAF71212FAAEF00C50C1D /* RileyLink.xcassets */; }; C1FFAF81213323CC00C50C1D /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1FFAF78213323CC00C50C1D /* OmniKit.framework */; }; @@ -588,7 +581,6 @@ C1FFB0022133241700C50C1D /* PacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFA2133241600C50C1D /* PacketTests.swift */; }; C1FFB0032133241700C50C1D /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */; }; C1FFB0042133241700C50C1D /* CRC16Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */; }; - C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */; }; C1FFB00E2133242C00C50C1D /* OmniPodPumpManager+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */; }; C1FFB00F2133242C00C50C1D /* OmniKitUI.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */; }; C1FFB0102133242C00C50C1D /* OmnipodSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */; }; @@ -1075,127 +1067,33 @@ 54DA4E841DFDC0A70007F489 /* SensorValueGlucoseEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SensorValueGlucoseEvent.swift; sourceTree = ""; }; 7D199DA3212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/MinimedPumpManager.strings; sourceTree = ""; }; 7D199DA4212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 7D199DA5212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/LoopKit.strings; sourceTree = ""; }; - 7D199DA6212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DA7212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D199DA8212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 7D199DA9212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DAA212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DAB212A159900241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DAC212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DAD212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DAE212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D199DAF212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 7D199DB0212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DB1212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D199DB2212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 7D199DB4212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 7D199DB5212A159A00241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366EF212527DA0028B67D /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = ""; }; 7D2366F8212528560028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 7D2366F9212528990028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 7D2366FA212529510028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MinimedPumpManager.strings; sourceTree = ""; }; 7D2366FB212529510028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366FC212529510028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LoopKit.strings; sourceTree = ""; }; - 7D2366FD212529520028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366FE212529520028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366FF212529520028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - 7D236700212529520028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236703212529520028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236704212529530028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236705212529530028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2367062125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MinimedPumpManager.strings; sourceTree = ""; }; 7D2367072125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - 7D2367082125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LoopKit.strings; sourceTree = ""; }; - 7D2367092125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23670A2125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23670B2125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - 7D23670C2125297B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23670F2125297C0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2367102125297C0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2367112125297C0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 7D236712212529810028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MinimedPumpManager.strings"; sourceTree = ""; }; 7D236713212529810028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - 7D236714212529820028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LoopKit.strings"; sourceTree = ""; }; - 7D236715212529820028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D236716212529820028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 7D236717212529820028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - 7D236718212529820028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23671B212529820028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23671C212529830028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23671D212529830028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 7D23671E212529890028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/MinimedPumpManager.strings; sourceTree = ""; }; 7D23671F212529890028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - 7D236720212529890028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/LoopKit.strings; sourceTree = ""; }; - 7D236721212529890028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236722212529890028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 7D236723212529890028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - 7D2367242125298A0028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2367272125298A0028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2367282125298A0028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2367292125298A0028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23672A212529940028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/MinimedPumpManager.strings; sourceTree = ""; }; 7D23672B212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - 7D23672C212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/LoopKit.strings; sourceTree = ""; }; - 7D23672D212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23672E212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23672F212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - 7D236730212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236733212529950028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236734212529960028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236735212529960028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2367362125299F0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/MinimedPumpManager.strings; sourceTree = ""; }; 7D2367372125299F0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - 7D236738212529A00028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/LoopKit.strings; sourceTree = ""; }; - 7D236739212529A00028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23673A212529A00028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23673B212529A00028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - 7D23673C212529A00028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23673F212529A00028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236740212529A10028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D236741212529A10028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23674621252A5E0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23674921252A5E0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23674C21252A5E0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23674F21252A5E0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23675221252A5E0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23675721252A5E0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MinimedPumpManager.strings; sourceTree = ""; }; - 7D23675821252A720028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23675A21252A720028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23675B21252A720028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23675C21252A720028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23675D21252A720028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23675F21252A720028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/MinimedPumpManager.strings; sourceTree = ""; }; - 7D23676021252A9D0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23676421252A9D0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23676621252A9D0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23676821252A9D0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23676A21252A9D0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23676E21252AA80028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23676F21252AA80028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677021252AA80028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677221252AA80028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677321252AA80028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677521252AB70028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677721252AB70028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677821252AB70028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677921252AB70028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677A21252AB70028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677C21252AC40028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677E21252AC40028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23677F21252AC40028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678021252AC40028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678121252AC40028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678421252AD30028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678521252AD30028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678621252AD30028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678721252AD30028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678821252AD30028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23678A21252AE10028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23678C21252AE10028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23678D21252AE10028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23678E21252AE10028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D23678F21252AE10028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 7D23679321252EBC0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 7D23679621252EBC0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 7D23679821252F050028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; @@ -1214,24 +1112,106 @@ 7D2367A52125303D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7D2367A62125304D0028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 7D2367A72125304D0028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - 7D4F0A611F8F226F00A55FB2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D4F0A621F8F226F00A55FB2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AACB1FE31CE500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AACC1FE31CE500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AACD1FE31DEA00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LoopKit.strings; sourceTree = ""; }; 7D68AACE1FE31DEB00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; - 7D68AACF1FE31DEB00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAD01FE31DEB00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAD21FE31DEC00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 7D68AAD31FE31DEC00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; - 7D68AAD41FE31DEC00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D70766E1FE092D4004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LoopKit.strings; sourceTree = ""; }; - 7D7076781FE092D6004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D70767D1FE092D6004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D7076871FE092D7004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D70768C1FE09310004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - 7D7076911FE09311004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D7076961FE09311004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFEA23369382005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFEC23369580005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BEFF023369861005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFF6233698BF005DCFD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEFF7233698C0005DCFD6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFF8233698C1005DCFD6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFF9233698C2005DCFD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFFA233698C3005DCFD6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFFB233698C4005DCFD6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFFC233698C5005DCFD6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFFD233698C6005DCFD6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEFFE233698C7005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00223369910005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00423369918005DCFD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF00523369919005DCFD6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0062336991B005DCFD6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0072336991B005DCFD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0082336991C005DCFD6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0092336991D005DCFD6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00A2336991E005DCFD6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00B2336991F005DCFD6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00C23369920005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00D2336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BF00E2336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF00F2336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0102336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0112336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0122336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0132336A2D3005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0142336A2D4005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0152336A2E3005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BF0162336A2E3005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0172336A2E3005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0182336A2E3005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0192336A2E3005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF01A2336A2E4005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF01B2336A2E4005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF01C2336A2E4005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF01D2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BF01E2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF01F2336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0202336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0212336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0222336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0232336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0242336A2EA005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0252336A2F1005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BF0262336A2F1005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0272336A2F1005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0282336A2F2005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0292336A2F2005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF02A2336A2F2005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF02B2336A2F2005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF02C2336A2F2005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF02D2336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BF02E2336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF02F2336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0302336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0312336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0322336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0332336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0342336A2FB005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF0352336A304005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/MinimedPumpManager.strings"; sourceTree = ""; }; + 7D9BF0362336A304005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF0372336A304005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF0382336A304005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF0392336A304005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF03A2336A305005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF03B2336A305005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF03C2336A305005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BF03E2336AE0B005DCFD6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/OmnipodPumpManager.storyboard; sourceTree = ""; }; + 7D9BF0412336AE28005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0432336AE38005DCFD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/OmnipodPumpManager.strings"; sourceTree = ""; }; + 7D9BF0452336AE3D005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0472336AE3E005DCFD6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0492336AE41005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF04B2336AE43005DCFD6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF04D2336AE45005DCFD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF04F2336AE47005DCFD6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0512336AE49005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0532336AE4B005DCFD6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0552336AE4E005DCFD6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF0572336AE50005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/OmnipodPumpManager.strings"; sourceTree = ""; }; + 7D9BF0592336AE53005DCFD6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF05B2336AE55005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF05D2336AE58005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF05F2336AE5A005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF14D23371407005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/MinimedPumpManager.strings; sourceTree = ""; }; + 7D9BF14E23371407005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/OmnipodPumpManager.strings; sourceTree = ""; }; + 7D9BF14F23371407005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF15023371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF15123371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF15223371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF15323371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF15423371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF15523371408005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideStatus.swift; sourceTree = ""; }; C104A9BF217E603E006E3C3E /* OmnipodReservoirView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodReservoirView.swift; sourceTree = ""; }; C104A9C0217E603E006E3C3E /* OmnipodReservoirView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OmnipodReservoirView.xib; sourceTree = ""; }; @@ -1276,10 +1256,8 @@ C12EA23C198B436800309FA4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; C12EA23E198B436800309FA4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; C12EA242198B436800309FA4 /* RileyLink-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RileyLink-Info.plist"; sourceTree = ""; }; - C12EA244198B436800309FA4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; C12EA253198B436800309FA4 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; C12EA25B198B436900309FA4 /* RileyLinkTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "RileyLinkTests-Info.plist"; sourceTree = ""; }; - C12EA25D198B436900309FA4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; C12EA25F198B436900309FA4 /* RileyLinkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RileyLinkTests.m; sourceTree = ""; }; C1330F421DBDA46400569064 /* ChangeSensorAlarmSilenceConfigPumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeSensorAlarmSilenceConfigPumpEvent.swift; sourceTree = ""; }; C133CF921D5943780034B82D /* PredictedBG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictedBG.swift; sourceTree = ""; }; @@ -1482,15 +1460,6 @@ C1FFAF542129450D00C50C1D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; C1FFAF552129450F00C50C1D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; C1FFAF562129451300C50C1D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - C1FFAF63212B126E00C50C1D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF66212B127C00C50C1D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF67212B127E00C50C1D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF68212B128000C50C1D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF69212B128300C50C1D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - C1FFAF6A212B128500C50C1D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF6B212B128800C50C1D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF6C212B128A00C50C1D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - C1FFAF6D212B128C00C50C1D /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; C1FFAF6E212CB4F100C50C1D /* RileyLinkConnectionManagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkConnectionManagerState.swift; sourceTree = ""; }; C1FFAF71212FAAEF00C50C1D /* RileyLink.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = RileyLink.xcassets; sourceTree = ""; }; C1FFAF78213323CC00C50C1D /* OmniKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OmniKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1539,7 +1508,6 @@ C1FFAFFB2133241600C50C1D /* BasalScheduleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleTests.swift; sourceTree = ""; }; C1FFAFFC2133241600C50C1D /* CRC16Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC16Tests.swift; sourceTree = ""; }; C1FFAFFD2133241600C50C1D /* OmniKitTests-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OmniKitTests-Bridging-Header.h"; sourceTree = ""; }; - C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = OmnipodPumpManager.storyboard; sourceTree = ""; }; C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OmniPodPumpManager+UI.swift"; sourceTree = ""; }; C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = OmniKitUI.xcassets; sourceTree = ""; }; C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OmnipodSettingsViewController.swift; sourceTree = ""; }; @@ -1752,7 +1720,6 @@ 431CE7711F98564100255374 /* RileyLinkBLEKit.h */, 431CE7721F98564100255374 /* Info.plist */, 7D23679221252EBC0028B67D /* Localizable.strings */, - 7D7076881FE092D7004AC8EA /* InfoPlist.strings */, 431CE79D1F9BE73900255374 /* BLEFirmwareVersion.swift */, 431CE7A01F9D195600255374 /* CBCentralManager.swift */, 431CE78E1F985B6E00255374 /* CBPeripheral.swift */, @@ -1781,7 +1748,6 @@ 43047FC31FAEC70600508343 /* RadioFirmwareVersionTests.swift */, 4322B75520282DA60002837D /* ResponseBufferTests.swift */, 431CE7801F98564200255374 /* Info.plist */, - 7D23674821252A5E0028B67D /* InfoPlist.strings */, ); path = RileyLinkBLEKitTests; sourceTree = ""; @@ -1830,7 +1796,6 @@ C1FFAF4B212944F600C50C1D /* Localizable.strings */, 4352A72720DEC9B700CAC200 /* MinimedKitUI.h */, 4352A72820DEC9B700CAC200 /* Info.plist */, - 7D23674E21252A5E0028B67D /* InfoPlist.strings */, 43709AC820DF1C9A00F941B3 /* MinimedKitUI.xcassets */, 43709AE020DF1D5400F941B3 /* MinimedPumpManager.storyboard */, 43709ACC20DF1CC900F941B3 /* Setup */, @@ -1874,7 +1839,6 @@ isa = PBXGroup; children = ( 43722FC01CB9F7640038B7F2 /* Info.plist */, - 7D23674521252A5E0028B67D /* InfoPlist.strings */, ); path = RileyLinkKitTests; sourceTree = ""; @@ -1890,7 +1854,6 @@ 43C246941D8918AE0031F8D1 /* Crypto */ = { isa = PBXGroup; children = ( - 7D7076791FE092D6004AC8EA /* InfoPlist.strings */, 43C246951D8918AE0031F8D1 /* Crypto.h */, 43C2469F1D8919E20031F8D1 /* Crypto.m */, 43C246961D8918AE0031F8D1 /* Info.plist */, @@ -1901,7 +1864,6 @@ 43D5E78F1FAF7BFB004ACDB7 /* RileyLinkKitUI */ = { isa = PBXGroup; children = ( - C1FFAF62212B126E00C50C1D /* InfoPlist.strings */, 43D5E7901FAF7BFB004ACDB7 /* RileyLinkKitUI.h */, 43D5E7911FAF7BFB004ACDB7 /* Info.plist */, 7D23679521252EBC0028B67D /* Localizable.strings */, @@ -2046,7 +2008,6 @@ isa = PBXGroup; children = ( 7D70768D1FE09310004AC8EA /* Localizable.strings */, - 7D70767E1FE092D6004AC8EA /* InfoPlist.strings */, 43D8709E20DE1CF5006B549E /* CGMManager */, C1EAD6B81C826B92006DBA60 /* Extensions */, 54BC44761DB46C3100340EED /* GlucoseEvents */, @@ -2073,7 +2034,6 @@ 54BC44721DB46A5200340EED /* GlucosePageTests.swift */, C12198621C8DF4C800BC374C /* HistoryPageTests.swift */, C10D9BD31C8269D500378342 /* Info.plist */, - 7D23674B21252A5E0028B67D /* InfoPlist.strings */, C1C357901C92733A009BDD4F /* MeterMessageTests.swift */, C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */, C1EAD6D31C826C43006DBA60 /* NSDataTests.swift */, @@ -2223,10 +2183,8 @@ isa = PBXGroup; children = ( 7D7076971FE09311004AC8EA /* Localizable.strings */, - 7D70766F1FE092D4004AC8EA /* LoopKit.strings */, C1EAD6EA1C8409A9006DBA60 /* RileyLink-Bridging-Header.h */, C12EA242198B436800309FA4 /* RileyLink-Info.plist */, - C12EA243198B436800309FA4 /* InfoPlist.strings */, ); name = "Supporting Files"; sourceTree = ""; @@ -2244,7 +2202,6 @@ isa = PBXGroup; children = ( C12EA25B198B436900309FA4 /* RileyLinkTests-Info.plist */, - C12EA25C198B436900309FA4 /* InfoPlist.strings */, ); name = "Supporting Files"; sourceTree = ""; @@ -2448,7 +2405,6 @@ C1B3830C1CD0665D00CE7782 /* NightscoutUploadKit */ = { isa = PBXGroup; children = ( - 7D7076921FE09311004AC8EA /* InfoPlist.strings */, C1AF21E91D4900300088C41D /* DeviceStatus */, C13D15591DAACE8400ADC044 /* Either.swift */, 43F348051D596270009933DC /* HKUnit.swift */, @@ -2467,7 +2423,6 @@ isa = PBXGroup; children = ( C1B3831D1CD0665D00CE7782 /* Info.plist */, - 7D23675121252A5E0028B67D /* InfoPlist.strings */, C1BC259723135EDB00E80E3F /* NightscoutProfileTests.swift */, ); path = NightscoutUploadKitTests; @@ -2562,6 +2517,7 @@ C1FFAF95213323E600C50C1D /* PumpManager */, C1FFAF7A213323CC00C50C1D /* OmniKit.h */, C1FFAF7B213323CC00C50C1D /* Info.plist */, + 7D9BEFF123369861005DCFD6 /* Localizable.strings */, ); path = OmniKit; sourceTree = ""; @@ -2654,7 +2610,8 @@ C1FFAFDC213323F900C50C1D /* Info.plist */, C1FFAFDB213323F900C50C1D /* OmniKitUI.h */, C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */, - C1FFB0052133242B00C50C1D /* OmnipodPumpManager.storyboard */, + 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */, + 7D9BF00323369910005DCFD6 /* Localizable.strings */, ); path = OmniKitUI; sourceTree = ""; @@ -3218,6 +3175,13 @@ nl, nb, pl, + vi, + ja, + sv, + da, + fi, + "pt-BR", + ro, ); mainGroup = C12EA22E198B436800309FA4; productRefGroup = C12EA238198B436800309FA4 /* Products */; @@ -3258,7 +3222,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D23674A21252A5E0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3268,7 +3231,6 @@ files = ( 43709ADE20DF1D5400F941B3 /* MinimedPumpManager.storyboard in Resources */, C1FFAF4D212944F600C50C1D /* Localizable.strings in Resources */, - 7D23675021252A5E0028B67D /* InfoPlist.strings in Resources */, 43709AC920DF1C9A00F941B3 /* MinimedKitUI.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3284,7 +3246,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D7076771FE092D6004AC8EA /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3293,7 +3254,6 @@ buildActionMask = 2147483647; files = ( 7D23679721252EBC0028B67D /* Localizable.strings in Resources */, - C1FFAF64212B126E00C50C1D /* InfoPlist.strings in Resources */, 43709AEC20E0056F00F941B3 /* RileyLinkKitUI.xcassets in Resources */, 43709AF020E0120F00F941B3 /* SetupImageTableViewCell.xib in Resources */, ); @@ -3303,7 +3263,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D70767C1FE092D6004AC8EA /* InfoPlist.strings in Resources */, 7D70768B1FE09310004AC8EA /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3312,7 +3271,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D23674D21252A5E0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3322,9 +3280,7 @@ files = ( C125729421220FEC0061BA2F /* MainStoryboard.storyboard in Resources */, C1FFAF72212FAAEF00C50C1D /* RileyLink.xcassets in Resources */, - 7D70766D1FE092D4004AC8EA /* LoopKit.strings in Resources */, C1FC49EC2135CB2D007D0788 /* LaunchScreen.storyboard in Resources */, - C12EA245198B436800309FA4 /* InfoPlist.strings in Resources */, 7D7076951FE09311004AC8EA /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3347,7 +3303,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D7076901FE09311004AC8EA /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3355,7 +3310,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D23675321252A5E0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3363,6 +3317,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7D9BEFEF23369861005DCFD6 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3378,8 +3333,9 @@ buildActionMask = 2147483647; files = ( C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib in Resources */, - C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */, + 7D9BF03D2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard in Resources */, C104A9C7217E9F35006E3C3E /* PodLifeHUDView.xib in Resources */, + 7D9BF00123369910005DCFD6 /* Localizable.strings in Resources */, C104A9C5217E645C006E3C3E /* HUDAssets.xcassets in Resources */, C1814B8A225FADFA008D2D8E /* ExpirationReminderDateTableViewCell.xib in Resources */, C1FFB00F2133242C00C50C1D /* OmniKitUI.xcassets in Resources */, @@ -4354,90 +4310,18 @@ 7D23675721252A5E0028B67D /* es */, 7D23675F21252A720028B67D /* ru */, 7D199DA3212A159900241026 /* pl */, + 7D9BEFEC23369580005DCFD6 /* en */, + 7D9BF00D2336A2D3005DCFD6 /* vi */, + 7D9BF0152336A2E3005DCFD6 /* ja */, + 7D9BF01D2336A2EA005DCFD6 /* sv */, + 7D9BF0252336A2F1005DCFD6 /* da */, + 7D9BF02D2336A2FB005DCFD6 /* fi */, + 7D9BF0352336A304005DCFD6 /* pt-BR */, + 7D9BF14D23371407005DCFD6 /* ro */, ); name = MinimedPumpManager.storyboard; sourceTree = ""; }; - 7D23674521252A5E0028B67D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D23674621252A5E0028B67D /* es */, - 7D23675A21252A720028B67D /* ru */, - 7D23676621252A9D0028B67D /* de */, - 7D23676E21252AA80028B67D /* fr */, - 7D23677A21252AB70028B67D /* it */, - 7D23677E21252AC40028B67D /* nb */, - 7D23678421252AD30028B67D /* nl */, - 7D23678C21252AE10028B67D /* zh-Hans */, - 7D199DAB212A159900241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D23674821252A5E0028B67D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D23674921252A5E0028B67D /* es */, - 7D23675821252A720028B67D /* ru */, - 7D23676021252A9D0028B67D /* de */, - 7D23677021252AA80028B67D /* fr */, - 7D23677721252AB70028B67D /* it */, - 7D23677C21252AC40028B67D /* nb */, - 7D23678521252AD30028B67D /* nl */, - 7D23678A21252AE10028B67D /* zh-Hans */, - 7D199DB1212A159A00241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D23674B21252A5E0028B67D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D23674C21252A5E0028B67D /* es */, - 7D23675B21252A720028B67D /* ru */, - 7D23676821252A9D0028B67D /* de */, - 7D23676F21252AA80028B67D /* fr */, - 7D23677921252AB70028B67D /* it */, - 7D23677F21252AC40028B67D /* nb */, - 7D23678621252AD30028B67D /* nl */, - 7D23678E21252AE10028B67D /* zh-Hans */, - 7D199DAA212A159900241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D23674E21252A5E0028B67D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D23674F21252A5E0028B67D /* es */, - 7D23675C21252A720028B67D /* ru */, - 7D23676421252A9D0028B67D /* de */, - 7D23677221252AA80028B67D /* fr */, - 7D23677821252AB70028B67D /* it */, - 7D23678121252AC40028B67D /* nb */, - 7D23678821252AD30028B67D /* nl */, - 7D23678D21252AE10028B67D /* zh-Hans */, - 7D199DB5212A159A00241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D23675121252A5E0028B67D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D23675221252A5E0028B67D /* es */, - 7D23675D21252A720028B67D /* ru */, - 7D23676A21252A9D0028B67D /* de */, - 7D23677321252AA80028B67D /* fr */, - 7D23677521252AB70028B67D /* it */, - 7D23678021252AC40028B67D /* nb */, - 7D23678721252AD30028B67D /* nl */, - 7D23678F21252AE10028B67D /* zh-Hans */, - 7D199DAD212A159A00241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; 7D23679221252EBC0028B67D /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -4451,6 +4335,13 @@ 7D2367A42125303D0028B67D /* zh-Hans */, 7D2367A62125304D0028B67D /* nl */, 7D199DAF212A159A00241026 /* pl */, + 7D9BF0102336A2D3005DCFD6 /* vi */, + 7D9BF0182336A2E3005DCFD6 /* ja */, + 7D9BF0202336A2EA005DCFD6 /* sv */, + 7D9BF0282336A2F2005DCFD6 /* da */, + 7D9BF0302336A2FB005DCFD6 /* fi */, + 7D9BF0382336A304005DCFD6 /* pt-BR */, + 7D9BF15123371408005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; @@ -4468,74 +4359,17 @@ 7D2367A52125303D0028B67D /* zh-Hans */, 7D2367A72125304D0028B67D /* nl */, 7D199DB2212A159A00241026 /* pl */, + 7D9BF0112336A2D3005DCFD6 /* vi */, + 7D9BF0192336A2E3005DCFD6 /* ja */, + 7D9BF0212336A2EA005DCFD6 /* sv */, + 7D9BF0292336A2F2005DCFD6 /* da */, + 7D9BF0312336A2FB005DCFD6 /* fi */, + 7D9BF0392336A304005DCFD6 /* pt-BR */, + 7D9BF15223371408005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; }; - 7D70766F1FE092D4004AC8EA /* LoopKit.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D70766E1FE092D4004AC8EA /* es */, - 7D68AACD1FE31DEA00522C49 /* ru */, - 7D2366FC212529510028B67D /* fr */, - 7D2367082125297B0028B67D /* de */, - 7D236714212529820028B67D /* zh-Hans */, - 7D236720212529890028B67D /* it */, - 7D23672C212529950028B67D /* nl */, - 7D236738212529A00028B67D /* nb */, - 7D199DA5212A159900241026 /* pl */, - ); - name = LoopKit.strings; - sourceTree = ""; - }; - 7D7076791FE092D6004AC8EA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D7076781FE092D6004AC8EA /* es */, - 7D68AACF1FE31DEB00522C49 /* ru */, - 7D236704212529530028B67D /* fr */, - 7D2367102125297C0028B67D /* de */, - 7D23671C212529830028B67D /* zh-Hans */, - 7D2367282125298A0028B67D /* it */, - 7D236734212529960028B67D /* nl */, - 7D236740212529A10028B67D /* nb */, - 7D199DAE212A159A00241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D70767E1FE092D6004AC8EA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D70767D1FE092D6004AC8EA /* es */, - 7D68AAD01FE31DEB00522C49 /* ru */, - 7D236700212529520028B67D /* fr */, - 7D23670C2125297B0028B67D /* de */, - 7D236718212529820028B67D /* zh-Hans */, - 7D2367242125298A0028B67D /* it */, - 7D236730212529950028B67D /* nl */, - 7D23673C212529A00028B67D /* nb */, - 7D199DA9212A159900241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D7076881FE092D7004AC8EA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D7076871FE092D7004AC8EA /* es */, - 7D68AAD21FE31DEC00522C49 /* ru */, - 7D236705212529530028B67D /* fr */, - 7D2367112125297C0028B67D /* de */, - 7D23671D212529830028B67D /* zh-Hans */, - 7D2367292125298A0028B67D /* it */, - 7D236735212529960028B67D /* nl */, - 7D236741212529A10028B67D /* nb */, - 7D199DB0212A159A00241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; 7D70768D1FE09310004AC8EA /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -4549,26 +4383,17 @@ 7D23672F212529950028B67D /* nl */, 7D23673B212529A00028B67D /* nb */, 7D199DA8212A159900241026 /* pl */, + 7D9BF00F2336A2D3005DCFD6 /* vi */, + 7D9BF0172336A2E3005DCFD6 /* ja */, + 7D9BF01F2336A2EA005DCFD6 /* sv */, + 7D9BF0272336A2F1005DCFD6 /* da */, + 7D9BF02F2336A2FB005DCFD6 /* fi */, + 7D9BF0372336A304005DCFD6 /* pt-BR */, + 7D9BF15023371408005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; }; - 7D7076921FE09311004AC8EA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D7076911FE09311004AC8EA /* es */, - 7D68AAD41FE31DEC00522C49 /* ru */, - 7D236703212529520028B67D /* fr */, - 7D23670F2125297C0028B67D /* de */, - 7D23671B212529820028B67D /* zh-Hans */, - 7D2367272125298A0028B67D /* it */, - 7D236733212529950028B67D /* nl */, - 7D23673F212529A00028B67D /* nb */, - 7D199DAC212A159A00241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; 7D7076971FE09311004AC8EA /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -4582,42 +4407,89 @@ 7D23672B212529950028B67D /* nl */, 7D2367372125299F0028B67D /* nb */, 7D199DA4212A159900241026 /* pl */, + 7D9BEFEA23369382005DCFD6 /* en */, + 7D9BF00E2336A2D3005DCFD6 /* vi */, + 7D9BF0162336A2E3005DCFD6 /* ja */, + 7D9BF01E2336A2EA005DCFD6 /* sv */, + 7D9BF0262336A2F1005DCFD6 /* da */, + 7D9BF02E2336A2FB005DCFD6 /* fi */, + 7D9BF0362336A304005DCFD6 /* pt-BR */, + 7D9BF14F23371407005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; }; - C12EA243198B436800309FA4 /* InfoPlist.strings */ = { + 7D9BEFF123369861005DCFD6 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - C12EA244198B436800309FA4 /* en */, - 7D4F0A611F8F226F00A55FB2 /* es */, - 7D68AACB1FE31CE500522C49 /* ru */, - 7D2366FD212529520028B67D /* fr */, - 7D2367092125297B0028B67D /* de */, - 7D236715212529820028B67D /* zh-Hans */, - 7D236721212529890028B67D /* it */, - 7D23672D212529950028B67D /* nl */, - 7D236739212529A00028B67D /* nb */, - 7D199DA6212A159900241026 /* pl */, - ); - name = InfoPlist.strings; + 7D9BEFF023369861005DCFD6 /* en */, + 7D9BEFF6233698BF005DCFD6 /* zh-Hans */, + 7D9BEFF7233698C0005DCFD6 /* nl */, + 7D9BEFF8233698C1005DCFD6 /* fr */, + 7D9BEFF9233698C2005DCFD6 /* de */, + 7D9BEFFA233698C3005DCFD6 /* it */, + 7D9BEFFB233698C4005DCFD6 /* nb */, + 7D9BEFFC233698C5005DCFD6 /* pl */, + 7D9BEFFD233698C6005DCFD6 /* ru */, + 7D9BEFFE233698C7005DCFD6 /* es */, + 7D9BF0132336A2D3005DCFD6 /* vi */, + 7D9BF01B2336A2E4005DCFD6 /* ja */, + 7D9BF0232336A2EA005DCFD6 /* sv */, + 7D9BF02B2336A2F2005DCFD6 /* da */, + 7D9BF0332336A2FB005DCFD6 /* fi */, + 7D9BF03B2336A305005DCFD6 /* pt-BR */, + 7D9BF15423371408005DCFD6 /* ro */, + ); + name = Localizable.strings; sourceTree = ""; }; - C12EA25C198B436900309FA4 /* InfoPlist.strings */ = { + 7D9BF00323369910005DCFD6 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - C12EA25D198B436900309FA4 /* en */, - 7D4F0A621F8F226F00A55FB2 /* es */, - 7D68AACC1FE31CE500522C49 /* ru */, - 7D2366FE212529520028B67D /* fr */, - 7D23670A2125297B0028B67D /* de */, - 7D236716212529820028B67D /* zh-Hans */, - 7D236722212529890028B67D /* it */, - 7D23672E212529950028B67D /* nl */, - 7D23673A212529A00028B67D /* nb */, - 7D199DA7212A159900241026 /* pl */, - ); - name = InfoPlist.strings; + 7D9BF00223369910005DCFD6 /* en */, + 7D9BF00423369918005DCFD6 /* zh-Hans */, + 7D9BF00523369919005DCFD6 /* nl */, + 7D9BF0062336991B005DCFD6 /* fr */, + 7D9BF0072336991B005DCFD6 /* de */, + 7D9BF0082336991C005DCFD6 /* it */, + 7D9BF0092336991D005DCFD6 /* nb */, + 7D9BF00A2336991E005DCFD6 /* pl */, + 7D9BF00B2336991F005DCFD6 /* ru */, + 7D9BF00C23369920005DCFD6 /* es */, + 7D9BF0142336A2D4005DCFD6 /* vi */, + 7D9BF01C2336A2E4005DCFD6 /* ja */, + 7D9BF0242336A2EA005DCFD6 /* sv */, + 7D9BF02C2336A2F2005DCFD6 /* da */, + 7D9BF0342336A2FB005DCFD6 /* fi */, + 7D9BF03C2336A305005DCFD6 /* pt-BR */, + 7D9BF15523371408005DCFD6 /* ro */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 7D9BF03E2336AE0B005DCFD6 /* Base */, + 7D9BF0412336AE28005DCFD6 /* en */, + 7D9BF0432336AE38005DCFD6 /* zh-Hans */, + 7D9BF0452336AE3D005DCFD6 /* da */, + 7D9BF0472336AE3E005DCFD6 /* nl */, + 7D9BF0492336AE41005DCFD6 /* fi */, + 7D9BF04B2336AE43005DCFD6 /* fr */, + 7D9BF04D2336AE45005DCFD6 /* de */, + 7D9BF04F2336AE47005DCFD6 /* it */, + 7D9BF0512336AE49005DCFD6 /* ja */, + 7D9BF0532336AE4B005DCFD6 /* nb */, + 7D9BF0552336AE4E005DCFD6 /* pl */, + 7D9BF0572336AE50005DCFD6 /* pt-BR */, + 7D9BF0592336AE53005DCFD6 /* ru */, + 7D9BF05B2336AE55005DCFD6 /* es */, + 7D9BF05D2336AE58005DCFD6 /* sv */, + 7D9BF05F2336AE5A005DCFD6 /* vi */, + 7D9BF14E23371407005DCFD6 /* ro */, + ); + name = OmnipodPumpManager.storyboard; sourceTree = ""; }; C1FFAF4B212944F600C50C1D /* Localizable.strings */ = { @@ -4633,26 +4505,17 @@ C1FFAF552129450F00C50C1D /* nl */, C1FFAF562129451300C50C1D /* nb */, 7D199DB4212A159A00241026 /* pl */, + 7D9BF0122336A2D3005DCFD6 /* vi */, + 7D9BF01A2336A2E4005DCFD6 /* ja */, + 7D9BF0222336A2EA005DCFD6 /* sv */, + 7D9BF02A2336A2F2005DCFD6 /* da */, + 7D9BF0322336A2FB005DCFD6 /* fi */, + 7D9BF03A2336A305005DCFD6 /* pt-BR */, + 7D9BF15323371408005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; }; - C1FFAF62212B126E00C50C1D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - C1FFAF63212B126E00C50C1D /* de */, - C1FFAF66212B127C00C50C1D /* es */, - C1FFAF67212B127E00C50C1D /* ru */, - C1FFAF68212B128000C50C1D /* fr */, - C1FFAF69212B128300C50C1D /* zh-Hans */, - C1FFAF6A212B128500C50C1D /* it */, - C1FFAF6B212B128800C50C1D /* nl */, - C1FFAF6C212B128A00C50C1D /* nb */, - C1FFAF6D212B128C00C50C1D /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ diff --git a/RileyLink/Base.lproj/Localizable.strings b/RileyLink/Base.lproj/Localizable.strings index a594bf5cd..7b8ff1d7f 100644 --- a/RileyLink/Base.lproj/Localizable.strings +++ b/RileyLink/Base.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "About"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Fetch CGM"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "Pump ID"; -/* The title text for the pump Region config value */ -"Pump Region" = "Pump Region"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; /* The default placeholder string for a credential */ "Required" = "Required"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "Site URL"; @@ -49,5 +49,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Upload To Nightscout"; -/* Label indicating validation is occurring */ -"Verifying" = "Verifying"; diff --git a/RileyLink/Models/NightscoutService.swift b/RileyLink/Models/NightscoutService.swift index c338d8194..cebfbb0a7 100644 --- a/RileyLink/Models/NightscoutService.swift +++ b/RileyLink/Models/NightscoutService.swift @@ -20,7 +20,7 @@ struct NightscoutService: ServiceAuthentication { credentials = [ ServiceCredential( title: LocalizedString("Site URL", comment: "The title of the nightscout site URL credential"), - placeholder: LocalizedString("http://mysite.azurewebsites.net", comment: "The placeholder text for the nightscout site URL credential"), + placeholder: LocalizedString("http://mysite.herokuapp.com", comment: "The placeholder text for the nightscout site URL credential"), isSecret: false, keyboardType: .URL, value: siteURL?.absoluteString diff --git a/RileyLink/da.lproj/Localizable.strings b/RileyLink/da.lproj/Localizable.strings new file mode 100644 index 000000000..45608cae5 --- /dev/null +++ b/RileyLink/da.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "About"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Add Account"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Slet Konto"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Indtast det 6-cifrede pumpe ID"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Hent CGM"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "Pumpe ID"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pumper"; + +/* The default placeholder string for a credential */ +"Required" = "Påkrævet"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Tryk for at gemme"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Upload til Nightscout"; + diff --git a/RileyLink/de.lproj/InfoPlist.strings b/RileyLink/de.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb..000000000 diff --git a/RileyLink/de.lproj/Localizable.strings b/RileyLink/de.lproj/Localizable.strings index 07bf675cc..81ca158ab 100644 --- a/RileyLink/de.lproj/Localizable.strings +++ b/RileyLink/de.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "Über"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Hol CGM"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "Pumpen-ID"; -/* The title text for the pump Region config value */ -"Pump Region" = "Pumpenregion"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "Die Pumpenregion wird auf der Rückseite der Pumpe als zwei der letzten drei Zeichen der Modellzeichenfolge aufgeführt, die etwa so lautet: MMT-551NAB oder MMT-515LWWS. Wenn Ihr Modell eine \"NA\" enthält, ist die Region North America. Wenn Ihr Modell ein \"WW\" enthält, ist die Region WorldWide."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; /* The default placeholder string for a credential */ "Required" = "Erforderlich"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "Website URL"; @@ -49,6 +49,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Upload zu NightScout"; -/* Label indicating validation is occurring */ -"Verifying" = "Überprüfen"; - diff --git a/RileyLink/de.lproj/LoopKit.strings b/RileyLink/de.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/de.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/en.lproj/InfoPlist.strings b/RileyLink/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLink/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLink/en.lproj/Localizable.strings b/RileyLink/en.lproj/Localizable.strings new file mode 100644 index 000000000..7b8ff1d7f --- /dev/null +++ b/RileyLink/en.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "About"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Add Account"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuration"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Delete Account"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Enter the 6-digit pump ID"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Fetch CGM"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "Pump ID"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; + +/* The default placeholder string for a credential */ +"Required" = "Required"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Tap to set"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Upload To Nightscout"; + diff --git a/RileyLink/es.lproj/InfoPlist.strings b/RileyLink/es.lproj/InfoPlist.strings deleted file mode 100644 index 92e48b3b0..000000000 --- a/RileyLink/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "${PRODUCT_NAME}"; - -/* Bundle name */ -"CFBundleName" = "${PRODUCT_NAME}"; - diff --git a/RileyLink/es.lproj/Localizable.strings b/RileyLink/es.lproj/Localizable.strings index e1cdcc8e5..a7469b24a 100644 --- a/RileyLink/es.lproj/Localizable.strings +++ b/RileyLink/es.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "Respecto a"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Obtener de CGM"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "ID de Microinfusora"; -/* The title text for the pump Region config value */ -"Pump Region" = "Región de Microinfusora"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "La región de la microinfusora puede ser encontrada impresa en la parte trasera como parte del código de modelo (REF), por ejemplo MMT-551AB o MMT-515LWWS. Si el código de modelo contiene \"NA\" o \"CA\", la región es Norte América. Si contiene \"WW\" la región es Mundial."; +/* Title text for section listing configured pumps */ +"Pumps" = "Microinfusoras"; /* The default placeholder string for a credential */ "Required" = "Requerido"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "Preubas de RileyLink"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "URL de sitio"; @@ -48,7 +48,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Subir a Nightscout"; - -/* Label indicating validation is occurring */ -"Verifying" = "Verificando"; - diff --git a/RileyLink/es.lproj/LoopKit.strings b/RileyLink/es.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/es.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/fi.lproj/Localizable.strings b/RileyLink/fi.lproj/Localizable.strings new file mode 100644 index 000000000..e0da63c48 --- /dev/null +++ b/RileyLink/fi.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "Tietoja"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Lisää tili"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API-salasana"; + +/* The title of the configuration section in settings */ +"Configuration" = "Kokoonpano"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Poista tili"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Syötä 6-numeroinen pumpun ID"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Hae CGM-data"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "Pumpun ID"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pumput"; + +/* The default placeholder string for a credential */ +"Required" = "Pakollinen"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Sivuston URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Napauta asettaaksesi"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Upload To Nightscout"; + diff --git a/RileyLink/fr.lproj/InfoPlist.strings b/RileyLink/fr.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb..000000000 diff --git a/RileyLink/fr.lproj/Localizable.strings b/RileyLink/fr.lproj/Localizable.strings index 44a519f68..1d000a2fa 100644 --- a/RileyLink/fr.lproj/Localizable.strings +++ b/RileyLink/fr.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "À propos"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Obtenir CGM"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "ID de la Pompe"; -/* The title text for the pump Region config value */ -"Pump Region" = "Région de Pompe"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "La région de la pompe est indiquée au dos de votre pompe par deux des trois derniers caractères de la chaîne du modèle, qui se lit comme suit: MMT-551NAB ou MMT-515LWWS. Si votre modèle contient un \"NA\" ou \"CA\", la région est l’Amérique du Nord. Si votre modèle contient un \"WW\", alors la région est Monde Entier."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pompes"; /* The default placeholder string for a credential */ "Required" = "Requis"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "URL du Site"; @@ -49,6 +49,4 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Télécharger vers Nightscout"; -/* Label indicating validation is occurring */ -"Verifying" = "Validation en cours"; diff --git a/RileyLink/fr.lproj/LoopKit.strings b/RileyLink/fr.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/fr.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/it.lproj/InfoPlist.strings b/RileyLink/it.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb..000000000 diff --git a/RileyLink/it.lproj/Localizable.strings b/RileyLink/it.lproj/Localizable.strings index b322f0404..33d2bd852 100644 --- a/RileyLink/it.lproj/Localizable.strings +++ b/RileyLink/it.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "Informazioni"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Sincronizza Sensore"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "ID Microinfusore"; -/* The title text for the pump Region config value */ -"Pump Region" = "Región de Microinfusora"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "La provenienza del microinfusore è segnalata sul retro del microinfusore come i due degli ultimi tre caratteri della stringa del modello, che si legge in questo modo: MMT-551NAB o MMT-515LWWS. Se il tuo modello ha un \"NA\" o \"CA\" in esso, la regione è Nord America. Se il tuo modello ha un \"WW\" al suo interno, la regione è Internazionale."; +/* Title text for section listing configured pumps */ +"Pumps" = "Microinfusore"; /* The default placeholder string for a credential */ "Required" = "Necessario"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "Sito URL"; @@ -48,7 +48,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Carica su Nightscout"; - -/* Label indicating validation is occurring */ -"Verifying" = "Sto verificando"; - diff --git a/RileyLink/it.lproj/LoopKit.strings b/RileyLink/it.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/it.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/ja.lproj/Localizable.strings b/RileyLink/ja.lproj/Localizable.strings new file mode 100644 index 000000000..ebe23e8d3 --- /dev/null +++ b/RileyLink/ja.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "情報"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "アカウントを追加"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "APIシークレット"; + +/* The title of the configuration section in settings */ +"Configuration" = "コンフィグレーション"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "アカウントを削除"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "6桁のポンプ ID"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "CGMを取得"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "ポンプ ID"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; + +/* The default placeholder string for a credential */ +"Required" = "必須"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "タップして確定"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "NightScoutをアップロード"; + diff --git a/RileyLink/nb.lproj/InfoPlist.strings b/RileyLink/nb.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb..000000000 diff --git a/RileyLink/nb.lproj/Localizable.strings b/RileyLink/nb.lproj/Localizable.strings index 5e90e9c80..0b17a94be 100644 --- a/RileyLink/nb.lproj/Localizable.strings +++ b/RileyLink/nb.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "Om"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Hent CGM"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "Pumpe-ID"; -/* The title text for the pump Region config value */ -"Pump Region" = "Pumperegion"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "Pumperegion er merket på baksiden av din pumpe som to av de tre siste tegnene i din modell, og ser ut noe som dette: MMT-551NAB, eller MMT-515LWWS. Hvis pumpa di har \"NA\" eller \"CA\" i navnet er regionen Nord-Amerika. Hvis den derimot har \"WW\" i navnet er regionen WorldWide."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumper"; /* The default placeholder string for a credential */ "Required" = "Påkrevd"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Sett opp Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "Nettstedslenke (URL)"; @@ -49,6 +49,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Last opp til Nightscout"; -/* Label indicating validation is occurring */ -"Verifying" = "Bekrefter"; - diff --git a/RileyLink/nb.lproj/LoopKit.strings b/RileyLink/nb.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/nb.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/nl.lproj/InfoPlist.strings b/RileyLink/nl.lproj/InfoPlist.strings deleted file mode 100644 index 92e48b3b0..000000000 --- a/RileyLink/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "${PRODUCT_NAME}"; - -/* Bundle name */ -"CFBundleName" = "${PRODUCT_NAME}"; - diff --git a/RileyLink/nl.lproj/Localizable.strings b/RileyLink/nl.lproj/Localizable.strings index ad1f13071..a5fc295ba 100644 --- a/RileyLink/nl.lproj/Localizable.strings +++ b/RileyLink/nl.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "Over"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Haal CGM op"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "Pomp ID"; -/* The title text for the pump Region config value */ -"Pump Region" = "Pomp regio"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "Pump Regio staat op de achterkant van uw pomp als twee van de laatste drie tekens van de modeltekenreeks, die er als volgt uitziet: MMT-551NAB of MMT-515LWWS. Als uw model een \"NA\" bevat, dan is de regio Noord Amerika. Als uw model een \"WW\" bevat, is de regio Wereldwijd."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; /* The default placeholder string for a credential */ "Required" = "Vereist"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "Webpagina URL"; @@ -49,6 +49,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Uploaden naar Nightscout"; -/* Label indicating validation is occurring */ -"Verifying" = "Verifiëren"; - diff --git a/RileyLink/nl.lproj/LoopKit.strings b/RileyLink/nl.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/nl.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/pl.lproj/InfoPlist.strings b/RileyLink/pl.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLink/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLink/pl.lproj/Localizable.strings b/RileyLink/pl.lproj/Localizable.strings index da259cd20..d3a31d1f3 100644 --- a/RileyLink/pl.lproj/Localizable.strings +++ b/RileyLink/pl.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "O aplikacji"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Pobierz dane z CGM"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "ID pompy"; -/* The title text for the pump Region config value */ -"Pump Region" = "Region zakupu pompy"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "Region zakupu pompy znajduje się na etykiecie, na odwrocie pompy. Region jest określony przez dwa z trzech ostatnich znaków określających model pompy. Jeśli Twój model zawiera sekwencję \”NA\”, wtedy region to Ameryka Północna. Jeśli Twój model zawiera sekwencję \”WW\”, wtedy region to Ogólnoświatowy. Przykładowe modele pomp z regionem: MMT-551NAB - region to Ameryka Północna; MMT-515LWWS - region to Ogólnoświatowy."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; /* The default placeholder string for a credential */ "Required" = "Wymagany"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "URL strony"; @@ -48,6 +48,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Wysyłaj do Nightscout"; - -/* Label indicating validation is occurring */ -"Verifying" = "Weryfikacja"; diff --git a/RileyLink/pt-BR.lproj/Localizable.strings b/RileyLink/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..fa8be6493 --- /dev/null +++ b/RileyLink/pt-BR.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "Sobre"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Adicionar Conta"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "Chave API"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuração"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Remover Conta"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Digite o ID da bomba de 6 dígitos"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Buscar CGM"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "ID da Bomba"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Bombas"; + +/* The default placeholder string for a credential */ +"Required" = "Obrigatório"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Toque para definir"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Enviar para Nightscout"; + diff --git a/RileyLink/ro.lproj/Localizable.strings b/RileyLink/ro.lproj/Localizable.strings new file mode 100644 index 000000000..834a01dd3 --- /dev/null +++ b/RileyLink/ro.lproj/Localizable.strings @@ -0,0 +1,55 @@ +/* The title of the about section */ +"About" = "Despre"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Adaugă cont"; + +/* Title text for button to set up a new minimed pump */ +"Add Minimed Pump" = "Adaugă pompă Minimed"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "Secret API"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configurație"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Șterge cont"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Introduceți ID-ul de pompă din 6 cifre"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Transferă din CGM"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "ID pompă"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pompe"; + +/* The default placeholder string for a credential */ +"Required" = "Necesar"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "URL site"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Apăsați pentru setare"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Încărcare în Nightscout"; + + diff --git a/RileyLink/ru.lproj/InfoPlist.strings b/RileyLink/ru.lproj/InfoPlist.strings deleted file mode 100644 index 92e48b3b0..000000000 --- a/RileyLink/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "${PRODUCT_NAME}"; - -/* Bundle name */ -"CFBundleName" = "${PRODUCT_NAME}"; - diff --git a/RileyLink/ru.lproj/Localizable.strings b/RileyLink/ru.lproj/Localizable.strings index ad8630efc..6dd77cfc6 100644 --- a/RileyLink/ru.lproj/Localizable.strings +++ b/RileyLink/ru.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "Про"; @@ -23,7 +20,7 @@ "Fetch CGM" = "Получить данные мониторинга"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://мойсайт. azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "Инд № помпы"; -/* The title text for the pump Region config value */ -"Pump Region" = "Регион помпы"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "Регион помпы находится на задней стенке помпы в виде двух из последних трех знаков вида MMT-551NAB или MMT-515LWWS. Если ваша модель имеет \"NA\" то это Северная Америка. Если \"WW\", то это остальной мир."; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; /* The default placeholder string for a credential */ "Required" = "Обязательное значение"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "URL сайта"; @@ -49,6 +49,4 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "Передать в Nightscout"; -/* Label indicating validation is occurring */ -"Verifying" = "Верифицируется"; diff --git a/RileyLink/ru.lproj/LoopKit.strings b/RileyLink/ru.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/ru.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLink/sv.lproj/Localizable.strings b/RileyLink/sv.lproj/Localizable.strings new file mode 100644 index 000000000..1e0d8823c --- /dev/null +++ b/RileyLink/sv.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "Om"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Lägg till konto"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Radera konto"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Ange ditt 6-siffriga pump-ID"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Hämta CGM"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "Pump ID"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; + +/* The default placeholder string for a credential */ +"Required" = "Krävs"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Tryck för att age"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Upload To Nightscout"; + diff --git a/RileyLink/vi.lproj/Localizable.strings b/RileyLink/vi.lproj/Localizable.strings new file mode 100644 index 000000000..7daee5d47 --- /dev/null +++ b/RileyLink/vi.lproj/Localizable.strings @@ -0,0 +1,51 @@ +/* The title of the about section */ +"About" = "About"; + +/* The title of the button to add the credentials for a service */ +"Add Account" = "Thêm tài khoản"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* The title of the configuration section in settings */ +"Configuration" = "Cấu hình"; + +/* The title of the button to remove the credentials for a service */ +"Delete Account" = "Xóa bỏ tài khoản"; + +/* The placeholder text instructing users how to enter a pump ID */ +"Enter the 6-digit pump ID" = "Nhập 6 số ID của bơm"; + +/* The title text for the pull cgm Data cell */ +"Fetch CGM" = "Lấy dữ liệu từ CGM"; + +/* The placeholder text for the nightscout site URL credential */ +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The title text for the pump ID config value */ +"Pump ID" = "Số ID của bơm"; + +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; + +/* The default placeholder string for a credential */ +"Required" = "Bắc buộc"; + +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The empty-state text for a configuration value */ +"Tap to set" = "Chạm để cài đặt"; + +/* The title text for the nightscout upload enabled switch cell */ +"Upload To Nightscout" = "Upload To Nightscout"; + diff --git a/RileyLink/zh-Hans.lproj/InfoPlist.strings b/RileyLink/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index 92e48b3b0..000000000 --- a/RileyLink/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "${PRODUCT_NAME}"; - -/* Bundle name */ -"CFBundleName" = "${PRODUCT_NAME}"; - diff --git a/RileyLink/zh-Hans.lproj/Localizable.strings b/RileyLink/zh-Hans.lproj/Localizable.strings index 4b1d010d6..f421a5574 100644 --- a/RileyLink/zh-Hans.lproj/Localizable.strings +++ b/RileyLink/zh-Hans.lproj/Localizable.strings @@ -1,6 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - /* The title of the about section */ "About" = "关于"; @@ -23,7 +20,7 @@ "Fetch CGM" = "获取动态血糖数据"; /* The placeholder text for the nightscout site URL credential */ -"http://mysite.azurewebsites.net" = "http://mysite.azurewebsites.net"; +"http://mysite.herokuapp.com" = "http://mysite.herokuapp.com"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -31,15 +28,18 @@ /* The title text for the pump ID config value */ "Pump ID" = "胰岛素泵序列号"; -/* The title text for the pump Region config value */ -"Pump Region" = "胰岛素泵所属销售市场i"; - -/* Instructions on selecting the pump region */ -"Pump Region is listed on the back of your pump as two of the last three characters of the model string, which reads something like this: MMT-551NAB, or MMT-515LWWS. If your model has an \"NA\" in it, then the region is North America. If your model has an \"WW\" in it, then the region is WorldWide." = "泵的区域可作为型号(REF)的一部分印在背面,例如:MMT-551NAB或MMT-515LWWS。如果型号包含“NA”或“CA”,则该区域为北美。如果如果包含“WW”,则该区域是全球范围的。"; +/* Title text for section listing configured pumps */ +"Pumps" = "Pumps"; /* The default placeholder string for a credential */ "Required" = "需要"; +/* Title for RileyLink Testing main view controller */ +"RileyLink Testing" = "RileyLink Testing"; + +/* Title text for button to set up omnipod */ +"Setup Omnipod" = "Setup Omnipod"; + /* The title of the nightscout site URL credential */ "Site URL" = "网址"; @@ -48,7 +48,3 @@ /* The title text for the nightscout upload enabled switch cell */ "Upload To Nightscout" = "上传到Nightscout"; - -/* Label indicating validation is occurring */ -"Verifying" = "确认"; - diff --git a/RileyLink/zh-Hans.lproj/LoopKit.strings b/RileyLink/zh-Hans.lproj/LoopKit.strings deleted file mode 100644 index bc1fda9fb..000000000 --- a/RileyLink/zh-Hans.lproj/LoopKit.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* The title of the action used to dismiss an error alert */ -"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; - diff --git a/RileyLinkBLEKit/RileyLinkDeviceError.swift b/RileyLinkBLEKit/RileyLinkDeviceError.swift index a3f690ba4..72c8f13f6 100644 --- a/RileyLinkBLEKit/RileyLinkDeviceError.swift +++ b/RileyLinkBLEKit/RileyLinkDeviceError.swift @@ -26,7 +26,7 @@ extension RileyLinkDeviceError: LocalizedError { case .invalidResponse(let response): return String(format: LocalizedString("Response %@ is invalid", comment: "Invalid response error description (1: response)"), String(describing: response)) case .writeSizeLimitExceeded(let maxLength): - return String(format: LocalizedString("Data exceededs maximum size of %@ bytes", comment: "Write size limit exceeded error description (1: size limit)"), NumberFormatter.localizedString(from: NSNumber(value: maxLength), number: .none)) + return String(format: LocalizedString("Data exceeded maximum size of %@ bytes", comment: "Write size limit exceeded error description (1: size limit)"), NumberFormatter.localizedString(from: NSNumber(value: maxLength), number: .none)) case .responseTimeout: return LocalizedString("Pump did not respond in time", comment: "Response timeout error description") case .unsupportedCommand(let command): diff --git a/RileyLinkBLEKit/da.lproj/Localizable.strings b/RileyLinkBLEKit/da.lproj/Localizable.strings new file mode 100644 index 000000000..6ef8f4daa --- /dev/null +++ b/RileyLinkBLEKit/da.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "Data overskred den maksimale størrelse på %@ bytes"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "Input %@ er ugyldigt"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "Sørg for at det eksterne apparat (eks. Rileylink) er tæt på, så bør problemet blive løst automatisk"; + +/* Timeout error description */ +"Peripheral did not respond in time" = "Eksternt apparat (eks. Rileylink) svarede ikke i tide"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "Eksternt apparat (eks. Rileylink) er ikke forbundet"; + +/* Response timeout error description */ +"Pump did not respond in time" = "Pumpe svarede ikke i tide"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "Svaret %@ er ugyldigt"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "RileyLink firmware understøtter ikke %@ kommandoen"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "RileyLinket var midlertidigt afbrudt"; + +/* Error description */ +"Unknown characteristic" = "Ukendt karakteristika"; diff --git a/RileyLinkBLEKit/de.lproj/InfoPlist.strings b/RileyLinkBLEKit/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/es.lproj/InfoPlist.strings b/RileyLinkBLEKit/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/fi.lproj/Localizable.strings b/RileyLinkBLEKit/fi.lproj/Localizable.strings new file mode 100644 index 000000000..4c0779df7 --- /dev/null +++ b/RileyLinkBLEKit/fi.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "Tietomäärä ylitti maksimikoon %@ tavua"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "Syöte %@ on virheellinen"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "Varmista, että laite on lähellä, jolloin ongelman pitäisi ratketa itsestään"; + +/* Timeout error description */ +"Peripheral did not respond in time" = "Ulkoinen laite ei vastannut ajoissa"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "Ulkoinen laite ei ole yhdistetty"; + +/* Response timeout error description */ +"Pump did not respond in time" = "Pumppu ei vastannut ajoissa"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "Vastaus %@ on virheellinen"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "RileyLink-laiteohelmisto ei tue %@ komentoa"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "Yhteys RileyLinkiin oli tilapäisesti poikki"; + +/* Error description */ +"Unknown characteristic" = "Tuntematon tunnus"; diff --git a/RileyLinkBLEKit/fr.lproj/InfoPlist.strings b/RileyLinkBLEKit/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/it.lproj/InfoPlist.strings b/RileyLinkBLEKit/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/ja.lproj/Localizable.strings b/RileyLinkBLEKit/ja.lproj/Localizable.strings new file mode 100644 index 000000000..bf0fa59d1 --- /dev/null +++ b/RileyLinkBLEKit/ja.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "データが最大サイズの %@バイトを超えました"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "%@のインプットは無効です"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "デバイスを近くにおいてください。問題は自動的に解決されます。"; + +/* Timeout error description */ +"Peripheral did not respond in time" = "危機が時間内に反応しませんでした"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "危機が接続されていません"; + +/* Response timeout error description */ +"Pump did not respond in time" = "ポンプが時間内に反応しませんでした"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "%@のレスポンスは無効です"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "RileyLinkファームウェアは%@ コマンドをサポートしていません"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "RileyLinkの接続が一時的に切れました"; + +/* Error description */ +"Unknown characteristic" = "エラー不明"; diff --git a/RileyLinkBLEKit/nb.lproj/InfoPlist.strings b/RileyLinkBLEKit/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/nl.lproj/InfoPlist.strings b/RileyLinkBLEKit/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/pl.lproj/InfoPlist.strings b/RileyLinkBLEKit/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/pt-BR.lproj/Localizable.strings b/RileyLinkBLEKit/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..ecfb95764 --- /dev/null +++ b/RileyLinkBLEKit/pt-BR.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "Dados excederam o tamanho máximo de %@ bytes"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "Entrada %@ é inválida"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "Confira que o dispositivo esteja próximo, e o problema deve se resolver automaticamente."; + +/* Timeout error description */ +"Peripheral did not respond in time" = "Acessório não respondeu a tempo"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "Acessório não está conectado"; + +/* Response timeout error description */ +"Pump did not respond in time" = "Bomba não respondeu a tempo"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "Resposta %@ é inválida"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "O firmware do RileyLink não suporta o comando %@"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "O RileyLink foi temporariamente desconectado"; + +/* Error description */ +"Unknown characteristic" = "Característica Desconhecida"; diff --git a/RileyLinkBLEKit/ro.lproj/Localizable.strings b/RileyLinkBLEKit/ro.lproj/Localizable.strings new file mode 100644 index 000000000..069dfd9e6 --- /dev/null +++ b/RileyLinkBLEKit/ro.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "Datele depășesc cantitatea maximă de %@ bytes"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "Introducerea %@ este invalidă"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "Plasarea dispozitivul în apropiere, ar trebui să rezolve problema"; + +/* Timeout error description */ +"Peripheral did not respond in time" = "Dispozitivul periferic nu a răspuns în timp util"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "Dispozitivul periferic nu este conectat"; + +/* Response timeout error description */ +"Pump did not respond in time" = "Pompa nu a răspuns în timp util"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "Răspunsul %@ este invalid"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "Firmware-ul RileyLink nu sprijină %@ comanda"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "RileyLink a fost temporar deconectat"; + +/* Error description */ +"Unknown characteristic" = "Caracteristică necunoscută"; diff --git a/RileyLinkBLEKit/ru.lproj/InfoPlist.strings b/RileyLinkBLEKit/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKit/sv.lproj/Localizable.strings b/RileyLinkBLEKit/sv.lproj/Localizable.strings new file mode 100644 index 000000000..2e1c2b7e2 --- /dev/null +++ b/RileyLinkBLEKit/sv.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "Data överstiger maximal antal av %@ bytes"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "Inmatning %@ ogiltig"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "Säkerställ att enhet är inom räckhåll, så borde problem lösas automatiskt"; + +/* Timeout error description */ +"Peripheral did not respond in time" = "Perifer enhet svarade inte i tid"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "Perifer enhet är inte ansluten"; + +/* Response timeout error description */ +"Pump did not respond in time" = "Pump svarade inte i tid"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "Respons %@ ogiltig"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "RileyLink firmware stöder inte kommandot %@"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "RileyLink var temporärt frånkopplad"; + +/* Error description */ +"Unknown characteristic" = "Okänd karakterisktika"; diff --git a/RileyLinkBLEKit/vi.lproj/Localizable.strings b/RileyLinkBLEKit/vi.lproj/Localizable.strings new file mode 100644 index 000000000..01a288af9 --- /dev/null +++ b/RileyLinkBLEKit/vi.lproj/Localizable.strings @@ -0,0 +1,29 @@ +/* Write size limit exceeded error description (1: size limit) */ +"Data exceeded maximum size of %@ bytes" = "Dữ liệu vượt quá dung lượng cho phép %@ bytes"; + +/* Invalid input error description (1: input) */ +"Input %@ is invalid" = "Nguồn nhập liệu %@ không tồn tại"; + +/* Recovery suggestion for unknown peripheral characteristic */ +"Make sure the device is nearby, and the issue should resolve automatically" = "Chắc chắn rằng thiết bị đang bên cạnh và các vấn đề sẽ được giải quyết tự động"; + +/* Timeout error description */ +"Peripheral did not respond in time" = "Ngoại vi không đáp ứng kịp thời"; + +/* Not ready error description */ +"Peripheral isnʼt connected" = "Ngoại vi không được kết nối"; + +/* Response timeout error description */ +"Pump did not respond in time" = "Bơm không phản ứng kịp lúc"; + +/* Invalid response error description (1: response) */ +"Response %@ is invalid" = "Phản hồi %@ không hợp lệ"; + +/* Unsupported command error description */ +"RileyLink firmware does not support the %@ command" = "Chương trình cơ sở của RileyLink không hỗ trợ lệnh %@"; + +/* Failure reason: unknown peripheral characteristic */ +"The RileyLink was temporarily disconnected" = "Rileylink tạm thời mất kết nối"; + +/* Error description */ +"Unknown characteristic" = "Đặc điểm không xác định"; diff --git a/RileyLinkBLEKit/zh-Hans.lproj/InfoPlist.strings b/RileyLinkBLEKit/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKit/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/de.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/es.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/fr.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/it.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/nb.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/nl.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/pl.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/ru.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkBLEKitTests/zh-Hans.lproj/InfoPlist.strings b/RileyLinkBLEKitTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkBLEKitTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/de.lproj/InfoPlist.strings b/RileyLinkKitTests/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/es.lproj/InfoPlist.strings b/RileyLinkKitTests/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/fr.lproj/InfoPlist.strings b/RileyLinkKitTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/it.lproj/InfoPlist.strings b/RileyLinkKitTests/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/nb.lproj/InfoPlist.strings b/RileyLinkKitTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/nl.lproj/InfoPlist.strings b/RileyLinkKitTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/pl.lproj/InfoPlist.strings b/RileyLinkKitTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/ru.lproj/InfoPlist.strings b/RileyLinkKitTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitTests/zh-Hans.lproj/InfoPlist.strings b/RileyLinkKitTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/Base.lproj/Localizable.strings b/RileyLinkKitUI/Base.lproj/Localizable.strings index 1150ab038..4f1beecdc 100644 --- a/RileyLinkKitUI/Base.lproj/Localizable.strings +++ b/RileyLinkKitUI/Base.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Awake Until"; - /* The title of the section describing commands */ "Commands" = "Commands"; @@ -31,22 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Last Awake"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Listening Off"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Name"; -/* The title of the cell showing the last idle */ -"On Idle" = "On Idle"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink allows for communication with the pump over Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Signal Strength"; - +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/da.lproj/Localizable.strings b/RileyLinkKitUI/da.lproj/Localizable.strings new file mode 100644 index 000000000..c3c082724 --- /dev/null +++ b/RileyLinkKitUI/da.lproj/Localizable.strings @@ -0,0 +1,41 @@ +/* The title of the section describing commands */ +"Commands" = "Kommandoer"; + +/* The connected state */ +"Connected" = "Tilsluttet"; + +/* The in-progress connecting state */ +"Connecting" = "Tilslutter"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Tilslutningstilstand"; + +/* The title of the section describing the device */ +"Device" = "Enhed"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "Enheder"; + +/* The disconnected state */ +"Disconnected" = "Frakoblet"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "Frakobler"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frekvens"; + +/* The title of the cell showing device name */ +"Name" = "Navn"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink tillader kommunikation med pumpen vha Bluetooth Lav Energi."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Signal Styrke"; + +/* The title of the cell showing uptime */ +"Uptime" = "Oppetid"; diff --git a/RileyLinkKitUI/de.lproj/InfoPlist.strings b/RileyLinkKitUI/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/de.lproj/Localizable.strings b/RileyLinkKitUI/de.lproj/Localizable.strings index 8594bf650..f2813507c 100644 --- a/RileyLinkKitUI/de.lproj/Localizable.strings +++ b/RileyLinkKitUI/de.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Aktiv bis"; - /* The title of the section describing commands */ "Commands" = "Befehle"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Zuletzt aktiv"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Signal aus"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Nombre"; -/* The title of the cell showing the last idle */ -"On Idle" = "im Leerlauf"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink ermöglicht Kommunikation zur Pumpe über Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Signalstärke"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/es.lproj/InfoPlist.strings b/RileyLinkKitUI/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/es.lproj/Localizable.strings b/RileyLinkKitUI/es.lproj/Localizable.strings index 59a7a923b..7d05e9519 100644 --- a/RileyLinkKitUI/es.lproj/Localizable.strings +++ b/RileyLinkKitUI/es.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Despierto hasta"; - /* The title of the section describing commands */ "Commands" = "Comandos"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Último Despierto"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Escuchando Apagado"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequencia"; /* The title of the cell showing device name */ "Name" = "Nombre"; -/* The title of the cell showing the last idle */ -"On Idle" = "En Inactivo"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink permite la comunicación con la microinfusora a través del uso de Bluetooth de baja energía."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Intensidad de señal"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/fi.lproj/Localizable.strings b/RileyLinkKitUI/fi.lproj/Localizable.strings new file mode 100644 index 000000000..13132f0f9 --- /dev/null +++ b/RileyLinkKitUI/fi.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* The title of the section describing commands */ +"Commands" = "Komennot"; + +/* The connected state */ +"Connected" = "Yhteydessä"; + +/* The in-progress connecting state */ +"Connecting" = "Yhdistetään"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Yhteyden tila"; + +/* The title of the section describing the device */ +"Device" = "Laite"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "Laitteet"; + +/* The disconnected state */ +"Disconnected" = "Ei yhteydessä"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "Katkaistaan"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Laiteohjelmisto"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Taajuus"; + +/* The title of the cell showing device name */ +"Name" = "Nimi"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink mahdollistaa tiedonsiirron pumpun kanssa Bluetooth Low Energy -yhteyden kautta."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Signaalin vahvuus"; + +/* The title of the cell showing uptime */ +"Uptime" = "Päällä"; + diff --git a/RileyLinkKitUI/fr.lproj/InfoPlist.strings b/RileyLinkKitUI/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/fr.lproj/Localizable.strings b/RileyLinkKitUI/fr.lproj/Localizable.strings index bd9f456b4..0f80f7015 100644 --- a/RileyLinkKitUI/fr.lproj/Localizable.strings +++ b/RileyLinkKitUI/fr.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Réveillez-vous jusqu’à"; - /* The title of the section describing commands */ "Commands" = "Commandes"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Dernier éveillé"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Listening Off"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Nom"; -/* The title of the cell showing the last idle */ -"On Idle" = "Au Repos"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink permet la communication avec la pompe via Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Force du signal"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/it.lproj/InfoPlist.strings b/RileyLinkKitUI/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/it.lproj/Localizable.strings b/RileyLinkKitUI/it.lproj/Localizable.strings index 147c33f67..3d0f629a0 100644 --- a/RileyLinkKitUI/it.lproj/Localizable.strings +++ b/RileyLinkKitUI/it.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Attivo fino"; - /* The title of the section describing commands */ "Commands" = "Comandi"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Ultimo risveglio"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Ascolto Spento"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Nome"; -/* The title of the cell showing the last idle */ -"On Idle" = "Inattivo"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink consente la comunicazione con il microinfusore tramite Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Potenza Segnale"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/ja.lproj/Localizable.strings b/RileyLinkKitUI/ja.lproj/Localizable.strings new file mode 100644 index 000000000..015ca15a0 --- /dev/null +++ b/RileyLinkKitUI/ja.lproj/Localizable.strings @@ -0,0 +1,41 @@ +/* The title of the section describing commands */ +"Commands" = "コマンド"; + +/* The connected state */ +"Connected" = "接続済み"; + +/* The in-progress connecting state */ +"Connecting" = "接続しています"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "接続状態"; + +/* The title of the section describing the device */ +"Device" = "デバイス"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "デバイス"; + +/* The disconnected state */ +"Disconnected" = "無接続"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "接続を切っています"; + +/* The title of the cell showing firmware version */ +"Firmware" = "ファームウェア"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; + +/* The title of the cell showing device name */ +"Name" = "機器名"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink は Bluetooth Low Energy を通してポンプと通信します。"; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "シグナル強度"; + +/* The title of the cell showing uptime */ +"Uptime" = "アップタイム"; diff --git a/RileyLinkKitUI/nb.lproj/InfoPlist.strings b/RileyLinkKitUI/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/nb.lproj/Localizable.strings b/RileyLinkKitUI/nb.lproj/Localizable.strings index ae10cc924..981153150 100644 --- a/RileyLinkKitUI/nb.lproj/Localizable.strings +++ b/RileyLinkKitUI/nb.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Våken til"; - /* The title of the section describing commands */ "Commands" = "Kommandoer"; @@ -29,23 +23,19 @@ "Disconnecting" = "Kobler fra"; /* The title of the cell showing firmware version */ -"Firmware" = "Firmware"; +"Firmware" = "Fastvare"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Sist våken"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Lytting skrudd av skrudd av"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frekvens"; /* The title of the cell showing device name */ "Name" = "Navn"; -/* The title of the cell showing the last idle */ -"On Idle" = "Pauset"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink tillater kommunikasjon med pumpen over Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Signalstyrke"; +/* The title of the cell showing uptime */ +"Uptime" = "Oppetid"; diff --git a/RileyLinkKitUI/nl.lproj/InfoPlist.strings b/RileyLinkKitUI/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/nl.lproj/Localizable.strings b/RileyLinkKitUI/nl.lproj/Localizable.strings index 45aa7b9ce..d872c7de2 100644 --- a/RileyLinkKitUI/nl.lproj/Localizable.strings +++ b/RileyLinkKitUI/nl.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Actief tot"; - /* The title of the section describing commands */ "Commands" = "Commando's"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Firmware"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Laatst actief"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Luisteren uit"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Naam"; -/* The title of the cell showing the last idle */ -"On Idle" = "Inactief"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink staat verbinding met de pomp toe via Bluetooth Low Energy (BLE of Bluetooth Smart)."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Signaalsterkte"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/pl.lproj/InfoPlist.strings b/RileyLinkKitUI/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/pl.lproj/Localizable.strings b/RileyLinkKitUI/pl.lproj/Localizable.strings index 668ffb0dc..fad90ce69 100644 --- a/RileyLinkKitUI/pl.lproj/Localizable.strings +++ b/RileyLinkKitUI/pl.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Aktywny od"; - /* The title of the section describing commands */ "Commands" = "Komendy"; @@ -31,22 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Oprogramowanie"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Ostatnio aktywny"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Brak danych o aktywności"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Nazwa"; -/* The title of the cell showing the last idle */ -"On Idle" = "Bezczynny od"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink będzie łączył się z pompą poprzez Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Siła sygnału"; - +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/pt-BR.lproj/Localizable.strings b/RileyLinkKitUI/pt-BR.lproj/Localizable.strings new file mode 100644 index 000000000..7a8492b88 --- /dev/null +++ b/RileyLinkKitUI/pt-BR.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* The title of the section describing commands */ +"Commands" = "Comandos"; + +/* The connected state */ +"Connected" = "Conectado"; + +/* The in-progress connecting state */ +"Connecting" = "Conectando"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Estado da Conexão"; + +/* The title of the section describing the device */ +"Device" = "Dispositivo"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "Dispositivos"; + +/* The disconnected state */ +"Disconnected" = "Desconectado"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "Desconectando"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequência"; + +/* The title of the cell showing device name */ +"Name" = "Nome"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "O RileyLink permite a comunicação com a bomba por Bluetooth de Baixa Energia."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Potência do Sinal"; + +/* The title of the cell showing uptime */ +"Uptime" = "Tempo de Atividade"; + diff --git a/RileyLinkKitUI/ro.lproj/Localizable.strings b/RileyLinkKitUI/ro.lproj/Localizable.strings new file mode 100644 index 000000000..786303fb6 --- /dev/null +++ b/RileyLinkKitUI/ro.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* The title of the section describing commands */ +"Commands" = "Comenzi"; + +/* The connected state */ +"Connected" = "Conectat"; + +/* The in-progress connecting state */ +"Connecting" = "Se conectează"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Stare conexiune"; + +/* The title of the section describing the device */ +"Device" = "Dispozitiv"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "Dispozitive"; + +/* The disconnected state */ +"Disconnected" = "Deconectat"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "Se deconectează"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frecvență"; + +/* The title of the cell showing device name */ +"Name" = "Nume"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink facilitează conexiunea cu pompa prin intermediul Bluetooth Low Energy."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Putere semnal"; + +/* The title of the cell showing uptime */ +"Uptime" = "Durată de la pornire"; + diff --git a/RileyLinkKitUI/ru.lproj/InfoPlist.strings b/RileyLinkKitUI/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/ru.lproj/Localizable.strings b/RileyLinkKitUI/ru.lproj/Localizable.strings index e0ce23218..1366edc51 100644 --- a/RileyLinkKitUI/ru.lproj/Localizable.strings +++ b/RileyLinkKitUI/ru.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "Рабочее состояние до"; - /* The title of the section describing commands */ "Commands" = "Команды"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "Прошивка"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "Недавнее состояние активности"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "Получаю данные от"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "Название"; -/* The title of the cell showing the last idle */ -"On Idle" = "Бездействие"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink позволяет вести коммуникацию с помпой через Bluetooth Low Energy."; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "Уровень сигнала"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/sv.lproj/Localizable.strings b/RileyLinkKitUI/sv.lproj/Localizable.strings new file mode 100644 index 000000000..12e24143b --- /dev/null +++ b/RileyLinkKitUI/sv.lproj/Localizable.strings @@ -0,0 +1,41 @@ +/* The title of the section describing commands */ +"Commands" = "Kommandon"; + +/* The connected state */ +"Connected" = "Ansluten"; + +/* The in-progress connecting state */ +"Connecting" = "Asluter"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Status på anslutning"; + +/* The title of the section describing the device */ +"Device" = "Enhet"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "Enheter"; + +/* The disconnected state */ +"Disconnected" = "Frånkopplad"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "Kopplar från"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; + +/* The title of the cell showing device name */ +"Name" = "Namn"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink kommuicerar med pump via Bluetooth lågenergianslutning."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Signalstyrka"; + +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkKitUI/vi.lproj/Localizable.strings b/RileyLinkKitUI/vi.lproj/Localizable.strings new file mode 100644 index 000000000..cdeaed7b9 --- /dev/null +++ b/RileyLinkKitUI/vi.lproj/Localizable.strings @@ -0,0 +1,41 @@ +/* The title of the section describing commands */ +"Commands" = "Commands"; + +/* The connected state */ +"Connected" = "Đã kết nối"; + +/* The in-progress connecting state */ +"Connecting" = "Đang kết nối"; + +/* The title of the cell showing BLE connection state */ +"Connection State" = "Tình trạng kết nối"; + +/* The title of the section describing the device */ +"Device" = "Thiết bị"; + +/* The title of the devices table section in RileyLink settings */ +"Devices" = "Thiết bị"; + +/* The disconnected state */ +"Disconnected" = "Đã ngắt kết nối"; + +/* The in-progress disconnecting state */ +"Disconnecting" = "Đang ngắt kết nối"; + +/* The title of the cell showing firmware version */ +"Firmware" = "Firmware"; + +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Tần số"; + +/* The title of the cell showing device name */ +"Name" = "Tên"; + +/* RileyLink setup description */ +"RileyLink allows for communication with the pump over Bluetooth Low Energy." = "RileyLink cho phép giao tiếp với bơm thông qua chuẩn kết nối bluetooth năng lượng thấp."; + +/* The title of the cell showing BLE signal strength (RSSI) */ +"Signal Strength" = "Cường độ tín hiệu"; + +/* The title of the cell showing uptime */ +"Uptime" = "Thời gian hoạt động"; diff --git a/RileyLinkKitUI/zh-Hans.lproj/InfoPlist.strings b/RileyLinkKitUI/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f904..000000000 --- a/RileyLinkKitUI/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/RileyLinkKitUI/zh-Hans.lproj/Localizable.strings b/RileyLinkKitUI/zh-Hans.lproj/Localizable.strings index 815d49a0c..5fdcc0201 100644 --- a/RileyLinkKitUI/zh-Hans.lproj/Localizable.strings +++ b/RileyLinkKitUI/zh-Hans.lproj/Localizable.strings @@ -1,9 +1,3 @@ -/* Unit format string for an RSSI value in decibles */ -"%@ dB" = "%@ dB"; - -/* The title of the cell describing an awake radio */ -"Awake Until" = "唤醒 "; - /* The title of the section describing commands */ "Commands" = "命令"; @@ -31,21 +25,17 @@ /* The title of the cell showing firmware version */ "Firmware" = "固件"; -/* The title of the cell describing an awake radio */ -"Last Awake" = "最近唤醒"; - -/* The title of the cell describing no radio awake data */ -"Listening Off" = "监听关闭"; +/* The title of the cell showing current rileylink frequency */ +"Frequency" = "Frequency"; /* The title of the cell showing device name */ "Name" = "设备名称"; -/* The title of the cell showing the last idle */ -"On Idle" = "空闲"; - /* RileyLink setup description */ "RileyLink allows for communication with the pump over Bluetooth Low Energy." = "允许RileyLink通过低功耗蓝牙与泵连接通信"; /* The title of the cell showing BLE signal strength (RSSI) */ "Signal Strength" = "信号强度"; +/* The title of the cell showing uptime */ +"Uptime" = "Uptime"; diff --git a/RileyLinkTests/de.lproj/InfoPlist.strings b/RileyLinkTests/de.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/en.lproj/InfoPlist.strings b/RileyLinkTests/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/es.lproj/InfoPlist.strings b/RileyLinkTests/es.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/fr.lproj/InfoPlist.strings b/RileyLinkTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/it.lproj/InfoPlist.strings b/RileyLinkTests/it.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/nb.lproj/InfoPlist.strings b/RileyLinkTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/nl.lproj/InfoPlist.strings b/RileyLinkTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/pl.lproj/InfoPlist.strings b/RileyLinkTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/ru.lproj/InfoPlist.strings b/RileyLinkTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/RileyLinkTests/zh-Hans.lproj/InfoPlist.strings b/RileyLinkTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/RileyLinkTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - From 0167844848a40a946cd259ff915e32019a9361b7 Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Sat, 28 Dec 2019 12:30:58 -0800 Subject: [PATCH 66/71] Upgrade Bolus Beeps to more general purpose Confirmation Beeps (#565) * Upgrade Bolus Beeps to more general purpose Confirmation Beeps which are Loop's version of the PDM's Confidence Reminders functionality that when enabled adds confirmation beeps for user initiated Pod activity when doing Suspend/Resume Delivery, Change Time Zone, Change Basal Rate, Test Command & upon completion of Pod Setup. * emitConfirmationBeep() simplifcation * beep when returning to normal basal if doing temp basal confirmation beeps * Remove the ability to easily customize the level of confirmation beeps --- OmniKit/PumpManager/OmnipodPumpManager.swift | 68 ++++++++++++------- .../PumpManager/OmnipodPumpManagerState.swift | 10 +-- .../OmnipodSettingsViewController.swift | 18 ++--- .../PodSetupCompleteViewController.swift | 10 +++ 4 files changed, 68 insertions(+), 38 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index 9fee9bf90..ba3144b35 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -13,6 +13,7 @@ import RileyLinkBLEKit import UserNotifications import os.log +fileprivate let tempBasalConfirmationBeeps: Bool = false // whether to emit temp basal confirmation beeps (for testing use) public enum ReservoirAlertState { @@ -365,13 +366,13 @@ extension OmnipodPumpManager { } // Thread-safe - public var bolusBeeps: Bool { + public var confirmationBeeps: Bool { get { - return state.bolusBeeps + return state.confirmationBeeps } set { setState { (state) in - state.bolusBeeps = newValue + state.confirmationBeeps = newValue } } } @@ -636,7 +637,13 @@ extension OmnipodPumpManager { #endif } - private func checkCannulaInsertionFinished() { + private func emitConfirmationBeep(session: PodCommsSession, beepConfigType: BeepConfigType) { + if self.confirmationBeeps{ + session.beepConfig(beepConfigType: beepConfigType, basalCompletionBeep: true, tempBasalCompletionBeep: tempBasalConfirmationBeeps, bolusCompletionBeep: true) + } + } + + public func checkCannulaInsertionFinished() { let deviceSelector = self.rileyLinkDeviceProvider.firstConnectedDevice self.podComms.runSession(withName: "Check cannula insertion finished", using: deviceSelector) { (result) in switch result { @@ -644,10 +651,10 @@ extension OmnipodPumpManager { do { try session.checkInsertionCompleted() } catch let error { - self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + self.log.error("Failed to fetch pod status: %{public}@", String(describing: error)) } case .failure(let error): - self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + self.log.error("Failed to fetch pod status: %{public}@", String(describing: error)) } } } @@ -687,7 +694,7 @@ extension OmnipodPumpManager { } } catch let error { completion?(.failure(error)) - self.log.error("Failed to fetch pump status: %{public}@", String(describing: error)) + self.log.error("Failed to fetch pod status: %{public}@", String(describing: error)) } } } @@ -744,7 +751,7 @@ extension OmnipodPumpManager { switch result { case .success(let session): do { - let beep = false + let beep = self.confirmationBeeps let _ = try session.setTime(timeZone: timeZone, basalSchedule: self.state.basalSchedule, date: Date(), acknowledgementBeep: beep, completionBeep: beep) self.setState { (state) in state.timeZone = timeZone @@ -801,7 +808,7 @@ extension OmnipodPumpManager { case .success: break } - let beep = false + let beep = self.confirmationBeeps let _ = try session.setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) self.setState { (state) in @@ -874,7 +881,7 @@ extension OmnipodPumpManager { } public func testingCommands(completion: @escaping (Error?) -> Void) { - // use hasSetupPod so that the user can see any fault info + // use hasSetupPod so the user can see any fault info and post fault commands can be attempted guard self.hasSetupPod else { completion(OmnipodPumpManagerError.noPodPaired) return @@ -886,6 +893,7 @@ extension OmnipodPumpManager { case .success(let session): do { try session.testingCommands() + self.emitConfirmationBeep(session: session, beepConfigType: .bipBip) completion(nil) } catch let error { completion(error) @@ -911,7 +919,10 @@ extension OmnipodPumpManager { self.podComms.runSession(withName: "Play Test Beeps", using: rileyLinkSelector) { (result) in switch result { case .success(let session): - session.beepConfig(beepConfigType: .bipBeepBipBeepBipBeepBipBeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: self.bolusBeeps) + let basalCompletionBeep = self.confirmationBeeps + let tempBasalCompletionBeep = self.confirmationBeeps && tempBasalConfirmationBeeps + let bolusCompletionBeep = self.confirmationBeeps + session.beepConfig(beepConfigType: .bipBeepBipBeepBipBeepBipBeep, basalCompletionBeep: basalCompletionBeep, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) // Don't bother emitting another beep sequence to approximate the PDM "Check alarms" function as the Pod beeping is // asynchronous to the UI which will have already printed Succeeded before the first beep sequence is done playing completion(nil) @@ -939,11 +950,14 @@ extension OmnipodPumpManager { case .success(let session): do { // read up to the most recent 50 entries from flash log + self.emitConfirmationBeep(session: session, beepConfigType: .bipBip) try session.readFlashLogsRequest(podInfoResponseSubType: .flashLogRecent) // read up to the previous 50 entries from flash log + self.emitConfirmationBeep(session: session, beepConfigType: .bipBip) try session.readFlashLogsRequest(podInfoResponseSubType: .dumpOlderFlashlog) + self.emitConfirmationBeep(session: session, beepConfigType: .beeeeeep) completion(nil) } catch let error { completion(error) @@ -954,22 +968,22 @@ extension OmnipodPumpManager { } } - public func setBolusBeeps(enabled: Bool, completion: @escaping (Error?) -> Void) { - self.bolusBeeps = enabled // set here to allow changes on a faulted Pod - self.log.default("Set Bolus Beeps to %s", String(describing: enabled)) + public func setConfirmationBeeps(enabled: Bool, completion: @escaping (Error?) -> Void) { + self.confirmationBeeps = enabled // set here to allow changes on a faulted Pod + self.log.default("Set Confirmation Beeps to %s", String(describing: enabled)) guard self.hasActivePod else { completion(nil) return } let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice - let name: String = enabled ? "Enable Bolus Beeps" : "Disable Bolus Beeps" + let name: String = enabled ? "Enable Confirmation Beeps" : "Disable Confirmation Beeps" self.podComms.runSession(withName: name, using: rileyLinkSelector) { (result) in switch result { case .success(let session): let beepConfigType: BeepConfigType = enabled ? .bipBip : .noBeep - let basalCompletionBeep = false - let tempBasalCompletionBeep = false + let basalCompletionBeep = enabled + let tempBasalCompletionBeep = enabled && tempBasalConfirmationBeeps let bolusCompletionBeep = enabled // enable/disable Pod completion beeps for any in-progress insulin delivery @@ -1092,7 +1106,7 @@ extension OmnipodPumpManager: PumpManager { state.suspendEngageState = .engaging }) - // N.B. with a deliveryType of .all and a beepType other then .noBeep, the Pod will emit 3 beeps! + // N.B. with a deliveryType of .all and a beepType other then .noBeep, the Pod will emit 3 beeps! Use .noBeep here & do beeping at end. let result = session.cancelDelivery(deliveryType: .all, beepType: .noBeep) switch result { case .certainFailure(let error): @@ -1100,6 +1114,10 @@ extension OmnipodPumpManager: PumpManager { case .uncertainFailure(let error): completion(error) case .success: + // Do a separate single confirmation beep if appropriate. There are no in-progress deliveries to worry about after the cancel all. + if self.confirmationBeeps { + session.beepConfig(beepConfigType: .beeeeeep, basalCompletionBeep: false, tempBasalCompletionBeep: false, bolusCompletionBeep: false) + } session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) } @@ -1137,7 +1155,7 @@ extension OmnipodPumpManager: PumpManager { do { let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) - let beep = false + let beep = self.confirmationBeeps let _ = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) @@ -1240,7 +1258,7 @@ extension OmnipodPumpManager: PumpManager { if podStatus.deliveryStatus == .suspended { do { let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) - let beep = false + let beep = self.confirmationBeeps podStatus = try session.resumeBasal(schedule: self.state.basalSchedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) } catch let error { completion(.failure(SetBolusError.certain(error as? PodCommsError ?? PodCommsError.commsError(error: error)))) @@ -1258,7 +1276,7 @@ extension OmnipodPumpManager: PumpManager { let dose = DoseEntry(type: .bolus, startDate: date, endDate: endDate, value: enactUnits, unit: .units) willRequest(dose) - let beep = self.bolusBeeps + let beep = self.confirmationBeeps let result = session.bolus(units: enactUnits, acknowledgementBeep: beep, completionBeep: beep) session.dosesForStorage() { (doses) -> Bool in return self.store(doses: doses, in: session) @@ -1313,7 +1331,7 @@ extension OmnipodPumpManager: PumpManager { } // when cancelling a bolus give a type 6 beeeeeep to match PDM if doing bolus confirmation beeps - let beeptype: BeepType = self.bolusBeeps ? .beeeeeep : .noBeep + let beeptype: BeepType = self.confirmationBeeps ? .beeeeeep : .noBeep let result = session.cancelDelivery(deliveryType: .bolus, beepType: beeptype) switch result { case .certainFailure(let error): @@ -1377,7 +1395,9 @@ extension OmnipodPumpManager: PumpManager { let status: StatusResponse let canceledDose: UnfinalizedDose? - let result = session.cancelDelivery(deliveryType: .tempBasal, beepType: .noBeep) + // if resuming a normal basal as denoted by a 0 duration temp basal, use a confirmation beep if appropriate + let beep: BeepType = duration < .ulpOfOne && self.confirmationBeeps && tempBasalConfirmationBeeps ? .beep : .noBeep + let result = session.cancelDelivery(deliveryType: .tempBasal, beepType: beep) switch result { case .certainFailure(let error): throw error @@ -1419,7 +1439,7 @@ extension OmnipodPumpManager: PumpManager { state.tempBasalEngageState = .engaging }) - let beep = false + let beep = self.confirmationBeeps && tempBasalConfirmationBeeps let result = session.setTempBasal(rate: rate, duration: duration, acknowledgementBeep: beep, completionBeep: beep) let basalStart = Date() let dose = DoseEntry(type: .tempBasal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: rate, unit: .unitsPerHour) diff --git a/OmniKit/PumpManager/OmnipodPumpManagerState.swift b/OmniKit/PumpManager/OmnipodPumpManagerState.swift index 62299dbc9..a486f27e8 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -30,7 +30,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { public var expirationReminderDate: Date? - public var bolusBeeps: Bool + public var confirmationBeeps: Bool // Temporal state not persisted @@ -56,7 +56,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { self.basalSchedule = basalSchedule self.rileyLinkConnectionManagerState = rileyLinkConnectionManagerState self.unstoredDoses = [] - self.bolusBeeps = false + self.confirmationBeeps = false } public init?(rawValue: RawValue) { @@ -131,7 +131,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { self.unstoredDoses = [] } - self.bolusBeeps = rawValue["bolusBeeps"] as? Bool ?? false + self.confirmationBeeps = rawValue["confirmationBeeps"] as? Bool ?? rawValue["bolusBeeps"] as? Bool ?? false } public var rawValue: RawValue { @@ -141,7 +141,7 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { "basalSchedule": basalSchedule.rawValue, "messageLog": messageLog.rawValue, "unstoredDoses": unstoredDoses.map { $0.rawValue }, - "bolusBeeps": bolusBeeps, + "confirmationBeeps": confirmationBeeps, ] if let podState = podState { @@ -190,7 +190,7 @@ extension OmnipodPumpManagerState: CustomDebugStringConvertible { "* tempBasalEngageState: \(String(describing: tempBasalEngageState))", "* lastPumpDataReportDate: \(String(describing: lastPumpDataReportDate))", "* isPumpDataStale: \(String(describing: isPumpDataStale))", - "* bolusBeeps: \(String(describing: bolusBeeps))", + "* confirmationBeeps: \(String(describing: confirmationBeeps))", String(reflecting: podState), String(reflecting: rileyLinkConnectionManagerState), String(reflecting: messageLog), diff --git a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift index fef475268..b556b6d0b 100644 --- a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -16,9 +16,9 @@ public class ConfirmationBeepsTableViewCell: TextButtonTableViewCell { public func updateTextLabel(enabled: Bool) { if enabled { - self.textLabel?.text = LocalizedString("Disable Bolus Beeps", comment: "Title text for button to disable bolus beeps") + self.textLabel?.text = LocalizedString("Disable Confirmation Beeps", comment: "Title text for button to disable confirmation beeps") } else { - self.textLabel?.text = LocalizedString("Enable Bolus Beeps", comment: "Title text for button to enable bolus beeps") + self.textLabel?.text = LocalizedString("Enable Confirmation Beeps", comment: "Title text for button to enable confirmation beeps") } } @@ -68,7 +68,7 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { lazy var confirmationBeepsTableViewCell: ConfirmationBeepsTableViewCell = { let cell = ConfirmationBeepsTableViewCell(style: .default, reuseIdentifier: nil) - cell.updateTextLabel(enabled: pumpManager.bolusBeeps) + cell.updateTextLabel(enabled: pumpManager.confirmationBeeps) return cell }() @@ -594,12 +594,12 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { } private func confirmationBeepsTapped() { - let confirmationBeeps: Bool = pumpManager.bolusBeeps + let confirmationBeeps: Bool = pumpManager.confirmationBeeps func done() { DispatchQueue.main.async { [weak self] in if let self = self { - self.confirmationBeepsTableViewCell.updateTextLabel(enabled: self.pumpManager.bolusBeeps) + self.confirmationBeepsTableViewCell.updateTextLabel(enabled: self.pumpManager.confirmationBeeps) self.confirmationBeepsTableViewCell.isLoading = false } } @@ -607,20 +607,20 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { confirmationBeepsTableViewCell.isLoading = true if confirmationBeeps { - pumpManager.setBolusBeeps(enabled: false, completion: { (error) in + pumpManager.setConfirmationBeeps(enabled: false, completion: { (error) in if let error = error { DispatchQueue.main.async { - let title = LocalizedString("Error disabling bolus beeps", comment: "The alert title for disable bolus beeps error") + let title = LocalizedString("Error disabling confirmation beeps", comment: "The alert title for disable confirmation beeps error") self.present(UIAlertController(with: error, title: title), animated: true) } } done() }) } else { - pumpManager.setBolusBeeps(enabled: true, completion: { (error) in + pumpManager.setConfirmationBeeps(enabled: true, completion: { (error) in if let error = error { DispatchQueue.main.async { - let title = LocalizedString("Error enabling bolus beeps", comment: "The alert title for enable bolus beeps error") + let title = LocalizedString("Error enabling confirmation beeps", comment: "The alert title for enable confirmation beeps error") self.present(UIAlertController(with: error, title: title), animated: true) } } diff --git a/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift index c5303901a..92b9c5e0e 100644 --- a/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift +++ b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift @@ -45,6 +45,16 @@ class PodSetupCompleteViewController: SetupTableViewController { if let replaceVC = navigationController as? PodReplacementNavigationController { replaceVC.completeSetup() } + if pumpManager.confirmationBeeps { + pumpManager.setConfirmationBeeps(enabled: true, completion: { (error) in + if let error = error { + DispatchQueue.main.async { + let title = LocalizedString("Error emitting completion confirmation beep", comment: "The alert title for emitting completion beep error") + self.present(UIAlertController(with: error, title: title), animated: true) + } + } + }) + } } override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { From 8dc55eb849a5c8860db61b372550a2899642fd4b Mon Sep 17 00:00:00 2001 From: Joe Moran Date: Sat, 28 Dec 2019 12:31:23 -0800 Subject: [PATCH 67/71] Provide an explicit Read Pod Status command for OmniKit's Pod Settings (#576) * Provide an explicit Read Pod Status command for OmniKit's Pod Settings Analogous functionality for MinimedKit's Read Pump Status command * Rework to allow for localization of Read Pod Status labels Improved error checking and log messages --- OmniKit/PumpManager/OmnipodPumpManager.swift | 74 +++++++++++++++++++ .../CommandResponseViewController.swift | 17 +++++ .../OmnipodSettingsViewController.swift | 14 +++- 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/OmniKit/PumpManager/OmnipodPumpManager.swift b/OmniKit/PumpManager/OmnipodPumpManager.swift index ba3144b35..59b7a0a28 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -880,6 +880,80 @@ extension OmnipodPumpManager { #endif } + private func podStatusString(status: StatusResponse) -> String { + var result, str: String + var delivered: Double + + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .full + formatter.allowedUnits = [.day, .hour, .minute] + if let timeStr = formatter.string(from: status.timeActive) { + str = timeStr + } else { + str = String(format: LocalizedString("%1$@ minutes", comment: "The format string for minutes (1: number of minutes string)"), String(describing: Int(status.timeActive / 60))) + } + result = String(format: LocalizedString("Pod Active: %1$@\n", comment: "The format string for Pod Active: (1: Pod active time string)"), str) + + result += String(format: LocalizedString("Delivery Status: %1$@\n", comment: "The format string for Delivery Status: (1: delivery status string)"), String(describing: status.deliveryStatus)) + + if let lastInsulinMeasurements = self.state.podState?.lastInsulinMeasurements { + delivered = lastInsulinMeasurements.delivered + } else { + delivered = status.insulin + } + result += String(format: LocalizedString("Total Insulin Delivered: %1$@ U\n", comment: "The format string for Total Insulin Delivered: (1: total insulin delivered string)"), delivered.twoDecimals) + + result += String(format: LocalizedString("Reservoir Level: %1$@ U\n", comment: "The format string for Reservoir Level: (1: reservoir level string)"), status.reservoirLevel?.twoDecimals ?? "50+") + + result += String(format: LocalizedString("Last Bolus Not Delivered: %1$@ U\n", comment: "The format string for Last Bolus Not Delivered: (1: bolus not delivered string)"), status.insulinNotDelivered.twoDecimals) + + if let podState = self.state.podState, + podState.activeAlerts.startIndex != podState.activeAlerts.endIndex + { + // generate a more helpful string with the actual alert names + str = String(describing: podState.activeAlerts) + } else { + str = String(describing: status.alerts) + } + result += String(format: LocalizedString("Alerts: %1$@\n", comment: "The format string for Alerts: (1: the alerts string)"), str) + + return result + } + + public func readPodStatus(completion: @escaping (String?) -> Void) { + guard self.hasActivePod else { + completion(String(describing: OmnipodPumpManagerError.noPodPaired)) + return + } + guard state.podState?.unfinalizedBolus?.scheduledCertainty == .uncertain || state.podState?.unfinalizedBolus?.isFinished != false else { + self.log.info("Skipping read pod status due to unfinalized bolus in progress.") + completion(String(describing: PodCommsError.unfinalizedBolus)) + return + } + + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + podComms.runSession(withName: "Read pod status", using: rileyLinkSelector) { (result) in + do { + switch result { + case .success(let session): + let status = try session.getStatus() + // self.emitConfirmationBeep(session: session, beepConfigType: .bipBip) + session.dosesForStorage({ (doses) -> Bool in + self.store(doses: doses, in: session) + }) + let statusString = self.podStatusString(status: status) + completion(statusString) + case .failure(let error): + completion(String(describing: error)) + throw error + } + } catch let error { + self.log.error("Failed to read pod status: %{public}@", String(describing: error)) + completion(String(describing: error)) + } + } + } + public func testingCommands(completion: @escaping (Error?) -> Void) { // use hasSetupPod so the user can see any fault info and post fault commands can be attempted guard self.hasSetupPod else { diff --git a/OmniKitUI/ViewControllers/CommandResponseViewController.swift b/OmniKitUI/ViewControllers/CommandResponseViewController.swift index 5f9e11dc4..578655110 100644 --- a/OmniKitUI/ViewControllers/CommandResponseViewController.swift +++ b/OmniKitUI/ViewControllers/CommandResponseViewController.swift @@ -44,6 +44,23 @@ extension CommandResponseViewController { } } + static func readPodStatus(pumpManager: OmnipodPumpManager) -> T { + return T { (completionHandler) -> String in + pumpManager.readPodStatus() { (error) in + let response: String + if let error = error { + response = String(describing: error) + } else { + response = self.successText + } + DispatchQueue.main.async { + completionHandler(response) + } + } + return LocalizedString("Read Pod Status…", comment: "Progress message for reading Pod status.") + } + } + static func testingCommands(pumpManager: OmnipodPumpManager) -> T { return T { (completionHandler) -> String in pumpManager.testingCommands() { (error) in diff --git a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift index b556b6d0b..0a860f2e7 100644 --- a/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -182,8 +182,9 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { private enum ActionsRow: Int, CaseIterable { case suspendResume = 0 - case testCommand + case readPodStatus case playTestBeeps + case testCommand case replacePod } @@ -310,6 +311,11 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { switch actions[indexPath.row] { case .suspendResume: return suspendResumeTableViewCell + case .readPodStatus: + let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) + cell.textLabel?.text = LocalizedString("Read Pod Status", comment: "The title of the command to read the pod status") + cell.accessoryType = .disclosureIndicator + return cell case .testCommand: let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.className, for: indexPath) cell.textLabel?.text = LocalizedString("Test Command", comment: "The title of the command to run the test command") @@ -464,6 +470,10 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { case .suspendResume: suspendResumeTapped() tableView.deselectRow(at: indexPath, animated: true) + case .readPodStatus: + let vc = CommandResponseViewController.readPodStatus(pumpManager: pumpManager) + vc.title = sender?.textLabel?.text + show(vc, sender: indexPath) case .testCommand: let vc = CommandResponseViewController.testingCommands(pumpManager: pumpManager) vc.title = sender?.textLabel?.text @@ -551,7 +561,7 @@ class OmnipodSettingsViewController: RileyLinkSettingsViewController { switch ActionsRow(rawValue: indexPath.row)! { case .suspendResume, .replacePod: break - case .testCommand, .playTestBeeps: + case .readPodStatus, .playTestBeeps, .testCommand: tableView.reloadRows(at: [indexPath], with: .fade) } case .configuration: From 2888970adca367922b436d5245e26b0738700c07 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 28 Dec 2019 15:46:19 -0600 Subject: [PATCH 68/71] Record resume time after canceling temp basal --- MinimedKit/PumpManager/MinimedPumpManager.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MinimedKit/PumpManager/MinimedPumpManager.swift b/MinimedKit/PumpManager/MinimedPumpManager.swift index 79da47b8f..4dcdd0e15 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -1013,11 +1013,15 @@ extension MinimedPumpManager: PumpManager { let dose = UnfinalizedDose(tempBasalRate: unitsPerHour, startTime: startDate, duration: duration) self.recents.tempBasalEngageState = .stable + + let isResumingScheduledBasal = duration < .ulpOfOne // If we were successful, then we know we aren't suspended self.setState({ (state) in if case .suspended = state.suspendState { state.suspendState = .resumed(startDate) + } else if isResumingScheduledBasal { + state.suspendState = .resumed(startDate) } let pumpModel = state.pumpModel @@ -1027,7 +1031,7 @@ extension MinimedPumpManager: PumpManager { state.pendingDoses.append(previousTempBasal) } - if duration < .ulpOfOne { + if isResumingScheduledBasal { state.unfinalizedTempBasal = nil } else { state.unfinalizedTempBasal = dose From 0dc526607ce2b06f48dadf58a3fd7b39fe11e452 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 31 Dec 2019 12:58:40 -0600 Subject: [PATCH 69/71] Expose methods to flush queue for faster bg uploads --- NightscoutUploadKit/NightscoutUploader.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NightscoutUploadKit/NightscoutUploader.swift b/NightscoutUploadKit/NightscoutUploader.swift index 463395a08..99fff292e 100644 --- a/NightscoutUploadKit/NightscoutUploader.swift +++ b/NightscoutUploadKit/NightscoutUploader.swift @@ -209,7 +209,7 @@ public class NightscoutUploader { // MARK: - Uploading - func flushAll() { + public func flushAll() { flushDeviceStatuses() flushEntries() flushTreatments() @@ -377,7 +377,7 @@ public class NightscoutUploader { } } - func flushEntries() { + public func flushEntries() { guard let url = url(for: .entries) else { return } From 60cd51ddd0ee307e56fd8208fe9361b654ca29fe Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 31 Dec 2019 15:25:44 -0600 Subject: [PATCH 70/71] Bump version for release --- Crypto/Info.plist | 2 +- MinimedKit/Info.plist | 2 +- MinimedKitPlugin/Info.plist | 2 +- MinimedKitTests/Info.plist | 2 +- MinimedKitUI/Info.plist | 2 +- NightscoutUploadKit/Info.plist | 2 +- NightscoutUploadKitTests/Info.plist | 2 +- OmniKit/Info.plist | 2 +- OmniKitPlugin/Info.plist | 2 +- OmniKitTests/Info.plist | 2 +- OmniKitUI/Info.plist | 2 +- RileyLink/RileyLink-Info.plist | 2 +- RileyLinkBLEKit/Info.plist | 2 +- RileyLinkBLEKitTests/Info.plist | 2 +- RileyLinkKit/Info.plist | 2 +- RileyLinkKitUI/Info.plist | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Crypto/Info.plist b/Crypto/Info.plist index 9a384cbc1..1c6f0bdd2 100644 --- a/Crypto/Info.plist +++ b/Crypto/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/MinimedKit/Info.plist b/MinimedKit/Info.plist index a60ea4a4e..21baa19b4 100644 --- a/MinimedKit/Info.plist +++ b/MinimedKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKitPlugin/Info.plist b/MinimedKitPlugin/Info.plist index d18af78ea..2cce66c09 100644 --- a/MinimedKitPlugin/Info.plist +++ b/MinimedKitPlugin/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/MinimedKitTests/Info.plist b/MinimedKitTests/Info.plist index 7506bdc29..fccbfa25b 100644 --- a/MinimedKitTests/Info.plist +++ b/MinimedKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/MinimedKitUI/Info.plist b/MinimedKitUI/Info.plist index 548b0a369..7a3ea75eb 100644 --- a/MinimedKitUI/Info.plist +++ b/MinimedKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/NightscoutUploadKit/Info.plist b/NightscoutUploadKit/Info.plist index a60ea4a4e..21baa19b4 100644 --- a/NightscoutUploadKit/Info.plist +++ b/NightscoutUploadKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/NightscoutUploadKitTests/Info.plist b/NightscoutUploadKitTests/Info.plist index db502d0cb..f7fe54e8c 100644 --- a/NightscoutUploadKitTests/Info.plist +++ b/NightscoutUploadKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/OmniKit/Info.plist b/OmniKit/Info.plist index 548b0a369..7a3ea75eb 100644 --- a/OmniKit/Info.plist +++ b/OmniKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/OmniKitPlugin/Info.plist b/OmniKitPlugin/Info.plist index 55e5988a7..94c4d6fb2 100644 --- a/OmniKitPlugin/Info.plist +++ b/OmniKitPlugin/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/OmniKitTests/Info.plist b/OmniKitTests/Info.plist index 6b54d75b2..713b7c93b 100644 --- a/OmniKitTests/Info.plist +++ b/OmniKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion 1 diff --git a/OmniKitUI/Info.plist b/OmniKitUI/Info.plist index 548b0a369..7a3ea75eb 100644 --- a/OmniKitUI/Info.plist +++ b/OmniKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLink/RileyLink-Info.plist b/RileyLink/RileyLink-Info.plist index 450c2d49c..6c9826a3c 100644 --- a/RileyLink/RileyLink-Info.plist +++ b/RileyLink/RileyLink-Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkBLEKit/Info.plist b/RileyLinkBLEKit/Info.plist index 548b0a369..7a3ea75eb 100644 --- a/RileyLinkBLEKit/Info.plist +++ b/RileyLinkBLEKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLinkBLEKitTests/Info.plist b/RileyLinkBLEKitTests/Info.plist index 6b54d75b2..713b7c93b 100644 --- a/RileyLinkBLEKitTests/Info.plist +++ b/RileyLinkBLEKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion 1 diff --git a/RileyLinkKit/Info.plist b/RileyLinkKit/Info.plist index a60ea4a4e..21baa19b4 100644 --- a/RileyLinkKit/Info.plist +++ b/RileyLinkKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKitUI/Info.plist b/RileyLinkKitUI/Info.plist index 548b0a369..7a3ea75eb 100644 --- a/RileyLinkKitUI/Info.plist +++ b/RileyLinkKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass From 697a0cc75f606083e6efbd53e953c9ee771bae0f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 31 Dec 2019 15:30:20 -0600 Subject: [PATCH 71/71] Use release version of LoopKit --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index df64400f1..2b50e2b96 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "dev" +github "LoopKit/LoopKit" ~> 3.0 github "LoopKit/MKRingProgressView" "appex-safe" diff --git a/Cartfile.resolved b/Cartfile.resolved index 5c609e184..63a505c21 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" +github "LoopKit/LoopKit" "v3.0" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a"