diff --git a/README-Combo.md b/README-Combo.md index 460e423e39..749083a83c 100644 --- a/README-Combo.md +++ b/README-Combo.md @@ -85,6 +85,8 @@ Usage: - The integration of the Combo with AndroidAPS is designed with the assumption that all inputs are made via AndroidAPS. Boluses entered on the pump will NOT be detected by AAPS and may therefore result in too much insulin being delivered. +- The pump's first basal rate profile is read on app start and is updated by AAPS. Manually changing + the pump's basal rate profile will lead to wrong basals being delivered and is NOT supported. - It's recommended to enable key lock on the pump to prevent bolusing from the pump, esp. when the pump was used before and quick bolusing was a habit. Also, with keylock enabled, accidentally pressing a key will NOT interrupt a running command diff --git a/Testing-Combo.md b/Testing-Combo.md index 56e39c07eb..f47aa4e0b2 100644 --- a/Testing-Combo.md +++ b/Testing-Combo.md @@ -74,12 +74,19 @@ - [ ] Check displayed data (state, battery, reservoir, temp basal) is the same as on the pump - [ ] Unsafe usage - - [ ] An extended or multiwave bolus given within the last six hour must raise an alert and - restrict the loop functionality to low-suspend only (setting maxIOB to zero) + - [ ] An active extended or multiwave bolus must raise an alert and + restrict the loop functionality to low-suspend only for the next 6h (setting maxIOB to zero) + and cancel an active TBR. - [ ] Closed loop functionality must resume 6 h after the last ext/multiwave bolus - - [ ] An active ext/multiwave bolus must also raise an alert and restrict the loop - - [ ] If a basal rate other than profile 1 is activated, this must also raise an alert and disable - the restrict the loop + - [ ] If a basal rate other than profile 1 is active on start, the pump must refuse to finish + initialization and disable the loop. When setting the profile to 1 and refreshing, + the pump must finish initialization and enable the loop (the overview screen will + still show "closed loop", but the Combo and Loop tabs will say the loop is disabled + due to a constraint violation). + - [ ] When changing profile to one other than the first after AAPS has started and read the first + basal profile, a warning must be shown, the loop must be disabled and the active TBR be cancelled. + - [ ] A request to change the AAPS profil (e.g. increase to 110%) must be rejected if the pump + doesn't have profile one active. - [ ] Reading/setting basal profile - [ ] AAPS reads basal rate properly - [ ] Test profile with 115% (or something like that) change to ask the diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java index 4dae17dba6..271a0de9a0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java @@ -41,6 +41,7 @@ import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; @@ -138,7 +139,9 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf String getStateSummary() { PumpState ps = pump.state; - if (ps.activeAlert != null) { + if (!validBasalRateProfileSelectedOnPump) { + return MainApp.sResources.getString(R.string.loopdisabled); + } else if (ps.activeAlert != null) { return ps.activeAlert.errorCode != null ? "E" + ps.activeAlert.errorCode + ": " + ps.activeAlert.message : "W" + ps.activeAlert.warningCode + ": " + ps.activeAlert.message; @@ -258,6 +261,11 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf return new PumpEnactResult().success(true).enacted(false); } + CommandResult stateResult = runCommand(null, 1, ruffyScripter::readPumpState); + if (stateResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + return new PumpEnactResult().success(false).enacted(false).comment(MainApp.sResources.getString(R.string.combo_force_disabled_notification)); + } + CommandResult setResult = runCommand(MainApp.sResources.getString(R.string.combo_activity_setting_basal_profile), 2, () -> ruffyScripter.setBasalProfile(requestedBasalProfile)); if (!setResult.success) { @@ -324,48 +332,54 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf return new Date(pump.lastSuccessfulCmdTime); } - /** - * Runs pump initializing if needed, checks for boluses given on the pump, updates the - * reservoir level and checks the running TBR on the pump. - */ + /** Runs pump initializing if needed and reads the pump state from the main screen. */ @Override public synchronized void getPumpStatus() { log.debug("getPumpStatus called"); if (!pump.initialized) { - long maxWait = System.currentTimeMillis() + 15 * 1000; - while (!ruffyScripter.isPumpAvailable()) { - log.debug("Waiting for ruffy service to come up ..."); - SystemClock.sleep(100); - if (System.currentTimeMillis() > maxWait) { - log.debug("ruffy service unavailable, wtf"); - return; - } + initializePump(); + } else { + runCommand(MainApp.sResources.getString(R.string.combo_pump_action_refreshing), 1, ruffyScripter::readPumpState); + } + } + + private synchronized void initializePump() { + long maxWait = System.currentTimeMillis() + 15 * 1000; + while (!ruffyScripter.isPumpAvailable()) { + log.debug("Waiting for ruffy service to come up ..."); + SystemClock.sleep(100); + if (System.currentTimeMillis() > maxWait) { + log.debug("ruffy service unavailable, wtf"); + return; } } - CommandResult stateResult = runCommand(pump.initialized ? MainApp.sResources.getString(R.string.combo_pump_action_refreshing) : MainApp.sResources.getString(R.string.combo_pump_action_initializing), - 1, ruffyScripter::readPumpState); + CommandResult stateResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_initializing),1, ruffyScripter::readPumpState); if (!stateResult.success) { return; } - // read basal profile into cache and update pump profile if needed - if (!pump.initialized) { - CommandResult readBasalResult = runCommand("Reading basal profile", 2, ruffyScripter::readBasalProfile); - if (!readBasalResult.success) { - return; - } - pump.basalProfile = readBasalResult.basalProfile; + if (stateResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + Notification n = new Notification(Notification.COMBO_PUMP_ALARM, + MainApp.sResources.getString(R.string.combo_force_disabled_notification), + Notification.URGENT); + n.soundId = R.raw.alarm; + MainApp.bus().post(new EventNewNotification(n)); + return; } - if (!pump.initialized) { - pump.initialized = true; - MainApp.bus().post(new EventInitializationChanged()); + // read basal profile into cache (KeepAlive will trigger a profile update if needed) + CommandResult readBasalResult = runCommand("Reading basal profile", 2, ruffyScripter::readBasalProfile); + if (!readBasalResult.success) { + return; } + pump.basalProfile = readBasalResult.basalProfile; + validBasalRateProfileSelectedOnPump = true; + pump.initialized = true; + MainApp.bus().post(new EventInitializationChanged()); - // ComboFragment updates state fully only after the pump has initialized, - // this fetches state again and updates the UI proper - runCommand(null, 0, ruffyScripter::readPumpState); + // ComboFragment updates state fully only after the pump has initialized, so run this manually here + updateLocalData(readBasalResult); } private void updateLocalData(CommandResult result) { @@ -716,9 +730,15 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf if (commandResult.success) { pump.lastSuccessfulCmdTime = System.currentTimeMillis(); - } - - if (commandResult.success) { + if (validBasalRateProfileSelectedOnPump && commandResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + validBasalRateProfileSelectedOnPump = false; + Notification n = new Notification(Notification.COMBO_PUMP_ALARM, + MainApp.sResources.getString(R.string.combo_force_disabled_notification), + Notification.URGENT); + n.soundId = R.raw.alarm; + MainApp.bus().post(new EventNewNotification(n)); + ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, null); + } updateLocalData(commandResult); } } finally { @@ -760,7 +780,7 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf notification.date = new Date(); notification.id = Notification.COMBO_PUMP_ALARM; notification.level = Notification.URGENT; - notification.text = MainApp.sResources.getString(R.string.combo_is_in_error_state); + notification.text = MainApp.sResources.getString(R.string.combo_is_in_error_state, activeAlert.errorCode, activeAlert.message); MainApp.bus().post(new EventNewNotification(notification)); return preCheckResult.success(false); } @@ -811,7 +831,7 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf if (commandResult == null) return; long lastViolation = 0; - if (commandResult.state.unsafeUsageDetected) { + if (commandResult.state.unsafeUsageDetected == PumpState.UNSUPPORTED_BOLUS_TYPE) { lastViolation = System.currentTimeMillis(); } else if (commandResult.lastBolus != null && !commandResult.lastBolus.isValid) { lastViolation = commandResult.lastBolus.timestamp; @@ -823,14 +843,15 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf } } if (lastViolation > 0) { - lowSuspendOnlyLoopEnforcetTill = lastViolation + 6 * 60 * 60 * 1000; - if (lowSuspendOnlyLoopEnforcetTill > System.currentTimeMillis() && violationWarningRaisedFor != lowSuspendOnlyLoopEnforcetTill) { + lowSuspendOnlyLoopEnforcedUntil = lastViolation + 6 * 60 * 60 * 1000; + if (lowSuspendOnlyLoopEnforcedUntil > System.currentTimeMillis() && violationWarningRaisedForBolusAt != lowSuspendOnlyLoopEnforcedUntil) { Notification n = new Notification(Notification.COMBO_PUMP_ALARM, - MainApp.sResources.getString(R.string.combo_force_disabled_notification), + MainApp.sResources.getString(R.string.combo_low_suspend_forced_notification), Notification.URGENT); n.soundId = R.raw.alarm; MainApp.bus().post(new EventNewNotification(n)); - violationWarningRaisedFor = lowSuspendOnlyLoopEnforcetTill; + violationWarningRaisedForBolusAt = lowSuspendOnlyLoopEnforcedUntil; + ConfigBuilderPlugin.getCommandQueue().cancelTempBasal(true, null); } } } @@ -1058,12 +1079,13 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf } // Constraints interface - private long lowSuspendOnlyLoopEnforcetTill = 0; - private long violationWarningRaisedFor = 0; + private long lowSuspendOnlyLoopEnforcedUntil = 0; + private long violationWarningRaisedForBolusAt = 0; + private boolean validBasalRateProfileSelectedOnPump = false; @Override public boolean isLoopEnabled() { - return true; + return validBasalRateProfileSelectedOnPump; } @Override @@ -1103,6 +1125,6 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf @Override public Double applyMaxIOBConstraints(Double maxIob) { - return lowSuspendOnlyLoopEnforcetTill < System.currentTimeMillis() ? maxIob : 0; + return lowSuspendOnlyLoopEnforcedUntil < System.currentTimeMillis() ? maxIob : 0; } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 93a23279d4..4a08c1662c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -300,7 +300,7 @@ AKT WEAR VP - TREAT + BEH TT TB SMS @@ -695,4 +695,36 @@ TBR wird abgebrochen TBR wird gesetzt (%d%% / %d min) Bolus (%.1f E) wird abgegeben + Historik wird gelesen + Basalratenprofil wird aktualisiert + Verbindung wurde unterbrochen, es wird versucht fortzusetzen + Der abgegebene Bolus konnte nicht bestätigt werden. Bitte prüfen Sie auf der Pumpe ob ein Bolus abgegeben wurde und erstellen Sie einen Eintrag im Careportal Reiter falls nötig. + Die Bolusabgabe ist fehlgeschlagen, es wurde scheinbar kein Bolus abgegeben. Bitte prüfen Sie auf der Pumpe ob ein Bolus abgegeben wurde. Um dopplte Boli durch Programmfehler zu vermeiden werden Boli nicht automatisch erneut versucht. + Wegen eines Fehlers wurden nur %.2f IE von den angeforderten %.2f IE abgegeben. Bitte prüfen Sie den abgegeben Bolus auf der Pumpe. + Historik + Status wird aktualisiert + Die Pumpe wird initialisiert + Jetzt + Nie + Ein TBR ABGEBROCHEN Alarm wurde bestätigt + Warnung + Leer + Niedrig + Normal + Durchschnitt: %3.1f IE + Maximum: %3.1f IE + Minimum: %3.1f IE + Diese Aktion wird von der Pumpe nicht unterstützt + Alarme + Die Batterie in der Pumpe ist fast leer + Das Reservoir in der Pumpe ist fast leer + Die Pumpe zeigt einen Fehler an E%d: %s + Unsichere Verwendung: die Pumpe hat nicht das erste Basalratenprofil als aktiv gesetzt. Der Loop wird deaktiviert bis dies korrigiert ist. + Unsichere Verwendung: ein erweiterter- oder Multiwave-Bolus ist aktiv. Der Loop wird für die nächsten 6 Stunden kein zusätzliches Insulin abgeben. + Um die Fehlerhistorik der Pumpe zu lesen, drücken Sie den ALARME Knopf lange.\nWARNUNG: Es gibt einen bekannten Fehler in der Pumpe der dazu führt, dass die Pumpe nach dieser Aktion erst wieder Verbindungen annimmt, wenn auf der Pumpe selbst ein Konpf gedrückt wird. Aus diesem Grund sollte diese Aktion nicht durchgeführt werden. + Bitte aktualisieren Sie die Uhr auf der Pumpe + Um die TDD-Statistik der Pumpe zu lesen, drücken Sie den TDDS Knopf lange.\nWARNUNG: Es gibt einen bekannten Fehler in der Pumpe der dazu führt, dass die Pumpe nach dieser Aktion erst wieder Verbindungen annimmt, wenn auf der Pumpe selbst ein Konpf gedrückt wird. Aus diesem Grund sollte diese Aktion nicht durchgeführt werden. + Sind Sie sich sicher, dass Sie diese Aktion ausführen möchen und verstehen Sie die Konsequenzen die sich daraus ergeben? + Diese Aktion wird die gesamte Historik und die Basalrate aus der Pumpe auslesen. Boli und temporäre Basalrate werden zu den Behandlungen hinzugefügt wenn diese noch nicht vorhanden sind. Dies kann zu doppelten Einträge und somit zu falschen IOB-Werten führen, da die Uhr der Pumpe ungenau ist. Diese Aktion sollte NIE durchgeführt werden wenn die Pumpe im Loop verwendet wird. Wenn Sie diese Aktion trotzdem durchführen möchten, drücken Sie lange erneut auf den diesen Knopf.\nWARNUNG: Es gibt einen bekannten Fehler in der Pumpe der dazu führt, dass die Pumpe nach dieser Aktion erst wieder Verbindungen annimmt, wenn auf der Pumpe selbst ein Konpf gedrückt wird. Aus diesem Grund sollte diese Aktion nicht durchgeführt werden. + Dringender Alarm diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 406dea0cf9..4ecffc9269 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -799,7 +799,6 @@ State Activity No connection for %d min - %s %d%% (%d min remaining) %.1f U (%s, %s) Initializing @@ -807,7 +806,6 @@ Suspended due to error Suspended by user Running - Checking pump history Cancelling TBR Setting TBR (%d%% / %d min) Bolusing (%.1f U) @@ -816,7 +814,8 @@ Use system notifications for alerts Never Requested operation not supported by pump - Unsafe usage: extended or multiwave boluses have been delivered within the last 6 hours or the selected basal rate is not 1. Loop mode has been set to low-suspend only until 6 hours after the last unsupported bolus or basal rate profile. Only normal boluses are supported in loop mode with basal rate profile 1. + Unsafe usage: extended or multiwave boluses are active. Loop mode has been set to low-suspend only 6 hours. Only normal boluses are supported in loop mode + Unsafe usage: the pump uses a different basal rate profile than the first. The loop has been disabled. Select the first profile on the pump and refresh. A bolus with the same amount was requested within the last minute. To prevent accidental double boluses and to guard against bugs this is disallowed. Now Reading pump history @@ -825,7 +824,7 @@ Setting basal profile Pump cartridge level is low Pump battery is low - Pump is in an error state, please check the pump + The pump is showing the error E%d: %s To read the pump\'s error history, long press the ALERTS button\n\nWARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided. To read the pump\'s TDD history, long press the TDDS button\n\nWARNING: this can trigger a bug which causes the pump to reject all connection attempts and requires pressing a button on the pump to recover and should therefore be avoided. sync_profile_to_pump @@ -843,7 +842,6 @@ TBR CANCELLED warning was confirmed Bolus delivery failed. It appears no bolus was delivered. To be sure, please check the pump to avoid a double bolus and then bolus again. To guard against bugs, boluses are not automatically retried. Only %.2f U of the requested bolus of %.2f U was delivered due to an error. Please check the pump to verify this and take appropriate actions. - Verifying delivered bolus Delivering the bolus and verifying the pump\'s history failed, please check the pump and manually create a bolus record using the Careportal tab if a bolus was delivered. Recovering from connection loss diff --git a/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/PumpState.java b/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/PumpState.java index 728b9f7fa0..917c563e77 100644 --- a/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/PumpState.java +++ b/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/PumpState.java @@ -30,8 +30,12 @@ public class PumpState { public int insulinState = UNKNOWN; public int activeBasalProfileNumber; + + public static final int SAFE_USAGE = 0; + public static final int UNSUPPORTED_BOLUS_TYPE = 1; + public static final int UNSUPPORTED_BASAL_RATE_PROFILE = 2; /** True if use of an extended or multiwave bolus has been detected */ - public boolean unsafeUsageDetected; + public int unsafeUsageDetected = SAFE_USAGE; public PumpState menu(String menu) { this.menu = menu; diff --git a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java index 44deb879e8..3cee006ae6 100644 --- a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java +++ b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java @@ -501,8 +501,10 @@ public class RuffyScripter implements RuffyCommands { BolusType bolusType = (BolusType) menu.getAttribute(MenuAttribute.BOLUS_TYPE); Integer activeBasalRate = (Integer) menu.getAttribute(MenuAttribute.BASAL_SELECTED); - if (bolusType != null && bolusType != BolusType.NORMAL || !activeBasalRate.equals(1)) { - state.unsafeUsageDetected = true; + if (!activeBasalRate.equals(1)) { + state.unsafeUsageDetected = PumpState.UNSUPPORTED_BASAL_RATE_PROFILE; + } else if (bolusType != null && bolusType != BolusType.NORMAL) { + state.unsafeUsageDetected = PumpState.UNSUPPORTED_BOLUS_TYPE; } else if (tbrPercentage != null && tbrPercentage != 100) { state.tbrActive = true; Double displayedTbr = (Double) menu.getAttribute(MenuAttribute.TBR); diff --git a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java index d778d08c13..1cd3f1df32 100644 --- a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java +++ b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import java.util.Arrays; import de.jotomo.ruffy.spi.BasalProfile; +import de.jotomo.ruffy.spi.PumpState; public class ReadBasalProfileCommand extends BaseCommand { private static final Logger log = LoggerFactory.getLogger(ReadBasalProfileCommand.class); @@ -17,6 +18,9 @@ public class ReadBasalProfileCommand extends BaseCommand { @Override public void execute() { scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + if (scripter.readPumpStateInternal().unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + throw new CommandException("Active basal rate profile != 1"); + } scripter.navigateToMenu(MenuType.BASAL_1_MENU); scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU); scripter.pressCheckKey(); diff --git a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java index a5e42014b6..f6ffd2216d 100644 --- a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java +++ b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.List; import de.jotomo.ruffy.spi.BasalProfile; +import de.jotomo.ruffy.spi.PumpState; public class SetBasalProfileCommand extends BaseCommand { private static final Logger log = LoggerFactory.getLogger(SetBasalProfileCommand.class); @@ -26,6 +27,9 @@ public class SetBasalProfileCommand extends BaseCommand { @Override public void execute() { scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + if (scripter.readPumpStateInternal().unsafeUsageDetected == PumpState.UNSUPPORTED_BASAL_RATE_PROFILE) { + throw new CommandException("Active basal rate profile != 1"); + } scripter.navigateToMenu(MenuType.BASAL_1_MENU); scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU); scripter.pressCheckKey();