diff --git a/README-Combo.md b/README-Combo.md index 08ea75b271..86ad5a73b2 100644 --- a/README-Combo.md +++ b/README-Combo.md @@ -11,7 +11,7 @@ Hardware requirements: Software to configure the pump. Roche sends out Smartpix devices and the configuration software free of charge to their customers upon request. -- A compatible phone: An Android phone with a phone running LineageOS 14.1 (formerly CyanogenMod) +- A compatible phone: An Android phone with a phone running LineageOS 14.1 (formerly CyanogenMod) or Android 8.1 (Oreo) - To build AndroidAPS with Combo support you need the latest Android Studio 3 version Limitations: @@ -49,6 +49,7 @@ Setup: menus/actions on the pump and hide those which are unsupported (extended/multiwave bolus, multiple basal rates), which cause the loop functionality to be restricted when used because it's not possible to run the loop in a safe manner when used. + - Verify the _Quick Info Text_ is set to "QUICK INFO" (without the quotes, found under _Insulin Pump Options_). - Set maximum TBR to 500% - Disable end of TBR alert - Set TBR duration step-size to 15 min diff --git a/app/build.gradle b/app/build.gradle index e5e9edaf9c..d6b4f914f6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,6 +178,10 @@ dependencies { compile 'junit:junit:4.12' testCompile 'org.json:json:20140107' testCompile 'org.mockito:mockito-core:2.7.22' + testCompile 'org.powermock:powermock-api-mockito2:1.7.3' + testCompile 'org.powermock:powermock-module-junit4-rule-agent:1.7.3' + testCompile 'org.powermock:powermock-module-junit4-rule:1.7.3' + testCompile 'org.powermock:powermock-module-junit4:1.7.3' androidTestCompile 'org.mockito:mockito-core:2.7.22' androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index b89da43de4..e4e627d711 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -30,7 +30,9 @@ import java.util.concurrent.TimeUnit; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.events.EventCareportalEventChange; import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.events.EventFoodDatabaseChanged; @@ -43,7 +45,10 @@ import info.nightscout.androidaps.events.EventReloadTreatmentData; import info.nightscout.androidaps.events.EventTempBasalChange; import info.nightscout.androidaps.events.EventTempTargetChange; import info.nightscout.androidaps.events.EventTreatmentChange; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRNSHistorySync; import info.nightscout.androidaps.plugins.PumpVirtual.VirtualPumpPlugin; import info.nightscout.utils.PercentageSplitter; @@ -1709,6 +1714,19 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { profileSwitch.percentage = trJson.getInt("percentage"); if (trJson.has("profileJson")) profileSwitch.profileJson = trJson.getString("profileJson"); + else { + ProfileStore store = ConfigBuilderPlugin.getActiveProfileInterface().getProfile(); + Profile profile = store.getSpecificProfile(profileSwitch.profileName); + if (profile != null) { + profileSwitch.profileJson = profile.getData().toString(); + log.debug("Profile switch prefilled with JSON from local store"); + } else { + Notification notification = new Notification(Notification.NO_LOCALE_PROFILE_FOUND, MainApp.sResources.getString(R.string.nolocaleprofilefound), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + log.debug("JSON for profile switch doesn't exist. Ignoring ..."); + return; + } + } if (trJson.has("profilePlugin")) profileSwitch.profilePlugin = trJson.getString("profilePlugin"); createOrUpdate(profileSwitch); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java index abd0f2b26b..1ef71c2f75 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/services/NSClientService.java @@ -114,6 +114,18 @@ public class NSClientService extends Service { initialize(); } + @Override + public void onCreate() { + super.onCreate(); + mWakeLock.acquire(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mWakeLock.release(); + } + public class LocalBinder extends Binder { public NSClientService getServiceInstance() { return NSClientService.this; @@ -182,8 +194,6 @@ public class NSClientService extends Service { public void initialize() { dataCounter = 0; - NSClientService.mWakeLock.acquire(); - readPreferences(); if (!nsAPISecret.equals("")) @@ -221,7 +231,6 @@ public class NSClientService extends Service { MainApp.bus().post(new EventNSClientNewLog("NSCLIENT", "No NS URL specified")); MainApp.bus().post(new EventNSClientStatus("Not configured")); } - NSClientService.mWakeLock.release(); } private Emitter.Listener onConnect = new Emitter.Listener() { @@ -242,6 +251,15 @@ public class NSClientService extends Service { public void destroy() { if (mSocket != null) { + mSocket.off(Socket.EVENT_CONNECT); + mSocket.off(Socket.EVENT_DISCONNECT); + mSocket.off(Socket.EVENT_PING); + mSocket.off("dataUpdate"); + mSocket.off("announcement"); + mSocket.off("alarm"); + mSocket.off("urgent_alarm"); + mSocket.off("clear_alarm"); + MainApp.bus().post(new EventNSClientNewLog("NSCLIENT", "destroy")); isConnected = false; hasWriteAuth = false; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index 4dcb174209..4c14f33533 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -248,9 +248,11 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { determineBasalResultAMA.changeRequested = false; // limit requests on openloop mode if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { - if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) + if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultAMA.rate == 0 && determineBasalResultAMA.duration == 0) { + // going to cancel + } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { determineBasalResultAMA.changeRequested = false; - if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) + } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) determineBasalResultAMA.changeRequested = false; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java index ac360d4ec8..d56dc44b89 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java @@ -232,9 +232,11 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { determineBasalResultMA.changeRequested = false; // limit requests on openloop mode if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { - if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) + if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultMA.rate == 0 && determineBasalResultMA.duration == 0) { + // going to cancel + } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { determineBasalResultMA.changeRequested = false; - if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) + } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) determineBasalResultMA.changeRequested = false; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java index 34ad8ff7ba..da2a6b04aa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/notifications/Notification.java @@ -60,6 +60,7 @@ public class Notification { public static final int MINIMAL_BASAL_VALUE_REPLACED = 29; public static final int BASAL_PROFILE_NOT_ALIGNED_TO_HOURS = 30; public static final int ZERO_VALUE_IN_PROFILE = 31; + public static final int NO_LOCALE_PROFILE_FOUND = 32; public int id; public Date date; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java index 7b72ee5d04..506bce7be5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java @@ -172,17 +172,17 @@ public class ComboFragment extends SubscriberFragment implements View.OnClickLis } // reservoir - int reservoirLevel = plugin.getPump().reservoirLevel; - reservoirView.setText(reservoirLevel == -1 ? "" : "" + reservoirLevel + " " - + MainApp.sResources.getString(R.string.treatments_wizard_unit_label)); if (ps.insulinState == PumpState.LOW) { reservoirView.setTextColor(Color.YELLOW); + reservoirView.setText(R.string.combo_reservoir_low); reservoirView.setTypeface(null, Typeface.BOLD); } else if (ps.insulinState == PumpState.EMPTY) { reservoirView.setTextColor(Color.RED); + reservoirView.setText(R.string.combo_reservoir_empty); reservoirView.setTypeface(null, Typeface.BOLD); } else { reservoirView.setTextColor(Color.WHITE); + reservoirView.setText(R.string.combo_reservoir_normal); reservoirView.setTypeface(null, Typeface.NORMAL); } 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 93b2e0af8c..bc27a3ebbc 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 @@ -339,7 +339,7 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf if (!pump.initialized) { initializePump(); } else { - runCommand(MainApp.sResources.getString(R.string.combo_pump_action_refreshing), 1, ruffyScripter::readReservoirLevelAndLastBolus); + runCommand(MainApp.sResources.getString(R.string.combo_pump_action_refreshing), 1, ruffyScripter::readPumpState); } } @@ -379,13 +379,10 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf MainApp.bus().post(new EventInitializationChanged()); // ComboFragment updates state fully only after the pump has initialized, so run this manually here - updateLocalData(runCommand(null, 1, ruffyScripter::readReservoirLevelAndLastBolus)); + updateLocalData(readBasalResult); } private void updateLocalData(CommandResult result) { - if (result.reservoirLevel != PumpState.UNKNOWN) { - pump.reservoirLevel = result.reservoirLevel; - } if (result.state.menu != null) { pump.state = result.state; } @@ -470,18 +467,13 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf } lastRequestedBolus = new Bolus(System.currentTimeMillis(), detailedBolusInfo.insulin, true); - CommandResult stateResult = runCommand(null, 1, ruffyScripter::readReservoirLevelAndLastBolus); + CommandResult stateResult = runCommand(null, 1, ruffyScripter::readPumpState); long pumpTimeWhenBolusWasRequested = stateResult .state.pumpTime; if (!stateResult.success || pumpTimeWhenBolusWasRequested == 0) { return new PumpEnactResult().success(false).enacted(false) .comment(MainApp.sResources.getString(R.string.combo_error_no_bolus_delivered)); } - if (stateResult.reservoirLevel < detailedBolusInfo.insulin) { - return new PumpEnactResult().success(false).enacted(false) - .comment(MainApp.sResources.getString(R.string.combo_reservoir_level_insufficient_for_bolus)); - } - try { pump.activity = MainApp.sResources.getString(R.string.combo_pump_action_bolusing, detailedBolusInfo.insulin); MainApp.bus().post(new EventComboPumpUpdateGUI()); @@ -1034,9 +1026,11 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf try { JSONObject pumpJson = new JSONObject(); pumpJson.put("clock", DateUtil.toISOString(pump.lastSuccessfulCmdTime)); - if (pump.reservoirLevel != -1) { - pumpJson.put("reservoir", pump.reservoirLevel); - } + + int level = 150; + if (pump.state.insulinState == PumpState.LOW) level = 8; + else if (pump.state.insulinState == PumpState.EMPTY) level = 0; + pumpJson.put("reservoir", level); JSONObject statusJson = new JSONObject(); statusJson.put("status", getStateSummary()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java index 0455fa3ada..5980cf11eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java @@ -17,7 +17,6 @@ class ComboPump { public volatile String activity; @NonNull volatile PumpState state = new PumpState(); - volatile int reservoirLevel = -1; @NonNull volatile BasalProfile basalProfile = new BasalProfile(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java index 7c777a5652..cb05517cfc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpVirtual/VirtualPumpPlugin.java @@ -227,7 +227,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface { @Override public boolean isThisProfileSet(Profile profile) { - return false; + return true; } @Override diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9438e06d19..8edea19afe 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -778,5 +778,4 @@ Standarwert: 2\nBolus snooze (\"Bolus-Schlummer\") bremst den Loop nach einem Mahleiten-Bolus, damit dieser nicht mit niedrigen TBR reagiert, wenn Du gerade gegessen hast. Beispiel: Der Standardwert 2 bewirkt, dass bei einem 3 Stunden DIA der Bolus snooze während 1.5 Stunden nach dem Bolus linear ausläuft (3 h Dia / 2 = 1.5 h Bolus snooze). Standardwert: 3.0\nDies ist eine Einstellung für die Standard-Kohlenhydrat-Absorptionswirkung pro 5 Minuten. Der Standardwert ist 3mg/dl/5min. Dies wirkt sich darauf aus, wie schnell der COB-Wert fällt und wieviel KH-Absorption bei der Berechnung des vorhergesagten BZ angenommen wird, wenn der BZ stärker als erwartet fällt oder nicht so stark wie erwartet steigt. Achtung! Normalerweise musst Du diese Werte nicht ändern. Bitte KLICKE HIER und LESE den Text. Verändere Werte erst, wenn Du den Inhalt des Textes verstanden hast. - Nicht mehr genug Insulin im Reservoir für den Bolus diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 03e37403f3..100d35e2d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -811,6 +811,7 @@ BG available from selected source Basal values not aligned to hours Zero value in profile + Received profile switch from NS but profile doesn\'t exist localy Stopping bolus delivery Bolus delivery stopped Programming pump for bolusing @@ -864,6 +865,5 @@ 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. 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 - Not enough insulin left in reservoir for bolus diff --git a/app/src/test/java/info/nightscout/utils/BolusWizardTest.java b/app/src/test/java/info/nightscout/utils/BolusWizardTest.java new file mode 100644 index 0000000000..21872f47bc --- /dev/null +++ b/app/src/test/java/info/nightscout/utils/BolusWizardTest.java @@ -0,0 +1,85 @@ +package info.nightscout.utils; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.PumpMDI.MDIPlugin; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by kuchjir on 12/12/2017. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest( { MainApp.class, GlucoseStatus.class, ConfigBuilderPlugin.class }) +public class BolusWizardTest { + private static final double PUMP_BOLUS_STEP = 0.1; + + @Test + /** Should calculate the same bolus when different blood glucose but both in target range */ + public void shuldCalculateTheSameBolusWhenBGsInRange() throws Exception { + BolusWizard bw = new BolusWizard(); + Profile profile = setupProfile(4d, 8d, 20d, 12d); + + Double bolusForBg42 = bw.doCalc(profile, null, 20, 0.0,4.2, 0d, 100d, true, true, false, false); + Double bolusForBg54 = bw.doCalc(profile, null, 20, 0.0,5.4, 0d, 100d, true, true, false, false); + Assert.assertEquals(bolusForBg42, bolusForBg54); + } + + @Test + public void shuldCalculateHigherBolusWhenHighBG() throws Exception { + BolusWizard bw = new BolusWizard(); + Profile profile = setupProfile(4d, 8d, 20d, 12d); + + Double bolusForHighBg = bw.doCalc(profile, null, 20, 0d,9.8, 0d, 100d, true, true, false, false); + Double bolusForBgInRange = bw.doCalc(profile, null, 20, 0.0,5.4, 0d, 100d, true, true, false, false); + Assert.assertTrue(bolusForHighBg > bolusForBgInRange); + } + + @Test + public void shuldCalculateLowerBolusWhenLowBG() throws Exception { + BolusWizard bw = new BolusWizard(); + Profile profile = setupProfile(4d, 8d, 20d, 12d); + + Double bolusForLowBg = bw.doCalc(profile, null, 20, 0d,3.6, 0d, 100d, true, true, false, false); + Double bolusForBgInRange = bw.doCalc(profile, null, 20, 0.0,5.4, 0d, 100d, true, true, false, false); + Assert.assertTrue(bolusForLowBg < bolusForBgInRange); + } + + private Profile setupProfile(Double targetLow, Double targetHigh, Double insulinSensitivityFactor, Double insulinToCarbRatio) { + Profile profile = mock(Profile.class); + when(profile.getTargetLow()).thenReturn(targetLow); + when(profile.getTargetHigh()).thenReturn(targetHigh); + when(profile.getIsf()).thenReturn(insulinSensitivityFactor); + when(profile.getIc()).thenReturn(insulinToCarbRatio); + + PowerMockito.mockStatic(GlucoseStatus.class); + when(GlucoseStatus.getGlucoseStatusData()).thenReturn(null); + + ConfigBuilderPlugin treatment = mock(ConfigBuilderPlugin.class); + IobTotal iobTotalZero = new IobTotal(System.currentTimeMillis()); + when(treatment.getLastCalculationTreatments()).thenReturn(iobTotalZero); + when(treatment.getLastCalculationTempBasals()).thenReturn(iobTotalZero); + PowerMockito.mockStatic(MainApp.class); + when(MainApp.getConfigBuilder()).thenReturn(treatment); + + PowerMockito.mockStatic(ConfigBuilderPlugin.class); + PumpInterface pump = MDIPlugin.getPlugin(); + pump.getPumpDescription().bolusStep = PUMP_BOLUS_STEP; + when(ConfigBuilderPlugin.getActivePump()).thenReturn(pump); + + return profile; + } +} \ No newline at end of file