PumpOpsSession.swift 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. //
  2. // PumpOpsSynchronous.swift
  3. // RileyLink
  4. //
  5. // Created by Pete Schwamb on 3/12/16.
  6. // Copyright © 2016 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. import LoopKit
  10. import RileyLinkBLEKit
  11. public protocol PumpOpsSessionDelegate: AnyObject {
  12. func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState)
  13. func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession)
  14. }
  15. public class PumpOpsSession {
  16. private(set) public var pump: PumpState {
  17. didSet {
  18. delegate.pumpOpsSession(self, didChange: pump)
  19. }
  20. }
  21. public let settings: PumpSettings
  22. private let messageSender: PumpMessageSender
  23. private unowned let delegate: PumpOpsSessionDelegate
  24. public init(settings: PumpSettings, pumpState: PumpState, messageSender: PumpMessageSender, delegate: PumpOpsSessionDelegate) {
  25. self.settings = settings
  26. self.pump = pumpState
  27. self.messageSender = messageSender
  28. self.delegate = delegate
  29. }
  30. }
  31. // MARK: - Wakeup and power
  32. extension PumpOpsSession {
  33. private static let minimumTimeBetweenWakeAttempts = TimeInterval(minutes: 1)
  34. /// Attempts to send initial short wakeup message that kicks off the wakeup process.
  35. ///
  36. /// If successful, still does not fully wake up the pump - only alerts it such that the longer wakeup message can be sent next.
  37. ///
  38. /// - Throws:
  39. /// - PumpCommandError.command containing:
  40. /// - PumpOpsError.couldNotDecode
  41. /// - PumpOpsError.crosstalk
  42. /// - PumpOpsError.deviceError
  43. /// - PumpOpsError.noResponse
  44. /// - PumpOpsError.unexpectedResponse
  45. /// - PumpOpsError.unknownResponse
  46. private func sendWakeUpBurst() throws {
  47. // Skip waking up if we recently tried
  48. guard pump.lastWakeAttempt == nil || pump.lastWakeAttempt!.timeIntervalSinceNow <= -PumpOpsSession.minimumTimeBetweenWakeAttempts
  49. else {
  50. return
  51. }
  52. pump.lastWakeAttempt = Date()
  53. let shortPowerMessage = PumpMessage(settings: settings, type: .powerOn)
  54. if pump.pumpModel == nil || !pump.pumpModel!.hasMySentry {
  55. // Older pumps have a longer sleep cycle between wakeups, so send an initial burst
  56. do {
  57. let _: PumpAckMessageBody = try messageSender.getResponse(to: shortPowerMessage, responseType: .pumpAck, repeatCount: 255, timeout: .milliseconds(1), retryCount: 0)
  58. }
  59. catch { }
  60. }
  61. do {
  62. let _: PumpAckMessageBody = try messageSender.getResponse(to: shortPowerMessage, responseType: .pumpAck, repeatCount: 255, timeout: .seconds(12), retryCount: 0)
  63. } catch let error as PumpOpsError {
  64. throw PumpCommandError.command(error)
  65. }
  66. }
  67. private func isPumpResponding() -> Bool {
  68. do {
  69. let _: GetPumpModelCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .getPumpModel), responseType: .getPumpModel, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 1)
  70. return true
  71. } catch {
  72. return false
  73. }
  74. }
  75. /// - Throws:
  76. /// - PumpCommandError.command
  77. /// - PumpCommandError.arguments
  78. /// - PumpOpsError.couldNotDecode
  79. /// - PumpOpsError.crosstalk
  80. /// - PumpOpsError.deviceError
  81. /// - PumpOpsError.noResponse
  82. /// - PumpOpsError.unexpectedResponse
  83. /// - PumpOpsError.unknownResponse
  84. private func wakeup(_ duration: TimeInterval = TimeInterval(minutes: 1)) throws {
  85. guard !pump.isAwake else {
  86. return
  87. }
  88. // Send a short message to the pump to see if its radio is still powered on
  89. if isPumpResponding() {
  90. // TODO: Convert logging
  91. NSLog("Pump responding despite our wake timer having expired. Extending timer")
  92. // By my observations, the pump stays awake > 1 minute past last comms. Usually
  93. // About 1.5 minutes, but we'll make it a minute to be safe.
  94. pump.awakeUntil = Date(timeIntervalSinceNow: TimeInterval(minutes: 1))
  95. return
  96. }
  97. // Command
  98. try sendWakeUpBurst()
  99. // Arguments
  100. do {
  101. let longPowerMessage = PumpMessage(settings: settings, type: .powerOn, body: PowerOnCarelinkMessageBody(duration: duration))
  102. let _: PumpAckMessageBody = try messageSender.getResponse(to: longPowerMessage, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  103. } catch let error as PumpOpsError {
  104. throw PumpCommandError.arguments(error)
  105. } catch {
  106. assertionFailure()
  107. }
  108. // TODO: Convert logging
  109. NSLog("Power on for %.0f minutes", duration.minutes)
  110. pump.awakeUntil = Date(timeIntervalSinceNow: duration)
  111. }
  112. }
  113. // MARK: - Single reads
  114. extension PumpOpsSession {
  115. /// Retrieves the pump model from either the state or from the cache
  116. ///
  117. /// - Parameter usingCache: Whether the pump state should be checked first for a known pump model
  118. /// - Returns: The pump model
  119. /// - Throws:
  120. /// - PumpCommandError.command
  121. /// - PumpCommandError.arguments
  122. /// - PumpOpsError.couldNotDecode
  123. /// - PumpOpsError.crosstalk
  124. /// - PumpOpsError.deviceError
  125. /// - PumpOpsError.noResponse
  126. /// - PumpOpsError.unexpectedResponse
  127. /// - PumpOpsError.unknownResponse
  128. public func getPumpModel(usingCache: Bool = true) throws -> PumpModel {
  129. if usingCache, let pumpModel = pump.pumpModel {
  130. return pumpModel
  131. }
  132. try wakeup()
  133. let body: GetPumpModelCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .getPumpModel), responseType: .getPumpModel, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  134. guard let pumpModel = PumpModel(rawValue: body.model) else {
  135. throw PumpOpsError.unknownPumpModel(body.model)
  136. }
  137. pump.pumpModel = pumpModel
  138. return pumpModel
  139. }
  140. /// Retrieves the pump firmware version
  141. ///
  142. /// - Returns: The pump firmware version as string
  143. /// - Throws:
  144. /// - PumpCommandError.command
  145. /// - PumpCommandError.arguments
  146. /// - PumpOpsError.couldNotDecode
  147. /// - PumpOpsError.crosstalk
  148. /// - PumpOpsError.deviceError
  149. /// - PumpOpsError.noResponse
  150. /// - PumpOpsError.unexpectedResponse
  151. /// - PumpOpsError.unknownResponse
  152. public func getPumpFirmwareVersion() throws -> String {
  153. try wakeup()
  154. let body: GetPumpFirmwareVersionMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readFirmwareVersion), responseType: .readFirmwareVersion, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  155. return body.version
  156. }
  157. /// - Throws:
  158. /// - PumpCommandError.command
  159. /// - PumpCommandError.arguments
  160. /// - PumpOpsError.couldNotDecode
  161. /// - PumpOpsError.crosstalk
  162. /// - PumpOpsError.deviceError
  163. /// - PumpOpsError.noResponse
  164. /// - PumpOpsError.unexpectedResponse
  165. /// - PumpOpsError.unknownResponse
  166. public func getBatteryStatus() throws -> GetBatteryCarelinkMessageBody {
  167. try wakeup()
  168. return try messageSender.getResponse(to: PumpMessage(settings: settings, type: .getBattery), responseType: .getBattery, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  169. }
  170. /// - Throws:
  171. /// - PumpCommandError.command
  172. /// - PumpCommandError.arguments
  173. /// - PumpOpsError.couldNotDecode
  174. /// - PumpOpsError.crosstalk
  175. /// - PumpOpsError.deviceError
  176. /// - PumpOpsError.noResponse
  177. /// - PumpOpsError.unexpectedResponse
  178. /// - PumpOpsError.unknownResponse
  179. internal func getPumpStatus() throws -> ReadPumpStatusMessageBody {
  180. try wakeup()
  181. return try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readPumpStatus), responseType: .readPumpStatus, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  182. }
  183. /// - Throws:
  184. /// - PumpCommandError.command
  185. /// - PumpCommandError.arguments
  186. /// - PumpOpsError.couldNotDecode
  187. /// - PumpOpsError.crosstalk
  188. /// - PumpOpsError.deviceError
  189. /// - PumpOpsError.noResponse
  190. /// - PumpOpsError.unexpectedResponse
  191. /// - PumpOpsError.unknownResponse
  192. public func getSettings() throws -> ReadSettingsCarelinkMessageBody {
  193. try wakeup()
  194. return try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readSettings), responseType: .readSettings, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  195. }
  196. /// Reads the pump's time, returning a set of DateComponents in the pump's presumed time zone.
  197. ///
  198. /// - Returns: The pump's time components including timeZone
  199. /// - Throws:
  200. /// - PumpCommandError.command
  201. /// - PumpCommandError.arguments
  202. /// - PumpOpsError.couldNotDecode
  203. /// - PumpOpsError.crosstalk
  204. /// - PumpOpsError.deviceError
  205. /// - PumpOpsError.noResponse
  206. /// - PumpOpsError.unexpectedResponse
  207. /// - PumpOpsError.unknownResponse
  208. public func getTime() throws -> DateComponents {
  209. try wakeup()
  210. let response: ReadTimeCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readTime), responseType: .readTime, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  211. var components = response.dateComponents
  212. components.timeZone = pump.timeZone
  213. return components
  214. }
  215. /// Reads Basal Schedule from the pump
  216. ///
  217. /// - Returns: The pump's standard basal schedule
  218. /// - Throws:
  219. /// - PumpCommandError.command
  220. /// - PumpCommandError.arguments
  221. /// - PumpOpsError.couldNotDecode
  222. /// - PumpOpsError.crosstalk
  223. /// - PumpOpsError.deviceError
  224. /// - PumpOpsError.noResponse
  225. /// - PumpOpsError.unexpectedResponse
  226. /// - PumpOpsError.unknownResponse
  227. public func getBasalSchedule(for profile: BasalProfile = .standard) throws -> BasalSchedule? {
  228. try wakeup()
  229. var isFinished = false
  230. var message = PumpMessage(settings: settings, type: profile.readMessageType)
  231. var scheduleData = Data()
  232. while (!isFinished) {
  233. let body: DataFrameMessageBody = try messageSender.getResponse(to: message, responseType: profile.readMessageType, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  234. scheduleData.append(body.contents)
  235. isFinished = body.isLastFrame
  236. message = PumpMessage(settings: settings, type: .pumpAck)
  237. }
  238. return BasalSchedule(rawValue: scheduleData)
  239. }
  240. /// - Throws:
  241. /// - PumpOpsError.couldNotDecode
  242. /// - PumpOpsError.crosstalk
  243. /// - PumpOpsError.deviceError
  244. /// - PumpOpsError.noResponse
  245. /// - PumpOpsError.unexpectedResponse
  246. /// - PumpOpsError.unknownResponse
  247. public func getOtherDevicesIDs() throws -> ReadOtherDevicesIDsMessageBody {
  248. try wakeup()
  249. return try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readOtherDevicesIDs), responseType: .readOtherDevicesIDs, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  250. }
  251. /// - Throws:
  252. /// - PumpOpsError.couldNotDecode
  253. /// - PumpOpsError.crosstalk
  254. /// - PumpOpsError.deviceError
  255. /// - PumpOpsError.noResponse
  256. /// - PumpOpsError.unexpectedResponse
  257. /// - PumpOpsError.unknownResponse
  258. public func getOtherDevicesEnabled() throws -> Bool {
  259. try wakeup()
  260. let response: ReadOtherDevicesStatusMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readOtherDevicesStatus), responseType: .readOtherDevicesStatus, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  261. return response.isEnabled
  262. }
  263. /// - Throws:
  264. /// - PumpOpsError.couldNotDecode
  265. /// - PumpOpsError.crosstalk
  266. /// - PumpOpsError.deviceError
  267. /// - PumpOpsError.noResponse
  268. /// - PumpOpsError.unexpectedResponse
  269. /// - PumpOpsError.unknownResponse
  270. public func getRemoteControlIDs() throws -> ReadRemoteControlIDsMessageBody {
  271. try wakeup()
  272. return try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readRemoteControlIDs), responseType: .readRemoteControlIDs, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  273. }
  274. }
  275. // MARK: - Aggregate reads
  276. public struct PumpStatus: Equatable {
  277. // Date components read from the pump, along with PumpState.timeZone
  278. public let clock: DateComponents
  279. public let batteryVolts: Measurement<UnitElectricPotentialDifference>
  280. public let batteryStatus: BatteryStatus
  281. public let suspended: Bool
  282. public let bolusing: Bool
  283. public let reservoir: Double
  284. public let model: PumpModel
  285. public let pumpID: String
  286. }
  287. extension PumpOpsSession {
  288. /// Reads the current insulin reservoir volume and the pump's date
  289. ///
  290. /// - Returns:
  291. /// - The reservoir volume, in units of insulin
  292. /// - DateCompoments representing the pump's clock
  293. /// - Throws:
  294. /// - PumpCommandError.command
  295. /// - PumpCommandError.arguments
  296. /// - PumpOpsError.couldNotDecode
  297. /// - PumpOpsError.crosstalk
  298. /// - PumpOpsError.deviceError
  299. /// - PumpOpsError.noResponse
  300. /// - PumpOpsError.unknownResponse
  301. public func getRemainingInsulin() throws -> (units: Double, clock: DateComponents) {
  302. let pumpModel = try getPumpModel()
  303. let pumpClock = try getTime()
  304. let reservoir: ReadRemainingInsulinMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readRemainingInsulin), responseType: .readRemainingInsulin, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  305. return (
  306. units: reservoir.getUnitsRemaining(insulinBitPackingScale: pumpModel.insulinBitPackingScale),
  307. clock: pumpClock
  308. )
  309. }
  310. /// Reads clock, reservoir, battery, bolusing, and suspended state from pump
  311. ///
  312. /// - Returns: The pump status
  313. /// - Throws:
  314. /// - PumpCommandError
  315. /// - PumpOpsError
  316. public func getCurrentPumpStatus() throws -> PumpStatus {
  317. let pumpModel = try getPumpModel()
  318. let battResp = try getBatteryStatus()
  319. let status = try getPumpStatus()
  320. let (reservoir, clock) = try getRemainingInsulin()
  321. return PumpStatus(
  322. clock: clock,
  323. batteryVolts: Measurement(value: battResp.volts, unit: UnitElectricPotentialDifference.volts),
  324. batteryStatus: battResp.status,
  325. suspended: status.suspended,
  326. bolusing: status.bolusing,
  327. reservoir: reservoir,
  328. model: pumpModel,
  329. pumpID: settings.pumpID
  330. )
  331. }
  332. }
  333. // MARK: - Command messages
  334. extension PumpOpsSession {
  335. /// - Throws: `PumpCommandError` specifying the failure sequence
  336. private func runCommandWithArguments<T: MessageBody>(_ message: PumpMessage, responseType: MessageType = .pumpAck, retryCount: Int = 3) throws -> T {
  337. do {
  338. try wakeup()
  339. let shortMessage = PumpMessage(packetType: message.packetType, address: message.address.hexadecimalString, messageType: message.messageType, messageBody: CarelinkShortMessageBody())
  340. let _: PumpAckMessageBody = try messageSender.getResponse(to: shortMessage, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  341. } catch let error as PumpOpsError {
  342. throw PumpCommandError.command(error)
  343. }
  344. do {
  345. return try messageSender.getResponse(to: message, responseType: responseType, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: retryCount)
  346. } catch let error as PumpOpsError {
  347. throw PumpCommandError.arguments(error)
  348. }
  349. }
  350. /// - Throws: `PumpCommandError` specifying the failure sequence
  351. public func pressButton(_ type: ButtonPressCarelinkMessageBody.ButtonType) throws {
  352. let message = PumpMessage(settings: settings, type: .buttonPress, body: ButtonPressCarelinkMessageBody(buttonType: type))
  353. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  354. }
  355. /// - Throws: `PumpCommandError` specifying the failure sequence
  356. public func setSuspendResumeState(_ state: SuspendResumeMessageBody.SuspendResumeState) throws {
  357. let message = PumpMessage(settings: settings, type: .suspendResume, body: SuspendResumeMessageBody(state: state))
  358. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  359. }
  360. /// - Throws: PumpCommandError
  361. public func selectBasalProfile(_ profile: BasalProfile) throws {
  362. let message = PumpMessage(settings: settings, type: .selectBasalProfile, body: SelectBasalProfileMessageBody(newProfile: profile))
  363. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  364. }
  365. /// - Throws: PumpCommandError
  366. public func setMaxBasalRate(unitsPerHour: Double) throws {
  367. guard let body = ChangeMaxBasalRateMessageBody(maxBasalUnitsPerHour: unitsPerHour) else {
  368. throw PumpCommandError.command(PumpOpsError.pumpError(PumpErrorCode.maxSettingExceeded))
  369. }
  370. let message = PumpMessage(settings: settings, type: .setMaxBasalRate, body: body)
  371. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  372. }
  373. /// - Throws: PumpCommandError
  374. public func setMaxBolus(units: Double) throws {
  375. guard let body = ChangeMaxBolusMessageBody(pumpModel: try getPumpModel(), maxBolusUnits: units) else {
  376. throw PumpCommandError.command(PumpOpsError.pumpError(PumpErrorCode.maxSettingExceeded))
  377. }
  378. let message = PumpMessage(settings: settings, type: .setMaxBolus, body: body)
  379. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  380. }
  381. /// Changes the current temporary basal rate
  382. ///
  383. /// - Parameters:
  384. /// - unitsPerHour: The new basal rate, in Units per hour
  385. /// - duration: The duration of the rate
  386. /// - Returns:
  387. /// - .success: A bool that indicates if the dose was confirmed successful
  388. /// - .failure: An error describing why the command failed
  389. public func setTempBasal(_ unitsPerHour: Double, duration: TimeInterval) -> Result<Bool,PumpCommandError> {
  390. let message = PumpMessage(settings: settings, type: .changeTempBasal, body: ChangeTempBasalCarelinkMessageBody(unitsPerHour: unitsPerHour, duration: duration))
  391. do {
  392. try wakeup()
  393. } catch {
  394. // Certain failure, as we haven't sent actual command yet; wakeup failed
  395. return .failure(.command(error as? PumpOpsError ?? PumpOpsError.rfCommsFailure(String(describing: error))))
  396. }
  397. do {
  398. let shortMessage = PumpMessage(packetType: message.packetType, address: message.address.hexadecimalString, messageType: message.messageType, messageBody: CarelinkShortMessageBody())
  399. let _: PumpAckMessageBody = try messageSender.getResponse(to: shortMessage, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  400. } catch {
  401. // Certain failure, as we haven't sent actual command yet; just preflight short command
  402. return .failure(.command(error as? PumpOpsError ?? PumpOpsError.rfCommsFailure(String(describing: error))))
  403. }
  404. var uncertainFailureError: PumpCommandError?
  405. do {
  406. let _: PumpAckMessageBody = try messageSender.getResponse(to: message, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 1)
  407. // Even in success case, we try to verify, below
  408. } catch PumpOpsError.pumpError(let errorCode) {
  409. return .failure(.arguments(.pumpError(errorCode)))
  410. } catch PumpOpsError.unknownPumpErrorCode(let errorCode) {
  411. return .failure(.arguments(.unknownPumpErrorCode(errorCode)))
  412. } catch {
  413. // Some pumps do not ACK a successful temp basal. Check manually to see if it was successful.
  414. uncertainFailureError = .command(error as? PumpOpsError ?? PumpOpsError.rfCommsFailure(String(describing: error)))
  415. }
  416. do {
  417. let response: ReadTempBasalCarelinkMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readTempBasal), responseType: .readTempBasal, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  418. // Duration is always whole minute values
  419. if response.timeRemaining == duration && response.rateType == .absolute {
  420. return .success(true)
  421. } else {
  422. // readTempBasal does not match what we attempted to command
  423. if let failureError = uncertainFailureError {
  424. return .failure(failureError)
  425. }
  426. // successful readTempBasal shows no temp basal running, so we failed
  427. return .failure(PumpCommandError.arguments(PumpOpsError.rfCommsFailure("Confirmed that temp basal failed, and ")))
  428. }
  429. } catch {
  430. // unsuccessful readTempBasal; assume command reached pump, but we're uncertain
  431. return .success(false)
  432. }
  433. }
  434. /// Changes the pump's clock to the specified date components in the system time zone
  435. ///
  436. /// - Parameter generator: A closure which returns the desired date components. An exeception is raised if the date components are not valid.
  437. /// - Throws: PumpCommandError
  438. public func setTime(_ generator: () -> DateComponents) throws {
  439. try wakeup()
  440. do {
  441. let shortMessage = PumpMessage(settings: settings, type: .changeTime)
  442. let _: PumpAckMessageBody = try messageSender.getResponse(to: shortMessage, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  443. } catch let error as PumpOpsError {
  444. throw PumpCommandError.command(error)
  445. }
  446. do {
  447. let components = generator()
  448. let message = PumpMessage(settings: settings, type: .changeTime, body: ChangeTimeCarelinkMessageBody(dateComponents: components)!)
  449. let _: PumpAckMessageBody = try messageSender.getResponse(to: message, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  450. self.pump.timeZone = components.timeZone?.fixed ?? .currentFixed
  451. } catch let error as PumpOpsError {
  452. throw PumpCommandError.arguments(error)
  453. }
  454. }
  455. public func setTimeToNow(in timeZone: TimeZone? = nil) throws {
  456. let timeZone = timeZone ?? pump.timeZone
  457. try setTime { () -> DateComponents in
  458. var calendar = Calendar(identifier: .gregorian)
  459. calendar.timeZone = timeZone
  460. var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
  461. components.timeZone = timeZone
  462. return components
  463. }
  464. }
  465. /// Sets a bolus
  466. ///
  467. /// *Note: Use at your own risk!*
  468. ///
  469. /// - Parameters:
  470. /// - units: The number of units to deliver
  471. /// - cancelExistingTemp: If true, additional pump commands will be issued to clear any running temp basal. Defaults to false.
  472. /// - Throws: SetBolusError describing the certainty of the underlying error
  473. public func setNormalBolus(units: Double) throws {
  474. let pumpModel: PumpModel
  475. try wakeup()
  476. pumpModel = try getPumpModel()
  477. let status = try getPumpStatus()
  478. if status.bolusing {
  479. throw PumpOpsError.bolusInProgress
  480. }
  481. if status.suspended {
  482. throw PumpOpsError.pumpSuspended
  483. }
  484. let message = PumpMessage(settings: settings, type: .bolus, body: BolusCarelinkMessageBody(units: units, insulinBitPackingScale: pumpModel.insulinBitPackingScale))
  485. let _: PumpAckMessageBody = try runCommandWithArguments(message, retryCount: 0)
  486. return
  487. }
  488. /// - Throws: PumpCommandError
  489. public func setRemoteControlEnabled(_ enabled: Bool) throws {
  490. let message = PumpMessage(settings: settings, type: .setRemoteControlEnabled, body: SetRemoteControlEnabledMessageBody(enabled: enabled))
  491. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  492. }
  493. /// - Throws: PumpCommandError
  494. public func setRemoteControlID(_ id: Data, atIndex index: Int) throws {
  495. guard let body = ChangeRemoteControlIDMessageBody(id: id, index: index) else {
  496. throw PumpCommandError.command(PumpOpsError.pumpError(PumpErrorCode.maxSettingExceeded))
  497. }
  498. let message = PumpMessage(settings: settings, type: .setRemoteControlID, body: body)
  499. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  500. }
  501. /// - Throws: PumpCommandError
  502. public func removeRemoteControlID(atIndex index: Int) throws {
  503. guard let body = ChangeRemoteControlIDMessageBody(id: nil, index: index) else {
  504. throw PumpCommandError.command(PumpOpsError.pumpError(PumpErrorCode.maxSettingExceeded))
  505. }
  506. let message = PumpMessage(settings: settings, type: .setRemoteControlID, body: body)
  507. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  508. }
  509. /// - Throws: `PumpCommandError` specifying the failure sequence
  510. public func setBasalSchedule(_ basalSchedule: BasalSchedule, for profile: BasalProfile) throws {
  511. let frames = DataFrameMessageBody.dataFramesFromContents(basalSchedule.rawValue)
  512. guard let firstFrame = frames.first else {
  513. return
  514. }
  515. let type: MessageType
  516. switch profile {
  517. case .standard:
  518. type = .setBasalProfileStandard
  519. case .profileA:
  520. type = .setBasalProfileA
  521. case .profileB:
  522. type = .setBasalProfileB
  523. }
  524. let message = PumpMessage(settings: settings, type: type, body: firstFrame)
  525. let _: PumpAckMessageBody = try runCommandWithArguments(message)
  526. for nextFrame in frames.dropFirst() {
  527. let message = PumpMessage(settings: settings, type: type, body: nextFrame)
  528. do {
  529. let _: PumpAckMessageBody = try messageSender.getResponse(to: message, responseType: .pumpAck, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  530. } catch let error as PumpOpsError {
  531. throw PumpCommandError.arguments(error)
  532. }
  533. }
  534. }
  535. public func getStatistics() throws -> RileyLinkStatistics {
  536. return try messageSender.getRileyLinkStatistics()
  537. }
  538. }
  539. // MARK: - MySentry (Watchdog) pairing
  540. extension PumpOpsSession {
  541. /// Pairs the pump with a virtual "watchdog" device to enable it to broadcast periodic status packets. Only pump models x23 and up are supported.
  542. ///
  543. /// - Parameter watchdogID: A 3-byte address for the watchdog device.
  544. /// - Throws:
  545. /// - PumpOpsError.couldNotDecode
  546. /// - PumpOpsError.crosstalk
  547. /// - PumpOpsError.deviceError
  548. /// - PumpOpsError.noResponse
  549. /// - PumpOpsError.unexpectedResponse
  550. /// - PumpOpsError.unknownResponse
  551. public func changeWatchdogMarriageProfile(_ watchdogID: Data) throws {
  552. let commandTimeout = TimeInterval(seconds: 30)
  553. // Wait for the pump to start polling
  554. guard let encodedData = try messageSender.listenForPacket(onChannel: 0, timeout: commandTimeout)?.data else {
  555. throw PumpOpsError.noResponse(during: "Watchdog listening")
  556. }
  557. guard let packet = MinimedPacket(encodedData: encodedData) else {
  558. throw PumpOpsError.couldNotDecode(rx: encodedData, during: "Watchdog listening")
  559. }
  560. guard let findMessage = PumpMessage(rxData: packet.data) else {
  561. // Unknown packet type or message type
  562. throw PumpOpsError.unknownResponse(rx: packet.data, during: "Watchdog listening")
  563. }
  564. guard findMessage.address.hexadecimalString == settings.pumpID && findMessage.packetType == .mySentry,
  565. let findMessageBody = findMessage.messageBody as? FindDeviceMessageBody, let findMessageResponseBody = MySentryAckMessageBody(sequence: findMessageBody.sequence, watchdogID: watchdogID, responseMessageTypes: [findMessage.messageType])
  566. else {
  567. throw PumpOpsError.unknownResponse(rx: packet.data, during: "Watchdog listening")
  568. }
  569. // Identify as a MySentry device
  570. let findMessageResponse = PumpMessage(packetType: .mySentry, address: settings.pumpID, messageType: .pumpAck, messageBody: findMessageResponseBody)
  571. let linkMessage = try messageSender.sendAndListen(findMessageResponse, repeatCount: 0, timeout: commandTimeout, retryCount: 3)
  572. guard let
  573. linkMessageBody = linkMessage.messageBody as? DeviceLinkMessageBody,
  574. let linkMessageResponseBody = MySentryAckMessageBody(sequence: linkMessageBody.sequence, watchdogID: watchdogID, responseMessageTypes: [linkMessage.messageType])
  575. else {
  576. throw PumpOpsError.unexpectedResponse(linkMessage, from: findMessageResponse)
  577. }
  578. // Acknowledge the pump linked with us
  579. let linkMessageResponse = PumpMessage(packetType: .mySentry, address: settings.pumpID, messageType: .pumpAck, messageBody: linkMessageResponseBody)
  580. try messageSender.send(linkMessageResponse)
  581. }
  582. }
  583. // MARK: - Tuning
  584. private extension PumpRegion {
  585. var scanFrequencies: [Measurement<UnitFrequency>] {
  586. let scanFrequencies: [Double]
  587. switch self {
  588. case .worldWide:
  589. scanFrequencies = [868.25, 868.30, 868.35, 868.40, 868.45, 868.50, 868.55, 868.60, 868.65]
  590. case .northAmerica, .canada:
  591. scanFrequencies = [916.45, 916.50, 916.55, 916.60, 916.65, 916.70, 916.75, 916.80]
  592. }
  593. return scanFrequencies.map {
  594. return Measurement<UnitFrequency>(value: $0, unit: .megahertz)
  595. }
  596. }
  597. }
  598. enum RXFilterMode: UInt8 {
  599. case wide = 0x50 // 300KHz
  600. case narrow = 0x90 // 150KHz
  601. }
  602. public struct FrequencyTrial {
  603. public var tries: Int = 0
  604. public var successes: Int = 0
  605. public var avgRSSI: Double = -99
  606. public var frequency: Measurement<UnitFrequency>
  607. init(frequency: Measurement<UnitFrequency>) {
  608. self.frequency = frequency
  609. }
  610. }
  611. public struct FrequencyScanResults {
  612. public var trials: [FrequencyTrial]
  613. public var bestFrequency: Measurement<UnitFrequency>
  614. }
  615. extension PumpOpsSession {
  616. /// - Throws:
  617. /// - PumpOpsError.deviceError
  618. /// - PumpOpsError.noResponse
  619. /// - PumpOpsError.rfCommsFailure
  620. public func tuneRadio(attempts: Int = 3) throws -> FrequencyScanResults {
  621. let region = self.settings.pumpRegion
  622. do {
  623. let results = try scanForPump(in: region.scanFrequencies, fallback: pump.lastValidFrequency, tries: attempts)
  624. pump.lastValidFrequency = results.bestFrequency
  625. pump.lastTuned = Date()
  626. delegate.pumpOpsSessionDidChangeRadioConfig(self)
  627. return results
  628. } catch let error as PumpOpsError {
  629. throw error
  630. } catch let error as LocalizedError {
  631. throw PumpOpsError.deviceError(error)
  632. }
  633. }
  634. /// - Throws: PumpOpsError.deviceError
  635. private func setRXFilterMode(_ mode: RXFilterMode) throws {
  636. let drate_e = UInt8(0x9) // exponent of symbol rate (16kbps)
  637. let chanbw = mode.rawValue
  638. do {
  639. try messageSender.updateRegister(.mdmcfg4, value: chanbw | drate_e)
  640. } catch let error as LocalizedError {
  641. throw PumpOpsError.deviceError(error)
  642. }
  643. }
  644. /// - Throws:
  645. /// - PumpOpsError.deviceError
  646. /// - RileyLinkDeviceError
  647. func configureRadio(for region: PumpRegion, frequency: Measurement<UnitFrequency>?) throws {
  648. try messageSender.resetRadioConfig()
  649. switch region {
  650. case .worldWide:
  651. //try session.updateRegister(.mdmcfg4, value: 0x59)
  652. try setRXFilterMode(.wide)
  653. //try session.updateRegister(.mdmcfg3, value: 0x66)
  654. //try session.updateRegister(.mdmcfg2, value: 0x33)
  655. try messageSender.updateRegister(.mdmcfg1, value: 0x62)
  656. try messageSender.updateRegister(.mdmcfg0, value: 0x1A)
  657. try messageSender.updateRegister(.deviatn, value: 0x13)
  658. case .northAmerica, .canada:
  659. //try session.updateRegister(.mdmcfg4, value: 0x99)
  660. try setRXFilterMode(.narrow)
  661. //try session.updateRegister(.mdmcfg3, value: 0x66)
  662. //try session.updateRegister(.mdmcfg2, value: 0x33)
  663. try messageSender.updateRegister(.mdmcfg1, value: 0x61)
  664. try messageSender.updateRegister(.mdmcfg0, value: 0x7E)
  665. try messageSender.updateRegister(.deviatn, value: 0x15)
  666. }
  667. if let frequency = frequency {
  668. try messageSender.setBaseFrequency(frequency)
  669. }
  670. }
  671. /// - Throws:
  672. /// - PumpOpsError.deviceError
  673. /// - PumpOpsError.noResponse
  674. /// - PumpOpsError.rfCommsFailure
  675. /// - LocalizedError
  676. private func scanForPump(in frequencies: [Measurement<UnitFrequency>], fallback: Measurement<UnitFrequency>?, tries: Int = 3) throws -> FrequencyScanResults {
  677. var trials = [FrequencyTrial]()
  678. let middleFreq = frequencies[frequencies.count / 2]
  679. do {
  680. // Needed to put the pump in listen mode
  681. try messageSender.setBaseFrequency(middleFreq)
  682. try wakeup()
  683. } catch {
  684. // Continue anyway; the pump likely heard us, even if we didn't hear it.
  685. }
  686. for freq in frequencies {
  687. var trial = FrequencyTrial(frequency: freq)
  688. try messageSender.setBaseFrequency(freq)
  689. var sumRSSI = 0
  690. for _ in 1...tries {
  691. // Ignore failures here
  692. let rfPacket = try? messageSender.sendAndListenForPacket(PumpMessage(settings: settings, type: .getPumpModel), repeatCount: 0, timeout: .milliseconds(130), retryCount: 3)
  693. if let rfPacket = rfPacket,
  694. let pkt = MinimedPacket(encodedData: rfPacket.data),
  695. let response = PumpMessage(rxData: pkt.data), response.messageType == .getPumpModel
  696. {
  697. sumRSSI += rfPacket.rssi
  698. trial.successes += 1
  699. }
  700. trial.tries += 1
  701. }
  702. // Mark each failure as a -99 rssi, so we can use highest rssi as best freq
  703. sumRSSI += -99 * (trial.tries - trial.successes)
  704. trial.avgRSSI = Double(sumRSSI) / Double(trial.tries)
  705. trials.append(trial)
  706. }
  707. let sortedTrials = trials.sorted(by: { (a, b) -> Bool in
  708. return a.avgRSSI > b.avgRSSI
  709. })
  710. guard sortedTrials.first!.successes > 0 else {
  711. try messageSender.setBaseFrequency(fallback ?? middleFreq)
  712. throw PumpOpsError.rfCommsFailure("No pump responses during scan")
  713. }
  714. let results = FrequencyScanResults(
  715. trials: trials,
  716. bestFrequency: sortedTrials.first!.frequency
  717. )
  718. try messageSender.setBaseFrequency(results.bestFrequency)
  719. return results
  720. }
  721. }
  722. // MARK: - Pump history
  723. extension PumpOpsSession {
  724. /// Fetches history entries which occurred on or after the specified date.
  725. ///
  726. /// It is possible for Minimed Pumps to non-atomically append multiple history entries with the same timestamp, for example, `BolusWizardEstimatePumpEvent` may appear and be read before `BolusNormalPumpEvent` is written. Therefore, the `startDate` parameter is used as part of an inclusive range, leaving the client to manage the possibility of duplicates.
  727. ///
  728. /// History timestamps are reconciled with UTC based on the `timeZone` property of PumpState, as well as recorded clock change events.
  729. ///
  730. /// - Parameter startDate: The earliest date of events to retrieve
  731. /// - Returns:
  732. /// - An array of fetched history entries, in ascending order of insertion
  733. /// - The pump model
  734. /// - Throws:
  735. /// - PumpCommandError.command
  736. /// - PumpCommandError.arguments
  737. /// - PumpOpsError.couldNotDecode
  738. /// - PumpOpsError.crosstalk
  739. /// - PumpOpsError.deviceError
  740. /// - PumpOpsError.noResponse
  741. /// - PumpOpsError.unknownResponse
  742. /// - HistoryPageError.invalidCRC
  743. /// - HistoryPageError.unknownEventType
  744. public func getHistoryEvents(since startDate: Date) throws -> ([TimestampedHistoryEvent], PumpModel) {
  745. try wakeup()
  746. let pumpModel = try getPumpModel()
  747. var events = [TimestampedHistoryEvent]()
  748. pages: for pageNum in 0..<16 {
  749. // TODO: Convert logging
  750. NSLog("Fetching page %d", pageNum)
  751. let pageData: Data
  752. do {
  753. pageData = try getHistoryPage(pageNum)
  754. } catch PumpCommandError.arguments(let error) {
  755. if case PumpOpsError.pumpError(.pageDoesNotExist) = error {
  756. return (events, pumpModel)
  757. }
  758. throw PumpCommandError.arguments(error)
  759. }
  760. var idx = 0
  761. let chunkSize = 256
  762. while idx < pageData.count {
  763. let top = min(idx + chunkSize, pageData.count)
  764. let range = Range(uncheckedBounds: (lower: idx, upper: top))
  765. // TODO: Convert logging
  766. NSLog(String(format: "HistoryPage %02d - (bytes %03d-%03d): ", pageNum, idx, top-1) + pageData.subdata(in: range).hexadecimalString)
  767. idx = top
  768. }
  769. let page = try HistoryPage(pageData: pageData, pumpModel: pumpModel)
  770. let (timestampedEvents, hasMoreEvents, _) = page.timestampedEvents(after: startDate, timeZone: pump.timeZone, model: pumpModel)
  771. events = timestampedEvents + events
  772. if !hasMoreEvents {
  773. break
  774. }
  775. }
  776. return (events, pumpModel)
  777. }
  778. private func getHistoryPage(_ pageNum: Int) throws -> Data {
  779. var frameData = Data()
  780. let msg = PumpMessage(settings: settings, type: .getHistoryPage, body: GetHistoryPageCarelinkMessageBody(pageNum: pageNum))
  781. var curResp: GetHistoryPageCarelinkMessageBody = try runCommandWithArguments(msg, responseType: .getHistoryPage)
  782. var expectedFrameNum = 1
  783. while(expectedFrameNum == curResp.frameNumber) {
  784. frameData.append(curResp.frame)
  785. expectedFrameNum += 1
  786. let msg = PumpMessage(settings: settings, type: .pumpAck)
  787. if !curResp.lastFrame {
  788. curResp = try messageSender.getResponse(to: msg, responseType: .getHistoryPage, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  789. } else {
  790. try messageSender.send(msg)
  791. break
  792. }
  793. }
  794. guard frameData.count == 1024 else {
  795. throw PumpOpsError.rfCommsFailure("Short history page: \(frameData.count) bytes. Expected 1024")
  796. }
  797. return frameData
  798. }
  799. }
  800. // MARK: - Glucose history
  801. extension PumpOpsSession {
  802. private func logGlucoseHistory(pageData: Data, pageNum: Int) {
  803. var idx = 0
  804. let chunkSize = 256
  805. while idx < pageData.count {
  806. let top = min(idx + chunkSize, pageData.count)
  807. let range = Range(uncheckedBounds: (lower: idx, upper: top))
  808. // TODO: Convert logging
  809. NSLog(String(format: "GlucosePage %02d - (bytes %03d-%03d): ", pageNum, idx, top-1) + pageData.subdata(in: range).hexadecimalString)
  810. idx = top
  811. }
  812. }
  813. /// Fetches glucose history entries which occurred on or after the specified date.
  814. ///
  815. /// History timestamps are reconciled with UTC based on the `timeZone` property of PumpState, as well as recorded clock change events.
  816. ///
  817. /// - Parameter startDate: The earliest date of events to retrieve
  818. /// - Returns: An array of fetched history entries, in ascending order of insertion
  819. /// - Throws:
  820. public func getGlucoseHistoryEvents(since startDate: Date) throws -> [TimestampedGlucoseEvent] {
  821. try wakeup()
  822. var events = [TimestampedGlucoseEvent]()
  823. let currentGlucosePage: ReadCurrentGlucosePageMessageBody = try messageSender.getResponse(to: PumpMessage(settings: settings, type: .readCurrentGlucosePage), responseType: .readCurrentGlucosePage, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  824. let startPage = Int(currentGlucosePage.pageNum)
  825. //max lookback of 15 pages or when page is 0
  826. let endPage = max(startPage - 15, 0)
  827. pages: for pageNum in stride(from: startPage, to: endPage - 1, by: -1) {
  828. // TODO: Convert logging
  829. NSLog("Fetching page %d", pageNum)
  830. var pageData: Data
  831. var page: GlucosePage
  832. do {
  833. pageData = try getGlucosePage(UInt32(pageNum))
  834. // logGlucoseHistory(pageData: pageData, pageNum: pageNum)
  835. page = try GlucosePage(pageData: pageData)
  836. if page.needsTimestamp && pageNum == startPage {
  837. // TODO: Convert logging
  838. NSLog(String(format: "GlucosePage %02d needs a new sensor timestamp, writing...", pageNum))
  839. let _ = try writeGlucoseHistoryTimestamp()
  840. //fetch page again with new sensor timestamp
  841. pageData = try getGlucosePage(UInt32(pageNum))
  842. logGlucoseHistory(pageData: pageData, pageNum: pageNum)
  843. page = try GlucosePage(pageData: pageData)
  844. }
  845. } catch PumpOpsError.pumpError {
  846. break pages
  847. }
  848. for event in page.events.reversed() {
  849. var timestamp = event.timestamp
  850. timestamp.timeZone = pump.timeZone
  851. if event is UnknownGlucoseEvent {
  852. continue pages
  853. }
  854. if let date = timestamp.date {
  855. if date < startDate && event is SensorTimestampGlucoseEvent {
  856. // TODO: Convert logging
  857. NSLog("Found reference event at (%@) to be before startDate(%@)", date as NSDate, startDate as NSDate)
  858. break pages
  859. } else {
  860. events.insert(TimestampedGlucoseEvent(glucoseEvent: event, date: date), at: 0)
  861. }
  862. }
  863. }
  864. }
  865. return events
  866. }
  867. private func getGlucosePage(_ pageNum: UInt32) throws -> Data {
  868. var frameData = Data()
  869. let msg = PumpMessage(settings: settings, type: .getGlucosePage, body: GetGlucosePageMessageBody(pageNum: pageNum))
  870. var curResp: GetGlucosePageMessageBody = try runCommandWithArguments(msg, responseType: .getGlucosePage)
  871. var expectedFrameNum = 1
  872. while(expectedFrameNum == curResp.frameNumber) {
  873. frameData.append(curResp.frame)
  874. expectedFrameNum += 1
  875. let msg = PumpMessage(settings: settings, type: .pumpAck)
  876. if !curResp.lastFrame {
  877. curResp = try messageSender.getResponse(to: msg, responseType: .getGlucosePage, repeatCount: 0, timeout: MinimedPumpMessageSender.standardPumpResponseWindow, retryCount: 3)
  878. } else {
  879. try messageSender.send(msg)
  880. break
  881. }
  882. }
  883. guard frameData.count == 1024 else {
  884. throw PumpOpsError.rfCommsFailure("Short glucose history page: \(frameData.count) bytes. Expected 1024")
  885. }
  886. return frameData
  887. }
  888. public func writeGlucoseHistoryTimestamp() throws -> Void {
  889. try wakeup()
  890. let shortWriteTimestamp = PumpMessage(settings: settings, type: .writeGlucoseHistoryTimestamp)
  891. let _: PumpAckMessageBody = try messageSender.getResponse(to: shortWriteTimestamp, responseType: .pumpAck, repeatCount: 0, timeout: .seconds(12), retryCount: 3)
  892. }
  893. }