TrioRemoteControl+Bolus.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import Foundation
  2. extension TrioRemoteControl {
  3. internal func handleBolusCommand(_ pushMessage: PushMessage) async throws {
  4. guard let bolusAmount = pushMessage.bolusAmount else {
  5. await logError("Command rejected: bolus amount is missing or invalid.", pushMessage: pushMessage)
  6. return
  7. }
  8. let maxBolus = await TrioApp.resolver.resolve(SettingsManager.self)?.pumpSettings.maxBolus ?? Decimal(0)
  9. if bolusAmount > maxBolus {
  10. await logError(
  11. "Command rejected: bolus amount (\(bolusAmount) units) exceeds the maximum allowed (\(maxBolus) units).",
  12. pushMessage: pushMessage
  13. )
  14. return
  15. }
  16. let maxIOB = settings.preferences.maxIOB
  17. let currentIOB = try await fetchCurrentIOB()
  18. if (currentIOB + bolusAmount) > maxIOB {
  19. await logError(
  20. "Command rejected: bolus amount (\(bolusAmount) units) would exceed max IOB (\(maxIOB) units). Current IOB: \(currentIOB) units.",
  21. pushMessage: pushMessage
  22. )
  23. return
  24. }
  25. let totalRecentBolusAmount =
  26. try await fetchTotalRecentBolusAmount(since: Date(timeIntervalSince1970: pushMessage.timestamp))
  27. if totalRecentBolusAmount >= bolusAmount * 0.2 {
  28. await logError(
  29. "Command rejected: boluses totaling more than 20% of the requested amount have been delivered since the command was sent.",
  30. pushMessage: pushMessage
  31. )
  32. return
  33. }
  34. debug(.remoteControl, "Enacting bolus command with amount: \(bolusAmount) units.")
  35. guard let apsManager = await TrioApp.resolver.resolve(APSManager.self) else {
  36. await logError(
  37. "Error: unable to process bolus command because the APS Manager is not available.",
  38. pushMessage: pushMessage
  39. )
  40. return
  41. }
  42. await apsManager.enactBolus(amount: Double(truncating: bolusAmount as NSNumber), isSMB: false, callback: nil)
  43. debug(
  44. .remoteControl,
  45. "Remote command processed successfully. \(pushMessage.humanReadableDescription())"
  46. )
  47. }
  48. private func fetchCurrentIOB() async throws -> Decimal {
  49. let predicate = NSPredicate.predicateFor30MinAgoForDetermination
  50. let determinations = try await CoreDataStack.shared.fetchEntitiesAsync(
  51. ofType: OrefDetermination.self,
  52. onContext: pumpHistoryFetchContext,
  53. predicate: predicate,
  54. key: "timestamp",
  55. ascending: false,
  56. fetchLimit: 1,
  57. propertiesToFetch: ["iob"]
  58. )
  59. guard let fetchedResults = determinations as? [[String: Any]],
  60. let firstResult = fetchedResults.first,
  61. let iob = firstResult["iob"] as? Decimal
  62. else {
  63. await logError("Failed to fetch current IOB.")
  64. throw CoreDataError.fetchError(function: #function, file: #file)
  65. }
  66. return iob
  67. }
  68. private func fetchTotalRecentBolusAmount(since date: Date) async throws -> Decimal {
  69. let predicate = NSPredicate(
  70. format: "type == %@ AND timestamp > %@",
  71. PumpEventStored.EventType.bolus.rawValue,
  72. date as NSDate
  73. )
  74. let results: Any = try await CoreDataStack.shared.fetchEntitiesAsync(
  75. ofType: PumpEventStored.self,
  76. onContext: pumpHistoryFetchContext,
  77. predicate: predicate,
  78. key: "timestamp",
  79. ascending: true,
  80. fetchLimit: nil,
  81. propertiesToFetch: ["bolus.amount"]
  82. )
  83. guard let bolusDictionaries = results as? [[String: Any]] else {
  84. await logError("Failed to cast fetched bolus events. Fetched entities type: \(type(of: results))")
  85. throw CoreDataError.fetchError(function: #function, file: #file)
  86. }
  87. let totalAmount = bolusDictionaries.compactMap { ($0["bolus.amount"] as? NSNumber)?.decimalValue }.reduce(0, +)
  88. return totalAmount
  89. }
  90. }