Merge pull request #17 from jotomo/combo-scripter-v2

10.01
This commit is contained in:
Simon Pauwels 2018-01-10 22:09:19 +01:00 committed by GitHub
commit 0c2ef80854
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 152 additions and 29 deletions

View file

@ -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

View file

@ -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'

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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());

View file

@ -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();

View file

@ -227,7 +227,7 @@ public class VirtualPumpPlugin implements PluginBase, PumpInterface {
@Override
public boolean isThisProfileSet(Profile profile) {
return false;
return true;
}
@Override

View file

@ -778,5 +778,4 @@
<string name="openapsama_bolussnooze_dia_divisor_summary">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).</string>
<string name="openapsama_min_5m_carbimpact_summary">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.</string>
<string name="openapsama_link_to_preferncejson_doc_txt">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.</string>
<string name="combo_reservoir_level_insufficient_for_bolus">Nicht mehr genug Insulin im Reservoir für den Bolus</string>
</resources>

View file

@ -811,6 +811,7 @@
<string name="hasbgdata">BG available from selected source</string>
<string name="basalprofilenotaligned">Basal values not aligned to hours</string>
<string name="zerovalueinprofile">Zero value in profile</string>
<string name="nolocaleprofilefound">Received profile switch from NS but profile doesn\'t exist localy</string>
<string name="bolusstopping">Stopping bolus delivery</string>
<string name="bolusstopped">Bolus delivery stopped</string>
<string name="combo_programming_bolus">Programming pump for bolusing</string>
@ -864,6 +865,5 @@
<string name="combo_error_partial_bolus_delivered">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.</string>
<string name="combo_error_bolus_verification_failed">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.</string>
<string name="combo_error_bolus_recovery_progress">Recovering from connection loss</string>
<string name="combo_reservoir_level_insufficient_for_bolus">Not enough insulin left in reservoir for bolus</string>
</resources>

View file

@ -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;
}
}