TrioRemoteControl+Bolus.swift 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import Foundation
  2. extension TrioRemoteControl {
  3. internal func handleBolusCommand(_ pushMessage: PushMessage) async {
  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 FreeAPSApp.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 = 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 = await fetchTotalRecentBolusAmount(since: Date(timeIntervalSince1970: pushMessage.timestamp))
  26. if totalRecentBolusAmount >= bolusAmount * 0.2 {
  27. await logError(
  28. "Command rejected: boluses totaling more than 20% of the requested amount have been delivered since the command was sent.",
  29. pushMessage: pushMessage
  30. )
  31. return
  32. }
  33. debug(.remoteControl, "Enacting bolus command with amount: \(bolusAmount) units.")
  34. guard let apsManager = await FreeAPSApp.resolver.resolve(APSManager.self) else {
  35. await logError(
  36. "Error: unable to process bolus command because the APS Manager is not available.",
  37. pushMessage: pushMessage
  38. )
  39. return
  40. }
  41. await apsManager.enactBolus(amount: Double(truncating: bolusAmount as NSNumber), isSMB: false)
  42. debug(
  43. .remoteControl,
  44. "Remote command processed successfully. \(pushMessage.humanReadableDescription())"
  45. )
  46. }
  47. private func fetchCurrentIOB() async -> Decimal {
  48. let predicate = NSPredicate.predicateFor30MinAgoForDetermination
  49. let determinations = await CoreDataStack.shared.fetchEntitiesAsync(
  50. ofType: OrefDetermination.self,
  51. onContext: pumpHistoryFetchContext,
  52. predicate: predicate,
  53. key: "timestamp",
  54. ascending: false,
  55. fetchLimit: 1,
  56. propertiesToFetch: ["iob"]
  57. )
  58. guard let fetchedResults = determinations as? [[String: Any]],
  59. let firstResult = fetchedResults.first,
  60. let iob = firstResult["iob"] as? Decimal
  61. else {
  62. await logError("Failed to fetch current IOB.")
  63. return Decimal(0)
  64. }
  65. return iob
  66. }
  67. private func fetchTotalRecentBolusAmount(since date: Date) async -> Decimal {
  68. let predicate = NSPredicate(
  69. format: "type == %@ AND timestamp > %@",
  70. PumpEventStored.EventType.bolus.rawValue,
  71. date as NSDate
  72. )
  73. let results: Any = await CoreDataStack.shared.fetchEntitiesAsync(
  74. ofType: PumpEventStored.self,
  75. onContext: pumpHistoryFetchContext,
  76. predicate: predicate,
  77. key: "timestamp",
  78. ascending: true,
  79. fetchLimit: nil,
  80. propertiesToFetch: ["bolus.amount"]
  81. )
  82. guard let bolusDictionaries = results as? [[String: Any]] else {
  83. await logError("Failed to cast fetched bolus events. Fetched entities type: \(type(of: results))")
  84. return 0
  85. }
  86. let totalAmount = bolusDictionaries.compactMap { ($0["bolus.amount"] as? NSNumber)?.decimalValue }.reduce(0, +)
  87. return totalAmount
  88. }
  89. }