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/.travis.yml b/.travis.yml index 565484ad1..e5e1d2a04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: objective-c -osx_image: xcode10 -xcode_project: RileyLink.xcodeproj -xcode_scheme: RileyLink +osx_image: xcode11 + +before_script: + - carthage bootstrap + script: - - xcodebuild -project RileyLink.xcodeproj -scheme RileyLink build -destination 'name=iPhone SE' test + - set -o pipefail && xcodebuild -project RileyLink.xcodeproj -scheme Shared build -destination 'name=iPhone 8' test | xcpretty diff --git a/Cartfile b/Cartfile index d484e8881..2b50e2b96 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1,2 @@ -github "LoopKit/LoopKit" ~> 2.2 +github "LoopKit/LoopKit" ~> 3.0 +github "LoopKit/MKRingProgressView" "appex-safe" diff --git a/Cartfile.resolved b/Cartfile.resolved index 790353934..63a505c21 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1,2 @@ -github "LoopKit/LoopKit" "v2.2.2" +github "LoopKit/LoopKit" "v3.0" +github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" 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/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/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/Crypto/Info.plist b/Crypto/Info.plist index a8e75ccfd..1c6f0bdd2 100644 --- a/Crypto/Info.plist +++ b/Crypto/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass 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/Info.plist b/MinimedKit/Info.plist index 449c7b6af..21baa19b4 100644 --- a/MinimedKit/Info.plist +++ b/MinimedKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleSignature ???? CFBundleVersion 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/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/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 } 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/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/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.. 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/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 471ae79aa..8d13ac324 100644 --- a/MinimedKit/Messages/PumpMessage.swift +++ b/MinimedKit/Messages/PumpMessage.swift @@ -44,11 +44,11 @@ public struct PumpMessage : CustomStringConvertible { buffer.append(messageType.rawValue) buffer.append(contentsOf: messageBody.txData) - return Data(bytes: buffer) + return Data(buffer) } 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/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/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/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/Messages/SuspendResumeMessageBody.swift b/MinimedKit/Messages/SuspendResumeMessageBody.swift new file mode 100644 index 000000000..a28b69a36 --- /dev/null +++ b/MinimedKit/Messages/SuspendResumeMessageBody.swift @@ -0,0 +1,25 @@ +// +// SuspendResumeMessageBody.swift +// MinimedKit +// +// Created by Pete Schwamb on 10/1/18. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +import Foundation + +public class SuspendResumeMessageBody: CarelinkLongMessageBody { + + public enum SuspendResumeState: UInt8 { + case suspend = 0x01 + case resume = 0x00 + } + + public convenience init(state: SuspendResumeState) { + let numArgs = 1 + let data = Data(hexadecimalString: String(format: "%02x%02x", numArgs, state.rawValue))! + + self.init(rxData: data)! + } + +} diff --git a/MinimedKit/Models/PumpModel.swift b/MinimedKit/Models/PumpModel.swift index 810d44d3e..b18debdcb 100644 --- a/MinimedKit/Models/PumpModel.swift +++ b/MinimedKit/Models/PumpModel.swift @@ -62,16 +62,16 @@ 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 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: @@ -87,6 +87,122 @@ 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...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) + 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 maximumBolusVolume: Int { + return 25 + } + + public var maximumBasalRate: Double { + return 35 + } + + public var supportedBolusVolumes: [Double] { + 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 { + return 48 + } + + 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) + } + + public func estimateTempBasalProgress(unitsPerHour: Double, duration: TimeInterval, elapsed: TimeInterval) -> (deliveredUnits: Double, progress: Double) { + let roundedVolume = round(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/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/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.. Double { return Double(availableData[index]) } 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) @@ -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/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/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.. (delay: TimeInterval, repeating: TimeInterval) { + let timeSinceStart = -dose.startDate.timeIntervalSinceNow + let duration = dose.endDate.timeIntervalSince(dose.startDate) + let timeBetweenPulses = duration / (Double(pumpModel.pulsesPerUnit) * dose.programmedUnits) + + 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 4039a36c4..4dcdd0e15 100644 --- a/MinimedKit/PumpManager/MinimedPumpManager.swift +++ b/MinimedKit/PumpManager/MinimedPumpManager.swift @@ -11,13 +11,25 @@ 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 class MinimedPumpManager: RileyLinkPumpManager { public init(state: MinimedPumpManagerState, rileyLinkDeviceProvider: RileyLinkDeviceProvider, rileyLinkConnectionManager: RileyLinkConnectionManager? = nil, pumpOps: PumpOps? = nil) { - self.state = state - + self.lockedState = Locked(state) + + self.hkDevice = 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 @@ -41,105 +53,136 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { rileyLinkConnectionManager.delegate = self } - public var rawState: PumpManager.RawStateValue { - return state.rawValue + public private(set) var pumpOps: PumpOps! + + // MARK: - PumpManager + + public let stateObservers = WeakSynchronizedSet() + + 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) + } + + guard oldValue != newValue else { + return returnValue + } + + let recents = self.recents + let oldStatus = status(for: oldValue, recents: recents) + let newStatus = status(for: newValue, recents: recents) - // TODO: apply lock - public private(set) var state: MinimedPumpManagerState { - didSet { - pumpManagerDelegate?.pumpManagerDidUpdateState(self) + // 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 } - 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 - } - } - - public weak var cgmManagerDelegate: CGMManagerDelegate? - - public weak var pumpManagerDelegate: PumpManagerDelegate? - - public let log = OSLog(category: "MinimedPumpManager") - - // MARK: - CGMManager - - public private(set) var sensorState: SensorDisplayable? - - // 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 { + setState { (state) in + state.batteryPercentage = newBatteryPercentage + } + } + } + if oldStatus != status { + notifyStatusObservers(oldStatus: oldStatus) } } } + private let lockedRecents = Locked(MinimedPumpManagerRecents()) - // TODO: Isolate to queue - private var latestPumpStatus: PumpStatus? + private let statusObservers = WeakSynchronizedSet() - // 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? { - if let status = latestPumpStatusFromMySentry { - return Double(status.batteryRemainingPercent) / 100 - } else if let status = latestPumpStatus { - return batteryChemistry.chargeRemaining(at: status.batteryVolts) - } else { - return nil + private func notifyStatusObservers(oldStatus: PumpManagerStatus) { + let status = self.status + pumpDelegate.notify { (delegate) in + delegate?.pumpManager(self, didUpdate: status, oldStatus: oldStatus) } - } - - 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) - } + private let cgmDelegate = WeakSynchronizedDelegate() + private let pumpDelegate = WeakSynchronizedDelegate() - public var pumpTimeZone: TimeZone { - return state.timeZone - } + public let log = OSLog(category: "MinimedPumpManager") - public static let localizedTitle = LocalizedString("Minimed 500/700 Series", comment: "Generic title of the minimed pump manager") + // MARK: - CGMManager - public var localizedTitle: String { - return String(format: LocalizedString("Minimed %@", comment: "Pump title (1: model number)"), state.pumpModel.rawValue) - } + private let hkDevice: HKDevice // MARK: - RileyLink Updates + override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { + get { + return state.rileyLinkConnectionManagerState + } + set { + setState { (state) in + state.rileyLinkConnectionManagerState = newValue + } + } + } + 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, @@ -148,21 +191,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 @@ -171,26 +213,26 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return [ "## MinimedPumpManager", "isPumpDataStale: \(isPumpDataStale)", - "latestPumpStatus: \(String(describing: latestPumpStatus))", - "latestPumpStatusFromMySentry: \(String(describing: latestPumpStatusFromMySentry))", - "lastAddedPumpEvents: \(lastAddedPumpEvents)", - "pumpBatteryChargeRemaining: \(String(reflecting: pumpBatteryChargeRemaining))", - "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() @@ -202,13 +244,15 @@ 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)) 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))) + } } } } @@ -217,6 +261,66 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } + private func runSuspendResumeOnSession(suspendResumeState: SuspendResumeMessageBody.SuspendResumeState, session: PumpOpsSession) throws { + defer { self.recents.suspendEngageState = .stable } + self.recents.suspendEngageState = suspendResumeState == .suspend ? .engaging : .disengaging + + 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())) + } + } + } + + 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 { + try self.runSuspendResumeOnSession(suspendResumeState: state, session: session) + self.storePendingPumpEvents({ (error) in + completion(error) + }) + } 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. @@ -228,7 +332,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") @@ -240,7 +344,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 } @@ -249,57 +353,41 @@ 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) + + recents.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))) + cgmDelegate.notify { (delegate) in + delegate?.cgmManager(self, didUpdateWith: .newData([sample])) } } + case .off: + // Enlite is disabled, so assert glucose from another source + pumpDelegate.notify { (delegate) in + delegate?.pumpManagerBLEHeartbeatDidFire(self) + } + default: + // Anything else is an Enlite error + // TODO: Provide info about status.glucose + 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))) } } @@ -311,35 +399,126 @@ 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?) { - pumpManagerDelegate?.pumpManager(self, didReadReservoirValue: units, at: date) { (result) in - /// TODO: Isolate to queue - - 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 + // Must be called from the sessionQueue + + setState { (state) in + state.lastReservoirReading = ReservoirReading(units: units, validAt: date) + } + + 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() + private func reconcilePendingDosesWith(_ events: [NewPumpEvent]) -> [NewPumpEvent] { + // Must be called from the sessionQueue + + 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 { + let mapping = ReconciledDoseMapping(startTime: startTime, uuid: uuid, eventRaw: eventRaw) + self.log.debug("Adding reconciliation mapping %@ -> %@", eventRaw.hexadecimalString, uuid.asRaw.hexadecimalString) + state.reconciliationMappings[eventRaw] = mapping + } + + // Reconcile any pending doses + 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 + } + + 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 + } + + state.reconciliationMappings = state.reconciliationMappings.filter { (key, value) -> Bool in + return value.startTime >= expirationCutoff + } + + // 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 = state.reconciliationMappings[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() + + mergedPumpEvents = reconciledHistoryEvents + allPending.filter { !$0.isReconciledWithHistory }.map { NewPumpEvent($0) } } + return mergedPumpEvents } - /// TODO: Isolate to queue /// Polls the pump for new history events and passes them to the loop manager /// /// - Parameters: @@ -354,18 +533,36 @@ 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() + // 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))) + +// if let lastEvent = historyEvents.last, let _ = lastEvent.pumpEvent as? ResumePumpEvent { +// self.log.default("Failing read when last event is resume.") +// throw PumpOpsError.rfCommsFailure("made up error") +// } + + // Reconcile history with pending doses + let newPumpEvents = historyEvents.pumpEvents(from: model) + let reconciledEvents = self.reconcilePendingDosesWith(newPumpEvents) + + self.pumpDelegate.notify({ (delegate) in + guard let delegate = delegate else { + preconditionFailure("pumpManagerDelegate cannot be nil") } - completion(error) + 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() + } + completion(error) + }) }) } catch let error { self.troubleshootPumpComms(using: device) @@ -376,7 +573,26 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { } } - /// TODO: Isolate to queue + private func storePendingPumpEvents(_ completion: @escaping (_ error: Error?) -> Void) { + // Must be called from the sessionQueue + 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 + 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? let pumpStatusAgeTolerance = rileyLinkDeviceProvider.idleListeningEnabled ? TimeInterval(minutes: 6) : TimeInterval(minutes: 4) @@ -384,11 +600,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 @@ -398,10 +616,197 @@ 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 { + setState { (state) in + state.preferredInsulinDataSource = newValue + } + } + } + + /// The pump battery chemistry, for voltage -> percentage calculation + public var batteryChemistry: BatteryChemistryType { + get { + return state.batteryChemistry + } + set { + setState { (state) in + 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 lastReconciliation: Date? { + return state.lastReconciliation + } + + private func status(for state: MinimedPumpManagerState, recents: MinimedPumpManagerRecents) -> PumpManagerStatus { + let basalDeliveryState: PumpManagerStatus.BasalDeliveryState + + 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.isFinished { + 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.isFinished { + bolusState = .inProgress(DoseEntry(bolus)) + } else { + bolusState = .none + } + } + + return PumpManagerStatus( + timeZone: state.timeZone, + device: hkDevice, + pumpBatteryChargeRemaining: state.batteryPercentage, + basalDeliveryState: basalDeliveryState, + 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 + } + + 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. */ - /// TODO: Isolate to queue public func assertCurrentPumpData() { rileyLinkDeviceProvider.assertIdleListening(forcingRestart: true) @@ -409,13 +814,15 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return } - self.log.default("Pump data is stale, fetching.") + 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) + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didError: error) + }) return } @@ -430,7 +837,9 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { // 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) + self.pumpDelegate.notify({ (delegate) in + delegate?.pumpManager(self, didAdjustPumpClockBy: date.timeIntervalSinceNow) + }) try session.setTimeToNow() guard let newDate = try session.getTime().date else { @@ -440,103 +849,151 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { date = newDate } - self.observeBatteryDuring { - self.latestPumpStatus = status - } + self.setState({ (state) in + if case .resumed = state.suspendState, status.suspended { + state.suspendState = .suspended(Date()) + } + }) - self.updateReservoirVolume(status.reservoir, at: date, withTimeLeft: nil) + self.recents.latestPumpStatus = status - 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) - } - } + 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.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 (_ units: Double, _ date: Date) -> Void, completion: @escaping (_ error: Error?) -> Void) { - guard units > 0 else { - completion(nil) + public func enactBolus(units: Double, at startDate: Date, willRequest: @escaping (_ dose: DoseEntry) -> Void, completion: @escaping (PumpManagerResult) -> Void) { + let enactUnits = roundToSupportedBolusVolume(units: units) + + guard enactUnits > 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 { - completion(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)) + completion(.failure(SetBolusError.certain(PumpManagerError.connection(MinimedPumpManagerError.noRileyLink)))) return } - if shouldReadReservoir { + if let unfinalizedBolus = self.state.unfinalizedBolus { + guard unfinalizedBolus.isFinished else { + completion(.failure(SetBolusError.certain(PumpManagerError.deviceState(MinimedPumpManagerError.bolusInProgress)))) + return + } + + self.setState({ (state) in + state.pendingDoses.append(unfinalizedBolus) + state.unfinalizedBolus = nil + }) + } + + 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)) { 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(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) + self.recents.bolusEngageState = .stable + completion(.failure(error)) return } } do { - willRequest(units, Date()) - try session.setNormalBolus(units: units) - completion(nil) + if case .suspended = self.state.suspendState { + do { + 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))) + 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 { + self.recents.bolusEngageState = .stable + completion(.failure(error)) + return + } + } + + let date = Date() + let deliveryTime = self.state.pumpModel.bolusDeliveryTime(units: enactUnits) + let requestedDose = UnfinalizedDose(bolusAmount: enactUnits, startTime: date, duration: deliveryTime) + willRequest(DoseEntry(requestedDose)) + + 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 doseStart = Date().addingTimeInterval(commsOffset) + + let dose = UnfinalizedDose(bolusAmount: enactUnits, startTime: doseStart, duration: deliveryTime) + self.setState({ (state) in + 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)) - completion(error) + self.recents.bolusEngageState = .stable + completion(.failure(error)) } } } + public func cancelBolus(completion: @escaping (PumpManagerResult) -> Void) { + self.recents.bolusEngageState = .disengaging + setSuspendResumeState(state: .suspend) { (error) in + self.recents.bolusEngageState = .stable + if let error = error { + completion(.failure(error)) + } else { + 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 { @@ -544,22 +1001,69 @@ public class MinimedPumpManager: RileyLinkPumpManager, PumpManager { return } + self.recents.tempBasalEngageState = .engaging + do { let response = try session.setTempBasal(unitsPerHour, duration: duration) let now = Date() let endDate = now.addingTimeInterval(response.timeRemaining) let startDate = endDate.addingTimeInterval(-duration) - completion(.success(DoseEntry( - type: .tempBasal, - startDate: startDate, - endDate: endDate, - value: response.rate, - unit: .unitsPerHour - ))) + + 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 + + state.unfinalizedTempBasal?.cancel(at: startDate, pumpModel: pumpModel) + if let previousTempBasal = state.unfinalizedTempBasal { + state.pendingDoses.append(previousTempBasal) + } + + if isResumingScheduledBasal { + state.unfinalizedTempBasal = nil + } else { + state.unfinalizedTempBasal = dose + } + }) + + self.storePendingPumpEvents({ (error) in + completion(.success(DoseEntry(dose))) + }) // 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.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)) + } + } + self.recents.tempBasalEngageState = .stable + return + } catch { + self.recents.tempBasalEngageState = .stable completion(.failure(error)) return } @@ -567,12 +1071,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() } @@ -582,56 +1088,42 @@ 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 let bolus = self.state.unfinalizedBolus, !bolus.isFinished { + return MinimedDoseProgressEstimator(dose: DoseEntry(bolus), pumpModel: state.pumpModel, reportingQueue: dispatchQueue) } + return nil } +} - - 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: PumpOpsDelegate { + public func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) { + setState { (pumpManagerState) in + pumpManagerState.pumpState = state } } +} - /// The pump battery chemistry, for voltage -> percentage calculation - public var batteryChemistry: BatteryChemistryType { +extension MinimedPumpManager: CGMManager { + public var device: HKDevice? { + return hkDevice + } + + 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 } @@ -644,8 +1136,8 @@ extension MinimedPumpManager: CGMManager { return nil } - public var device: HKDevice? { - return nil + public var sensorState: SensorDisplayable? { + return recents.sensorState } public func fetchNewDataIfNeeded(_ completion: @escaping (CGMResult) -> Void) { @@ -655,7 +1147,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) @@ -667,7 +1161,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 @@ -688,4 +1182,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 new file mode 100644 index 000000000..b551d0eb2 --- /dev/null +++ b/MinimedKit/PumpManager/MinimedPumpManagerRecents.swift @@ -0,0 +1,53 @@ +// +// MinimedPumpManagerRecents.swift +// MinimedKit +// +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation +import LoopKit + +struct MinimedPumpManagerRecents: Equatable { + + 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 + + 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 + suspendEngageState: \(suspendEngageState) + bolusEngageState: \(bolusEngageState) + tempBasalEngageState: \(tempBasalEngageState) + 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 dfeb8222f..04faa8971 100644 --- a/MinimedKit/PumpManager/MinimedPumpManagerState.swift +++ b/MinimedKit/PumpManager/MinimedPumpManagerState.swift @@ -9,6 +9,38 @@ 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 @@ -17,28 +49,32 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { public var batteryChemistry: BatteryChemistryType - public var preferredInsulinDataSource: InsulinDataSource + public var batteryPercentage: Double? - public var pumpColor: PumpColor + public var suspendState: SuspendState - public var pumpModel: PumpModel + public var lastReservoirReading: ReservoirReading? - public var pumpID: String + public var lastTuned: Date? // In-memory only - public var pumpRegion: PumpRegion - public var lastValidFrequency: Measurement? - public var lastTuned: Date? + public var preferredInsulinDataSource: InsulinDataSource + + public let pumpColor: PumpColor + + public let pumpModel: PumpModel + + public let pumpFirmwareVersion: String + + public let pumpID: String + + public let pumpRegion: PumpRegion public var pumpSettings: PumpSettings { get { return PumpSettings(pumpID: pumpID, pumpRegion: pumpRegion) } - set { - pumpID = newValue.pumpID - pumpRegion = newValue.pumpRegion - } } public var pumpState: PumpState { @@ -51,9 +87,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 +97,37 @@ 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 var unfinalizedBolus: UnfinalizedDose? + + public var unfinalizedTempBasal: UnfinalizedDose? + + // Doses we're tracking that haven't shown up in history yet + public var pendingDoses: [UnfinalizedDose] + + // 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, recentlyReconciledEvents: [Data:ReconciledDoseMapping]? = nil, lastReconciliation: Date? = 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.suspendState = suspendState self.lastValidFrequency = lastValidFrequency + self.batteryPercentage = batteryPercentage + self.lastReservoirReading = lastReservoirReading + self.unfinalizedBolus = unfinalizedBolus + self.unfinalizedTempBasal = unfinalizedTempBasal + self.pendingDoses = pendingDoses ?? [] + self.reconciliationMappings = recentlyReconciledEvents ?? [:] + self.lastReconciliation = lastReconciliation } public init?(rawValue: RawValue) { @@ -112,6 +166,20 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { rileyLinkConnectionManagerState = RileyLinkConnectionManagerState(rawValue: rawState) } } + + 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 { @@ -119,17 +187,70 @@ 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 + } + + 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 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 { + recentlyReconciledEvents = [:] + } + + let lastReconciliation = rawValue["lastReconciliation"] as? Date + self.init( batteryChemistry: batteryChemistry, preferredInsulinDataSource: insulinDataSource, pumpColor: pumpColor, pumpID: pumpID, pumpModel: pumpModel, + pumpFirmwareVersion: pumpFirmwareVersion, pumpRegion: pumpRegion, rileyLinkConnectionManagerState: rileyLinkConnectionManagerState, timeZone: timeZone, - lastValidFrequency: lastValidFrequency + suspendState: suspendState, + lastValidFrequency: lastValidFrequency, + batteryPercentage: batteryPercentage, + lastReservoirReading: lastReservoirReading, + unfinalizedBolus: unfinalizedBolus, + unfinalizedTempBasal: unfinalizedTempBasal, + pendingDoses: pendingDoses, + recentlyReconciledEvents: recentlyReconciledEvents, + lastReconciliation: lastReconciliation ) } @@ -140,19 +261,22 @@ public struct MinimedPumpManagerState: RawRepresentable, Equatable { "pumpColor": pumpColor.rawValue, "pumpID": pumpID, "pumpModel": pumpModel.rawValue, + "pumpFirmwareVersion": pumpFirmwareVersion, "pumpRegion": pumpRegion.rawValue, "timeZone": timeZone.secondsFromGMT(), - + "suspendState": suspendState.rawValue, "version": MinimedPumpManagerState.version, - ] - - if let rileyLinkConnectionManagerState = rileyLinkConnectionManagerState { - value["rileyLinkConnectionManagerState"] = rileyLinkConnectionManagerState.rawValue - } - - if let frequency = lastValidFrequency?.converted(to: .megahertz) { - value["lastValidFrequency"] = frequency.value - } + "pendingDoses": pendingDoses.map { $0.rawValue }, + "recentlyReconciledEvents": reconciliationMappings.values.map { $0.rawValue }, + ] + + 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 } @@ -169,14 +293,74 @@ extension MinimedPumpManagerState: CustomDebugStringConvertible { return [ "## MinimedPumpManagerState", "batteryChemistry: \(batteryChemistry)", + "batteryPercentage: \(String(describing: batteryPercentage))", + "suspendState: \(suspendState)", + "lastValidFrequency: \(String(describing: lastValidFrequency))", "preferredInsulinDataSource: \(preferredInsulinDataSource)", "pumpColor: \(pumpColor)", "pumpID: ✔︎", "pumpModel: \(pumpModel.rawValue)", + "pumpFirmwareVersion: \(pumpFirmwareVersion)", "pumpRegion: \(pumpRegion)", - "lastValidFrequency: \(String(describing: lastValidFrequency))", + "reservoirUnits: \(String(describing: lastReservoirReading?.units))", + "reservoirValidAt: \(String(describing: lastReservoirReading?.validAt))", + "unfinalizedBolus: \(String(describing: unfinalizedBolus))", + "unfinalizedTempBasal: \(String(describing: unfinalizedTempBasal))", + "pendingDoses: \(pendingDoses)", "timeZone: \(timeZone)", + "recentlyReconciledEvents: \(reconciliationMappings.values.map { "\($0.eventRaw.hexadecimalString) -> \($0.uuid)" })", + "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/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/MinimedKit/PumpManager/PumpOps.swift b/MinimedKit/PumpManager/PumpOps.swift index 6b6ff57b9..8b8d6a65f 100644 --- a/MinimedKit/PumpManager/PumpOps.swift +++ b/MinimedKit/PumpManager/PumpOps.swift @@ -10,37 +10,27 @@ import Foundation import RileyLinkKit import RileyLinkBLEKit import os.log +import LoopKit public protocol PumpOpsDelegate: class { + // TODO: Audit clients of this as its called on the session queue func pumpOps(_ pumpOps: PumpOps, didChange state: PumpState) } public class PumpOps { - private let log = OSLog(category: "PumpOps") public let pumpSettings: PumpSettings - private var pumpState: PumpState { - didSet { - delegate?.pumpOps(self, didChange: pumpState) + public 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,28 @@ 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) - } - - public func getPumpState(_ completion: @escaping (_ state: PumpState) -> Void) { - sessionQueue.async { - completion(self.pumpState) + _ = configuredDevices.mutate { (value) in + value.remove(device) } } } - +// 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 +135,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/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/MinimedKit/PumpManager/UnfinalizedDose.swift b/MinimedKit/PumpManager/UnfinalizedDose.swift new file mode 100644 index 000000000..e342cdf5c --- /dev/null +++ b/MinimedKit/PumpManager/UnfinalizedDose.swift @@ -0,0 +1,270 @@ +// +// 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 + } + + let doseType: DoseType + public var units: Double + 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 uuid: UUID + + 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 isFinished: 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 isFinished 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.programmedUnits = nil + self.isReconciledWithHistory = isReconciledWithHistory + self.uuid = UUID() + } + + 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.programmedUnits = nil + self.isReconciledWithHistory = isReconciledWithHistory + self.uuid = UUID() + } + + init(suspendStartTime: Date, isReconciledWithHistory: Bool = false) { + self.doseType = .suspend + self.units = 0 + self.startTime = suspendStartTime + self.duration = 0 + self.isReconciledWithHistory = isReconciledWithHistory + self.uuid = UUID() + } + + init(resumeStartTime: Date, isReconciledWithHistory: Bool = false) { + self.doseType = .resume + self.units = 0 + self.startTime = resumeStartTime + self.duration = 0 + self.isReconciledWithHistory = isReconciledWithHistory + self.uuid = UUID() + } + + public mutating func cancel(at date: Date, pumpModel: PumpModel) { + guard date < finishTime else { + return + } + + let programmedUnits = units + self.programmedUnits = programmedUnits + let newDuration = date.timeIntervalSince(startTime) + + switch doseType { + case .bolus: + (units,_) = pumpModel.estimateBolusProgress(elapsed: newDuration, programmedUnits: programmedUnits) + case .tempBasal: + programmedTempRate = rate + (units,_) = pumpModel.estimateTempBasalProgress(unitsPerHour: rate, duration: duration, elapsed: newDuration) + default: + break + } + duration = newDuration + } + + public var description: String { + switch doseType { + case .bolus: + return "Bolus units:\(programmedUnits ?? units) \(startTime)" + case .tempBasal: + return "TempBasal rate:\(programmedTempRate ?? 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.programmedUnits = scheduledUnits + } + + if let scheduledTempRate = rawValue["scheduledTempRate"] as? Double { + self.programmedTempRate = scheduledTempRate + } + + if let uuidString = rawValue["uuid"] as? String { + if let uuid = UUID(uuidString: uuidString) { + self.uuid = uuid + } else { + return nil + } + } else { + self.uuid = UUID() + } + + 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, + "uuid": uuid.uuidString, + ] + + if let scheduledUnits = programmedUnits { + rawValue["scheduledUnits"] = scheduledUnits + } + + if let scheduledTempRate = programmedTempRate { + rawValue["scheduledTempRate"] = scheduledTempRate + } + + return rawValue + } +} + +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.uuid.asRaw + 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 { + let newDose = dose?.replacingAttributes(startDate: newDate, duration: newDuration) + return NewPumpEvent(date: newDate, dose: newDose, isMutable: mutable, raw: newRaw, title: title, type: type) + } +} + +extension DoseEntry { + init (_ dose: UnfinalizedDose) { + switch dose.doseType { + case .bolus: + 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.programmedTempRate ?? dose.rate, unit: .unitsPerHour, deliveredUnits: dose.finalizedUnits) + case .suspend: + self = DoseEntry(suspendDate: dose.startTime) + case .resume: + self = DoseEntry(resumeDate: dose.startTime) + } + } + + func replacingAttributes(startDate newStartDate: Date, duration newDuration: TimeInterval) -> DoseEntry { + let value: Double + switch unit { + case .units: + value = programmedUnits + case .unitsPerHour: + value = unitsPerHour + } + let newEndDate = newStartDate.addingTimeInterval(newDuration) + return DoseEntry(type: type, startDate: newStartDate, endDate: newEndDate, value: value, unit: unit, deliveredUnits: deliveredUnits, description: description, syncIdentifier: syncIdentifier) + } +} + +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.programmedUnits ?? dose.units + case .tempBasal: + return type == .tempBasal && eventDose.unitsPerHour == dose.programmedTempRate ?? dose.rate + case .suspend: + return type == .suspend + case .resume: + return type == .resume + } + }) + } +} + +extension UUID { + var asRaw: Data { + return withUnsafePointer(to: self) { + Data(bytes: $0, count: MemoryLayout.size(ofValue: self)) + } + } +} 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/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/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/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/MinimedKitPlugin/Info.plist b/MinimedKitPlugin/Info.plist new file mode 100644 index 000000000..2cce66c09 --- /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 + 3.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/MinimedKitTests/Info.plist b/MinimedKitTests/Info.plist index de2b7324d..fccbfa25b 100644 --- a/MinimedKitTests/Info.plist +++ b/MinimedKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleSignature ???? CFBundleVersion 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/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/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/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/PumpEvents/ResumePumpEventTests.swift b/MinimedKitTests/PumpEvents/ResumePumpEventTests.swift new file mode 100644 index 000000000..6df1b9b0c --- /dev/null +++ b/MinimedKitTests/PumpEvents/ResumePumpEventTests.swift @@ -0,0 +1,24 @@ +// +// ResumePumpEventTests.swift +// MinimedKitTests +// +// Created by Pete Schwamb on 11/10/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import XCTest + +@testable import MinimedKit + +class ResumePumpEventTests: XCTestCase { + + func testRemotelyTriggeredFlag() { + + let localResume = ResumePumpEvent(availableData: Data(hexadecimalString: "1f20a4e30e0a13")!, pumpModel: .model523)! + XCTAssert(!localResume.wasRemotelyTriggered) + + let remoteResume = ResumePumpEvent(availableData: Data(hexadecimalString: "1f209de40e4a13")!, pumpModel: .model523)! + XCTAssert(remoteResume.wasRemotelyTriggered) + } +} + diff --git a/MinimedKitTests/PumpOpsSynchronousTests.swift b/MinimedKitTests/PumpOpsSynchronousTests.swift index 36adc1079..e84d8b0d1 100644 --- a/MinimedKitTests/PumpOpsSynchronousTests.swift +++ b/MinimedKitTests/PumpOpsSynchronousTests.swift @@ -253,35 +253,28 @@ 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 } - func createSquareBolusEvent2016() -> 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..([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()) - XCTAssertFalse(sut.isMutable()) + XCTAssertFalse(sut.isMutable(forPump: .model522)) } 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()) - 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/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/Base.lproj/MinimedPumpManager.storyboard b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard index 7acfa46f5..62b2593aa 100644 --- a/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard +++ b/MinimedKitUI/Base.lproj/MinimedPumpManager.storyboard @@ -1,13 +1,9 @@ - - - - + + - - - + @@ -18,7 +14,7 @@ - + @@ -40,12 +36,12 @@ - + - + @@ -53,8 +49,10 @@ + + @@ -63,13 +61,14 @@ + - + @@ -78,7 +77,7 @@ @@ -98,6 +97,7 @@ + @@ -108,7 +108,7 @@ - + @@ -116,7 +116,6 @@ - @@ -136,13 +135,14 @@ + - + @@ -150,7 +150,6 @@ - @@ -159,9 +158,10 @@ + - + @@ -176,6 +176,7 @@ + @@ -222,12 +223,12 @@ - + - + @@ -242,13 +243,14 @@ + - + @@ -265,7 +267,7 @@ - + @@ -282,7 +284,7 @@ - + @@ -299,7 +301,7 @@ - + @@ -316,7 +318,7 @@ - + @@ -337,15 +339,14 @@ - + - - + @@ -354,9 +355,10 @@ + - + @@ -371,6 +373,7 @@ + @@ -397,12 +400,12 @@ - + - + @@ -417,9 +420,10 @@ + - + @@ -435,9 +439,10 @@ + - + @@ -452,6 +457,7 @@ + @@ -476,10 +482,10 @@ - + - + @@ -494,6 +500,7 @@ + @@ -517,12 +524,12 @@ - + - + @@ -540,9 +547,10 @@ + - + @@ -557,6 +565,7 @@ + @@ -598,6 +607,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/Info.plist b/MinimedKitUI/Info.plist index 011e22e48..7a3ea75eb 100644 --- a/MinimedKitUI/Info.plist +++ b/MinimedKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/MinimedKitUI/MinimedHUDProvider.swift b/MinimedKitUI/MinimedHUDProvider.swift new file mode 100644 index 000000000..80c33527a --- /dev/null +++ b/MinimedKitUI/MinimedHUDProvider.swift @@ -0,0 +1,132 @@ +// +// 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 { + + 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.stateObservers.insert(self, queue: .main) + } + + 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] + } +} + +extension MinimedHUDProvider: MinimedPumpManagerStateObserver { + func didUpdatePumpManagerState(_ state: MinimedPumpManagerState) { + dispatchPrecondition(condition: .onQueue(.main)) + self.state = state + } +} diff --git a/MinimedKitUI/MinimedPumpManager+UI.swift b/MinimedKitUI/MinimedPumpManager+UI.swift index 36485b663..714c2f283 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) { @@ -67,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))) @@ -88,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/MinimedPumpSettingsViewController.swift b/MinimedKitUI/MinimedPumpSettingsViewController.swift index e20230667..d7bbf008e 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, queue: .main) + + 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: @@ -214,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.navigationController?.popViewController(animated: true) + self.pumpManager.notifyDelegateOfDeactivation { + DispatchQueue.main.async { + self.done() + } + } }) present(confirmVC, animated: true) { @@ -238,8 +283,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 +292,7 @@ class MinimedPumpSettingsViewController: RileyLinkSettingsViewController { case .preferredInsulinDataSource: break } - case .rileyLinks: - break - case .delete: + case .info, .actions, .rileyLinks, .delete: break } @@ -286,8 +327,42 @@ 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, 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 + } +} private extension UIAlertController { convenience init(pumpDeletionHandler handler: @escaping () -> Void) { 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 c2b49749e..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 } @@ -34,7 +36,7 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { private var pumpRegionCode: RegionCode? { didSet { regionAndColorPickerCell.regionLabel.text = pumpRegionCode?.region.description - regionAndColorPickerCell.regionLabel.textColor = .darkText + regionAndColorPickerCell.regionLabel.textColor = nil updateStateForSettings() } @@ -60,6 +62,8 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { private var pumpOps: PumpOps? private var pumpState: PumpState? + + private var pumpFirmwareVersion: String? var maxBasalRateUnitsPerHour: Double? @@ -75,19 +79,20 @@ 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, + suspendState: .resumed(Date())) } } @@ -178,7 +183,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 +253,8 @@ class MinimedPumpIDSetupViewController: SetupTableViewController { _ = try session.tuneRadio() let model = try session.getPumpModel() var isSentrySetUpNeeded = false + + self.pumpFirmwareVersion = try session.getPumpFirmwareVersion() // Radio if model.hasMySentry { @@ -265,7 +272,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) @@ -309,7 +316,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..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 { @@ -52,6 +56,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 +78,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 +121,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..5058fedfd 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 @@ -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/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift b/MinimedKitUI/Setup/MinimedPumpSettingsSetupViewController.swift index da4465acb..154cd9310 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 @@ -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 = setupViewController?.basalSchedule { + 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.state.timeZone } vc.title = sender?.textLabel?.text @@ -139,8 +142,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 +153,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/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 2f50eec3d..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$@"; @@ -41,7 +38,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 +57,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,29 +72,38 @@ "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 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" = "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"; +/* 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"; @@ -167,10 +179,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,17 +191,19 @@ "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"; +/* 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/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/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/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/NightscoutUploadKit/Info.plist b/NightscoutUploadKit/Info.plist index 449c7b6af..21baa19b4 100644 --- a/NightscoutUploadKit/Info.plist +++ b/NightscoutUploadKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleSignature ???? CFBundleVersion 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/NightscoutProfile.swift b/NightscoutUploadKit/NightscoutProfile.swift index cbee2faa9..19d284792 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 { @@ -36,7 +42,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 @@ -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 }, @@ -69,13 +75,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 +99,113 @@ 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(duration: TimeInterval, targetRange: ClosedRange?, insulinNeedsScaleFactor: Double?, symbol: String?, 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? + let deviceToken: Data? + let bundleIdentifier: String? + + 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 + self.minimumBGGuard = minimumBGGuard + self.preMealTargetRange = preMealTargetRange + self.maximumBasalRatePerHour = maximumBasalRatePerHour + self.maximumBolus = maximumBolus + self.deviceToken = deviceToken + self.bundleIdentifier = bundleIdentifier + } + + 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 + } + + if let deviceToken = deviceToken { + rval["deviceToken"] = deviceToken.hexadecimalString + } + + if let bundleIdentifier = bundleIdentifier { + rval["bundleIdentifier"] = bundleIdentifier + } + + return rval + } +} 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..99fff292e 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 { @@ -14,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 { @@ -47,54 +49,31 @@ public class NightscoutUploader { self.siteURL = siteURL 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 + 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. /// /// - 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. @@ -102,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]() @@ -111,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) } @@ -130,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]() @@ -139,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) } @@ -151,79 +135,43 @@ public class NightscoutUploader { completionHandler(errors.first) } } - - public func uploadDeviceStatus(_ status: DeviceStatus) { - deviceStatuses.append(status.dictionaryRepresentation) - 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) { + /// 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 + } - 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 - } - }() + let queryItems = ids.map { URLQueryItem(name: "find[_id][$in][]", value: $0) } + guard let url = url(for: .treatments, queryItems: queryItems) else { + completionHandler(UploadError.missingConfiguration) + return + } - // 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" + dataAccessQueue.async { + self.callNS(nil, url: url, method: "DELETE") { (result) in + switch result { + case .success( _): + completionHandler(nil) + case .failure(let error): + completionHandler(error) } - }() - - let entry = NightscoutEntry(glucose: glucose, timestamp: sensorDate, device: device, glucoseType: .Sensor, previousSGV: previousSGV, previousSGVNotActive: previousSGVNotActive, direction: direction) - entries.append(entry) + } } - flushAll() } + + public func uploadDeviceStatus(_ status: DeviceStatus) { + deviceStatuses.append(status.dictionaryRepresentation) + flushAll() + } + public func uploadSGV(glucoseMGDL: Int, at date: Date, direction: String?, device: String) { let entry = NightscoutEntry( glucose: glucoseMGDL, @@ -237,48 +185,44 @@ 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) { - 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 - func flushAll() { + public func flushAll() { flushDeviceStatuses() flushEntries() 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) @@ -286,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) @@ -300,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 { @@ -334,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") @@ -417,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") @@ -431,10 +377,14 @@ public class NightscoutUploader { } } - func flushEntries() { + public 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") @@ -447,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") @@ -462,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/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/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/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/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift index 6e5204908..c066ed05b 100644 --- a/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift +++ b/NightscoutUploadKit/Treatments/TempBasalNightscoutTreatment.swift @@ -17,27 +17,29 @@ public class TempBasalNightscoutTreatment: NightscoutTreatment { let rate: Double + 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) { + 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 self.duration = duration + self.amount = amount - 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] { var rval = super.dictionaryRepresentation rval["temp"] = temp.rawValue rval["rate"] = rate - if let absolute = absolute { - rval["absolute"] = absolute - } - rval["duration"] = duration + rval["absolute"] = absolute + rval["duration"] = duration.minutes + rval["amount"] = amount return rval } } 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/Info.plist b/NightscoutUploadKitTests/Info.plist index 01eb78a1c..f7fe54e8c 100644 --- a/NightscoutUploadKitTests/Info.plist +++ b/NightscoutUploadKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleSignature ???? CFBundleVersion 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/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/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/Info.plist b/OmniKit/Info.plist index 011e22e48..7a3ea75eb 100644 --- a/OmniKit/Info.plist +++ b/OmniKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/OmniKit/MessageTransport/CRC16.swift b/OmniKit/MessageTransport/CRC16.swift index 33091ec20..70a339e06 100644 --- a/OmniKit/MessageTransport/CRC16.swift +++ b/OmniKit/MessageTransport/CRC16.swift @@ -44,7 +44,7 @@ let crc16Table: [UInt16] = [ public extension Sequence where Element == UInt8 { - public func crc16() -> UInt16 { + func crc16() -> UInt16 { var acc: UInt16 = 0 for byte in self { diff --git a/OmniKit/MessageTransport/CRC8.swift b/OmniKit/MessageTransport/CRC8.swift index 3b541df6b..46889130d 100644 --- a/OmniKit/MessageTransport/CRC8.swift +++ b/OmniKit/MessageTransport/CRC8.swift @@ -29,7 +29,7 @@ fileprivate let crcTable: [UInt8] = [ public extension Sequence where Element == UInt8 { - public func crc8() -> UInt8 { + func crc8() -> UInt8 { var crc: UInt8 = 0 for byte in self { diff --git a/OmniKit/MessageTransport/Message.swift b/OmniKit/MessageTransport/Message.swift index ac2fde6fc..87f806323 100644 --- a/OmniKit/MessageTransport/Message.swift +++ b/OmniKit/MessageTransport/Message.swift @@ -20,11 +20,13 @@ struct Message { let address: UInt32 let messageBlocks: [MessageBlock] let sequenceNum: Int + let expectFollowOnMessage: Bool - init(address: UInt32, messageBlocks: [MessageBlock], sequenceNum: Int) { + 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 { @@ -39,6 +41,7 @@ struct Message { 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) @@ -56,7 +59,7 @@ struct Message { throw MessageBlockError.unknownBlockType(rawVal: data[idx]) } do { - let block = try blockType.blockType.init(encodedData: data.suffix(from: idx)) + let block = try blockType.blockType.init(encodedData: Data(data.suffix(from: idx))) blocks.append(block) idx += Int(block.data.count) } catch (let error) { @@ -74,7 +77,7 @@ struct Message { cmdData.append(cmd.data) } - let b9: UInt8 = (UInt8(sequenceNum & 0b11111) << 2) + UInt8((cmdData.count >> 8) & 0b11) + 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)) @@ -83,5 +86,23 @@ struct Message { 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 index 21d492edd..42404bcc4 100644 --- a/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/AssignAddressCommand.swift @@ -16,7 +16,7 @@ public struct AssignAddressCommand : MessageBlock { let address: UInt32 public var data: Data { - var data = Data(bytes: [ + var data = Data([ blockType.rawValue, 4 ]) diff --git a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift index 2c5648cb9..92829d3e7 100644 --- a/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/BasalScheduleExtraCommand.swift @@ -12,23 +12,24 @@ public struct BasalScheduleExtraCommand : MessageBlock { public let blockType: MessageBlockType = .basalScheduleExtra - public let confidenceReminder: Bool + public let acknowledgementBeep: Bool + public let completionBeep: Bool public let programReminderInterval: TimeInterval public let currentEntryIndex: UInt8 public let remainingPulses: Double - public let delayUntilNextPulse: TimeInterval + public let delayUntilNextTenthOfPulse: TimeInterval public let rateEntries: [RateEntry] public var data: Data { - let reminders = (UInt8(programReminderInterval.minutes) & 0x3f) + (confidenceReminder ? (1<<6) : 0) - var data = Data(bytes: [ + let beepOptions = (UInt8(programReminderInterval.minutes) & 0x3f) + (completionBeep ? (1<<6) : 0) + (acknowledgementBeep ? (1<<7) : 0) + var data = Data([ blockType.rawValue, UInt8(8 + rateEntries.count * 6), - reminders, + beepOptions, currentEntryIndex ]) - data.appendBigEndian(UInt16(remainingPulses * 10)) - data.appendBigEndian(UInt32(delayUntilNextPulse.hundredthsOfMilliseconds)) + data.appendBigEndian(UInt16(round(remainingPulses * 10))) + data.appendBigEndian(UInt32(round(delayUntilNextTenthOfPulse.milliseconds * 1000))) for entry in rateEntries { data.append(entry.data) } @@ -42,13 +43,14 @@ public struct BasalScheduleExtraCommand : MessageBlock { let length = encodedData[1] let numEntries = (length - 8) / 6 - confidenceReminder = encodedData[2] & (1<<6) != 0 + 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) - delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) + delayUntilNextTenthOfPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) var entries = [RateEntry]() for entryIndex in (0.. 0 ? squareWaveDuration / Double(pulseCountX10) : 0 + data.appendBigEndian(UInt32(timeBetweenExtendedPulses.hundredthsOfMilliseconds)) return data } @@ -34,14 +46,32 @@ public struct BolusExtraCommand : MessageBlock { if encodedData.count < 15 { throw MessageBlockError.notEnoughData } - byte2 = encodedData[2] + + 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 - unknownSection = encodedData.subdata(in: 5..<9) + + 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(units: Double, byte2: UInt8, unknownSection: Data) { + 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 self.units = units - self.byte2 = byte2 - self.unknownSection = unknownSection + self.timeBetweenPulses = timeBetweenPulses + self.squareWaveUnits = squareWaveUnits + self.squareWaveDuration = squareWaveDuration } } + diff --git a/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift b/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift index 140c9cd21..db6e880a3 100644 --- a/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/CancelDeliveryCommand.swift @@ -8,26 +8,12 @@ import Foundation + + public struct CancelDeliveryCommand : NonceResyncableMessageBlock { public let blockType: MessageBlockType = .cancelDelivery - public struct DeliveryType: OptionSet { - public let rawValue: UInt8 - - static let none = DeliveryType(rawValue: 0) - static let basal = DeliveryType(rawValue: 1 << 0) - static let tempBasal = DeliveryType(rawValue: 1 << 1) - static let bolus = DeliveryType(rawValue: 1 << 2) - static let extendedBolus = DeliveryType(rawValue: 1 << 3) - - static let all: DeliveryType = [.none, .basal, .tempBasal, .bolus, .extendedBolus] - - public init(rawValue: UInt8) { - self.rawValue = rawValue - } - } - // ID1:1f00ee84 PTYPE:PDM SEQ:26 ID2:1f00ee84 B9:ac BLEN:7 MTYPE:1f05 BODY:e1f78752078196 CRC:03 // Cancel bolus @@ -47,14 +33,30 @@ public struct CancelDeliveryCommand : NonceResyncableMessageBlock { // 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: ConfigureAlertsCommand.BeepType + public let beepType: BeepType public var nonce: UInt32 public var data: Data { - var data = Data(bytes: [ + var data = Data([ blockType.rawValue, 5, ]) @@ -69,10 +71,10 @@ public struct CancelDeliveryCommand : NonceResyncableMessageBlock { } self.nonce = encodedData[2...].toBigEndian(UInt32.self) self.deliveryType = DeliveryType(rawValue: encodedData[6] & 0xf) - self.beepType = ConfigureAlertsCommand.BeepType(rawValue: encodedData[6] >> 4)! + self.beepType = BeepType(rawValue: encodedData[6] >> 4)! } - public init(nonce: UInt32, deliveryType: DeliveryType, beepType: ConfigureAlertsCommand.BeepType) { + 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 index 53808a8b2..e9cf6d904 100644 --- a/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/ConfigureAlertsCommand.swift @@ -10,151 +10,13 @@ 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: [ + var data = Data([ blockType.rawValue, UInt8(4 + configurations.count * AlertConfiguration.length), ]) @@ -189,3 +51,79 @@ public struct ConfigureAlertsCommand : NonceResyncableMessageBlock { self.configurations = configurations } } + +// MARK: - AlertConfiguration encoding/decoding +extension AlertConfiguration { + public init(encodedData: Data) throws { + if encodedData.count < 6 { + throw MessageBlockError.notEnoughData + } + + let alertTypeBits = encodedData[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 secondsUntilAlert): + // round the time to alert to the nearest minute + let minutes = UInt16((secondsUntilAlert + 30).minutes) + data.appendBigEndian(minutes) + } + data.append(beepRepeat.rawValue) + data.append(beepType.rawValue) + + return data + } +} diff --git a/OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift b/OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift similarity index 83% rename from OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift rename to OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift index 491c3af64..d6108d0bb 100644 --- a/OmniKit/MessageTransport/MessageBlocks/SetPodTimeCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/ConfigurePodCommand.swift @@ -1,5 +1,5 @@ // -// SetupPodCommand.swift +// ConfigurePodCommand.swift // OmniKit // // Created by Pete Schwamb on 2/17/18. @@ -8,7 +8,7 @@ import Foundation -public struct SetupPodCommand : MessageBlock { +public struct ConfigurePodCommand : MessageBlock { public let blockType: MessageBlockType = .setupPod @@ -16,6 +16,7 @@ public struct SetupPodCommand : MessageBlock { 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) @@ -31,7 +32,7 @@ public struct SetupPodCommand : MessageBlock { // 03 13 1f08ced2 14 04 09 0b 11 0b 08 0000a640 00097c27 83e4 public var data: Data { - var data = Data(bytes: [ + var data = Data([ blockType.rawValue, 19, ]) @@ -43,11 +44,11 @@ public struct SetupPodCommand : MessageBlock { let hour = UInt8(dateComponents.hour ?? 0) let minute = UInt8(dateComponents.minute ?? 0) - let data2: Data = Data(bytes: [ + let data2: Data = Data([ UInt8(0x14), // Unknown - UInt8(0x04), // Unknown - day, + packetTimeoutLimit, month, + day, year, hour, minute @@ -63,9 +64,10 @@ public struct SetupPodCommand : MessageBlock { throw MessageBlockError.notEnoughData } self.address = encodedData[2...].toBigEndian(UInt32.self) + packetTimeoutLimit = encodedData[7] var components = DateComponents() - components.day = Int(encodedData[8]) - components.month = Int(encodedData[9]) + 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]) @@ -74,10 +76,11 @@ public struct SetupPodCommand : MessageBlock { self.tid = encodedData[17...].toBigEndian(UInt32.self) } - public init(address: UInt32, dateComponents: DateComponents, lot: UInt32, tid: UInt32) { + 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 index 77f42470f..184c62145 100644 --- a/OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/DeactivatePodCommand.swift @@ -18,7 +18,7 @@ public struct DeactivatePodCommand : NonceResyncableMessageBlock { // e1f78752 07 8196 public var data: Data { - var data = Data(bytes: [ + var data = Data([ blockType.rawValue, 4, ]) 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 index 6a1ec59f8..d68ebdbbc 100644 --- a/OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/GetStatusCommand.swift @@ -9,44 +9,39 @@ import Foundation public struct GetStatusCommand : MessageBlock { - - public enum StatusType: UInt8 { - case normal = 0x00 - case configuredAlerts = 0x01 - case faultEvents = 0x02 - case dataLog = 0x03 - case faultDataInitializationTime = 0x04 - case hardcodedValues = 0x06 - case resetStatus = 0x46 // including state, initialization time, any faults - case dumpRecentFlashLog = 0x50 - case dumpOlderFlashlog = 0x51 // but dumps entries before the last 50 - // https://github.com/openaps/openomni/wiki/Command-0E-Status-Request - } - + // OFF 1 2 + // Oe 01 TT + public let blockType: MessageBlockType = .getStatus public let length: UInt8 = 1 - public let requestType: StatusType - - public init(requestType: StatusType = .normal) { - self.requestType = requestType + public let podInfoType: PodInfoResponseSubType + + public init(podInfoType: PodInfoResponseSubType = .normal) { + self.podInfoType = podInfoType } - public init(encodedData: Data) throws { + public init(encodedData: Data) throws { if encodedData.count < 3 { throw MessageBlockError.notEnoughData } - guard let requestType = StatusType(rawValue: encodedData[2]) else { - throw MessageError.unknownValue(value: encodedData[2], typeDescription: "StatusType") + guard let podInfoType = PodInfoResponseSubType(rawValue: encodedData[2]) else { + throw MessageError.unknownValue(value: encodedData[2], typeDescription: "PodInfoResponseSubType") } - self.requestType = requestType + self.podInfoType = podInfoType } public var data: Data { - var data = Data(bytes: [ + var data = Data([ blockType.rawValue, length ]) - data.append(requestType.rawValue) + 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 index 5b7d1a281..61d524627 100644 --- a/OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift +++ b/OmniKit/MessageTransport/MessageBlocks/MessageBlock.swift @@ -1,24 +1,29 @@ // -// Command.swift +// 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 statusError = 0x02 + 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 @@ -26,16 +31,19 @@ public enum MessageBlockType: UInt8 { 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 .statusError: - return StatusError.self + case .acknowledgeAlert: + return AcknowledgeAlertCommand.self + case .podInfoResponse: + return PodInfoResponse.self case .setupPod: - return SetupPodCommand.self + return ConfigurePodCommand.self case .errorResponse: return ErrorResponse.self case .assignAddress: @@ -56,8 +64,12 @@ public enum MessageBlockType: UInt8 { return StatusResponse.self case .tempBasalExtra: return TempBasalExtraCommand.self + case .beepConfig: + return BeepConfigCommand.self case .cancelDelivery: return CancelDeliveryCommand.self + case .faultConfig: + return FaultConfigCommand.self } } } diff --git a/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift b/OmniKit/MessageTransport/MessageBlocks/PodInfo.swift new file mode 100644 index 000000000..dc79e1fef --- /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 PodInfoFlashLogPrevious.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..a8c98a39d --- /dev/null +++ b/OmniKit/MessageTransport/MessageBlocks/PodInfoFlashLogRecent.swift @@ -0,0 +1,68 @@ +// +// PodInfoFlashLogRecent.swift +// OmniKit +// +// Created by Eelke Jager on 26/09/2018. +// Copyright © 2018 Pete Schwamb. All rights reserved. +// + +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 ... + + public var podInfoType : PodInfoResponseSubType = .flashLogRecent + 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.data = encodedData + self.nEntries = nLogEntriesReported + self.hexWordLog = encodedData.subdata(in: 3.. ScheduleTypeCode { switch self { @@ -36,24 +35,25 @@ public struct SetInsulinScheduleCommand : NonceResyncableMessageBlock { fileprivate var data: Data { switch self { case .basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table): - var data = Data(bytes: [currentSegment]) + 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 multiplier): - let pulseCount = UInt16(units / podPulseSize) + 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(bytes: [numHalfHourSegments]) + 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(bytes: [UInt8(table.numSegments())]) + var data = Data([UInt8(table.numSegments())]) data.appendBigEndian(secondsRemaining << 3) data.appendBigEndian(firstSegmentPulses) for entry in table.entries { @@ -84,7 +84,7 @@ public struct SetInsulinScheduleCommand : NonceResyncableMessageBlock { public let deliverySchedule: DeliverySchedule public var data: Data { - var data = Data(bytes: [ + var data = Data([ blockType.rawValue, UInt8(7 + deliverySchedule.data.count), ]) @@ -140,7 +140,8 @@ public struct SetInsulinScheduleCommand : NonceResyncableMessageBlock { let fieldA = encodedData[10...].toBigEndian(UInt16.self) let unitRate = encodedData[12...].toBigEndian(UInt16.self) let units = Double(unitRate) * 0.1 * duration.hours - deliverySchedule = .bolus(units: units, multiplier: fieldA / unitRate) + let multiplier = fieldA / unitRate + deliverySchedule = .bolus(units: units, timeBetweenPulses: Double(multiplier) / 8) } guard checksum == deliverySchedule.checksum() else { @@ -155,20 +156,27 @@ public struct SetInsulinScheduleCommand : NonceResyncableMessageBlock { public init(nonce: UInt32, tempBasalRate: Double, duration: TimeInterval) { self.nonce = nonce - let pulsesPerSegment = tempBasalRate * BasalDeliveryTable.segmentDuration / TimeInterval(hours: 1) / podPulseSize - let basalSchedule = BasalSchedule(entries: [BasalScheduleEntry(rate: tempBasalRate, duration: duration)]) - let table = BasalDeliveryTable(schedule: basalSchedule) - self.deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.tempBasal(secondsRemaining: 30*60, firstSegmentPulses: UInt16(pulsesPerSegment), table: table) + let pulsesPerHour = Int(round(tempBasalRate / Pod.pulseSize)) + let table = BasalDeliveryTable(tempBasalRate: tempBasalRate, duration: duration) + self.deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.tempBasal(secondsRemaining: 30*60, firstSegmentPulses: UInt16(pulsesPerHour / 2), table: table) } public init(nonce: UInt32, basalSchedule: BasalSchedule, scheduleOffset: TimeInterval) { + let scheduleOffsetNearestSecond = round(scheduleOffset) let table = BasalDeliveryTable(schedule: basalSchedule) + let rate = basalSchedule.rateAt(offset: scheduleOffsetNearestSecond) + + let segment = Int(scheduleOffsetNearestSecond / BasalDeliveryTable.segmentDuration) + + let segmentOffset = round(scheduleOffsetNearestSecond.truncatingRemainder(dividingBy: BasalDeliveryTable.segmentDuration)) + + let timeRemainingInSegment = BasalDeliveryTable.segmentDuration - segmentOffset - let segment = Int(scheduleOffset / BasalDeliveryTable.segmentDuration) - let timeRemainingInSegment = BasalDeliveryTable.segmentDuration - scheduleOffset.truncatingRemainder(dividingBy: BasalDeliveryTable.segmentDuration) - let rate = basalSchedule.rateAt(offset: scheduleOffset) - let pulsesPerSegment = rate * BasalDeliveryTable.segmentDuration / TimeInterval(hours: 1) / podPulseSize - let pulsesRemainingInSegment = timeRemainingInSegment / BasalDeliveryTable.segmentDuration * pulsesPerSegment + let timeBetweenPulses: TimeInterval = .hours(1) / (rate / Pod.pulseSize) + + let offsetToNextTenth = timeRemainingInSegment.truncatingRemainder(dividingBy: timeBetweenPulses / 10.0) + + let pulsesRemainingInSegment = (timeRemainingInSegment + timeBetweenPulses / 10.0 - offsetToNextTenth) / timeBetweenPulses self.deliverySchedule = SetInsulinScheduleCommand.DeliverySchedule.basalSchedule(currentSegment: UInt8(segment), secondsRemaining: UInt16(timeRemainingInSegment), pulsesRemaining: UInt16(pulsesRemainingInSegment), table: table) self.nonce = nonce @@ -179,3 +187,8 @@ fileprivate func calculateChecksum(_ data: Data) -> 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/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 index 6e5789557..28911d1d3 100644 --- a/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift +++ b/OmniKit/MessageTransport/MessageBlocks/StatusResponse.swift @@ -11,7 +11,7 @@ import Foundation public struct StatusResponse : MessageBlock { public enum DeliveryStatus: UInt8, CustomStringConvertible { - case deliveryInterrupted = 0 + case suspended = 0 case normal = 1 case tempBasalRunning = 2 case priming = 4 @@ -29,10 +29,10 @@ public struct StatusResponse : MessageBlock { public var description: String { switch self { - case .deliveryInterrupted: - return LocalizedString("Interrupted", comment: "Delivery status when delivery is interrupted") + 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") + 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: @@ -44,131 +44,17 @@ public struct StatusResponse : MessageBlock { } } } - - 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 podProgressStatus: PodProgressStatus 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 alerts: AlertSet public let data: Data @@ -185,10 +71,10 @@ public struct StatusResponse : MessageBlock { } self.deliveryStatus = deliveryStatus - guard let reservoirStatus = ReservoirStatus(rawValue: encodedData[1] & 0xf) else { - throw MessageError.unknownValue(value: encodedData[1] & 0xf, typeDescription: "ReservoirStatus") + guard let podProgressStatus = PodProgressStatus(rawValue: encodedData[1] & 0xf) else { + throw MessageError.unknownValue(value: encodedData[1] & 0xf, typeDescription: "PodProgressStatus") } - self.reservoirStatus = reservoirStatus + self.podProgressStatus = podProgressStatus let minutes = ((Int(encodedData[7]) & 0x7f) << 6) + (Int(encodedData[8]) >> 2) self.timeActive = TimeInterval(minutes: Double(minutes)) @@ -196,21 +82,26 @@ public struct StatusResponse : MessageBlock { 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.insulin = Double(highInsulinBits | midInsulinBits | lowInsulinBits) / Pod.pulsesPerUnit self.podMessageCounter = (encodedData[4] >> 3) & 0xf - self.insulinNotDelivered = podPulseSize * Double((Int(encodedData[4] & 0x3) << 8) | Int(encodedData[5])) + self.insulinNotDelivered = Double((Int(encodedData[4] & 0x3) << 8) | Int(encodedData[5])) / Pod.pulsesPerUnit - self.alarms = PodAlarmState(rawValue: ((encodedData[6] & 0x7f) << 1) | (encodedData[7] >> 7)) + self.alerts = AlertSet(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 + 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 index 0ff5c99b8..c9bad0eae 100644 --- a/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift +++ b/OmniKit/MessageTransport/MessageBlocks/TempBasalExtraCommand.swift @@ -10,27 +10,28 @@ import Foundation public struct TempBasalExtraCommand : MessageBlock { - public let confidenceReminder: Bool + public let acknowledgementBeep: Bool + 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 public var data: Data { - let reminders = (UInt8(programReminderInterval.minutes) & 0x3f) + (confidenceReminder ? (1<<6) : 0) - var data = Data(bytes: [ + let beepOptions = (UInt8(programReminderInterval.minutes) & 0x3f) + (completionBeep ? (1<<6) : 0) + (acknowledgementBeep ? (1<<7) : 0) + var data = Data([ blockType.rawValue, UInt8(8 + rateEntries.count * 6), - reminders, + beepOptions, 0 ]) - data.appendBigEndian(UInt16(remainingPulses * 10)) + 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) @@ -43,36 +44,45 @@ public struct TempBasalExtraCommand : MessageBlock { let length = encodedData[1] let numEntries = (length - 8) / 6 - confidenceReminder = encodedData[2] & (1<<6) != 0 + 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) - delayUntilNextPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter)) + if remainingPulses == 0 { + delayUntilFirstTenthOfPulse = TimeInterval(hundredthsOfMilliseconds: Double(timerCounter) / 10) + } else { + delayUntilFirstTenthOfPulse = 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 packetNumber = 0 - private(set) var messageNumber = 0 + 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 - - init(session: CommandSession, address: UInt32 = 0xffffffff, ackAddress: UInt32? = nil) { + 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) { @@ -41,29 +119,50 @@ class MessageTransport { return Packet(address: address, packetType: .ack, sequenceNum: packetNumber, data: Data(bigEndian: ackAddress)) } - func ackUntilQuiet() throws { + func ackUntilQuiet() { let packetData = makeAckPacket().encoded() - var quiet = false - while !quiet { + var lastHeardAt = Date() + let quietWindow = TimeInterval(milliseconds: 300) + while lastHeardAt.timeIntervalSinceNow > -quietWindow { do { - let _ = try session.sendAndListen(packetData, repeatCount: 5, timeout: TimeInterval(milliseconds: 600), retryCount: 0, preambleExtension: TimeInterval(milliseconds: 40)) + 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. - quiet = true + break + } catch { + continue } } incrementPacketNumber() } - - func exchangePackets(packet: Packet, repeatCount: Int = 0, packetResponseTimeout: TimeInterval = .milliseconds(165), exchangeTimeout:TimeInterval = .seconds(20), preambleExtension: TimeInterval = .milliseconds(127)) throws -> Packet { + + /// 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 = 20 + 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) @@ -72,20 +171,27 @@ class MessageTransport { do { candidatePacket = try Packet(rfPacket: rfPacket) - } catch { + log.default("Received packet (%d): %@", rfPacket.rssi, rfPacket.data.hexadecimalString) + } catch PacketError.insufficientData { + log.default("Insufficient packet data: %@", rfPacket.data.hexadecimalString) + continue + } catch let error { + log.default("Packet error: %@", String(describing: error)) continue } - + guard candidatePacket.address == packet.address else { + log.default("Address %@ does not match %@", String(describing: candidatePacket.address), String(describing: packet.address)) continue } - guard candidatePacket.sequenceNum == ((packetNumber + 1) & 0b11111) else { + guard candidatePacket.sequenceNum == ((packet.sequenceNum + 1) & 0b11111) else { + log.default("Sequence %@ does not match %@", String(describing: candidatePacket.sequenceNum), String(describing: ((packet.sequenceNum + 1) & 0b11111))) continue } // Once we have verification that the POD heard us, we can increment our counters - incrementPacketNumber(2) + incrementPacketNumber() return candidatePacket } catch RileyLinkDeviceError.responseTimeout { @@ -95,15 +201,28 @@ class MessageTransport { throw PodCommsError.noResponse } - - func send(_ messageBlocks: [MessageBlock]) throws -> Message { - let message = Message(address: address, messageBlocks: messageBlocks, sequenceNum: messageNumber) + + /// 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) @@ -114,50 +233,62 @@ class MessageTransport { return response } } - }() + }() guard responsePacket.packetType != .ack else { - log.debug("Pod responded with ack instead of response: %@", String(describing: responsePacket)) - incrementMessageNumber() + messageLogger?.didReceive(responsePacket.data) + log.default("Pod responded with ack instead of response: %@", String(describing: responsePacket)) throw PodCommsError.podAckedInsteadOfReturningResponse } // 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 { - return try Message(encodedData: responseData) + let msg = try Message(encodedData: responseData) + 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)) + log.default("Expected CON packet, received; %@", String(describing: conPacket)) 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 } } - }() - - try ackUntilQuiet() + }() + + ackUntilQuiet() guard response.messageBlocks.count > 0 else { - log.debug("Empty response") + log.default("Empty response") throw PodCommsError.emptyResponse } if response.messageBlocks[0].blockType != .errorResponse { - incrementMessageNumber(2) + 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 } } + 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 index fef1a3e58..cb601a237 100644 --- a/OmniKit/MessageTransport/Packet.swift +++ b/OmniKit/MessageTransport/Packet.swift @@ -5,8 +5,14 @@ // 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) +} -import RileyLinkBLEKit public enum PacketType: UInt8 { case pod = 0b111 @@ -47,13 +53,13 @@ public struct Packet { init(encodedData: Data) throws { guard encodedData.count >= 7 else { // Not enough data for packet - throw PodCommsError.invalidData + throw PacketError.insufficientData } self.address = encodedData[0...].toBigEndian(UInt32.self) guard let packetType = PacketType(rawValue: encodedData[4] >> 5) else { - throw PodCommsError.unknownPacketType(rawType: encodedData[4]) + throw PacketError.unknownPacketType(rawType: encodedData[4]) } self.packetType = packetType self.sequenceNum = Int(encodedData[4] & 0b11111) @@ -63,7 +69,7 @@ public struct Packet { // Check crc guard encodedData[0.. 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..4f14889a9 --- /dev/null +++ b/OmniKit/Model/BeepType.swift @@ -0,0 +1,46 @@ +// +// 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 $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 + case bipBeepBipBeepBipBeepBipBeep = 0x2 + case bipBip = 0x3 + case beep = 0x4 + case beepBeepBeep = 0x5 + case beeeeeep = 0x6 + case bipBipBipbipBipBip = 0x7 + case beeepBeeep = 0x8 + // values greater than 0x8 for $19 and $1F commands can fault pod! +} + +// 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 { + // 0 always returns an error response for Beep Config + 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 response for Beep Config + case beepBeep = 0xB + case beeep = 0xC + case bipBeeeeep = 0xD + case fiveSecondBeep = 0xE // can only be used if Pod is currently suspended! + case noBeep = 0xF +} 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..24c8ff874 --- /dev/null +++ b/OmniKit/Model/Pod.swift @@ -0,0 +1,113 @@ +// +// 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 = 1 / Pod.pulseSize + + // 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) + + // 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) + + // 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 + + // 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 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) + 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..847acb420 --- /dev/null +++ b/OmniKit/Model/UnfinalizedDose.swift @@ -0,0 +1,278 @@ +// +// 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? // Tracks the scheduled units, as boluses may be canceled before finishing, at which point units would reflect actual delivered volume. + var scheduledTempRate: Double? // Tracks the original temp rate, as during finalization the units are discretized to pump pulses, changing the actual rate + 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 isFinished: Bool { + return progress >= 1 + } + + // Units per hour + public var rate: Double { + guard let duration = duration else { + return 0 + } + 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 + 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) { + guard let finishTime = finishTime, date < finishTime else { + return + } + + scheduledUnits = units + 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 + } + } + + 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 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: + 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 scheduledTempRate = rawValue["scheduledTempRate"] as? Double { + self.scheduledTempRate = scheduledTempRate + } + + 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 scheduledTempRate = scheduledTempRate { + rawValue["scheduledTempRate"] = scheduledTempRate + } + + 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: dose.isMutable, 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) + } + } +} diff --git a/OmniKit/PumpManager/MessageLog.swift b/OmniKit/PumpManager/MessageLog.swift new file mode 100644 index 000000000..1dfa0bcec --- /dev/null +++ b/OmniKit/PumpManager/MessageLog.swift @@ -0,0 +1,93 @@ +// +// 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 = ["\n### MessageLog"] + for entry in entries { + lines.append("* " + entry.description) + } + lines.append("") + 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 index 8c5b0257a..59b7a0a28 100644 --- a/OmniKit/PumpManager/OmnipodPumpManager.swift +++ b/OmniKit/PumpManager/OmnipodPumpManager.swift @@ -10,271 +10,1614 @@ import HealthKit import LoopKit import RileyLinkKit import RileyLinkBLEKit +import UserNotifications import os.log -public class OmnipodPumpManager: RileyLinkPumpManager, PumpManager { +fileprivate let tempBasalConfirmationBeeps: Bool = false // whether to emit temp basal confirmation beeps (for testing use) + + +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 pumpBatteryChargeRemaining: Double? + 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 pumpRecordsBasalProfileStartEvents = false + 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) + } + + 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 + delegate?.pumpManagerDidUpdateState(self) + } + + let oldStatus = status(for: oldValue) + let newStatus = status(for: newValue) + + if oldStatus != newStatus { + 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 var pumpReservoirCapacity: Double = 200 + public func addPodStateObserver(_ observer: PodStateObserver, queue: DispatchQueue) { + podStateObservers.insert(observer, queue: queue) + } - public var pumpTimeZone: TimeZone { - return state.podState.timeZone + 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(state.lastPumpDataReportDate ?? .distantPast) + } + + switch state.suspendEngageState { + case .engaging: + return .suspending + case .disengaging: + return .resuming + 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) + } + } + } + + private func bolusState(for state: OmnipodPumpManagerState) -> PumpManagerStatus.BolusState { + guard let podState = state.podState else { + return .none + } + + switch state.bolusEngageState { + case .engaging: + return .initiating + case .disengaging: + return .canceling + case .stable: + if let bolus = podState.unfinalizedBolus, !bolus.isFinished { + return .inProgress(DoseEntry(bolus)) + } + } + 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 hasSetupPod: Bool { + return state.hasSetupPod + } + + // Thread-safe + public var expirationReminderDate: Date? { + get { + return state.expirationReminderDate + } + set { + // Setting a new value reschedules notifications + setState { (state) in + state.expirationReminderDate = newValue + } + } + } + + // Thread-safe + public var confirmationBeeps: Bool { + get { + return state.confirmationBeeps + } + set { + setState { (state) in + state.confirmationBeeps = 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 + podState.expiresAt = start + .hours(72) + + 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 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 { + case .success(let session): + do { + try session.checkInsertionCompleted() + } catch let error { + self.log.error("Failed to fetch pod status: %{public}@", String(describing: error)) + } + case .failure(let error): + self.log.error("Failed to fetch pod status: %{public}@", String(describing: error)) + } + } + } + + public func refreshStatus(completion: ((_ result: PumpManagerResult) -> Void)? = nil) { + guard self.hasActivePod else { + completion?(.failure(OmnipodPumpManagerError.noPodPaired)) + return + } + + self.getPodStatus(storeDosesOnSuccess: false, completion: completion) + } + + // MARK: - Pump Commands + + private func getPodStatus(storeDosesOnSuccess: Bool, completion: ((_ result: PumpManagerResult) -> Void)? = nil) { + 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 + } + + 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() + if storeDosesOnSuccess { + session.dosesForStorage({ (doses) -> Bool in + self.store(doses: doses, in: session) + }) + } + completion?(.success(status)) + case .failure(let error): + throw error + } + } catch let error { + completion?(.failure(error)) + self.log.error("Failed to fetch pod 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?.isFinished != 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 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 + } + 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?.isFinished != 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 beep = self.confirmationBeeps + let _ = try session.setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: beep, completionBeep: beep) + + 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 + } + + 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 { + 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 { + try session.testingCommands() + self.emitConfirmationBeep(session: session, beepConfigType: .bipBip) + 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 + } + 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): + 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) + 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 { + // 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) + } + case .failure(let error): + completion(error) + } + } + } + + 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 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 = enabled + let tempBasalCompletionBeep = enabled && tempBasalConfirmationBeeps + 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) + } + } + } +} + +// 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 lastReconciliation: Date? { + return self.state.podState?.lastInsulinMeasurements?.validTime + } + + 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() + } } - - private var lastPumpDataReportDate: Date? - - public func assertCurrentPumpData() { - - let pumpStatusAgeTolerance = TimeInterval(minutes: 4) - - - guard (lastPumpDataReportDate ?? .distantPast).timeIntervalSinceNow < -pumpStatusAgeTolerance else { + + 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 } - 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) - }) + let rileyLinkSelector = self.rileyLinkDeviceProvider.firstConnectedDevice + self.podComms.runSession(withName: "Suspend", using: rileyLinkSelector) { (result) in - 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) + let session: PodCommsSession + switch result { + case .success(let s): + session = s + case .failure(let error): + completion(error) + return + } + + defer { + self.setState({ (state) in + state.suspendEngageState = .stable + }) + } + self.setState({ (state) in + state.suspendEngageState = .engaging + }) + + // 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): + completion(error) + 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) + } + completion(nil) + } + } + } + + 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.suspendEngageState = .stable + }) + } + self.setState({ (state) in + state.suspendEngageState = .disengaging + }) + + do { + let scheduleOffset = self.state.timeZone.scheduleOffset(forDate: Date()) + 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) + } + 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(storeDosesOnSuccess: true) { (response) in + self.pumpDelegate.notify({ (delegate) in + switch response { + case .success: + self.log.default("Recommending Loop") + delegate?.pumpManagerRecommendsLoop(self) case .failure(let error): - throw 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) + } } - } catch let error { - self.log.error("Failed to fetch pump status: %{public}@", String(describing: 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 (Double, Date) -> Void, completion: @escaping (Error?) -> Void) { - - let rileyLinkSelector = rileyLinkDeviceProvider.firstConnectedDevice - podComms.runSession(withName: "Bolus", using: rileyLinkSelector) { (result) in - + 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(SetBolusError.certain(error)) + completion(.failure(SetBolusError.certain(error))) return } - let podStatus: StatusResponse - + defer { + self.setState({ (state) in + state.bolusEngageState = .stable + }) + } + self.setState({ (state) in + state.bolusEngageState = .engaging + }) + + var podStatus: StatusResponse + do { podStatus = try session.getStatus() } catch let error { - completion(SetBolusError.certain(error as? PodCommsError ?? PodCommsError.commsError(error: 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()) + 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)))) + return + } + } + guard !podStatus.deliveryStatus.bolusing else { - completion(SetBolusError.certain(PodCommsError.unfinalizedBolus)) + completion(.failure(SetBolusError.certain(PodCommsError.unfinalizedBolus))) return } - - session.finalizeDoses(deliveryStatus: podStatus.deliveryStatus, storageHandler: { ( _ ) -> Bool in - return false - }) - - willRequest(units, Date()) - - let result = session.bolus(units: units) - + + 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 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) + } + switch result { case .success: - completion(nil) + completion(.success(dose)) case .certainFailure(let error): - completion(SetBolusError.certain(error)) + completion(.failure(SetBolusError.certain(error))) case .uncertainFailure(let error): - completion(SetBolusError.uncertain(error)) + completion(.failure(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)) + + 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(PumpManagerResult.failure(error)) + completion(.failure(error)) return } - + do { - let podStatus = try session.getStatus() - if podStatus.deliveryStatus.tempBasalRunning { - let cancelStatus = try session.cancelDelivery(deliveryType: .tempBasal, beepType: .noBeep) + defer { + self.setState({ (state) in + state.bolusEngageState = .stable + }) + } + 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 + } + } - guard !cancelStatus.deliveryStatus.tempBasalRunning else { - throw PodCommsError.unfinalizedTempBasal + // when cancelling a bolus give a type 6 beeeeeep to match PDM if doing bolus confirmation beeps + let beeptype: BeepType = self.confirmationBeeps ? .beeeeeep : .noBeep + let result = session.cancelDelivery(deliveryType: .bolus, beepType: beeptype) + switch result { + case .certainFailure(let error): + throw error + case .uncertainFailure(let error): + throw error + case .success(_, let canceledBolus): + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) } + + let canceledDoseEntry: DoseEntry? = canceledBolus != nil ? DoseEntry(canceledBolus!) : nil + completion(.success(canceledDoseEntry)) } - - session.finalizeDoses(deliveryStatus: podStatus.deliveryStatus, storageHandler: { ( _ ) -> Bool in - return false + } 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 + 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?.isFinished != false else { + self.log.info("Not enacting temp basal because podState indicates unfinalized bolus in progress.") + return .unfinalizedBolus + } + + return nil }) - + + if let error = preError { + throw error + } + + let status: StatusResponse + let canceledDose: UnfinalizedDose? + + // 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 + case .uncertainFailure(let error): + throw error + case .success(let cancelTempStatus, let dose): + status = cancelTempStatus + canceledDose = dose + } + + 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 + } + + 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() - let dose = DoseEntry(type: .basal, startDate: cancelTime, endDate: cancelTime, value: 0, unit: .unitsPerHour) - completion(PumpManagerResult.success(dose)) + 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) + session.dosesForStorage() { (doses) -> Bool in + return self.store(doses: doses, in: session) + } + completion(.success(dose)) } else { - let result = session.setTempBasal(rate: unitsPerHour, duration: duration, confidenceReminder: false, programReminderInterval: 0) + self.setState({ (state) in + state.tempBasalEngageState = .engaging + }) + + let beep = self.confirmationBeeps && tempBasalConfirmationBeeps + let result = session.setTempBasal(rate: rate, duration: duration, acknowledgementBeep: beep, completionBeep: beep) let basalStart = Date() - let dose = DoseEntry(type: .basal, startDate: basalStart, endDate: basalStart.addingTimeInterval(duration), value: unitsPerHour, unit: .unitsPerHour) + 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(PumpManagerResult.success(dose)) + completion(.success(dose)) case .uncertainFailure(let error): self.log.error("Temp basal uncertain error: %@", String(describing: error)) - completion(PumpManagerResult.success(dose)) + completion(.success(dose)) case .certainFailure(let error): - completion(PumpManagerResult.failure(error)) + completion(.failure(error)) } } } catch let error { - completion(PumpManagerResult.failure(error)) + self.log.error("Error during temp basal: %@", String(describing: error)) + completion(.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 + /// 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) } - - 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 + return nil } - - override public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? { - get { - return state.rileyLinkConnectionManagerState + + // 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() } - set { - state.rileyLinkConnectionManagerState = newValue + + semaphore.wait() + + if success { + setState { (state) in + state.lastPumpDataReportDate = Date() + } } + return success } - // TODO: apply lock - public private(set) var state: OmnipodPumpManagerState { - didSet { - pumpManagerDelegate?.pumpManagerDidUpdateState(self) + 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, 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.info("DU: Stored pod events: %@", String(describing: doses)) + } + + completion(error) + }) } } - - 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) +} + +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)) + } } - - // MARK: - CustomDebugStringConvertible - - override public var debugDescription: String { - return [ - "## OmnipodPumpManager", - "state: \(state.debugDescription)", - "", - "podComms: \(String(reflecting: podComms))", - "", - super.debugDescription, - ].joined(separator: "\n") + func didReceive(_ message: Data) { + log.default("didReceive: %{public}@", message.hexadecimalString) + setState { (state) in + state.messageLog.record(MessageLogEntry(messageDirection: .receive, timestamp: Date(), data: message)) + } } - - // 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)) + 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.default("Resolved bolus uncertainty: did bolus") + } else if podState.unfinalizedBolus == nil { + self.log.default("Resolved bolus uncertainty: did not bolus") + } } - success = error == nil - semaphore.signal() - }) - semaphore.wait() - - if success { - self.lastPumpDataReportDate = Date() + state.podState = podState } - 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 index 859316697..a486f27e8 100644 --- a/OmniKit/PumpManager/OmnipodPumpManagerState.swift +++ b/OmniKit/PumpManager/OmnipodPumpManagerState.swift @@ -10,29 +10,97 @@ import RileyLinkKit import RileyLinkBLEKit import LoopKit + public struct OmnipodPumpManagerState: RawRepresentable, Equatable { public typealias RawValue = PumpManager.RawStateValue - public static let version = 1 + public static let version = 2 + + public var podState: PodState? + + public var timeZone: TimeZone - public var podState: PodState + public var basalSchedule: BasalSchedule public var rileyLinkConnectionManagerState: RileyLinkConnectionManagerState? - public init(podState: PodState, rileyLinkConnectionManagerState: RileyLinkConnectionManagerState?) { + public var messageLog = MessageLog() + + public var unstoredDoses: [UnfinalizedDose] + + public var expirationReminderDate: Date? + + public var confirmationBeeps: Bool + + // Temporal state not persisted + + 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 + + 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 = [] + self.confirmationBeeps = false } public init?(rawValue: RawValue) { - guard - let podStateRaw = rawValue["podState"] as? PodState.RawValue, - let podState = PodState(rawValue: podStateRaw) - else - { + + 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) @@ -42,37 +110,90 @@ public struct OmnipodPumpManagerState: RawRepresentable, Equatable { 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 = [] + } + + self.confirmationBeeps = rawValue["confirmationBeeps"] as? Bool ?? rawValue["bolusBeeps"] as? Bool ?? false } public var rawValue: RawValue { var value: [String : Any] = [ - "podState": podState.rawValue, - "version": OmnipodPumpManagerState.version, + "timeZone": timeZone.secondsFromGMT(), + "basalSchedule": basalSchedule.rawValue, + "messageLog": messageLog.rawValue, + "unstoredDoses": unstoredDoses.map { $0.rawValue }, + "confirmationBeeps": confirmationBeeps, ] + 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 { - static let idleListeningEnabledDefaults: RileyLinkDevice.IdleListeningState = .enabled(timeout: .minutes(4), channel: 0) + var hasActivePod: Bool { + return podState?.isActive == true + } + + var hasSetupPod: Bool { + return podState?.isSetupComplete == 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 [ - "## MinimedPumpManagerState", + "## OmnipodPumpManagerState", + "* timeZone: \(timeZone)", + "* basalSchedule: \(String(describing: basalSchedule))", + "* expirationReminderDate: \(String(describing: expirationReminderDate))", + "* unstoredDoses: \(String(describing: unstoredDoses))", + "* suspendEngageState: \(String(describing: suspendEngageState))", + "* bolusEngageState: \(String(describing: bolusEngageState))", + "* tempBasalEngageState: \(String(describing: tempBasalEngageState))", + "* lastPumpDataReportDate: \(String(describing: lastPumpDataReportDate))", + "* isPumpDataStale: \(String(describing: isPumpDataStale))", + "* confirmationBeeps: \(String(describing: confirmationBeeps))", String(reflecting: podState), String(reflecting: rileyLinkConnectionManagerState), - ].joined(separator: "\n") + String(reflecting: messageLog), + ].joined(separator: "\n") } } diff --git a/OmniKit/PumpManager/PodComms.swift b/OmniKit/PumpManager/PodComms.swift index 2d01dfb78..0e01afb10 100644 --- a/OmniKit/PumpManager/PodComms.swift +++ b/OmniKit/PumpManager/PodComms.swift @@ -11,126 +11,188 @@ import RileyLinkBLEKit import LoopKit import os.log -public protocol PodCommsDelegate: class { - func podComms(_ podComms: PodComms, didChange state: PodState) +protocol PodCommsDelegate: class { + func podComms(_ podComms: PodComms, didChange podState: PodState) } -public class PodComms { +class PodComms: CustomDebugStringConvertible { - private var configuredDevices: Set = Set() + private let configuredDevices: Locked> = Locked(Set()) - private weak var delegate: PodCommsDelegate? - - private let sessionQueue = DispatchQueue(label: "com.rileylink.OmniKit.PodComms", qos: .utility) + weak var delegate: PodCommsDelegate? + + weak var messageLogger: MessageLogger? public let log = OSLog(category: "PodComms") - - private var podState: PodState { + + // Only valid to access on the session serial queue + private var podState: PodState? { didSet { - self.delegate?.podComms(self, didChange: podState) + if let newValue = podState, newValue != oldValue { + //log.debug("Notifying delegate of new podState: %{public}@", String(reflecting: newValue)) + delegate?.podComms(self, didChange: newValue) + } } } - - public init(podState: PodState, delegate: PodCommsDelegate?) { + + init(podState: PodState?) { self.podState = podState - self.delegate = delegate + 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 + ) } - public enum PairResults { - case success(podState: PodState) - case failure(Error) + 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 + } } - public class func pair(using deviceSelector: @escaping (_ completion: @escaping (_ device: RileyLinkDevice?) -> Void) -> Void, timeZone: TimeZone, completion: @escaping (PairResults) -> Void) + 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 { - completion(.failure(PodCommsError.noRileyLinkAvailable)) + block(.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) + self.configureDevice(device, with: commandSession) - 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) + if self.podState == nil { + self.podState = try self.assignAddress(commandSession: commandSession) } - guard config2.pairingState == .paired else { - throw PodCommsError.invalidData + guard self.podState != nil else { + block(.failure(PodCommsError.noPodPaired)) + return } - 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)) + + 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))) } } } } - public enum SessionRunResult { + 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() + 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 } - - 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() - } + + 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)) } - - semaphore.wait() } } // Must be called from within the RileyLinkDevice sessionQueue private func configureDevice(_ device: RileyLinkDevice, with session: CommandSession) { - guard !self.configuredDevices.contains(device) else { + session.assertOnSessionQueue() + + guard !self.configuredDevices.value.contains(device) else { return } @@ -148,7 +210,9 @@ public class PodComms { NotificationCenter.default.addObserver(self, selector: #selector(deviceRadioConfigDidChange(_:)), name: .DeviceConnectionStateDidChange, object: device) log.debug("added device %{public}@ to configuredDevices", device.name ?? "unknown") - configuredDevices.insert(device) + _ = configuredDevices.mutate { (value) in + value.insert(device) + } } @objc private func deviceRadioConfigDidChange(_ note: Notification) { @@ -159,10 +223,34 @@ public class PodComms { NotificationCenter.default.removeObserver(self, name: .DeviceRadioConfigDidChange, object: device) NotificationCenter.default.removeObserver(self, name: .DeviceConnectionStateDidChange, object: device) - configuredDevices.remove(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 { @@ -218,10 +306,24 @@ private extension CommandSession { try updateRegister(.sync1, value: 0xA5) try updateRegister(.sync0, value: 0x5A) } -} -extension PodComms: PodCommsSessionDelegate { - public func podCommsSession(_ podCommsSession: PodCommsSession, didChange state: PodState) { - self.podState = state + // 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 index 11a05b334..2168ad579 100644 --- a/OmniKit/PumpManager/PodCommsSession.swift +++ b/OmniKit/PumpManager/PodCommsSession.swift @@ -12,9 +12,8 @@ import LoopKit import os.log public enum PodCommsError: Error { + case noPodPaired case invalidData - case crcMismatch - case unknownPacketType(rawType: UInt8) case noResponse case emptyResponse case podAckedInsteadOfReturningResponse @@ -25,18 +24,18 @@ public enum PodCommsError: Error { 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 .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: @@ -46,17 +45,22 @@ extension PodCommsError: LocalizedError { case .unexpectedPacketType: return nil case .unexpectedResponse: - return nil + 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 bolus could not be completed due to exiting bolus in progress") + 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 } @@ -64,11 +68,9 @@ extension PodCommsError: LocalizedError { public var failureReason: String? { switch self { - case .invalidData: - return nil - case .crcMismatch: + case .noPodPaired: return nil - case .unknownPacketType: + case .invalidData: return nil case .noResponse: return nil @@ -85,11 +87,15 @@ extension PodCommsError: LocalizedError { 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") + return nil 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") + return nil case .nonceResyncFailed: return nil + case .podSuspended: + return nil + case .podFault: + return nil case .commsError: return nil } @@ -97,11 +103,9 @@ extension PodCommsError: LocalizedError { public var recoverySuggestion: String? { switch self { - case .invalidData: - return nil - case .crcMismatch: + case .noPodPaired: return nil - case .unknownPacketType: + 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") @@ -118,53 +122,102 @@ extension PodCommsError: LocalizedError { 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") + 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 temp basal could not be completed due to existing temp basal in progress") + 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 { + private let useCancelNoneForStatus: Bool = false // whether to always use a cancel none to get status public let log = OSLog(category: "PodCommsSession") private var podState: PodState { didSet { + assertOnSessionQueue() delegate.podCommsSession(self, didChange: podState) } } private unowned let delegate: PodCommsSessionDelegate - private let transport: MessageTransport + private var transport: MessageTransport init(podState: PodState, transport: MessageTransport, delegate: PodCommsSessionDelegate) { self.podState = podState self.transport = transport self.delegate = delegate + self.transport.delegate = self } + private func handlePodFault(fault: PodInfoFaultEvent) { + 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() + podState.unfinalizedTempBasal?.cancel(at: now) + podState.unfinalizedBolus?.cancel(at: now, withRemaining: fault.insulinNotDelivered) + } + } - func send(_ messageBlocks: [MessageBlock]) throws -> T { + /// 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 + 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 - let response = try transport.send(blocksToSend) + + 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 + //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 @@ -172,21 +225,26 @@ public class PodCommsSession { let responseType = response.messageBlocks[0].blockType if responseType == .errorResponse, + let sentNonce = sentNonce, 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) + podState.resyncNonce(syncWord: errorResponse.nonceSearchKey, sentNonce: sentNonce, messageSequenceNum: message.sequenceNum) + 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 { return block } }) + podState.advanceToNextNonce() + } else if let fault = response.fault { + handlePodFault(fault: fault) + throw PodCommsError.podFault(fault: fault) } else { log.error("Unexpected response: %@", String(describing: response.messageBlocks[0])) throw PodCommsError.unexpectedResponse(response: responseType) @@ -196,214 +254,423 @@ public class PodCommsSession { throw PodCommsError.nonceResyncFailed } - public func configurePod() throws { + // Returns time at which prime is expected to finish. + public func prime() throws -> TimeInterval { //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() + + let primeDuration = TimeInterval(seconds: 55) // a bit more than (Pod.primeUnits / Pod.primeDeliveryRate) - //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() + // 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)]) + let finishSetupReminder = PodAlert.finishSetupReminder + 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 + // Mark 2.6U delivery with 1 second between pulses 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 primeFinishTime = Date() + primeDuration + podState.primeFinishTime = primeFinishTime + podState.setupProgress = .startingPrime + + 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: primeUnits, byte2: 0, unknownSection: Data(hexadecimalString: "000186a0")!) - let _: StatusResponse = try send([scheduleCommand, bolusExtraCommand]) - podState.advanceToNextNonce() + 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 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() + 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 + } + } + + podState.setupProgress = .settingInitialBasalSchedule + // Set basal schedule + let _ = try setBasalSchedule(schedule: basalSchedule, scheduleOffset: scheduleOffset) + podState.setupProgress = .initialBasalScheduleSet + 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) + let status: StatusResponse = try send([configureAlerts]) + for alert in alerts { + podState.registerConfiguredAlert(slot: alert.configuration.slot, alert: alert) + } + podState.updateFromStatusResponse(status) + return status + } + + // 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 { + log.info("Skip beep config with faulted pod") + return + } + + let beepConfigCommand = BeepConfigCommand(beepConfigType: beepConfigType, basalCompletionBeep: basalCompletionBeep, tempBasalCompletionBeep: tempBasalCompletionBeep, bolusCompletionBeep: bolusCompletionBeep) + do { + let statusResponse: StatusResponse = try send([beepConfigCommand]) + podState.updateFromStatusResponse(statusResponse) + } 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) + + 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()]) + if status.podProgressStatus == .cannulaInserting { + podState.setupProgress = .cannulaInserting + podState.updateFromStatusResponse(status) + return insertionWait // Not sure when it started, wait full time to be sure + } + if status.podProgressStatus.readyForDelivery { + 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 + 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) + try configureAlerts([expirationAdvisoryAlarm, shutdownImminentAlarm]) + } + + // Mark 0.5U delivery with 1 second between pulses for cannula insertion + + let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerPrimePulse) + let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: Pod.cannulaInsertionUnits, timeBetweenPulses: timeBetweenPulses) + let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) + + podState.setupProgress = .startingInsertCannula + let bolusExtraCommand = BolusExtraCommand(units: Pod.cannulaInsertionUnits, 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()]) + if response.podProgressStatus.readyForDelivery { + markSetupProgressCompleted(statusResponse: response) + } + podState.updateFromStatusResponse(response) + } + } + // Throws SetBolusError public enum DeliveryCommandResult { case success(statusResponse: StatusResponse) case certainFailure(error: PodCommsError) case uncertainFailure(error: PodCommsError) } + + public enum CancelDeliveryResult { + case success(statusResponse: StatusResponse, canceledDose: UnfinalizedDose?) + case certainFailure(error: PodCommsError) + case uncertainFailure(error: PodCommsError) + } + - public func bolus(units: Double) -> DeliveryCommandResult { + public func bolus(units: Double, acknowledgementBeep: Bool = false, completionBeep: Bool = false, programReminderInterval: TimeInterval = 0) -> DeliveryCommandResult { - let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: units, multiplier: 16) + let timeBetweenPulses = TimeInterval(seconds: Pod.secondsPerBolusPulse) + 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, byte2: 0, unknownSection: Data(hexadecimalString: "00030d40")!) + // 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, programReminderInterval: programReminderInterval) do { let statusResponse: StatusResponse = try send([bolusScheduleCommand, bolusExtraCommand]) - podState.unfinalizedBolus = UnfinalizedDose(bolusAmount: units, startTime: Date(), scheduledCertainty: .certain) - podState.advanceToNextNonce() + 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) + } } } - public func setTempBasal(rate: Double, duration: TimeInterval, confidenceReminder: 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, confidenceReminder: confidenceReminder, programReminderInterval: programReminderInterval) + let tempBasalExtraCommand = TempBasalExtraCommand(rate: rate, duration: duration, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) - guard podState.unfinalizedBolus == nil else { - return DeliveryCommandResult.certainFailure(error: .unfinalizedTempBasal) + guard podState.unfinalizedBolus?.isFinished != false else { + return DeliveryCommandResult.certainFailure(error: .unfinalizedBolus) } do { - let statusResponse: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) + let status: StatusResponse = try send([tempBasalCommand, tempBasalExtraCommand]) podState.unfinalizedTempBasal = UnfinalizedDose(tempBasalRate: rate, startTime: Date(), duration: duration, scheduledCertainty: .certain) - podState.advanceToNextNonce() - return DeliveryCommandResult.success(statusResponse: statusResponse) + 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:ConfigureAlertsCommand.BeepType) throws -> StatusResponse { - + public func cancelDelivery(deliveryType: CancelDeliveryCommand.DeliveryType, beepType: BeepType) -> CancelDeliveryResult { + 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 + do { + let status: StatusResponse = try send([cancelDelivery]) + 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 > now + { + podState.unfinalizedTempBasal?.cancel(at: now) + if !deliveryType.contains(.basal) { + podState.suspendState = .resumed(now) + } + canceledDose = podState.unfinalizedTempBasal + log.info("Interrupted temp basal: %@", String(describing: canceledDose)) + } + + if let unfinalizedBolus = podState.unfinalizedBolus, + let finishTime = unfinalizedBolus.finishTime, + deliveryType.contains(.bolus), + finishTime > now + { + podState.unfinalizedBolus?.cancel(at: now, withRemaining: status.insulinNotDelivered) + canceledDose = podState.unfinalizedBolus + log.info("Interrupted bolus: %@", String(describing: canceledDose)) + } + + podState.updateFromStatusResponse(status) + + return CancelDeliveryResult.success(statusResponse: status, canceledDose: canceledDose) + + } 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 { - //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) + try cancelNone() // reads status & verifies nonce by doing a cancel none } - 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 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): + 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: acknowledgementBeep, completionBeep: completionBeep) + return status + } } - public func setBasalSchedule(schedule: BasalSchedule, scheduleOffset: TimeInterval, confidenceReminder: Bool, programReminderInterval: TimeInterval) throws { + 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, 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]) + let basalExtraCommand = BasalScheduleExtraCommand.init(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) do { - let _: StatusResponse = try send([configureAlerts]) - } catch PodCommsError.podAckedInsteadOfReturningResponse { - print("pod acked?") + let status: StatusResponse = try send([basalScheduleCommand, basalExtraCommand]) + let now = Date() + podState.suspendState = .resumed(now) + podState.unfinalizedResume = UnfinalizedDose(resumeStartTime: now, 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 { - podState.advanceToNextNonce() + let status = try setBasalSchedule(schedule: schedule, scheduleOffset: scheduleOffset, acknowledgementBeep: acknowledgementBeep, completionBeep: completionBeep, programReminderInterval: programReminderInterval) - // Insert Cannula - // 1a0e7e30bf16020065010050000a000a - let insertionBolusAmount = 0.5 - let bolusSchedule = SetInsulinScheduleCommand.DeliverySchedule.bolus(units: insertionBolusAmount, multiplier: 8) - let bolusScheduleCommand = SetInsulinScheduleCommand(nonce: podState.currentNonce, deliverySchedule: bolusSchedule) + podState.suspendState = .resumed(Date()) - // 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() + 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 + + 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 + } + + @discardableResult 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 + if useCancelNoneForStatus { + return try cancelNone() // functional replacement for getStatus() } + let statusResponse: StatusResponse = try send([GetStatusCommand()]) + podState.updateFromStatusResponse(statusResponse) + return statusResponse } - - 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. + 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.default("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 deactivatePod() throws { + + if podState.fault == nil && !podState.isSuspended { + 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) - return try send([deactivatePod]) + + do { + let _: StatusResponse = try send([deactivatePod]) + } catch let error as PodCommsError { + switch error { + case .podFault, .unexpectedResponse: + break + default: + throw error + } + } } - func finalizeDoses(deliveryStatus: StatusResponse.DeliveryStatus, storageHandler: ([UnfinalizedDose]) -> Bool) { - self.podState.finalizeDoses(deliveryStatus: deliveryStatus) - if storageHandler(podState.finalizedDoses) { - log.info("Finalized %@", String(describing: podState.finalizedDoses)) + 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 doses: %@", String(describing: dosesToStore)) self.podState.finalizedDoses.removeAll() } } - - func podStatusUpdated(_ status: StatusResponse) { - podState.lastInsulinMeasurements = PodInsulinMeasurements(statusResponse: status, validTime: Date()) + + 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..f62d076a8 --- /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.programmedUnits) ?? dose.programmedUnits + 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 / TimeInterval(hours: 1)) + 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 index 26aa2e171..d67906007 100644 --- a/OmniKit/PumpManager/PodInsulinMeasurements.swift +++ b/OmniKit/PumpManager/PodInsulinMeasurements.swift @@ -13,37 +13,36 @@ public struct PodInsulinMeasurements: RawRepresentable, Equatable { public let validTime: Date public let delivered: Double - public let notDelivered: 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 - self.notDelivered = statusResponse.insulinNotDelivered 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) { 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 + let delivered = rawValue["delivered"] as? Double else { return nil } self.validTime = validTime self.delivered = delivered - self.notDelivered = notDelivered - self.reservoirVolume = reservoirVolume + self.reservoirVolume = rawValue["reservoirVolume"] as? Double } public var rawValue: RawValue { var rawValue: RawValue = [ "validTime": validTime, - "delivered": delivered, - "notDelivered": notDelivered + "delivered": delivered ] if let reservoirVolume = reservoirVolume { diff --git a/OmniKit/PumpManager/PodState.swift b/OmniKit/PumpManager/PodState.swift index 8166e03a9..e66ae4ce2 100644 --- a/OmniKit/PumpManager/PodState.swift +++ b/OmniKit/PumpManager/PodState.swift @@ -8,46 +8,127 @@ 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 let activatedAt: Date - public var timeZone: TimeZone + + 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 public let tid: UInt32 + var activeAlertSlots: AlertSet public var lastInsulinMeasurements: PodInsulinMeasurements? - var unfinalizedBolus: UnfinalizedDose? - var unfinalizedTempBasal: UnfinalizedDose? + + public var unfinalizedBolus: UnfinalizedDose? + public var unfinalizedTempBasal: UnfinalizedDose? + public var unfinalizedSuspend: UnfinalizedDose? + public var unfinalizedResume: UnfinalizedDose? + var finalizedDoses: [UnfinalizedDose] - - public var expiresAt: Date { - return activatedAt + .days(3) + + public var dosesToStore: [UnfinalizedDose] { + return finalizedDoses + [unfinalizedTempBasal, unfinalizedSuspend, unfinalizedBolus].compactMap {$0} } - - public var deliveryScheduleUncertain: Bool { - return unfinalizedBolus?.scheduledCertainty == .uncertain || unfinalizedTempBasal?.scheduledCertainty == .uncertain + + public var suspendState: SuspendState + + public var isSuspended: Bool { + if case .suspended = suspendState { + return true + } + return false + } + + 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, activatedAt: Date, timeZone: TimeZone, piVersion: String, pmVersion: String, lot: UInt32, tid: UInt32) { + public init(address: UInt32, 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 = [] + self.suspendState = .resumed(Date()) + 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 + } + + // variation on isActive that doesn't care if Pod is faulted + public var isSetupComplete: Bool { + return setupProgress == .completed + } + public mutating func advanceToNextNonce() { nonceState.advanceToNextNonce() } @@ -59,40 +140,95 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl 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)) + nonceState = NonceState(lot: lot, tid: tid, seed: seed) } - public mutating func finalizeDoses(deliveryStatus: StatusResponse.DeliveryStatus) { + public mutating func updateFromStatusResponse(_ response: StatusResponse) { let now = Date() - - if let bolus = unfinalizedBolus { - if bolus.finishTime <= now { - finalizedDoses.append(bolus) + let activatedAtComputed = now - response.timeActive + if activatedAt == nil { + self.activatedAt = activatedAtComputed + } + let expiresAtComputed = activatedAtComputed + Pod.nominalPodLife + 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, setupUnitsDelivered: setupUnitsDelivered) + activeAlertSlots = response.alerts + } + + public mutating func registerConfiguredAlert(slot: AlertSlot, alert: PodAlert) { + configuredAlerts[slot] = alert + } + + public mutating func finalizeFinishedDoses() { + if let bolus = unfinalizedBolus, bolus.isFinished { + finalizedDoses.append(bolus) + unfinalizedBolus = nil + } + + if let tempBasal = unfinalizedTempBasal, tempBasal.isFinished { + 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 - } 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) + 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 - } else if tempBasal.scheduledCertainty == .uncertain { - if deliveryStatus.tempBasalRunning { - // Temp basal did schedule - unfinalizedTempBasal?.scheduledCertainty = .certain + } + } + + 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 { - // Temp basal didn't happen - unfinalizedTempBasal = nil + // Suspend wasn't enacted + unfinalizedSuspend = nil } } + + if let resume = unfinalizedResume, suspend.startTime < resume.startTime { + finalizedDoses.append(suspend) + finalizedDoses.append(resume) + unfinalizedSuspend = nil + unfinalizedResume = nil + } } } @@ -103,9 +239,6 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl 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, @@ -116,56 +249,133 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl 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 + + 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.nominalPodLife + } + } + + if let setupUnitsDelivered = rawValue["setupUnitsDelivered"] as? Double { + self.setupUnitsDelivered = setupUnitsDelivered + } + + if let suspended = rawValue["suspended"] as? Bool { + // 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.unfinalizedBolus = nil + return nil } - if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue, - let unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) + if let rawUnfinalizedBolus = rawValue["unfinalizedBolus"] as? UnfinalizedDose.RawValue { - self.unfinalizedTempBasal = unfinalizedTempBasal - } else { - self.unfinalizedTempBasal = nil + self.unfinalizedBolus = UnfinalizedDose(rawValue: rawUnfinalizedBolus) } - - if let rawLastInsulinMeasurements = rawValue["lastInsulinMeasurements"] as? PodInsulinMeasurements.RawValue + + if let rawUnfinalizedTempBasal = rawValue["unfinalizedTempBasal"] as? UnfinalizedDose.RawValue + { + self.unfinalizedTempBasal = UnfinalizedDose(rawValue: rawUnfinalizedTempBasal) + } + + if let rawUnfinalizedSuspend = rawValue["unfinalizedSuspend"] as? UnfinalizedDose.RawValue { + self.unfinalizedSuspend = UnfinalizedDose(rawValue: rawUnfinalizedSuspend) + } + + if let rawUnfinalizedResume = rawValue["unfinalizedResume"] as? UnfinalizedDose.RawValue + { + self.unfinalizedResume = UnfinalizedDose(rawValue: rawUnfinalizedResume) + } + + 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] - { + 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, - "activatedAt": activatedAt, - "timeZone": timeZone.secondsFromGMT(), "piVersion": piVersion, "pmVersion": pmVersion, "lot": lot, "tid": tid, - "finalizedDoses": finalizedDoses.map( { $0.rawValue }) + "suspendState": suspendState.rawValue, + "finalizedDoses": finalizedDoses.map( { $0.rawValue }), + "alerts": activeAlertSlots.rawValue, + "messageTransportState": messageTransportState.rawValue, + "setupProgress": setupProgress.rawValue ] if let unfinalizedBolus = self.unfinalizedBolus { @@ -175,10 +385,45 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl 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 let expiresAt = expiresAt { + 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) }) + rawValue["configuredAlerts"] = rawConfiguredAlerts + } return rawValue } @@ -187,19 +432,30 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl 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))", + "### PodState", + "* address: \(String(format: "%04X", address))", + "* activatedAt: \(String(reflecting: activatedAt))", + "* expiresAt: \(String(reflecting: expiresAt))", + "* setupUnitsDelivered: \(String(reflecting: setupUnitsDelivered))", + "* piVersion: \(piVersion)", + "* pmVersion: \(pmVersion)", + "* lot: \(lot)", + "* tid: \(tid)", + "* suspendState: \(suspendState)", + "* 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))", "", - ].joined(separator: "\n") + fault != nil ? String(reflecting: fault!) : "fault: nil", + "", + ].joined(separator: "\n") } } @@ -209,16 +465,15 @@ fileprivate struct NonceState: RawRepresentable, Equatable { 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 + 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) + table[0] += UInt32((seed & 0x00ff)) + table[1] += UInt32((seed & 0xff00) >> 8) for i in 0..<16 { table[2 + i] = generateEntry() @@ -226,11 +481,11 @@ fileprivate struct NonceState: RawRepresentable, Equatable { 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) + 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() { @@ -266,3 +521,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/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/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/OmniKitPlugin/Info.plist b/OmniKitPlugin/Info.plist new file mode 100644 index 000000000..94c4d6fb2 --- /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 + 3.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/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 index 32e1ad1bd..1e122195b 100644 --- a/OmniKitTests/BasalScheduleTests.swift +++ b/OmniKitTests/BasalScheduleTests.swift @@ -49,7 +49,7 @@ class BasalScheduleTests: XCTestCase { func testBasalScheduleCommandFromSchedule() { // Encode from schedule - let entry = BasalScheduleEntry(rate: 0.05, duration: .hours(24)) + let entry = BasalScheduleEntry(rate: 0.05, startTime: 0) let schedule = BasalSchedule(entries: [entry]) let cmd = SetInsulinScheduleCommand(nonce: 0x01020304, basalSchedule: schedule, scheduleOffset: .hours(8.25)) @@ -67,6 +67,8 @@ class BasalScheduleTests: XCTestCase { } 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) } @@ -77,11 +79,12 @@ class BasalScheduleTests: XCTestCase { let cmd = try BasalScheduleExtraCommand(encodedData: Data(hexadecimalString: "130e40001aea001e84803840005b8d80")!) - XCTAssertEqual(true, cmd.confidenceReminder) + 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.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 20), cmd.delayUntilNextTenthOfPulse) XCTAssertEqual(1, cmd.rateEntries.count) let entry = cmd.rateEntries[0] XCTAssertEqual(TimeInterval(seconds: 60), entry.delayBetweenPulses) @@ -94,24 +97,25 @@ class BasalScheduleTests: XCTestCase { // 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) + let cmd = BasalScheduleExtraCommand(currentEntryIndex: 0, remainingPulses: 689, delayUntilNextTenthOfPulse: TimeInterval(seconds: 20), rateEntries: rateEntries, acknowledgementBeep: false, completionBeep: true, programReminderInterval: 0) - XCTAssertEqual("130e40001aea001e84803840005b8d80", cmd.data.hexadecimalString) + XCTAssertEqual("130e40001aea01312d003840005b8d80", cmd.data.hexadecimalString) } func testBasalScheduleExtraCommandFromSchedule() { // Encode from schedule - let entry = BasalScheduleEntry(rate: 0.05, duration: .hours(24)) + let entry = BasalScheduleEntry(rate: 0.05, startTime: 0) let schedule = BasalSchedule(entries: [entry]) - let cmd = BasalScheduleExtraCommand(schedule: schedule, scheduleOffset: .hours(8.25), confidenceReminder: true, programReminderInterval: 60) + let cmd = BasalScheduleExtraCommand(schedule: schedule, scheduleOffset: .hours(8.25), acknowledgementBeep: false, completionBeep: true, programReminderInterval: 60) - XCTAssertEqual(true, cmd.confidenceReminder) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) XCTAssertEqual(60, cmd.programReminderInterval) XCTAssertEqual(0, cmd.currentEntryIndex) - XCTAssertEqual(16, cmd.remainingPulses) - XCTAssertEqual(TimeInterval(minutes: 45), cmd.delayUntilNextPulse) + 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) @@ -120,6 +124,104 @@ class BasalScheduleTests: XCTestCase { 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(units: bolusAmount, acknowledgementBeep: false, completionBeep: true, programReminderInterval: .hours(1)) + 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(units: bolusAmount, acknowledgementBeep: false, completionBeep: false, programReminderInterval: .hours(1)) + XCTAssertEqual("170d3c019a00030d40000000000000", bolusExtraCommand.data.hexadecimalString) + } + + func testCancelBolusCommand() { do { // Decode 1f 05 4d91f8ff 64 diff --git a/OmniKitTests/Info.plist b/OmniKitTests/Info.plist index 477c24867..713b7c93b 100644 --- a/OmniKitTests/Info.plist +++ b/OmniKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion 1 diff --git a/OmniKitTests/MessageTests.swift b/OmniKitTests/MessageTests.swift index ff1f263fa..763d5d296 100644 --- a/OmniKitTests/MessageTests.swift +++ b/OmniKitTests/MessageTests.swift @@ -36,11 +36,11 @@ class MessageTests: XCTestCase { XCTAssertEqual(TimeInterval(minutes: 4261), statusResponse.timeActive) XCTAssertEqual(.normal, statusResponse.deliveryStatus) - XCTAssertEqual(.aboveFiftyUnits, statusResponse.reservoirStatus) + XCTAssertEqual(.aboveFiftyUnits, statusResponse.podProgressStatus) XCTAssertEqual(6.3, statusResponse.insulin, accuracy: 0.01) XCTAssertEqual(0, statusResponse.insulinNotDelivered) XCTAssertEqual(3, statusResponse.podMessageCounter) - XCTAssert(statusResponse.alarms.isEmpty) + XCTAssert(statusResponse.alerts.isEmpty) XCTAssertEqual("1f00ee84300a1d18003f1800004297ff8128", msg.encoded().hexadecimalString) } catch (let error) { @@ -105,7 +105,7 @@ class MessageTests: XCTestCase { do { let message = try Message(encodedData: Data(hexadecimalString: "ffffffff04170115020700020700020e0000a5ad00053030971f08686301fd")!) let config = message.messageBlocks[0] as! VersionResponse - XCTAssertEqual(.pairingExpired, config.pairingState) + XCTAssertEqual(.pairingExpired, config.setupState) } catch (let error) { XCTFail("message decoding threw error: \(error)") } @@ -128,21 +128,21 @@ class MessageTests: XCTestCase { func testSetupPodCommand() { do { var components = DateComponents() - components.day = 6 - components.month = 12 + components.day = 12 + components.month = 6 components.year = 2016 components.hour = 13 components.minute = 47 // Decode - let decoded = try SetupPodCommand(encodedData: Data(hexadecimalString: "03131f0218c31404060c100d2f0000a4be0004e4a1")!) + 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 = SetupPodCommand(address: 0x1f0218c3, dateComponents: components, lot: 0x0000a4be, tid: 0x0004e4a1) + let encoded = ConfigurePodCommand(address: 0x1f0218c3, dateComponents: components, lot: 0x0000a4be, tid: 0x0004e4a1) XCTAssertEqual("03131f0218c31404060c100d2f0000a4be0004e4a1", encoded.data.hexadecimalString) } catch (let error) { @@ -162,9 +162,9 @@ class MessageTests: XCTestCase { let cmd = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0ebed2e16b02010a0101a000340034")!) XCTAssertEqual(0xbed2e16b, cmd.nonce) - if case SetInsulinScheduleCommand.DeliverySchedule.bolus(let units, let multiplier) = cmd.deliverySchedule { + if case SetInsulinScheduleCommand.DeliverySchedule.bolus(let units, let timeBetweenPulses) = cmd.deliverySchedule { XCTAssertEqual(2.6, units) - XCTAssertEqual(0x8, multiplier) + XCTAssertEqual(.seconds(1), timeBetweenPulses) } else { XCTFail("Expected ScheduleEntry.bolus type") } @@ -179,8 +179,8 @@ class MessageTests: XCTestCase { do { // Decode let status = try StatusResponse(encodedData: Data(hexadecimalString: "1d28008200004446ebff")!) - XCTAssert(status.alarms.contains(.oneHourExpiry)) - XCTAssert(status.alarms.contains(.podExpired)) + XCTAssert(status.alerts.contains(.slot3)) + XCTAssert(status.alerts.contains(.slot7)) } catch (let error) { XCTFail("message decoding threw error: \(error)") } @@ -190,16 +190,16 @@ class MessageTests: XCTestCase { // 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) + 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 = ConfigureAlertsCommand.AlertConfiguration(alertType: .endOfService, audible: true, autoOffModifier: false, duration: .minutes(0), expirationType: .time(podHardExpirationTime), beepType: .beeeeeep, beepRepeat: 2) + 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 = ConfigureAlertsCommand.AlertConfiguration(alertType: .autoOff, audible: false, autoOffModifier: true, duration: .minutes(15), expirationType: .time(0), beepType: .bipBeepBipBeepBipBeepBipBeep, beepRepeat: 2) + 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]) @@ -210,24 +210,26 @@ class MessageTests: XCTestCase { XCTAssertEqual(3, decoded.configurations.count) let config1 = decoded.configurations[0] - XCTAssertEqual(.timerLimit, config1.alertType) - XCTAssertEqual(true, config1.audible) + XCTAssertEqual(.slot7, config1.slot) + XCTAssertEqual(true, config1.active) XCTAssertEqual(false, config1.autoOffModifier) XCTAssertEqual(.hours(7), config1.duration) - if case ConfigureAlertsCommand.ExpirationType.time(let duration) = config1.expirationType { + if case AlertTrigger.timeUntilAlert(let duration) = config1.trigger { XCTAssertEqual(podSoftExpirationTime, duration) } - XCTAssertEqual(.beepBeepBeep, config1.beepType) + XCTAssertEqual(.every60Minutes, config1.beepRepeat) + XCTAssertEqual(.bipBeepBipBeepBipBeepBipBeep, config1.beepType) - let cfg = try ConfigureAlertsCommand.AlertConfiguration(encodedData: Data(hexadecimalString: "4c0000640102")!) - XCTAssertEqual(.lowReservoir, cfg.alertType) - XCTAssertEqual(true, cfg.audible) + 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 ConfigureAlertsCommand.ExpirationType.reservoir(let volume) = cfg.expirationType { + if case AlertTrigger.unitsRemaining(let volume) = cfg.trigger { XCTAssertEqual(10, volume) } - XCTAssertEqual(.beepBeepBeepBeep, cfg.beepType) + XCTAssertEqual(.every1MinuteFor3MinutesAndRepeatEvery60Minutes, cfg.beepRepeat) + XCTAssertEqual(.bipBeepBipBeepBipBeepBipBeep, cfg.beepType) } catch (let error) { diff --git a/OmniKitTests/PodCommsSessionTests.swift b/OmniKitTests/PodCommsSessionTests.swift new file mode 100644 index 000000000..e058dac03 --- /dev/null +++ b/OmniKitTests/PodCommsSessionTests.swift @@ -0,0 +1,107 @@ +// +// PodCommsSessionTests.swift +// OmniKitTests +// +// Created by Pete Schwamb on 3/25/19. +// Copyright © 2019 Pete Schwamb. All rights reserved. +// + +import Foundation + +import XCTest +@testable import OmniKit + +class MockMessageTransport: MessageTransport { + var delegate: MessageTransportDelegate? + + var messageNumber: Int + + var responseMessageBlocks = [MessageBlock]() + public var sentMessages = [Message]() + + var address: UInt32 + + var sentMessageHandler: ((Message) -> 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 + + // 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..4184fe624 --- /dev/null +++ b/OmniKitTests/PodInfoTests.swift @@ -0,0 +1,402 @@ +// +// 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: "50008634212e00392031003c212d004120300044202c0049212e004c212b0051202f0054212c00592030805c202d806120308000212e800521318008202f800d20328010202f801521318018202f801d21318020202e8025213300282032002d2135003021310035213400382131003d2035004020310045213300482030004d21320050212f0055203300582030805d21328060202f800120308004202c80092131800c2130801121328014203180192133801c2031802120328024213200292035002c21310031213400")!) + XCTAssertEqual(.flashLogRecent, decoded.podInfoType) + XCTAssertEqual(134, decoded.indexLastEntry) + 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)") + } + } + + 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 index 557b7fb8a..4ec244422 100644 --- a/OmniKitTests/PodStateTests.swift +++ b/OmniKitTests/PodStateTests.swift @@ -10,10 +10,9 @@ import XCTest @testable import OmniKit class PodStateTests: XCTestCase { - + func testNonceValues() { - - var podState = PodState(address: 0x1f000000, activatedAt: Date(), timeZone: .currentFixed, piVersion: "1.1.0", pmVersion: "1.1.0", lot: 42560, tid: 661771) + var podState = PodState(address: 0x1f000000, piVersion: "1.1.0", pmVersion: "1.1.0", lot: 42560, tid: 661771) XCTAssertEqual(podState.currentNonce, 0x8c61ee59) podState.advanceToNextNonce() @@ -24,10 +23,10 @@ class PodStateTests: XCTestCase { XCTAssertEqual(podState.currentNonce, 0xacf076ca) } - func testNonceSync() { + func testResyncNonce() { do { let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a62b0002249da11f00ee860318")!) - var podState = PodState(address: 0x1f00ee86, activatedAt: Date(), timeZone: .currentFixed, piVersion: "1.1.0", pmVersion: "1.1.0", lot: config.lot, tid: config.tid) + 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) @@ -52,43 +51,5 @@ class PodStateTests: XCTestCase { XCTFail("message decoding threw error: \(error)") } } - - func testNonceSync2() { - do { - let config = try VersionResponse(encodedData: Data(hexadecimalString: "011502070002070002020000a7420007f050961f0b35570264")!) - var podState = PodState(address: 0x1f0b3557, activatedAt: Date(), timeZone: .currentFixed, piVersion: "1.1.0", pmVersion: "1.1.0", lot: config.lot, tid: config.tid) - -// XCTAssertEqual(42125, config.lot) -// XCTAssertEqual(170175, config.tid) -// -// XCTAssertEqual(0x8d27868e, podState.currentNonce) - - let sentCommand = try SetInsulinScheduleCommand(encodedData: Data(hexadecimalString: "1a0e8ecd89d702002501002000020002")!) - -// // ID1:1f07b1ee PTYPE:PDM SEQ:03 ID2:1f07b1ee B9:04 BLEN:7 BODY:1f05851072aa620017 CRC:0a -// let sentPacket = try Packet(encodedData: Data(hexadecimalString: "1f07b1eea31f07b1ee04071f05851072aa6200170a")!) -// let sentMessage = try Message(encodedData: sentPacket.data) -// let sentCommand = sentMessage.messageBlocks[0] as! CancelDeliveryCommand - - let errorResponse = try ErrorResponse(encodedData: Data(hexadecimalString: "060314a197820c")!) - - let sequenceNum = 13 - - podState.resyncNonce(syncWord: errorResponse.nonceSearchKey, sentNonce: sentCommand.nonce, messageSequenceNum: sequenceNum) - - // 1a0eda1289d702002501002000020002 - - XCTAssertEqual(0xda1289d7, podState.currentNonce) - - -// 2016-10-10T11:23:05.433141 ID1:1f07b1ee PTYPE:PDM SEQ:03 ID2:1f07b1ee B9:04 BLEN:7 BODY:1f05851072aa620017 CRC:0a -// 2016-10-10T11:23:05.793768 ID1:1f07b1ee PTYPE:POD SEQ:04 ID2:1f07b1ee B9:08 BLEN:5 BODY:0603142ffa83cd CRC:35 -// 2016-10-10T11:23:06.182224 ID1:1f07b1ee PTYPE:PDM SEQ:06 ID2:1f07b1ee B9:04 BLEN:7 BODY:1f05f1488fc3620229 CRC:7b - - } catch (let error) { - XCTFail("message decoding threw error: \(error)") - } - } - } diff --git a/OmniKitTests/StatusTests.swift b/OmniKitTests/StatusTests.swift index e9a4735cf..612e42053 100644 --- a/OmniKitTests/StatusTests.swift +++ b/OmniKitTests/StatusTests.swift @@ -11,141 +11,83 @@ import XCTest @testable import OmniKit class StatusTests: XCTestCase { - //func testStatusErrorConfiguredAlerts() { - // // 02 13 01 0000 0000 0000 0000 0000 0000 0000 0000 0000 - // do { - // // Decode - // let decoded = try StatusError(encodedData: Data(hexadecimalString: "021301000000000000000000000000000000000000")!) - // XCTAssertEqual(.configuredAlerts, decoded.requestedType) - // } catch (let error) { - // XCTFail("message decoding threw error: \(error)") - // } - //} - - // end of suspend - // 0213010000000000000000000000000bd70c400000828c - - func testStatusErrorNoFaultAlerts() { - // 02 16 02 08 01 0000 0a 0038 00 0000 03ff 0087 00 00 00 95 ff 0000 //recorded an extra 81 + func testStatusRequestCommand() { + // 0e 01 00 do { + // Encode + let encoded = GetStatusCommand(podInfoType: .normal) + XCTAssertEqual("0e0100", encoded.data.hexadecimalString) + // Decode - let decoded = try StatusError(encodedData: Data(hexadecimalString: "021602080100000a003800000003ff008700000095ff0000")!) - XCTAssertEqual(.faultEvents, decoded.requestedType) - XCTAssertEqual(22, decoded.length) - XCTAssertEqual(.aboveFiftyUnits, decoded.reservoirStatus) - XCTAssertEqual(.basal, decoded.deliveryInProgressType) - XCTAssertEqual(0000, decoded.insulinNotDelivered) - XCTAssertEqual(0x0a, decoded.podMessageCounter) - XCTAssertEqual(00, decoded.originalLoggedFaultEvent) - XCTAssertEqual(0000, decoded.faultEventTimeSinceActivation) - XCTAssertEqual(51.15, decoded.insulinRemaining, accuracy: 0.05) - XCTAssertEqual(135*60, decoded.timeActive, accuracy: 0) // timeActive converts minutes to seconds - XCTAssertEqual(00, decoded.secondaryLoggedFaultEvent) - XCTAssertEqual(false, decoded.logEventError) - XCTAssertEqual(.insulinStateCorruptionDuringErrorLogging, decoded.infoLoggedFaultEvent) - XCTAssertEqual(.initialized, decoded.reservoirStatusAtFirstLoggedFaultEvent) - XCTAssertEqual(9, decoded.recieverLowGain) - XCTAssertEqual(5, decoded.radioRSSI) - XCTAssertEqual(.inactive, decoded.reservoirStatusAtFirstLoggedFaultEventCheck) + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0100")!) + XCTAssertEqual(.normal, decoded.podInfoType) } catch (let error) { XCTFail("message decoding threw error: \(error)") } } - - func testStatusErrorFaultAlert() { - // 02 16 02 0d 00 0000 06 0034 5c 0001 03ff 0001 00 00 05 a1 05 0186 + + func testStatusResponse46UnitsLeft() { + /// 1d19050ec82c08376f9801dc do { // Decode - let decoded = try StatusError(encodedData: Data(hexadecimalString: "0216020d0000000600345c000103ff0001000005a1050186")!) - XCTAssertEqual(.faultEvents, decoded.requestedType) - XCTAssertEqual(22, decoded.length) - XCTAssertEqual(.errorEventLoggedShuttingDown, decoded.reservoirStatus) - XCTAssertEqual(.none, decoded.deliveryInProgressType) - XCTAssertEqual(0000, decoded.insulinNotDelivered) - XCTAssertEqual(6, decoded.podMessageCounter) - XCTAssertEqual(92, decoded.originalLoggedFaultEvent) - XCTAssertEqual(0001*60, decoded.faultEventTimeSinceActivation) - XCTAssertEqual(51.15, decoded.insulinRemaining, accuracy: 0.05) - XCTAssertEqual(0001*60, decoded.timeActive, accuracy: 0) // timeActive converts minutes to seconds - XCTAssertEqual(00, decoded.secondaryLoggedFaultEvent) - XCTAssertEqual(false, decoded.logEventError) - XCTAssertEqual(.insulinStateCorruptionDuringErrorLogging, decoded.infoLoggedFaultEvent) - XCTAssertEqual(.readyForInjection, decoded.reservoirStatusAtFirstLoggedFaultEvent) - XCTAssertEqual(10, decoded.recieverLowGain) - XCTAssertEqual(1, decoded.radioRSSI) - XCTAssertEqual(.readyForInjection, decoded.reservoirStatusAtFirstLoggedFaultEventCheck) + 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 testStatusErrorDeliveryErrorDuringPriming() { - //0216 BODY:020f0000000900345c000103ff0001000005ae05602903 + func testStatusRequestCommandConfiguredAlerts() { + // 0e 01 01 do { + // Encode + let encoded = GetStatusCommand(podInfoType: .configuredAlerts) + XCTAssertEqual("0e0101", encoded.data.hexadecimalString) + // Decode - let decoded = try StatusError(encodedData: Data(hexadecimalString: "0216020f0000000900345c000103ff0001000005ae05602903")!) - XCTAssertEqual(.faultEvents, decoded.requestedType) - XCTAssertEqual(22, decoded.length) - XCTAssertEqual(.inactive, decoded.reservoirStatus) - XCTAssertEqual(.none, decoded.deliveryInProgressType) - XCTAssertEqual(0000, decoded.insulinNotDelivered) - XCTAssertEqual(9, decoded.podMessageCounter) - XCTAssertEqual(92, decoded.originalLoggedFaultEvent) - XCTAssertEqual(0001*60, decoded.faultEventTimeSinceActivation) - XCTAssertEqual(51.15, decoded.insulinRemaining, accuracy: 0.05) - XCTAssertEqual(0001*60, decoded.timeActive, accuracy: 0) // timeActive converts minutes to seconds - XCTAssertEqual(00, decoded.secondaryLoggedFaultEvent) - XCTAssertEqual(false, decoded.logEventError) - XCTAssertEqual(.insulinStateCorruptionDuringErrorLogging, decoded.infoLoggedFaultEvent) - XCTAssertEqual(.readyForInjection, decoded.reservoirStatusAtFirstLoggedFaultEvent) - XCTAssertEqual(10, decoded.recieverLowGain) - XCTAssertEqual(14, decoded.radioRSSI) - XCTAssertEqual(.readyForInjection, decoded.reservoirStatusAtFirstLoggedFaultEventCheck) + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0101")!) + XCTAssertEqual(.configuredAlerts, decoded.podInfoType) } catch (let error) { XCTFail("message decoding threw error: \(error)") } + } - func testStatusErrorDuringPriming() { - // Needle cap accidentally removed before priming started leaking and gave error: - // 0216020d0000000600008f000003ff0000000003a20386a002 + + func testStatusRequestCommandFaultEvents() { + // 0e 01 02 do { + // Encode + let encoded = GetStatusCommand(podInfoType: .faultEvents) + XCTAssertEqual("0e0102", encoded.data.hexadecimalString) + // Decode - let decoded = try StatusError(encodedData: Data(hexadecimalString: "0216020d0000000600008f000003ff0000000003a20386a002")!) - XCTAssertEqual(.faultEvents, decoded.requestedType) - XCTAssertEqual(22, decoded.length) - XCTAssertEqual(.errorEventLoggedShuttingDown, decoded.reservoirStatus) - XCTAssertEqual(.none, decoded.deliveryInProgressType) - XCTAssertEqual(0000, decoded.insulinNotDelivered) - XCTAssertEqual(6, decoded.podMessageCounter) - XCTAssertEqual(143, decoded.originalLoggedFaultEvent) - XCTAssertEqual(0000*60, decoded.faultEventTimeSinceActivation) - XCTAssertEqual(51.15, decoded.insulinRemaining, accuracy: 0.05) - XCTAssertEqual(0000*60, decoded.timeActive, accuracy: 0) // timeActive converts minutes to seconds - XCTAssertEqual(00, decoded.secondaryLoggedFaultEvent) - XCTAssertEqual(false, decoded.logEventError) - XCTAssertEqual(.insulinStateCorruptionDuringErrorLogging, decoded.infoLoggedFaultEvent) - XCTAssertEqual(.pairingSuccess, decoded.reservoirStatusAtFirstLoggedFaultEvent) - XCTAssertEqual(10, decoded.recieverLowGain) - XCTAssertEqual(2, decoded.radioRSSI) - XCTAssertEqual(.pairingSuccess, decoded.reservoirStatusAtFirstLoggedFaultEventCheck) + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0102")!) + XCTAssertEqual(.faultEvents, decoded.podInfoType) } catch (let error) { XCTFail("message decoding threw error: \(error)") } + } - - - func testStatusRequestCommand() { - // 0e 01 00 + + func testStatusRequestCommandResetStatus() { + // 0e 01 46 do { // Encode - let encoded = GetStatusCommand(requestType: .normal) - XCTAssertEqual("0e0100", encoded.data.hexadecimalString) + let encoded = GetStatusCommand(podInfoType: .resetStatus) + XCTAssertEqual("0e0146", encoded.data.hexadecimalString) // Decode - let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0100")!) - XCTAssertEqual(.normal, decoded.requestType) + let decoded = try GetStatusCommand(encodedData: Data(hexadecimalString: "0e0146")!) + XCTAssertEqual(.resetStatus, decoded.podInfoType) } catch (let error) { XCTFail("message decoding threw error: \(error)") } @@ -153,3 +95,6 @@ class StatusTests: XCTestCase { } } + + + diff --git a/OmniKitTests/TempBasalTests.swift b/OmniKitTests/TempBasalTests.swift index c41cf6d33..55ae8b3c1 100644 --- a/OmniKitTests/TempBasalTests.swift +++ b/OmniKitTests/TempBasalTests.swift @@ -12,9 +12,42 @@ 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) @@ -124,15 +157,18 @@ class TempBasalTests: XCTestCase { 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(true, cmd.confidenceReminder) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 18000), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(0, cmd.remainingPulses) XCTAssertEqual(1, cmd.rateEntries.count) let entry = cmd.rateEntries[0] - XCTAssertEqual(TimeInterval(seconds: 18000), entry.delayBetweenPulses) + XCTAssertEqual(TimeInterval(seconds: 1800), entry.delayBetweenPulses) XCTAssertEqual(TimeInterval(minutes: 30), entry.duration) XCTAssertEqual(0, entry.rate) @@ -141,7 +177,7 @@ class TempBasalTests: XCTestCase { } // Encode - let cmd = TempBasalExtraCommand(rate: 0, duration: .hours(0.5), confidenceReminder: true, programReminderInterval: .minutes(60)) + let cmd = TempBasalExtraCommand(rate: 0, duration: .hours(0.5), acknowledgementBeep: false, completionBeep: true, programReminderInterval: .minutes(60)) XCTAssertEqual("160e7c0000006b49d20000006b49d200", cmd.data.hexadecimalString) } @@ -149,13 +185,14 @@ class TempBasalTests: XCTestCase { do { // 0 U/h for 3 hours let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "162c7c0000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d200")!) - XCTAssertEqual(true, cmd.confidenceReminder) + XCTAssertEqual(false, cmd.acknowledgementBeep) + XCTAssertEqual(true, cmd.completionBeep) XCTAssertEqual(.minutes(60), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 18000), cmd.delayUntilNextPulse) + XCTAssertEqual(TimeInterval(seconds: 1800), cmd.delayUntilFirstTenthOfPulse) XCTAssertEqual(0, cmd.remainingPulses) XCTAssertEqual(6, cmd.rateEntries.count) let entry = cmd.rateEntries[0] - XCTAssertEqual(TimeInterval(seconds: 18000), entry.delayBetweenPulses) + XCTAssertEqual(TimeInterval(seconds: 1800), entry.delayBetweenPulses) XCTAssertEqual(TimeInterval(minutes: 30), entry.duration) XCTAssertEqual(0, entry.rate) @@ -164,7 +201,7 @@ class TempBasalTests: XCTestCase { } // Encode - let cmd = TempBasalExtraCommand(rate: 0, duration: .hours(3), confidenceReminder: true, programReminderInterval: .minutes(60)) + let cmd = TempBasalExtraCommand(rate: 0, duration: .hours(3), acknowledgementBeep: false, completionBeep: true, programReminderInterval: .minutes(60)) XCTAssertEqual("162c7c0000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d20000006b49d200", cmd.data.hexadecimalString) } @@ -204,9 +241,10 @@ class TempBasalTests: XCTestCase { // 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(true, cmd.confidenceReminder) + 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] @@ -219,18 +257,42 @@ class TempBasalTests: XCTestCase { } // Encode - let cmd = TempBasalExtraCommand(rate: 30, duration: .hours(0.5), confidenceReminder: true, programReminderInterval: .minutes(60)) + 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.confidenceReminder) + 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] @@ -243,18 +305,18 @@ class TempBasalTests: XCTestCase { } // Encode - let cmd = TempBasalExtraCommand(rate: 30, duration: .hours(12), confidenceReminder: false, programReminderInterval: .minutes(60)) + 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 - // Decode 16 14 00 00 f5af 00092ba9 f5af 00092ba9 2319 00092ba9 - let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "16140000f5af00092ba9f5af00092ba9231900092ba9")!) - XCTAssertEqual(false, cmd.confidenceReminder) - XCTAssertEqual(.minutes(0), cmd.programReminderInterval) - XCTAssertEqual(TimeInterval(seconds: 6.01001), cmd.delayUntilNextPulse) + 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.delayUntilFirstTenthOfPulse) XCTAssertEqual(6289.5, cmd.remainingPulses) XCTAssertEqual(2, cmd.rateEntries.count) let entry1 = cmd.rateEntries[0] @@ -267,34 +329,7 @@ class TempBasalTests: XCTestCase { XCTFail("message decoding threw error: \(error)") } - // Encode (note, this produces a different encoding than we saw above, as we split at a different point; we'll test - // that the difference ends up with the same result below. - let cmd = TempBasalExtraCommand(rate: 29.95, duration: .hours(12), confidenceReminder: false, programReminderInterval: .minutes(60)) - XCTAssertEqual("16143c00f61800092ba9f61800092ba922af00092ba9", cmd.data.hexadecimalString) - - // Test that our variation on splitting up delivery produces the same overall rate and duration - do { - // 29.95 U/h for 12 hours - // Decode 16 14 3c 00 f618 00092ba9 f618 00092ba9 22af 00092ba9 - let cmd = try TempBasalExtraCommand(encodedData: Data(hexadecimalString: "16140000f5af00092ba9f5af00092ba9231900092ba9")!) - XCTAssertEqual(false, cmd.confidenceReminder) - XCTAssertEqual(0, 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) } - - - // 16 14 00 00 f5af 00092ba9 f5af 00092ba9 2319 00092ba9 - - } diff --git a/OmniKitUI/Base.lproj/OmnipodPumpManager.storyboard b/OmniKitUI/Base.lproj/OmnipodPumpManager.storyboard new file mode 100644 index 000000000..f606b65ad --- /dev/null +++ b/OmniKitUI/Base.lproj/OmnipodPumpManager.storyboard @@ -0,0 +1,670 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OmniKitUI/CommandResponseViewController.swift b/OmniKitUI/CommandResponseViewController.swift deleted file mode 100644 index 0adf2295d..000000000 --- a/OmniKitUI/CommandResponseViewController.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// 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(podComms: PodComms?, rileyLinkDeviceProvider: RileyLinkDeviceProvider) -> 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 index 011e22e48..7a3ea75eb 100644 --- a/OmniKitUI/Info.plist +++ b/OmniKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json index 54267cedb..27c1a878d 100644 --- a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json +++ b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "pod_1x.png", + "filename" : "pod1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "pod_1x-2.png", + "filename" : "pod2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "pod_1x-1.png", + "filename" : "pod3x.png", "scale" : "3x" } ], diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod1x.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod1x.png new file mode 100644 index 000000000..e15cf636c Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod1x.png differ diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod2x.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod2x.png new file mode 100644 index 000000000..7656aef0a Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod2x.png differ diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod3x.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod3x.png new file mode 100644 index 000000000..9d91d8108 Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod3x.png differ 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 e17525d5f..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-1.png and /dev/null differ 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 07bbbf958..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x-2.png and /dev/null differ diff --git a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x.png b/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x.png deleted file mode 100644 index 99c6dbffe..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/Pod.imageset/pod_1x.png and /dev/null differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json index 55591ed5b..00500888f 100644 --- a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json +++ b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/Contents.json @@ -2,16 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "PodBottom1x.jpg", + "filename" : "PodBottom1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "PodBottom2x.jpg", + "filename" : "PodBottom2x.png", "scale" : "2x" }, { "idiom" : "universal", + "filename" : "PodBottom3x.png", "scale" : "3x" } ], diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.jpg b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.jpg deleted file mode 100644 index 15fc7397a..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.jpg and /dev/null differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.png b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.png new file mode 100644 index 000000000..109d65d8a Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom1x.png differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.jpg b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.jpg deleted file mode 100644 index fe0682182..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.jpg and /dev/null differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.png b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.png new file mode 100644 index 000000000..33024fcc8 Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom2x.png differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom3x.png b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom3x.png new file mode 100644 index 000000000..57d238250 Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/PodBottom.imageset/PodBottom3x.png differ 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 000000000..5ef798057 Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@1x.png differ 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 000000000..92e6ef21e Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@2x.png differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@3x.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@3x.png new file mode 100644 index 000000000..f3571b9db Binary files /dev/null and b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/PodLarge@3x.png differ 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 b89405d93..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-1.png and /dev/null differ diff --git a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-2.png b/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-2.png deleted file mode 100644 index 3aab40869..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x-2.png and /dev/null differ 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 086e4a4c9..000000000 Binary files a/OmniKitUI/OmniKitUI.xcassets/PodLarge.imageset/pod_1x.png and /dev/null differ 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/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/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..2079180d5 --- /dev/null +++ b/OmniKitUI/PumpManager/OmnipodHUDProvider.swift @@ -0,0 +1,210 @@ +// +// 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: Pod.nominalPodLife) + } + } + + 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 { + 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 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 + { + 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) + } + + let podLifeHUDView = PodLifeHUDView.instantiate() + podLifeHUDView.setPodLifeCycle(startTime: podActivatedAt, lifetime: lifetime) + + return [reservoirView, podLifeHUDView] + } + + 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..578655110 --- /dev/null +++ b/OmniKitUI/ViewControllers/CommandResponseViewController.swift @@ -0,0 +1,97 @@ +// +// 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 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 + 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.") + } + } + + 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/Pairing/InsertCannulaSetupViewController.swift b/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift similarity index 55% rename from OmniKitUI/Pairing/InsertCannulaSetupViewController.swift rename to OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift index fbc7b2019..628b881a9 100644 --- a/OmniKitUI/Pairing/InsertCannulaSetupViewController.swift +++ b/OmniKitUI/ViewControllers/InsertCannulaSetupViewController.swift @@ -22,6 +22,17 @@ class InsertCannulaSetupViewController: SetupTableViewController { @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() { @@ -37,10 +48,10 @@ class InsertCannulaSetupViewController: SetupTableViewController { // MARK: - UITableViewDelegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard continueState != .inserting else { + if case .startingInsertion = continueState { return } - + tableView.deselectRow(at: indexPath, animated: true) } @@ -48,7 +59,9 @@ class InsertCannulaSetupViewController: SetupTableViewController { private enum State { case initial - case inserting + case startingInsertion + case inserting(finishTime: CFTimeInterval) + case fault case ready } @@ -59,11 +72,18 @@ class InsertCannulaSetupViewController: SetupTableViewController { activityIndicator.state = .hidden footerView.primaryButton.isEnabled = true footerView.primaryButton.setConnectTitle() - case .inserting: - activityIndicator.state = .loading + case .startingInsertion: + activityIndicator.state = .indeterminantProgress footerView.primaryButton.isEnabled = false - footerView.primaryButton.setConnectTitle() 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 @@ -89,87 +109,60 @@ class InsertCannulaSetupViewController: SetupTableViewController { } } - tableView.beginUpdates() - loadingLabel.text = errorText - - let isHidden = (errorText == nil) - loadingLabel.isHidden = isHidden - tableView.endUpdates() + loadingText = errorText - // If we changed the error text, update the continue state - if !isHidden { + // 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 } } } - - override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { - return continueState == .ready + + private func navigateToReplacePod() { + performSegue(withIdentifier: "ReplacePod", sender: nil) } - + override func continueButtonPressed(_ sender: Any) { - - if case .ready = continueState { - super.continueButtonPressed(sender) - } else if case .initial = continueState { - continueState = .inserting + 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: { - 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) - } - } - } - }) + self.navigateToReplacePod() }) 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)) { + private 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 } - } catch let error { - DispatchQueue.main.async { - self.lastError = error - } - } - case .failure(let error): - DispatchQueue.main.async { + case .failure(let error): self.lastError = error } } @@ -181,25 +174,29 @@ 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: NSLocalizedString("Are you sure you want to shutdown this pod?", comment: "Confirmation message for shutting down a pod"), + message: LocalizedString("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"), + title: LocalizedString("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") + 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/Pairing/OmnipodPumpManagerSetupViewController.swift b/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift similarity index 56% rename from OmniKitUI/Pairing/OmnipodPumpManagerSetupViewController.swift rename to OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift index a3eaef610..76f3617e2 100644 --- a/OmniKitUI/Pairing/OmnipodPumpManagerSetupViewController.swift +++ b/OmniKitUI/ViewControllers/OmnipodPumpManagerSetupViewController.swift @@ -22,11 +22,15 @@ public class OmnipodPumpManagerSetupViewController: RileyLinkManagerSetupViewCon 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 + if #available(iOSApplicationExtension 13.0, *) { + view.backgroundColor = .systemBackground + } else { + view.backgroundColor = .white + } navigationBar.shadowImage = UIImage() if let omnipodPairingViewController = topViewController as? PairPodSetupViewController, let rileyLinkPumpManager = rileyLinkPumpManager { @@ -65,21 +69,49 @@ public class OmnipodPumpManagerSetupViewController: RileyLinkManagerSetupViewCon } } + 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 } } - - func completeSetup() { + override open func finishedSetup() { if let pumpManager = pumpManager { - setupDelegate?.pumpManagerSetupViewController(self, didSetUpPumpManager: 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..0a860f2e7 --- /dev/null +++ b/OmniKitUI/ViewControllers/OmnipodSettingsViewController.swift @@ -0,0 +1,899 @@ +// +// 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 + +public class ConfirmationBeepsTableViewCell: TextButtonTableViewCell { + + public func updateTextLabel(enabled: Bool) { + if enabled { + self.textLabel?.text = LocalizedString("Disable Confirmation Beeps", comment: "Title text for button to disable confirmation beeps") + } else { + self.textLabel?.text = LocalizedString("Enable Confirmation Beeps", comment: "Title text for button to enable confirmation beeps") + } + } + + override public func loadingStatusChanged() { + self.isEnabled = !isLoading + } +} + +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 + }() + + lazy var confirmationBeepsTableViewCell: ConfirmationBeepsTableViewCell = { + let cell = ConfirmationBeepsTableViewCell(style: .default, reuseIdentifier: nil) + cell.updateTextLabel(enabled: pumpManager.confirmationBeeps) + 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 + + 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) + } + + @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 readPodStatus + case playTestBeeps + case testCommand + 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 enableDisableConfirmationBeeps + } + + 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 .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") + 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 + + 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 + } + 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 .enableDisableConfirmationBeeps: + return confirmationBeepsTableViewCell + } + + 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.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 +// 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.isSuspended, 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 .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 + 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 { + 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 .enableDisableConfirmationBeeps: + confirmationBeepsTapped() + tableView.deselectRow(at: indexPath, animated: true) + } + 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, .status: + break + case .actions: + switch ActionsRow(rawValue: indexPath.row)! { + case .suspendResume, .replacePod: + break + case .readPodStatus, .playTestBeeps, .testCommand: + tableView.reloadRows(at: [indexPath], with: .fade) + } + case .configuration: + switch ConfigurationRow(rawValue: indexPath.row)! { + case .reminder, .enableDisableConfirmationBeeps: + break + case .timeZoneOffset: + tableView.reloadRows(at: [indexPath], with: .fade) + } + 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) + } + } + } + } + } + + private func confirmationBeepsTapped() { + let confirmationBeeps: Bool = pumpManager.confirmationBeeps + + func done() { + DispatchQueue.main.async { [weak self] in + if let self = self { + self.confirmationBeepsTableViewCell.updateTextLabel(enabled: self.pumpManager.confirmationBeeps) + self.confirmationBeepsTableViewCell.isLoading = false + } + } + } + + confirmationBeepsTableViewCell.isLoading = true + if confirmationBeeps { + pumpManager.setConfirmationBeeps(enabled: false, completion: { (error) in + if let error = error { + DispatchQueue.main.async { + 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.setConfirmationBeeps(enabled: true, completion: { (error) in + if let error = error { + DispatchQueue.main.async { + 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) + } + } + done() + }) + } + } +} + +extension OmnipodSettingsViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController, vc === presentedViewController { + dismiss(animated: true, 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..19d6fc25a --- /dev/null +++ b/OmniKitUI/ViewControllers/PodReplacementNavigationController.swift @@ -0,0 +1,78 @@ +// +// 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() + + if #available(iOSApplicationExtension 13.0, *) { + // Prevent interactive dismissal + isModalInPresentation = true + } + + 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) { + completionDelegate?.completionNotifyingDidComplete(self) + } +} 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..92b9c5e0e --- /dev/null +++ b/OmniKitUI/ViewControllers/PodSetupCompleteViewController.swift @@ -0,0 +1,76 @@ +// +// 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() + } + 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? { + 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..e6a666f8e --- /dev/null +++ b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.swift @@ -0,0 +1,81 @@ +// +// 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! { + 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 + } + + switch effectiveUserInterfaceLayoutDirection { + case .leftToRight: + dateLabel?.textAlignment = .right + case .rightToLeft: + dateLabel?.textAlignment = .left + @unknown default: + dateLabel?.textAlignment = .right + } + } + } + + 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..9379825d0 --- /dev/null +++ b/OmniKitUI/Views/ExpirationReminderDateTableViewCell.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..bd5c9bd61 --- /dev/null +++ b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Pod Life.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "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 000000000..c2ce51715 Binary files /dev/null and b/OmniKitUI/Views/HUDAssets.xcassets/pod_life/pod_life.imageset/Pod Life.pdf differ 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 000000000..623015046 Binary files /dev/null and b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir.imageset/reservoir.pdf differ 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 000000000..040769397 Binary files /dev/null and b/OmniKitUI/Views/HUDAssets.xcassets/reservoir/pod_reservoir_mask.imageset/reservoir_mask.pdf differ 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..8d8443097 --- /dev/null +++ b/OmniKitUI/Views/OmnipodReservoirView.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OmniKitUI/Views/PodLifeHUDView.swift b/OmniKitUI/Views/PodLifeHUDView.swift new file mode 100644 index 000000000..25a298ea8 --- /dev/null +++ b/OmniKitUI/Views/PodLifeHUDView.swift @@ -0,0 +1,175 @@ +// +// 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! { + 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! { + didSet { + alertLabel.alpha = 0 + alertLabel.textColor = UIColor.white + alertLabel.layer.cornerRadius = 9 + 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? + 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 = 0.5 + } else { + self.endColor = stateColors?.error + progressRing.shadowOpacity = 0.8 + } + + let remaining = (lifetime - age) + + // Update time label and caption + if alertState == .fault { + timeLabel.isHidden = true + 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") + } 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..701140eff --- /dev/null +++ b/OmniKitUI/Views/PodLifeHUDView.xib @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 baca73ef4..bdfb3a5af 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 */; }; @@ -51,7 +37,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 */; }; @@ -90,10 +75,8 @@ 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 */; }; 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 */; }; @@ -101,7 +84,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 */; }; @@ -128,22 +110,18 @@ 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 */; }; 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 */; }; @@ -155,8 +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 */; }; - 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 */; }; @@ -164,6 +140,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 */; }; @@ -172,7 +149,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 */; }; @@ -210,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 */; }; @@ -265,25 +242,29 @@ 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 */; }; - 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 */; }; + 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 */; }; @@ -292,6 +273,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 */; }; @@ -299,29 +281,49 @@ 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 */; }; + C127160A2378C2270093DAB7 /* ResumePumpEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12716092378C2270093DAB7 /* ResumePumpEventTests.swift */; }; 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 */; }; 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 */; }; - 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 */; }; + 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 */; }; + 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 */; }; + 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 */; }; 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 */; }; @@ -335,10 +337,20 @@ 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 */; }; + 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 */; }; + 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 */; }; 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 */; }; @@ -348,6 +360,8 @@ 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 */; }; @@ -410,6 +424,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 */; }; @@ -424,10 +439,52 @@ 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 */; }; + 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 */; }; + 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 */; }; @@ -463,7 +520,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 */; }; @@ -474,11 +530,11 @@ 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 */; }; @@ -498,7 +554,7 @@ 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 */; }; + 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 */; }; @@ -508,7 +564,7 @@ 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 */; }; + 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 */; }; @@ -525,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 */; }; @@ -545,6 +600,22 @@ 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 */ @@ -555,110 +626,110 @@ remoteGlobalIDString = 431CE76E1F98564100255374; remoteInfo = RileyLinkBLEKit; }; - 431CE77B1F98564200255374 /* PBXContainerItemProxy */ = { + 431CE7821F98564200255374 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; }; - 431CE7821F98564200255374 /* PBXContainerItemProxy */ = { + 4352A72A20DEC9B700CAC200 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 431CE76E1F98564100255374; - remoteInfo = RileyLinkBLEKit; + remoteGlobalIDString = 4352A72420DEC9B700CAC200; + remoteInfo = MinimedKitUI; }; - 4327EEB820E1E55D002598CB /* PBXContainerItemProxy */ = { + 43722FC11CB9F7640038B7F2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; }; - 4327EEBA20E1E562002598CB /* PBXContainerItemProxy */ = { + 437DE4FC229BB0D1003B1074 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; }; - 4352A71020DEC67300CAC200 /* PBXContainerItemProxy */ = { + 437DE4FE229BB0E3003B1074 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; + remoteGlobalIDString = C1FFAF77213323CC00C50C1D; + remoteInfo = OmniKit; }; - 4352A71320DEC68700CAC200 /* PBXContainerItemProxy */ = { + 437DE500229BB0E9003B1074 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; }; - 4352A72A20DEC9B700CAC200 /* PBXContainerItemProxy */ = { + 43C246A21D891D6C0031F8D1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 4352A72420DEC9B700CAC200; - remoteInfo = MinimedKitUI; + remoteGlobalIDString = 43C246921D8918AE0031F8D1; + remoteInfo = Crypto; }; - 4352A73520DECAE900CAC200 /* PBXContainerItemProxy */ = { + 43C246A41D891DB80031F8D1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; + remoteGlobalIDString = 43C246921D8918AE0031F8D1; + remoteInfo = Crypto; }; - 4352A74420DED49C00CAC200 /* PBXContainerItemProxy */ = { + 43D5E7931FAF7BFB004ACDB7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; }; - 43722FB91CB9F7640038B7F2 /* PBXContainerItemProxy */ = { + A9B839C622809D8D004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; remoteInfo = RileyLinkKit; }; - 43722FC11CB9F7640038B7F2 /* PBXContainerItemProxy */ = { + A9B839C822809D91004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; - remoteInfo = RileyLinkKit; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; }; - 43C246A21D891D6C0031F8D1 /* PBXContainerItemProxy */ = { + A9B839CA22809DB3004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43C246921D8918AE0031F8D1; - remoteInfo = Crypto; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; }; - 43C246A41D891DB80031F8D1 /* PBXContainerItemProxy */ = { + A9B839D022809DE7004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43C246921D8918AE0031F8D1; - remoteInfo = Crypto; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; }; - 43D5E7931FAF7BFB004ACDB7 /* PBXContainerItemProxy */ = { + A9B839D222809DF3004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; - remoteInfo = RileyLinkKitUI; + remoteGlobalIDString = C10D9BC01C8269D500378342; + remoteInfo = MinimedKit; }; - 43FB610820DDF509002B996B /* PBXContainerItemProxy */ = { + A9B839D422809DF3004E745E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = 43FB610120DDEF26002B996B; - remoteInfo = Cartfile; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; }; C10D9BCC1C8269D500378342 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -674,26 +745,75 @@ remoteGlobalIDString = C10D9BC01C8269D500378342; remoteInfo = MinimedKit; }; - C12EA257198B436900309FA4 /* PBXContainerItemProxy */ = { + C136AA4523117228008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = 43722FAD1CB9F7630038B7F2; + remoteInfo = RileyLinkKit; }; - C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C136AA4723117228008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C1B3830A1CD0665D00CE7782; - remoteInfo = NightscoutUploadKit; + remoteGlobalIDString = 431CE76E1F98564100255374; + remoteInfo = RileyLinkBLEKit; }; - C1B383181CD0665D00CE7782 /* PBXContainerItemProxy */ = { + C136AA4923117228008A320D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + 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; + }; + C136AA6C231188F1008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D5E78D1FAF7BFB004ACDB7; + remoteInfo = RileyLinkKitUI; + }; + C136AA6E231188F1008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C10D9BC01C8269D500378342; + remoteInfo = MinimedKit; + }; + C136AA70231188F1008A320D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C12EA22F198B436800309FA4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4352A72420DEC9B700CAC200; + remoteInfo = MinimedKitUI; }; C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -702,19 +822,19 @@ remoteGlobalIDString = C1B3830A1CD0665D00CE7782; remoteInfo = NightscoutUploadKit; }; - C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */ = { + C1BC25992313843700E80E3F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C1FFAF77213323CC00C50C1D; - remoteInfo = OmniKit; + remoteGlobalIDString = C1B3830A1CD0665D00CE7782; + remoteInfo = NightscoutUploadKit; }; - C1FFAF84213323CC00C50C1D /* PBXContainerItemProxy */ = { + C1FFAF82213323CC00C50C1D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C12EA22F198B436800309FA4 /* Project object */; proxyType = 1; - remoteGlobalIDString = C12EA236198B436800309FA4; - remoteInfo = RileyLink; + remoteGlobalIDString = C1FFAF77213323CC00C50C1D; + remoteInfo = OmniKit; }; C1FFAF8B213323CC00C50C1D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -730,34 +850,9 @@ 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 */ - 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; @@ -777,6 +872,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 */ @@ -830,7 +934,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 = ""; }; @@ -850,13 +953,12 @@ 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 = ""; }; - 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 = ""; }; @@ -883,6 +985,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 = ""; }; @@ -912,8 +1015,9 @@ 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 = ""; }; + 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 = ""; }; 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 = ""; }; @@ -963,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 = ""; }; @@ -1102,24 +1112,112 @@ 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 = ""; }; + 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; }; @@ -1127,6 +1225,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 = ""; }; @@ -1135,38 +1235,52 @@ 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 = ""; }; C125729321220FEC0061BA2F /* MainStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainStoryboard.storyboard; sourceTree = ""; }; C12572972125FA390061BA2F /* RileyLinkConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RileyLinkConnectionManager.swift; sourceTree = ""; }; C12616431B685F0A001FAD87 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + C12716092378C2270093DAB7 /* ResumePumpEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumePumpEventTests.swift; sourceTree = ""; }; C1271B061A9A34E900B7C949 /* Log.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Log.m; sourceTree = ""; }; C1271B081A9A350400B7C949 /* Log.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Log.h; sourceTree = ""; }; C1274F761D8232580002912B /* DailyTotal515PumpEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DailyTotal515PumpEvent.swift; sourceTree = ""; }; 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; }; 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 = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; - C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; name = NightscoutPumpEventsTests.swift; path = ../RileyLinkTests/NightscoutPumpEventsTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 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 = ""; }; 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 = ""; }; @@ -1182,9 +1296,15 @@ 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 = ""; }; + 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 = ""; }; - 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 = ""; }; @@ -1193,6 +1313,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; }; @@ -1246,7 +1368,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 = ""; }; @@ -1258,6 +1379,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 = ""; }; @@ -1275,6 +1397,10 @@ 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 = ""; }; + 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 = ""; }; @@ -1320,6 +1446,8 @@ 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 = ""; }; @@ -1332,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; }; @@ -1361,7 +1480,7 @@ 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 = ""; }; + 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 = ""; }; @@ -1371,7 +1490,7 @@ 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 = ""; }; + 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 = ""; }; @@ -1389,11 +1508,26 @@ 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 */ @@ -1401,6 +1535,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1B44CA7224BDFDF00DE47E5 /* LoopKit.framework in Frameworks */, 43C0196C1FA6B8AE007ABFA1 /* CoreBluetooth.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1410,6 +1545,7 @@ buildActionMask = 2147483647; files = ( 431CE7781F98564200255374 /* RileyLinkBLEKit.framework in Frameworks */, + C136AA76231234E1008A320D /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1433,15 +1569,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; @@ -1486,6 +1613,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 */, @@ -1499,15 +1627,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C12EA24F198B436800309FA4 /* Frameworks */ = { + 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 = ( - 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; }; @@ -1516,7 +1653,6 @@ buildActionMask = 2147483647; files = ( 43C246A61D891DBF0031F8D1 /* Crypto.framework in Frameworks */, - C1B383301CD0680800CE7782 /* MinimedKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1524,12 +1660,18 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 43C9071B1D863772002BAD29 /* MinimedKit.framework in Frameworks */, C1B383151CD0665D00CE7782 /* NightscoutUploadKit.framework in Frameworks */, 4352A74B20DED87F00CAC200 /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; + C1BB128221CB5603009A29B5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C1FFAF74213323CC00C50C1D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1556,6 +1698,7 @@ C1FFB02421343EC200C50C1D /* OmniKit.framework in Frameworks */, C1FFB02521343F0300C50C1D /* LoopKit.framework in Frameworks */, C1FFB02621343F0600C50C1D /* LoopKitUI.framework in Frameworks */, + C136AA752311CFC3008A320D /* MKRingProgressView.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1566,6 +1709,7 @@ isa = PBXGroup; children = ( 2F962EC71E7074E60070EFBD /* BolusNormalPumpEventTests.swift */, + C12716092378C2270093DAB7 /* ResumePumpEventTests.swift */, ); path = PumpEvents; sourceTree = ""; @@ -1576,7 +1720,6 @@ 431CE7711F98564100255374 /* RileyLinkBLEKit.h */, 431CE7721F98564100255374 /* Info.plist */, 7D23679221252EBC0028B67D /* Localizable.strings */, - 7D7076881FE092D7004AC8EA /* InfoPlist.strings */, 431CE79D1F9BE73900255374 /* BLEFirmwareVersion.swift */, 431CE7A01F9D195600255374 /* CBCentralManager.swift */, 431CE78E1F985B6E00255374 /* CBPeripheral.swift */, @@ -1605,7 +1748,6 @@ 43047FC31FAEC70600508343 /* RadioFirmwareVersionTests.swift */, 4322B75520282DA60002837D /* ResponseBufferTests.swift */, 431CE7801F98564200255374 /* Info.plist */, - 7D23674821252A5E0028B67D /* InfoPlist.strings */, ); path = RileyLinkBLEKitTests; sourceTree = ""; @@ -1654,7 +1796,6 @@ C1FFAF4B212944F600C50C1D /* Localizable.strings */, 4352A72720DEC9B700CAC200 /* MinimedKitUI.h */, 4352A72820DEC9B700CAC200 /* Info.plist */, - 7D23674E21252A5E0028B67D /* InfoPlist.strings */, 43709AC820DF1C9A00F941B3 /* MinimedKitUI.xcassets */, 43709AE020DF1D5400F941B3 /* MinimedPumpManager.storyboard */, 43709ACC20DF1CC900F941B3 /* Setup */, @@ -1664,6 +1805,7 @@ 43709AC220DF1C8B00F941B3 /* MinimedPumpSettingsViewController.swift */, 43709AC020DF1C8B00F941B3 /* PumpModel.swift */, 43709AC120DF1C8B00F941B3 /* RadioSelectionTableViewController.swift */, + C16E61252208EC580069F357 /* MinimedHUDProvider.swift */, ); path = MinimedKitUI; sourceTree = ""; @@ -1697,15 +1839,21 @@ isa = PBXGroup; children = ( 43722FC01CB9F7640038B7F2 /* Info.plist */, - 7D23674521252A5E0028B67D /* InfoPlist.strings */, ); path = RileyLinkKitTests; sourceTree = ""; }; + 437DE504229C898D003B1074 /* Scripts */ = { + isa = PBXGroup; + children = ( + 437DE508229C8A05003B1074 /* copy-frameworks.sh */, + ); + path = Scripts; + sourceTree = ""; + }; 43C246941D8918AE0031F8D1 /* Crypto */ = { isa = PBXGroup; children = ( - 7D7076791FE092D6004AC8EA /* InfoPlist.strings */, 43C246951D8918AE0031F8D1 /* Crypto.h */, 43C2469F1D8919E20031F8D1 /* Crypto.m */, 43C246961D8918AE0031F8D1 /* Info.plist */, @@ -1716,13 +1864,11 @@ 43D5E78F1FAF7BFB004ACDB7 /* RileyLinkKitUI */ = { isa = PBXGroup; children = ( - C1FFAF62212B126E00C50C1D /* InfoPlist.strings */, 43D5E7901FAF7BFB004ACDB7 /* RileyLinkKitUI.h */, 43D5E7911FAF7BFB004ACDB7 /* Info.plist */, 7D23679521252EBC0028B67D /* Localizable.strings */, 43709AEB20E0056F00F941B3 /* RileyLinkKitUI.xcassets */, C170C98D1CECD6F300F3D8E5 /* CBPeripheralState.swift */, - C170C9961CECD80000F3D8E5 /* CommandResponseViewController.swift */, 439731261CF21C3C00F474E5 /* RileyLinkDeviceTableViewCell.swift */, C170C9981CECD80000F3D8E5 /* RileyLinkDeviceTableViewController.swift */, 435D26B320DA0AAE00891C17 /* RileyLinkDevicesHeaderView.swift */, @@ -1733,6 +1879,7 @@ 43709AED20E008F300F941B3 /* SetupImageTableViewCell.swift */, 43709AEF20E0120F00F941B3 /* SetupImageTableViewCell.xib */, C1B4A9581D1E6357003B8985 /* UITableViewCell.swift */, + 43F89CA022BDFB8D006BB54E /* UIActivityIndicatorView.swift */, ); path = RileyLinkKitUI; sourceTree = ""; @@ -1749,15 +1896,19 @@ 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 */, + C16E61212208C7A80069F357 /* ReservoirReading.swift */, 43D8709420DE1C91006B549E /* RileyLinkDevice.swift */, + C1F8B1DE223AB1DF00DD66CF /* MinimedDoseProgressEstimator.swift */, + C164A56122F1F0A6000E3FA5 /* UnfinalizedDose.swift */, ); path = PumpManager; sourceTree = ""; @@ -1778,7 +1929,6 @@ C1EAD6BA1C826B92006DBA60 /* Data.swift */, 4352A73D20DED01700CAC200 /* HKUnit.swift */, 431185AE1CF25A590059ED98 /* IdentifiableClass.swift */, - 435D26B720DC83E400891C17 /* Locked.swift */, C14FFC5A1D3D74F90049CF85 /* NibLoadable.swift */, 43323EA61FA81A0F003FB0FA /* NumberFormatter.swift */, 431CE7941F9B0DAE00255374 /* OSLog.swift */, @@ -1786,6 +1936,7 @@ 4345D1CD1DA16AF300BAAD22 /* TimeZone.swift */, C14FFC601D3D75470049CF85 /* UIColor.swift */, 7D2366EF212527DA0028B67D /* LocalizedString.swift */, + C145BF9D2219F2EC00A977CB /* Comparable.swift */, ); path = Common; sourceTree = ""; @@ -1839,11 +1990,24 @@ 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 = ( 7D70768D1FE09310004AC8EA /* Localizable.strings */, - 7D70767E1FE092D6004AC8EA /* InfoPlist.strings */, 43D8709E20DE1CF5006B549E /* CGMManager */, C1EAD6B81C826B92006DBA60 /* Extensions */, 54BC44761DB46C3100340EED /* GlucoseEvents */, @@ -1870,7 +2034,6 @@ 54BC44721DB46A5200340EED /* GlucosePageTests.swift */, C12198621C8DF4C800BC374C /* HistoryPageTests.swift */, C10D9BD31C8269D500378342 /* Info.plist */, - 7D23674B21252A5E0028B67D /* InfoPlist.strings */, C1C357901C92733A009BDD4F /* MeterMessageTests.swift */, C1F6EB8C1F89C45500CFE393 /* MinimedPacketTests.swift */, C1EAD6D31C826C43006DBA60 /* NSDataTests.swift */, @@ -1946,8 +2109,12 @@ C1FFAF79213323CC00C50C1D /* OmniKit */, C1FFAF86213323CC00C50C1D /* OmniKitTests */, C1FFAFDA213323F900C50C1D /* OmniKitUI */, + C1BB128621CB5603009A29B5 /* OmniKitPacketParser */, + C136AA2B23116E32008A320D /* OmniKitPlugin */, + C136AA5F231187B0008A320D /* MinimedKitPlugin */, C12EA239198B436800309FA4 /* Frameworks */, C12EA238198B436800309FA4 /* Products */, + 437DE504229C898D003B1074 /* Scripts */, ); sourceTree = ""; }; @@ -1955,11 +2122,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 */, @@ -1970,6 +2135,9 @@ C1FFAF78213323CC00C50C1D /* OmniKit.framework */, C1FFAF80213323CC00C50C1D /* OmniKitTests.xctest */, C1FFAFD9213323F900C50C1D /* OmniKitUI.framework */, + C1BB128521CB5603009A29B5 /* OmniKitPacketParser */, + C136AA2A23116E32008A320D /* OmniKitPlugin.loopplugin */, + C136AA5E231187B0008A320D /* MinimedKitPlugin.loopplugin */, ); name = Products; sourceTree = ""; @@ -1977,10 +2145,10 @@ C12EA239198B436800309FA4 /* Frameworks */ = { isa = PBXGroup; children = ( + C13F0436230B1DE6001413FF /* MKRingProgressView.framework */, 43FB610B20DDF55F002B996B /* LoopKit.framework */, 43FB610A20DDF55E002B996B /* LoopKitUI.framework */, 43CA93241CB8BB33000026B5 /* CoreBluetooth.framework */, - 4370A3791FAF8A7400EC666A /* CoreBluetooth.framework */, C12616431B685F0A001FAD87 /* CoreData.framework */, C12EA23A198B436800309FA4 /* Foundation.framework */, C12EA23C198B436800309FA4 /* CoreGraphics.framework */, @@ -2015,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 = ""; @@ -2036,17 +2202,37 @@ isa = PBXGroup; children = ( C12EA25B198B436900309FA4 /* RileyLinkTests-Info.plist */, - C12EA25C198B436900309FA4 /* InfoPlist.strings */, ); 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 = ( C1FFAFA0213323E800C50C1D /* CRC8.swift */, C1FFAF9E213323E700C50C1D /* CRC16.swift */, C1FFAFB5213323E900C50C1D /* Packet.swift */, + C1BB12B321CB5697009A29B5 /* Packet+RFPacket.swift */, C1FFAF9B213323E700C50C1D /* MessageTransport.swift */, C1FFAF9C213323E700C50C1D /* Message.swift */, C1FFAFA3213323E800C50C1D /* MessageBlocks */, @@ -2054,15 +2240,20 @@ path = MessageTransport; sourceTree = ""; }; - C13BD641214033B0006D7F19 /* Delivery */ = { + 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 = Delivery; + path = Model; sourceTree = ""; }; C14FFC4D1D3D6D8E0049CF85 /* Models */ = { @@ -2075,6 +2266,15 @@ path = Models; sourceTree = ""; }; + C168C3FE21AEF85400ADE90E /* PumpManager */ = { + isa = PBXGroup; + children = ( + C1FFB0062133242B00C50C1D /* OmniPodPumpManager+UI.swift */, + C16C3D4321AC40AF00401105 /* OmnipodHUDProvider.swift */, + ); + path = PumpManager; + sourceTree = ""; + }; C1842BB91C8E15C600DB42AC /* PumpEvents */ = { isa = PBXGroup; children = ( @@ -2168,6 +2368,7 @@ isa = PBXGroup; children = ( C1AF21E11D4838C90088C41D /* DeviceStatus.swift */, + 7DEFE05222ED1C2300FCD378 /* OverrideStatus.swift */, C1AF21E51D48667F0088C41D /* UploaderStatus.swift */, C1AF21E71D4866960088C41D /* PumpStatus.swift */, C1A492621D4A5A19008964FF /* IOBStatus.swift */, @@ -2196,6 +2397,7 @@ C1AF21EF1D4901220088C41D /* TempBasalNightscoutTreatment.swift */, 492526701E4521FB00ACBA5F /* NoteNightscoutTreatment.swift */, C1D00EA01E8986F900B733B7 /* PumpResumeTreatment.swift */, + C121FE04233FA20E00630EB5 /* OverrideTreatment.swift */, ); path = Treatments; sourceTree = ""; @@ -2203,13 +2405,11 @@ C1B3830C1CD0665D00CE7782 /* NightscoutUploadKit */ = { isa = PBXGroup; children = ( - 7D7076921FE09311004AC8EA /* InfoPlist.strings */, C1AF21E91D4900300088C41D /* DeviceStatus */, C13D15591DAACE8400ADC044 /* Either.swift */, 43F348051D596270009933DC /* HKUnit.swift */, C1B3830F1CD0665D00CE7782 /* Info.plist */, 546145C01DCEB47600DC6DEB /* NightscoutEntry.swift */, - C1842C2A1C90DFB600DB42AC /* NightscoutPumpEvents.swift */, C1842C281C908A3C00DB42AC /* NightscoutUploader.swift */, C1B3830D1CD0665D00CE7782 /* NightscoutUploadKit.h */, 43B0ADC81D1268B300AAD278 /* TimeFormat.swift */, @@ -2222,9 +2422,8 @@ C1B3831A1CD0665D00CE7782 /* NightscoutUploadKitTests */ = { isa = PBXGroup; children = ( - C14C8A7F1C9CFBEE000F72C5 /* NightscoutPumpEventsTests.swift */, C1B3831D1CD0665D00CE7782 /* Info.plist */, - 7D23675121252A5E0028B67D /* InfoPlist.strings */, + C1BC259723135EDB00E80E3F /* NightscoutProfileTests.swift */, ); path = NightscoutUploadKitTests; sourceTree = ""; @@ -2241,6 +2440,14 @@ name = Managers; sourceTree = ""; }; + C1BB128621CB5603009A29B5 /* OmniKitPacketParser */ = { + isa = PBXGroup; + children = ( + C1BB128721CB5603009A29B5 /* main.swift */, + ); + path = OmniKitPacketParser; + sourceTree = ""; + }; C1EAD6B81C826B92006DBA60 /* Extensions */ = { isa = PBXGroup; children = ( @@ -2254,10 +2461,7 @@ C1EAD6BC1C826B92006DBA60 /* Messages */ = { isa = PBXGroup; children = ( - C1EAD6B21C826B6D006DBA60 /* PumpMessage.swift */, - C1EAD6B11C826B6D006DBA60 /* PacketType.swift */, - C1EAD6B01C826B6D006DBA60 /* MessageType.swift */, - 4352A70E20DEC47800CAC200 /* Models */, + C145BFA3221BBA7E00A977CB /* SuspendResumeMessageBody.swift */, C121989E1C8DFC2200BC374C /* BolusCarelinkMessageBody.swift */, C1711A551C94F13400CB25BD /* ButtonPressCarelinkMessageBody.swift */, C1EAD6BD1C826B92006DBA60 /* CarelinkMessageBody.swift */, @@ -2272,26 +2476,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 */, @@ -2302,12 +2511,13 @@ C1FFAF79213323CC00C50C1D /* OmniKit */ = { isa = PBXGroup; children = ( - C13BD641214033B0006D7F19 /* Delivery */, + C13BD641214033B0006D7F19 /* Model */, C13BD64021403362006D7F19 /* MessageTransport */, C1FFAFA1213323E800C50C1D /* Extensions */, C1FFAF95213323E600C50C1D /* PumpManager */, C1FFAF7A213323CC00C50C1D /* OmniKit.h */, C1FFAF7B213323CC00C50C1D /* Info.plist */, + 7D9BEFF123369861005DCFD6 /* Localizable.strings */, ); path = OmniKit; sourceTree = ""; @@ -2315,15 +2525,20 @@ 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 */, - C1FFAF89213323CC00C50C1D /* Info.plist */, ); path = OmniKitTests; sourceTree = ""; @@ -2331,12 +2546,15 @@ 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 = ""; @@ -2352,22 +2570,33 @@ 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 */, + E9C06B2921506BF300B602AD /* AcknowledgeAlertCommand.swift */, + C1FFAFB0213323E800C50C1D /* AssignAddressCommand.swift */, C1FFAFAD213323E800C50C1D /* BasalScheduleExtraCommand.swift */, + C1FFAFAA213323E800C50C1D /* BolusExtraCommand.swift */, C1FFAFAE213323E800C50C1D /* CancelDeliveryCommand.swift */, - C1FFAFAF213323E800C50C1D /* SetPodTimeCommand.swift */, - C1FFAFB0213323E800C50C1D /* AssignAddressCommand.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 */, - C1FFAFB3213323E800C50C1D /* ConfigureAlertsCommand.swift */, + C1FFAFAF213323E800C50C1D /* ConfigurePodCommand.swift */, + C1FFAFAB213323E800C50C1D /* StatusResponse.swift */, + C1FFAFA6213323E800C50C1D /* TempBasalExtraCommand.swift */, + C1FFAFA4213323E800C50C1D /* VersionResponse.swift */, + C11F6B7D21C9646300752BBC /* FaultConfigCommand.swift */, + D807D7D9228913EC006BCDF0 /* BeepConfigCommand.swift */, ); path = MessageBlocks; sourceTree = ""; @@ -2375,27 +2604,32 @@ 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 */, + C168C3FE21AEF85400ADE90E /* PumpManager */, + C1FFB0092133242C00C50C1D /* ViewControllers */, + C104A9BE217E6027006E3C3E /* Views */, C1FFAFDC213323F900C50C1D /* Info.plist */, + C1FFAFDB213323F900C50C1D /* OmniKitUI.h */, + C1FFB0072133242C00C50C1D /* OmniKitUI.xcassets */, + 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */, + 7D9BF00323369910005DCFD6 /* Localizable.strings */, ); path = OmniKitUI; sourceTree = ""; }; - C1FFB0092133242C00C50C1D /* Pairing */ = { + C1FFB0092133242C00C50C1D /* ViewControllers */ = { isa = PBXGroup; children = ( + C1FC49ED2135DD56007D0788 /* CommandResponseViewController.swift */, + C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */, C1FFB00C2133242C00C50C1D /* OmnipodPumpManagerSetupViewController.swift */, + C1FFB0082133242C00C50C1D /* OmnipodSettingsViewController.swift */, C1F1A5E9215164FA00F0B820 /* PairPodSetupViewController.swift */, - C1F1A5EB2151F73F00F0B820 /* InsertCannulaSetupViewController.swift */, + C168C3FF21AEF8DE00ADE90E /* PodReplacementNavigationController.swift */, + C127D923215C002A0031799D /* PodSettingsSetupViewController.swift */, C1F1A5ED2151FBA700F0B820 /* PodSetupCompleteViewController.swift */, + C168C40121AFACA600ADE90E /* ReplacePodViewController.swift */, ); - path = Pairing; + path = ViewControllers; sourceTree = ""; }; /* End PBXGroup section */ @@ -2449,6 +2683,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; @@ -2501,12 +2751,12 @@ 431CE7731F98564200255374 /* Sources */, 431CE7741F98564200255374 /* Frameworks */, 431CE7751F98564200255374 /* Resources */, + C136AA7723123A5D008A320D /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( 431CE77A1F98564200255374 /* PBXTargetDependency */, - 431CE77C1F98564200255374 /* PBXTargetDependency */, ); name = RileyLinkBLEKitTests; productName = RileyLinkBLEKitTests; @@ -2525,7 +2775,8 @@ buildRules = ( ); dependencies = ( - 4352A73620DECAE900CAC200 /* PBXTargetDependency */, + A9B839D322809DF3004E745E /* PBXTargetDependency */, + A9B839D522809DF3004E745E /* PBXTargetDependency */, ); name = MinimedKitUI; productName = MinimedKitUI; @@ -2544,32 +2795,13 @@ buildRules = ( ); dependencies = ( - 4352A71120DEC67300CAC200 /* PBXTargetDependency */, + A9B839CB22809DB3004E745E /* PBXTargetDependency */, ); name = RileyLinkKit; productName = RileyLinkKit; 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" */; @@ -2600,7 +2832,7 @@ buildRules = ( ); dependencies = ( - 43FB610920DDF509002B996B /* PBXTargetDependency */, + A9B839D122809DE7004E745E /* PBXTargetDependency */, ); name = RileyLinkKitUI; productName = RileyLinkKitUI; @@ -2619,7 +2851,8 @@ buildRules = ( ); dependencies = ( - 4352A71420DEC68700CAC200 /* PBXTargetDependency */, + A9B839C722809D8D004E745E /* PBXTargetDependency */, + A9B839C922809D91004E745E /* PBXTargetDependency */, ); name = MinimedKit; productName = MinimedKit; @@ -2633,12 +2866,12 @@ C10D9BC61C8269D500378342 /* Sources */, C10D9BC71C8269D500378342 /* Frameworks */, C10D9BC81C8269D500378342 /* Resources */, + C136AA7823123B8C008A320D /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( C10D9BCD1C8269D500378342 /* PBXTargetDependency */, - 4327EEB920E1E55D002598CB /* PBXTargetDependency */, ); name = MinimedKitTests; productName = MinimedKitTests; @@ -2658,7 +2891,6 @@ buildRules = ( ); dependencies = ( - 4352A74520DED49C00CAC200 /* PBXTargetDependency */, 43C246A31D891D6C0031F8D1 /* PBXTargetDependency */, C10D9BD51C8269D500378342 /* PBXTargetDependency */, 43722FC21CB9F7640038B7F2 /* PBXTargetDependency */, @@ -2674,23 +2906,53 @@ productReference = C12EA237198B436800309FA4 /* RileyLink.app */; productType = "com.apple.product-type.application"; }; - C12EA251198B436800309FA4 /* RileyLinkTests */ = { + C136AA2923116E32008A320D /* OmniKitPlugin */ = { isa = PBXNativeTarget; - buildConfigurationList = C12EA266198B436900309FA4 /* Build configuration list for PBXNativeTarget "RileyLinkTests" */; + buildConfigurationList = C136AA3F23116E32008A320D /* Build configuration list for PBXNativeTarget "OmniKitPlugin" */; buildPhases = ( - C12EA24E198B436800309FA4 /* Sources */, - C12EA24F198B436800309FA4 /* Frameworks */, - C12EA250198B436800309FA4 /* Resources */, + C136AA2523116E32008A320D /* Headers */, + C136AA2623116E32008A320D /* Sources */, + C136AA2723116E32008A320D /* Frameworks */, + C136AA2823116E32008A320D /* Resources */, + C136AA4323116F4D008A320D /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( - C12EA258198B436900309FA4 /* PBXTargetDependency */, - ); - name = RileyLinkTests; - productName = RileyLinkTests; - productReference = C12EA252198B436800309FA4 /* RileyLinkTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; + 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; @@ -2718,19 +2980,35 @@ C1B383101CD0665D00CE7782 /* Sources */, C1B383111CD0665D00CE7782 /* Frameworks */, C1B383121CD0665D00CE7782 /* Resources */, - 43B6E0151D24E4610022E6D7 /* CopyFiles */, + A9B839DC2280A178004E745E /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( - C1B383171CD0665D00CE7782 /* PBXTargetDependency */, - C1B383191CD0665D00CE7782 /* PBXTargetDependency */, + C1BC259A2313843700E80E3F /* PBXTargetDependency */, ); name = NightscoutUploadKitTests; productName = NightscoutUploadKitTests; 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" */; @@ -2743,7 +3021,7 @@ buildRules = ( ); dependencies = ( - C1FFB02021343B9700C50C1D /* PBXTargetDependency */, + 437DE4FD229BB0D1003B1074 /* PBXTargetDependency */, ); name = OmniKit; productName = OmniKit; @@ -2757,12 +3035,12 @@ C1FFAF7C213323CC00C50C1D /* Sources */, C1FFAF7D213323CC00C50C1D /* Frameworks */, C1FFAF7E213323CC00C50C1D /* Resources */, + C14A538023123CF500C86755 /* Copy Frameworks with Carthage */, ); buildRules = ( ); dependencies = ( C1FFAF83213323CC00C50C1D /* PBXTargetDependency */, - C1FFAF85213323CC00C50C1D /* PBXTargetDependency */, ); name = OmniKitTests; productName = OmniKitTests; @@ -2781,7 +3059,8 @@ buildRules = ( ); dependencies = ( - C1FFB02321343EB500C50C1D /* PBXTargetDependency */, + 437DE501229BB0E9003B1074 /* PBXTargetDependency */, + 437DE4FF229BB0E3003B1074 /* PBXTargetDependency */, ); name = OmniKitUI; productName = OmniKitUI; @@ -2794,95 +3073,95 @@ C12EA22F198B436800309FA4 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0940; - LastUpgradeCheck = 1000; + LastSwiftUpdateCheck = 1030; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Pete Schwamb"; TargetAttributes = { 431CE76E1F98564100255374 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; 431CE7761F98564200255374 = { CreatedOnToolsVersion = 9.0; LastSwiftMigration = 1000; - ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; + ProvisioningStyle = Manual; }; 4352A72420DEC9B700CAC200 = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; 43722FAD1CB9F7630038B7F2 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; - }; - 43722FB61CB9F7640038B7F2 = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; - TestTargetID = C12EA236198B436800309FA4; + LastSwiftMigration = 1020; }; 43C246921D8918AE0031F8D1 = { CreatedOnToolsVersion = 8.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; 43D5E78D1FAF7BFB004ACDB7 = { CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; - 43FB610120DDEF26002B996B = { - CreatedOnToolsVersion = 9.4.1; - ProvisioningStyle = Automatic; - }; C10D9BC01C8269D500378342 = { CreatedOnToolsVersion = 7.2.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; C10D9BC91C8269D500378342 = { CreatedOnToolsVersion = 7.2.1; LastSwiftMigration = 1000; - TestTargetID = C12EA236198B436800309FA4; + ProvisioningStyle = Manual; }; C12EA236198B436800309FA4 = { - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + C136AA2923116E32008A320D = { + CreatedOnToolsVersion = 10.3; + LastSwiftMigration = 1030; ProvisioningStyle = Automatic; }; - C12EA251198B436800309FA4 = { + C136AA5D231187B0008A320D = { + CreatedOnToolsVersion = 10.3; + LastSwiftMigration = 1030; ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; }; C1B3830A1CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; C1B383131CD0665D00CE7782 = { CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 1000; + LastSwiftMigration = 1030; + ProvisioningStyle = Manual; + }; + C1BB128421CB5603009A29B5 = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; }; C1FFAF77213323CC00C50C1D = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; C1FFAF7F213323CC00C50C1D = { CreatedOnToolsVersion = 9.4.1; LastSwiftMigration = 1000; ProvisioningStyle = Automatic; - TestTargetID = C12EA236198B436800309FA4; }; C1FFAFD8213323F900C50C1D = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = C12EA232198B436800309FA4 /* Build configuration list for PBXProject "RileyLink" */; compatibilityVersion = "Xcode 6.3"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -2896,6 +3175,13 @@ nl, nb, pl, + vi, + ja, + sv, + da, + fi, + "pt-BR", + ro, ); mainGroup = C12EA22E198B436800309FA4; productRefGroup = C12EA238198B436800309FA4 /* Products */; @@ -2903,22 +3189,22 @@ projectRoot = ""; targets = ( C12EA236198B436800309FA4 /* RileyLink */, - C12EA251198B436800309FA4 /* RileyLinkTests */, - C10D9BC01C8269D500378342 /* MinimedKit */, - C10D9BC91C8269D500378342 /* MinimedKitTests */, - 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */, - 43722FB61CB9F7640038B7F2 /* RileyLinkKitTests */, - C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */, - C1B383131CD0665D00CE7782 /* NightscoutUploadKitTests */, 43C246921D8918AE0031F8D1 /* Crypto */, 431CE76E1F98564100255374 /* RileyLinkBLEKit */, 431CE7761F98564200255374 /* RileyLinkBLEKitTests */, + 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */, 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */, + C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */, + C1B383131CD0665D00CE7782 /* NightscoutUploadKitTests */, + C10D9BC01C8269D500378342 /* MinimedKit */, 4352A72420DEC9B700CAC200 /* MinimedKitUI */, - 43FB610120DDEF26002B996B /* Cartfile */, + C10D9BC91C8269D500378342 /* MinimedKitTests */, + C136AA5D231187B0008A320D /* MinimedKitPlugin */, C1FFAF77213323CC00C50C1D /* OmniKit */, C1FFAF7F213323CC00C50C1D /* OmniKitTests */, C1FFAFD8213323F900C50C1D /* OmniKitUI */, + C136AA2923116E32008A320D /* OmniKitPlugin */, + C1BB128421CB5603009A29B5 /* OmniKitPacketParser */, ); }; /* End PBXProject section */ @@ -2936,7 +3222,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D23674A21252A5E0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2946,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; @@ -2958,19 +3242,10 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 43722FB51CB9F7640038B7F2 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 7D23674721252A5E0028B67D /* InfoPlist.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 43C246911D8918AE0031F8D1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D7076771FE092D6004AC8EA /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2979,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 */, ); @@ -2989,7 +3263,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D70767C1FE092D6004AC8EA /* InfoPlist.strings in Resources */, 7D70768B1FE09310004AC8EA /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2998,7 +3271,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D23674D21252A5E0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3008,18 +3280,22 @@ 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; }; - C12EA250198B436800309FA4 /* Resources */ = { + C136AA2823116E32008A320D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C136AA5C231187B0008A320D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C12EA25E198B436900309FA4 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3027,7 +3303,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D7076901FE09311004AC8EA /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3035,7 +3310,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D23675321252A5E0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3043,6 +3317,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7D9BEFEF23369861005DCFD6 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3057,7 +3332,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1FFB00D2133242C00C50C1D /* OmnipodPumpManager.storyboard in Resources */, + C104A9C2217E603E006E3C3E /* OmnipodReservoirView.xib 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 */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3071,8 +3351,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 = ( @@ -3081,21 +3361,131 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; + }; + A9B839DC2280A178004E745E /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + 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"; }; - 43FB610520DDEF32002B996B /* Build Carthage Dependencies */ = { + C14A538023123CF500C86755 /* 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 = ( ); 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 = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -3119,7 +3509,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 */, @@ -3147,12 +3536,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 */, @@ -3178,16 +3570,6 @@ 4352A74120DED23100CAC200 /* RileyLinkDeviceManager.swift in Sources */, 431CE7961F9B0F0200255374 /* OSLog.swift in Sources */, 435D26B020DA08CE00891C17 /* RileyLinkPumpManager.swift in Sources */, - 435D26B920DC83F300891C17 /* Locked.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 43722FB31CB9F7640038B7F2 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4384C8C91FB941FB00D916E6 /* TimeInterval.swift in Sources */, - 43A9E50E1F6B865000307931 /* Data.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3215,9 +3597,9 @@ 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 */, - 43D5E79B1FAF7C47004ACDB7 /* CommandResponseViewController.swift in Sources */, 43709ABF20DF1C6400F941B3 /* RileyLinkSettingsViewController.swift in Sources */, 43709ABD20DF1C6400F941B3 /* RileyLinkSetupTableViewController.swift in Sources */, 43D5E79C1FAF7C47004ACDB7 /* RileyLinkDeviceTableViewController.swift in Sources */, @@ -3277,6 +3659,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 */, @@ -3322,6 +3705,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 */, @@ -3331,6 +3715,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 */, @@ -3338,6 +3723,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 */, @@ -3353,6 +3739,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 */, @@ -3395,6 +3782,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 */, @@ -3437,6 +3825,7 @@ 54BC44A31DB7021B00340EED /* SensorStatusGlucoseEventTests.swift in Sources */, C1EAD6E41C82BA87006DBA60 /* CRC16Tests.swift in Sources */, 2F962ECA1E70831F0070EFBD /* PumpModelTests.swift in Sources */, + C127160A2378C2270093DAB7 /* ResumePumpEventTests.swift in Sources */, 546A85CE1DF7B99D00733213 /* SensorPacketGlucoseEventTests.swift in Sources */, 43CA93311CB97191000026B5 /* ReadTempBasalCarelinkMessageBodyTests.swift in Sources */, 54BC44A91DB704A600340EED /* CalBGForGHGlucoseEventTests.swift in Sources */, @@ -3502,11 +3891,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C12EA24E198B436800309FA4 /* Sources */ = { + C136AA2623116E32008A320D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C12EA260198B436900309FA4 /* RileyLinkTests.m in Sources */, + 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; }; @@ -3514,7 +3913,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 */, @@ -3528,6 +3926,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 */, @@ -3535,6 +3934,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 */, @@ -3545,6 +3945,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 */, ); @@ -3554,7 +3955,57 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 43B6E0121D24E2320022E6D7 /* NightscoutPumpEventsTests.swift in Sources */, + C1BC259823135EDB00E80E3F /* NightscoutProfileTests.swift in Sources */, + ); + 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 */, + C15500272308FA1000345FCC /* BeepType.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 */, + C15500282308FA2F00345FCC /* BeepConfigCommand.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; }; @@ -3562,40 +4013,60 @@ 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 /* StatusError.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 */, - C1FFAFCD213323E900C50C1D /* SetPodTimeCommand.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 */, @@ -3609,13 +4080,18 @@ 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; }; @@ -3624,16 +4100,26 @@ 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 */, @@ -3648,60 +4134,35 @@ 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 */; - }; - 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 */ = { + 43722FC21CB9F7640038B7F2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 4352A73520DECAE900CAC200 /* PBXContainerItemProxy */; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = 43722FC11CB9F7640038B7F2 /* PBXContainerItemProxy */; }; - 4352A74520DED49C00CAC200 /* PBXTargetDependency */ = { + 437DE4FD229BB0D1003B1074 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 4352A74420DED49C00CAC200 /* PBXContainerItemProxy */; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = 437DE4FC229BB0D1003B1074 /* PBXContainerItemProxy */; }; - 43722FBA1CB9F7640038B7F2 /* PBXTargetDependency */ = { + 437DE4FF229BB0E3003B1074 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; - targetProxy = 43722FB91CB9F7640038B7F2 /* PBXContainerItemProxy */; + target = C1FFAF77213323CC00C50C1D /* OmniKit */; + targetProxy = 437DE4FE229BB0E3003B1074 /* PBXContainerItemProxy */; }; - 43722FC21CB9F7640038B7F2 /* PBXTargetDependency */ = { + 437DE501229BB0E9003B1074 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; - targetProxy = 43722FC11CB9F7640038B7F2 /* PBXContainerItemProxy */; + target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; + targetProxy = 437DE500229BB0E9003B1074 /* PBXContainerItemProxy */; }; 43C246A31D891D6C0031F8D1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -3718,10 +4179,35 @@ target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; targetProxy = 43D5E7931FAF7BFB004ACDB7 /* PBXContainerItemProxy */; }; - 43FB610920DDF509002B996B /* PBXTargetDependency */ = { + A9B839C722809D8D004E745E /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 43FB610120DDEF26002B996B /* Cartfile */; - targetProxy = 43FB610820DDF509002B996B /* PBXContainerItemProxy */; + 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 */; + }; + 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 */; }; C10D9BCD1C8269D500378342 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -3733,36 +4219,71 @@ target = C10D9BC01C8269D500378342 /* MinimedKit */; targetProxy = C10D9BD41C8269D500378342 /* PBXContainerItemProxy */; }; - C12EA258198B436900309FA4 /* PBXTargetDependency */ = { + C136AA4623117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C12EA257198B436900309FA4 /* PBXContainerItemProxy */; + target = 43722FAD1CB9F7630038B7F2 /* RileyLinkKit */; + targetProxy = C136AA4523117228008A320D /* PBXContainerItemProxy */; }; - C1B383171CD0665D00CE7782 /* PBXTargetDependency */ = { + C136AA4823117228008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; - targetProxy = C1B383161CD0665D00CE7782 /* PBXContainerItemProxy */; + target = 431CE76E1F98564100255374 /* RileyLinkBLEKit */; + targetProxy = C136AA4723117228008A320D /* PBXContainerItemProxy */; + }; + C136AA4A23117228008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D5E78D1FAF7BFB004ACDB7 /* RileyLinkKitUI */; + targetProxy = C136AA4923117228008A320D /* PBXContainerItemProxy */; + }; + C136AA4C23117228008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1FFAF77213323CC00C50C1D /* OmniKit */; + targetProxy = C136AA4B23117228008A320D /* PBXContainerItemProxy */; + }; + C136AA4E23117228008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1FFAFD8213323F900C50C1D /* OmniKitUI */; + targetProxy = C136AA4D23117228008A320D /* PBXContainerItemProxy */; + }; + C136AA69231188F1008A320D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + 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 */; }; - C1B383191CD0665D00CE7782 /* PBXTargetDependency */ = { + C136AA71231188F1008A320D /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = C12EA236198B436800309FA4 /* RileyLink */; - targetProxy = C1B383181CD0665D00CE7782 /* PBXContainerItemProxy */; + target = 4352A72420DEC9B700CAC200 /* MinimedKitUI */; + targetProxy = C136AA70231188F1008A320D /* PBXContainerItemProxy */; }; C1B3831F1CD0665D00CE7782 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; targetProxy = C1B3831E1CD0665D00CE7782 /* PBXContainerItemProxy */; }; + C1BC259A2313843700E80E3F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C1B3830A1CD0665D00CE7782 /* NightscoutUploadKit */; + targetProxy = C1BC25992313843700E80E3F /* 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 */; @@ -3773,16 +4294,6 @@ 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 */ @@ -3799,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 = ( @@ -3896,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 = ""; @@ -3913,75 +4359,18 @@ 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 */ = { + 7D70768D1FE09310004AC8EA /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 7D70768C1FE09310004AC8EA /* es */, @@ -3994,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 = ( @@ -4027,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 = ""; + }; + 7D9BF00323369910005DCFD6 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 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 = ""; }; - C12EA25C198B436900309FA4 /* InfoPlist.strings */ = { + 7D9BF03F2336AE0B005DCFD6 /* OmnipodPumpManager.storyboard */ = { 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; + 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 */ = { @@ -4078,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 */ @@ -4118,8 +4536,12 @@ 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)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4128,12 +4550,8 @@ 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; @@ -4156,8 +4574,12 @@ 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)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RileyLinkBLEKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4166,10 +4588,6 @@ 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; @@ -4184,7 +4602,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; @@ -4193,12 +4611,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; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -4212,7 +4629,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 = ""; @@ -4222,16 +4639,16 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.RileyLinkBLEKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; 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"; @@ -4239,14 +4656,12 @@ 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; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 46; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -4263,8 +4678,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 = ""; }; @@ -4273,6 +4686,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"; @@ -4280,15 +4694,13 @@ 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"; 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)", @@ -4303,8 +4715,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 = ""; }; @@ -4319,16 +4729,16 @@ 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 = ( "$(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; @@ -4338,11 +4748,7 @@ 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 = ""; }; @@ -4358,16 +4764,16 @@ 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 = ( "$(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; @@ -4377,67 +4783,11 @@ 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 = ""; }; 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; - SWIFT_VERSION = 4.2; - 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; - SWIFT_VERSION = 4.2; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; - }; - name = Release; - }; 43C2469C1D8918AE0031F8D1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4448,12 +4798,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"; @@ -4463,8 +4813,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; @@ -4480,12 +4828,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"; @@ -4494,8 +4842,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; @@ -4503,7 +4849,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"; @@ -4516,7 +4862,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)", @@ -4533,8 +4879,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 = ""; }; @@ -4543,7 +4887,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"; @@ -4557,7 +4901,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)", @@ -4572,29 +4916,11 @@ 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; }; - 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 = { @@ -4603,17 +4929,17 @@ 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 = ( "$(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; @@ -4623,11 +4949,7 @@ 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 = ""; }; @@ -4642,16 +4964,16 @@ 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 = ( "$(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; @@ -4661,10 +4983,6 @@ 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 = ""; }; @@ -4676,6 +4994,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; @@ -4689,10 +5008,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; - SWIFT_VERSION = 4.2; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Debug; }; @@ -4702,6 +5020,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 = ""; @@ -4716,9 +5035,8 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.MinimedKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; }; name = Release; }; @@ -4726,10 +5044,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++"; @@ -4755,9 +5069,13 @@ 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; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -4782,7 +5100,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"; @@ -4793,10 +5111,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++"; @@ -4822,9 +5136,13 @@ 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; + 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; @@ -4842,7 +5160,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"; @@ -4871,8 +5189,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; }; @@ -4898,48 +5214,160 @@ 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; }; name = Release; }; - C12EA267198B436900309FA4 /* Debug */ = { + C136AA3B23116E32008A320D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; + 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_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = "RileyLinkTests/RileyLinkTests-Info.plist"; + 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"; - PRODUCT_BUNDLE_IDENTIFIER = "com.rileylink.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RileyLinkTests; - PROVISIONING_PROFILE_SPECIFIER = ""; + 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"; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSION_INFO_PREFIX = ""; + WRAPPER_EXTENSION = loopplugin; }; name = Debug; }; - C12EA268198B436900309FA4 /* Release */ = { + C136AA3C23116E32008A320D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RileyLink.app/RileyLink"; + 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_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; - INFOPLIST_FILE = "RileyLinkTests/RileyLinkTests-Info.plist"; + 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"; - PRODUCT_BUNDLE_IDENTIFIER = "com.rileylink.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = RileyLinkTests; - PROVISIONING_PROFILE_SPECIFIER = ""; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; + 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; }; @@ -4952,11 +5380,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; @@ -4968,8 +5396,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; @@ -4984,11 +5410,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; @@ -4999,8 +5425,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; @@ -5009,8 +5433,10 @@ 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; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -5024,9 +5450,10 @@ 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; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -5034,8 +5461,10 @@ 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; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; @@ -5050,14 +5479,63 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.rileylink.NightscoutUploadKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; + }; + 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"; @@ -5065,15 +5543,13 @@ 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; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5090,8 +5566,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 = ""; }; @@ -5100,6 +5574,7 @@ 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"; @@ -5107,16 +5582,14 @@ 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; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5131,8 +5604,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 = ""; }; @@ -5162,9 +5633,7 @@ 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; }; @@ -5191,15 +5660,14 @@ 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 = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -5207,15 +5675,13 @@ 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; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5232,8 +5698,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 = ""; }; @@ -5242,6 +5706,7 @@ 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"; @@ -5249,16 +5714,14 @@ 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; + CURRENT_PROJECT_VERSION = 44; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 45; + DYLIB_CURRENT_VERSION = 44; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -5273,8 +5736,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 = ""; }; @@ -5319,15 +5780,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 = ( @@ -5346,15 +5798,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 = ( @@ -5391,11 +5834,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C12EA266198B436900309FA4 /* Build configuration list for PBXNativeTarget "RileyLinkTests" */ = { + 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 = ( - C12EA267198B436900309FA4 /* Debug */, - C12EA268198B436900309FA4 /* Release */, + C136AA64231187B0008A320D /* Debug */, + C136AA65231187B0008A320D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -5418,6 +5870,15 @@ 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 = ( 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 + + + diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme deleted file mode 100644 index f975dc656..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/Crypto.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKitUI.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKitUI.xcscheme deleted file mode 100644 index d158b7222..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 8d3b61a21..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/NightscoutUploadKit.xcscheme +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme similarity index 67% rename from RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme rename to RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme index 7aba6b753..dcd0133b4 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/MinimedKit.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/OmniKitPacketParser.xcscheme @@ -1,6 +1,6 @@ @@ -28,23 +28,13 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - @@ -61,15 +51,22 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> - + - + + + + + @@ -79,15 +76,16 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> - + - + diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme index e0ec922c5..87790fde6 100644 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLink.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + - - - - @@ -77,9 +136,9 @@ skipped = "NO"> @@ -97,9 +156,9 @@ skipped = "NO"> @@ -107,9 +166,9 @@ skipped = "NO"> diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkBLEKit.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkBLEKit.xcscheme deleted file mode 100644 index 01dad6b1e..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 e9f4c7102..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 57cfa79f1..000000000 --- a/RileyLink.xcodeproj/xcshareddata/xcschemes/RileyLinkKitUI.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme new file mode 100644 index 000000000..4c669824b --- /dev/null +++ b/RileyLink.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/DeviceDataManager.swift b/RileyLink/DeviceDataManager.swift index f72d5ddff..13d301ae5 100644 --- a/RileyLink/DeviceDataManager.swift +++ b/RileyLink/DeviceDataManager.swift @@ -15,7 +15,9 @@ import MinimedKitUI import NightscoutUploadKit import LoopKit import LoopKitUI +import UserNotifications import os.log +import UserNotifications class DeviceDataManager { @@ -60,9 +62,6 @@ extension DeviceDataManager: PumpManagerDelegate { log.debug("didAdjustPumpClockBy %@", adjustment) } - func pumpManagerDidUpdatePumpBatteryChargeRemaining(_ pumpManager: PumpManager, oldValue: Double?) { - } - func pumpManagerDidUpdateState(_ pumpManager: PumpManager) { UserDefaults.standard.pumpManagerRawValue = pumpManager.rawValue } @@ -70,11 +69,11 @@ extension DeviceDataManager: PumpManagerDelegate { func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) { } - func pumpManagerShouldProvideBLEHeartbeat(_ pumpManager: PumpManager) -> Bool { + func pumpManagerMustProvideBLEHeartbeat(_ pumpManager: PumpManager) -> Bool { return true } - func pumpManager(_ pumpManager: PumpManager, didUpdateStatus status: PumpManagerStatus) { + func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) { } func pumpManagerWillDeactivate(_ pumpManager: PumpManager) { @@ -88,7 +87,7 @@ extension DeviceDataManager: PumpManagerDelegate { log.error("pumpManager didError %@", String(describing: error)) } - func pumpManager(_ pumpManager: PumpManager, didReadPumpEvents events: [NewPumpEvent], completion: @escaping (_ error: Error?) -> 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) { @@ -100,8 +99,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/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/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/RileyLink-Info.plist b/RileyLink/RileyLink-Info.plist index f4fde960f..6c9826a3c 100644 --- a/RileyLink/RileyLink-Info.plist +++ b/RileyLink/RileyLink-Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLink/View Controllers/MainViewController.swift b/RileyLink/View Controllers/MainViewController.swift index 11d11fbdc..2c30b3262 100644 --- a/RileyLink/View Controllers/MainViewController.swift +++ b/RileyLink/View Controllers/MainViewController.swift @@ -166,8 +166,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] @@ -175,10 +173,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 @@ -189,6 +188,7 @@ class MainViewController: RileyLinkSettingsViewController { rileyLinkManagerViewController.rileyLinkPumpManager = RileyLinkPumpManager(rileyLinkDeviceProvider: deviceDataManager.rileyLinkConnectionManager.deviceProvider) } setupViewController.setupDelegate = self + setupViewController.completionDelegate = self present(setupViewController, animated: true, completion: nil) } } @@ -206,15 +206,18 @@ class MainViewController: RileyLinkSettingsViewController { } } +extension MainViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController, presentedViewController === 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/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/CBCentralManager.swift b/RileyLinkBLEKit/CBCentralManager.swift index 465e1da9b..9504df977 100644 --- a/RileyLinkBLEKit/CBCentralManager.swift +++ b/RileyLinkBLEKit/CBCentralManager.swift @@ -19,6 +19,8 @@ extension CBCentralManager { case .connected: delegate?.centralManager?(self, didConnect: peripheral) case .connecting, .disconnected, .disconnecting: + fallthrough + @unknown default: connect(peripheral, options: options) } } @@ -32,6 +34,8 @@ extension CBCentralManager { case .disconnected: delegate?.centralManager?(self, didDisconnectPeripheral: peripheral, error: nil) case .connected, .connecting, .disconnecting: + fallthrough + @unknown default: cancelPeripheralConnection(peripheral) } } @@ -53,6 +57,8 @@ extension CBManagerState { return "Unknown" case .unsupported: return "Unsupported" + @unknown default: + return "Unknown: \(rawValue)" } } } diff --git a/RileyLinkBLEKit/Command.swift b/RileyLinkBLEKit/Command.swift index c011d10bd..d38555d3b 100644 --- a/RileyLinkBLEKit/Command.swift +++ b/RileyLinkBLEKit/Command.swift @@ -49,7 +49,7 @@ struct GetPacket: Command { } var data: Data { - var data = Data(bytes: [ + var data = Data([ RileyLinkCommand.getPacket.rawValue, listenChannel ]) @@ -63,7 +63,7 @@ struct GetVersion: Command { typealias ResponseType = GetVersionResponse var data: Data { - return Data(bytes: [RileyLinkCommand.getVersion.rawValue]) + return Data([RileyLinkCommand.getVersion.rawValue]) } } @@ -97,7 +97,7 @@ struct SendAndListen: Command { } var data: Data { - var data = Data(bytes: [ + var data = Data([ RileyLinkCommand.sendAndListen.rawValue, sendChannel, repeatCount @@ -145,7 +145,7 @@ struct SendPacket: Command { } var data: Data { - var data = Data(bytes: [ + var data = Data([ RileyLinkCommand.sendPacket.rawValue, sendChannel, repeatCount, @@ -188,7 +188,7 @@ struct UpdateRegister: Command { } var data: Data { - var data = Data(bytes: [ + var data = Data([ RileyLinkCommand.updateRegister.rawValue, register.address.rawValue, register.value @@ -217,7 +217,7 @@ struct ReadRegister: Command { } var data: Data { - var data = Data(bytes: [ + var data = Data([ RileyLinkCommand.readRegister.rawValue, address.rawValue, ]) @@ -246,7 +246,7 @@ struct SetModeRegisters: Command { } var data: Data { - var data = Data(bytes: [ + var data = Data([ RileyLinkCommand.setModeRegisters.rawValue, registerMode.rawValue ]) @@ -271,7 +271,7 @@ struct SetSoftwareEncoding: Command { } var data: Data { - return Data(bytes: [ + return Data([ RileyLinkCommand.setSWEncoding.rawValue, encodingType.rawValue ]) @@ -289,7 +289,7 @@ struct SetPreamble: Command { } var data: Data { - var data = Data(bytes: [RileyLinkCommand.setPreamble.rawValue]) + var data = Data([RileyLinkCommand.setPreamble.rawValue]) data.appendBigEndian(preambleValue) return data @@ -309,7 +309,7 @@ struct SetLEDMode: Command { } var data: Data { - return Data(bytes: [RileyLinkCommand.led.rawValue, led.rawValue, mode.rawValue]) + return Data([RileyLinkCommand.led.rawValue, led.rawValue, mode.rawValue]) } } @@ -318,7 +318,7 @@ struct ResetRadioConfig: Command { typealias ResponseType = CodeResponse var data: Data { - return Data(bytes: [RileyLinkCommand.resetRadioConfig.rawValue]) + return Data([RileyLinkCommand.resetRadioConfig.rawValue]) } } @@ -326,6 +326,6 @@ struct GetStatistics: Command { typealias ResponseType = GetStatisticsResponse var data: Data { - return Data(bytes: [RileyLinkCommand.getStatistics.rawValue]) + return Data([RileyLinkCommand.getStatistics.rawValue]) } } diff --git a/RileyLinkBLEKit/CommandSession.swift b/RileyLinkBLEKit/CommandSession.swift index 29cfca190..70d48af05 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 } @@ -298,4 +298,8 @@ public struct CommandSession { _ = try writeCommand(command, timeout: 0) } + /// Asserts that the caller is currently on the session's queue + public func assertOnSessionQueue() { + dispatchPrecondition(condition: .onQueue(manager.queue)) + } } diff --git a/RileyLinkBLEKit/Info.plist b/RileyLinkBLEKit/Info.plist index 011e22e48..7a3ea75eb 100644 --- a/RileyLinkBLEKit/Info.plist +++ b/RileyLinkBLEKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/RileyLinkBLEKit/PeripheralManager.swift b/RileyLinkBLEKit/PeripheralManager.swift index c845733f0..3e938f6f5 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() @@ -315,7 +316,7 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { commandLock.lock() - if let index = commandConditions.index(where: { (condition) -> Bool in + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .discoverServices = condition { return true } else { @@ -336,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 { @@ -357,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 { @@ -378,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 { @@ -399,7 +400,9 @@ extension PeripheralManager: CBPeripheralDelegate { func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { commandLock.lock() - if let index = commandConditions.index(where: { (condition) -> Bool in + var notifyDelegate = false + + if let index = commandConditions.firstIndex(where: { (condition) -> Bool in if case .valueUpdate(characteristic: characteristic, matching: let matching) = condition { return matching?(characteristic.value) ?? true } else { @@ -415,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/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..d0faf5875 100644 --- a/RileyLinkBLEKit/RFPacket.swift +++ b/RileyLinkBLEKit/RFPacket.swift @@ -8,13 +8,13 @@ import Foundation -public struct RFPacket { +public struct RFPacket : CustomStringConvertible { public let data: Data let packetCounter: Int public let rssi: Int init?(rfspyResponse: Data) { - guard rfspyResponse.count > 2 else { + guard rfspyResponse.count >= 2 else { return nil } @@ -32,5 +32,10 @@ public struct RFPacket { self.data = rfspyResponse.subdata(in: startIndex.advanced(by: 2).. Void) { + manager.queue.asyncAfter(deadline: deadline, execute: execute) + } } @@ -116,8 +118,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) } } @@ -135,17 +137,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 +180,58 @@ extension RileyLinkDevice { } func setIdleListeningState(_ state: IdleListeningState) { - queue.async { - self.idleListeningState = state + os_unfair_lock_lock(&lock) + 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) { - 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 +241,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) } } } @@ -303,7 +323,7 @@ extension RileyLinkDevice: PeripheralManagerDelegate { self.log.debug("Idle error received: %@", String(describing: response.code)) case .success: if let packet = response.packet { - self.log.debug("Idle packet received: %@", String(describing: value)) + self.log.debug("Idle packet received: %@", value.hexadecimalString) NotificationCenter.default.post( name: .DevicePacketReceived, object: self, @@ -362,17 +382,23 @@ 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 ?? "")", - "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/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/RileyLinkDeviceManager.swift b/RileyLinkBLEKit/RileyLinkDeviceManager.swift index f76423464..b1cdd08df 100644 --- a/RileyLinkBLEKit/RileyLinkDeviceManager.swift +++ b/RileyLinkBLEKit/RileyLinkDeviceManager.swift @@ -7,6 +7,7 @@ import CoreBluetooth import os.log +import LoopKit public class RileyLinkDeviceManager: NSObject { @@ -15,7 +16,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 +162,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) @@ -198,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/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/Info.plist b/RileyLinkBLEKitTests/Info.plist index 477c24867..713b7c93b 100644 --- a/RileyLinkBLEKitTests/Info.plist +++ b/RileyLinkBLEKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion 1 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/RileyLinkKit/Info.plist b/RileyLinkKit/Info.plist index 449c7b6af..21baa19b4 100644 --- a/RileyLinkKit/Info.plist +++ b/RileyLinkKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleSignature ???? CFBundleVersion diff --git a/RileyLinkKit/PumpOpsSession.swift b/RileyLinkKit/PumpOpsSession.swift index 8c8d70887..dd699efde 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 @@ -298,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 @@ -333,7 +353,7 @@ extension PumpOpsSession { let reservoir: ReadRemainingInsulinMessageBody = try session.getResponse(to: PumpMessage(settings: settings, type: .readRemainingInsulin), responseType: .readRemainingInsulin) return ( - units: reservoir.getUnitsRemainingForStrokes(pumpModel.strokesPerUnit), + units: reservoir.getUnitsRemaining(insulinBitPackingScale: pumpModel.insulinBitPackingScale), clock: pumpClock ) } @@ -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 { @@ -431,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)) @@ -449,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. @@ -465,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 { @@ -561,23 +592,9 @@ extension PumpOpsSession { } do { - let message = PumpMessage(settings: settings, type: .bolus, body: BolusCarelinkMessageBody(units: units, strokesPerUnit: pumpModel.strokesPerUnit)) - - 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) + let message = PumpMessage(settings: settings, type: .bolus, body: BolusCarelinkMessageBody(units: units, insulinBitPackingScale: pumpModel.insulinBitPackingScale)) - 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 { @@ -771,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] } @@ -807,11 +824,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() @@ -861,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) @@ -881,7 +898,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]() @@ -896,7 +913,6 @@ extension PumpOpsSession { } for freq in frequencies { - let tries = 3 var trial = FrequencyTrial(frequency: freq) try session.setBaseFrequency(freq) diff --git a/RileyLinkKit/RileyLinkPumpManager.swift b/RileyLinkKit/RileyLinkPumpManager.swift index 9cd8f3465..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 { @@ -14,6 +15,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) @@ -22,18 +24,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) - /// 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 /// @@ -50,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") @@ -74,6 +73,8 @@ extension RileyLinkPumpManager { return } + device.assertOnSessionQueue() + self.device(device, didReceivePacket: packet) } @@ -82,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) { diff --git a/RileyLinkKitTests/Info.plist b/RileyLinkKitTests/Info.plist index de2b7324d..7506bdc29 100644 --- a/RileyLinkKitTests/Info.plist +++ b/RileyLinkKitTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0 CFBundleSignature ???? CFBundleVersion 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/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/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" - } -} diff --git a/RileyLinkKitUI/Info.plist b/RileyLinkKitUI/Info.plist index 011e22e48..7a3ea75eb 100644 --- a/RileyLinkKitUI/Info.plist +++ b/RileyLinkKitUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 3.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass 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/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/RileyLinkDevicesTableViewDataSource.swift b/RileyLinkKitUI/RileyLinkDevicesTableViewDataSource.swift index 3a0dcd5a6..0efb5f7b5 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 @@ -118,15 +120,15 @@ 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 } } } @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/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift b/RileyLinkKitUI/RileyLinkManagerSetupViewController.swift index 2f0e1859e..e6102afd9 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,11 +21,18 @@ open class RileyLinkManagerSetupViewController: UINavigationController, PumpMana open weak var setupDelegate: PumpManagerSetupViewControllerDelegate? + open weak var completionDelegate: CompletionDelegate? + open var rileyLinkPumpManager: RileyLinkPumpManager? open override func viewDidLoad() { super.viewDidLoad() - + + if #available(iOSApplicationExtension 13.0, *) { + // Prevent interactive dismissal + isModalInPresentation = true + } + delegate = self } @@ -37,4 +44,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..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! } @@ -140,11 +146,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() { 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 + } + } +} 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/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) - } -} diff --git a/RileyLinkTests/RileyLinkTests-Info.plist b/RileyLinkTests/RileyLinkTests-Info.plist index 4809fa317..b6c35c5c8 100644 --- a/RileyLinkTests/RileyLinkTests-Info.plist +++ b/RileyLinkTests/RileyLinkTests-Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.1.1 + 2.2.0 CFBundleSignature ???? CFBundleVersion 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 */ - diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh new file mode 100755 index 000000000..a7308ed86 --- /dev/null +++ b/Scripts/copy-frameworks.sh @@ -0,0 +1,38 @@ +#!/bin/sh -e + +# copy-frameworks.sh +# Loop +# +# 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" +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_VAR="SCRIPT_INPUT_FILE_${COUNTER}" + CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_VAR/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" + if [ -e "${CARTHAGE_BUILD_FILE}" ]; then + echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_VAR}\"" + export ${SCRIPT_INPUT_VAR}="${CARTHAGE_BUILD_FILE}" + elif [ -e "${!SCRIPT_INPUT_VAR}" ]; then + echo "Using original path: \"${!SCRIPT_INPUT_VAR}\"" + else + echo "ERROR: Input file not found at \"${!SCRIPT_INPUT_VAR}\"" + exit 1 + fi + # Resolve any symlinks + export ${SCRIPT_INPUT_VAR}="$(readlink "${!SCRIPT_INPUT_VAR}" || echo "${!SCRIPT_INPUT_VAR}")" + echo "copy-frameworks resolved path: ${!SCRIPT_INPUT_VAR}" +done + +echo "Copy Frameworks with Carthage" +carthage copy-frameworks