// // SetInsulinScheduleCommand.swift // OmniKit // // Created by Pete Schwamb on 2/24/18. // Copyright © 2018 Pete Schwamb. All rights reserved. // import Foundation public struct SetInsulinScheduleCommand : NonceResyncableMessageBlock { fileprivate enum ScheduleTypeCode: UInt8 { case basalSchedule = 0 case tempBasal = 1 case bolus = 2 } public enum DeliverySchedule { case basalSchedule(currentSegment: UInt8, secondsRemaining: UInt16, pulsesRemaining: UInt16, table: BasalDeliveryTable) case tempBasal(secondsRemaining: UInt16, firstSegmentPulses: UInt16, table: BasalDeliveryTable) case bolus(units: Double, timeBetweenPulses: TimeInterval) fileprivate func typeCode() -> ScheduleTypeCode { switch self { case .basalSchedule: return .basalSchedule case .tempBasal: return .tempBasal case .bolus: return .bolus } } fileprivate var data: Data { switch self { case .basalSchedule(let currentSegment, let secondsRemaining, let pulsesRemaining, let table): var data = Data([currentSegment]) data.appendBigEndian(secondsRemaining << 3) data.appendBigEndian(pulsesRemaining) for entry in table.entries { data.append(entry.data) } return data case .bolus(let units, let timeBetweenPulses): let pulseCount = UInt16(round(units / Pod.pulseSize)) let multiplier = UInt16(round(timeBetweenPulses * 8)) let fieldA = pulseCount * multiplier let numHalfHourSegments: UInt8 = 1 var data = Data([numHalfHourSegments]) data.appendBigEndian(fieldA) data.appendBigEndian(pulseCount) data.appendBigEndian(pulseCount) return data case .tempBasal(let secondsRemaining, let firstSegmentPulses, let table): var data = Data([UInt8(table.numSegments())]) data.appendBigEndian(secondsRemaining << 3) data.appendBigEndian(firstSegmentPulses) for entry in table.entries { data.append(entry.data) } return data } } fileprivate func checksum() -> UInt16 { switch self { case .basalSchedule( _, _, _, let table): return data[0..<5].reduce(0) { $0 + UInt16($1) } + table.entries.reduce(0) { $0 + $1.checksum() } case .bolus: return data[0..<7].reduce(0) { $0 + UInt16($1) } case .tempBasal(_, _, let table): return data[0..<5].reduce(0) { $0 + UInt16($1) } + table.entries.reduce(0) { $0 + $1.checksum() } } } } public let blockType: MessageBlockType = .setInsulinSchedule public var nonce: UInt32 public let deliverySchedule: DeliverySchedule public var data: Data { var data = Data([ blockType.rawValue, UInt8(7 + deliverySchedule.data.count), ]) data.appendBigEndian(nonce) data.append(deliverySchedule.typeCode().rawValue) data.appendBigEndian(deliverySchedule.checksum()) data.append(deliverySchedule.data) return data } public init(encodedData: Data) throws { if encodedData.count < 6 { throw MessageBlockError.notEnoughData } let length = encodedData[1] nonce = encodedData[2...].toBigEndian(UInt32.self) let checksum = encodedData[7...].toBigEndian(UInt16.self) guard let scheduleTypeCode = ScheduleTypeCode(rawValue: encodedData[6]) else { throw MessageError.unknownValue(value: encodedData[6], typeDescription: "ScheduleTypeCode") } switch scheduleTypeCode { case .basalSchedule: var entries = [BasalTableEntry]() let numEntries = (length - 12) / 2 for i in 0..> 3 let pulsesRemaining = encodedData[12...].toBigEndian(UInt16.self) let table = BasalDeliveryTable(entries: entries) deliverySchedule = .basalSchedule(currentSegment: currentTableIndex, secondsRemaining: secondsRemaining, pulsesRemaining: pulsesRemaining, table: table) case .tempBasal: let secondsRemaining = encodedData[10...].toBigEndian(UInt16.self) >> 3 let firstSegmentPulses = encodedData[12...].toBigEndian(UInt16.self) var entries = [BasalTableEntry]() let numEntries = (length - 12) / 2 for i in 0.. UInt16 { return data.reduce(0) { $0 + UInt16($1) } } extension SetInsulinScheduleCommand: CustomDebugStringConvertible { public var debugDescription: String { return "SetInsulinScheduleCommand(nonce:\(nonce), \(deliverySchedule))" } }