package info.nightscout.androidaps.plugins.Wear; import android.os.Handler; import android.os.HandlerThread; import android.support.annotation.NonNull; import com.j256.ormlite.dao.Dao; import java.sql.SQLException; import java.text.DecimalFormat; import java.util.Date; import info.nightscout.androidaps.BuildConfig; 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.GlucoseStatus; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.plugins.Actions.dialogs.FillDialog; import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile; import info.nightscout.androidaps.plugins.TempTargetRange.TempTargetRangePlugin; import info.nightscout.androidaps.plugins.TempTargetRange.events.EventTempTargetRangeChange; import info.nightscout.utils.BolusWizard; import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; import info.nightscout.utils.ToastUtils; /** * Created by adrian on 09/02/17. */ public class ActionStringHandler { public static final int TIMEOUT = 65 * 1000; private static long lastSentTimestamp = 0; private static String lastConfirmActionString = null; private static BolusWizard lastBolusWizard = null; private static HandlerThread handlerThread = new HandlerThread(FillDialog.class.getSimpleName()); static { handlerThread.start(); } public synchronized static void handleInitiate(String actionstring){ if(!BuildConfig.WEAR_CONTROL) return; lastBolusWizard = null; String rTitle = "CONFIRM"; //TODO: i18n String rMessage = ""; String rAction = ""; // do the parsing and check constraints String[] act = actionstring.split("\\s+"); if ("fillpreset".equals(act[0])) { ///////////////////////////////////// PRIME/FILL double amount = 0d; if ("1".equals(act[1])) { amount = SP.getDouble("fill_button1", 0.3); } else if ("2".equals(act[1])) { amount = SP.getDouble("fill_button2", 0d); } else if ("3".equals(act[1])) { amount = SP.getDouble("fill_button3", 0d); } else { return; } Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(amount); rMessage += MainApp.instance().getString(R.string.primefill) + ": " + insulinAfterConstraints + "U"; if (insulinAfterConstraints - amount != 0) rMessage += "\n" + MainApp.instance().getString(R.string.constraintapllied); rAction += "fill " + insulinAfterConstraints; } else if ("fill".equals(act[0])) { ////////////////////////////////////////////// PRIME/FILL double amount = SafeParse.stringToDouble(act[1]); Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(amount); rMessage += MainApp.instance().getString(R.string.primefill) + ": " + insulinAfterConstraints + "U"; if (insulinAfterConstraints - amount != 0) rMessage += "\n" + MainApp.instance().getString(R.string.constraintapllied); rAction += "fill " + insulinAfterConstraints; } else if ("bolus".equals(act[0])) { ////////////////////////////////////////////// BOLUS double insulin = SafeParse.stringToDouble(act[1]); int carbs = SafeParse.stringToInt(act[2]); Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(insulin); Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(carbs); rMessage += MainApp.instance().getString(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"; rMessage += MainApp.instance().getString(R.string.carbs) + ": " + carbsAfterConstraints + "g"; if ((insulinAfterConstraints - insulin != 0) || (carbsAfterConstraints - carbs != 0)) { rMessage += "\n" + MainApp.instance().getString(R.string.constraintapllied); } rAction += "bolus " + insulinAfterConstraints + " " + carbsAfterConstraints; } else if ("temptarget".equals(act[0])) { ///////////////////////////////////////////////////////// TEMPTARGET boolean isMGDL = Boolean.parseBoolean(act[1]); NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile(); TempTargetRangePlugin tempTargetRangePlugin = (TempTargetRangePlugin) MainApp.getSpecificPlugin(TempTargetRangePlugin.class); if (!(Config.APS && tempTargetRangePlugin != null && tempTargetRangePlugin.isEnabled(PluginBase.GENERAL))) { sendError("TempTargets not possible! Please check your configuration."); return; } if (profile == null) { sendError("No profile found!"); return; } if(profile.getUnits().equals(Constants.MGDL) != isMGDL){ sendError("Different units used on watch and phone!"); return; } int duration = SafeParse.stringToInt(act[2]); if (duration == 0){ rMessage += "Zero-Temp-Target - cancelling running Temp-Targets?"; rAction = "temptarget true 0 0 0"; } else { double low = SafeParse.stringToDouble(act[3]); double high = SafeParse.stringToDouble(act[4]); if(!isMGDL){ low *= Constants.MMOLL_TO_MGDL; high *= Constants.MMOLL_TO_MGDL; } if (low < Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) { sendError("Min-BG out of range!"); return; } if (high < Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) { sendError("Max-BG out of range!"); return; } rMessage += "Temptarget:\nMin: " + act[3] + "\nMax: " + act[4] + "\nDuration: " + act[2]; rAction = actionstring; } } else if ("status".equals(act[0])) { ////////////////////////////////////////////// STATUS rTitle = "STATUS"; rAction = "statusmessage"; if("pump".equals(act[1])){ rTitle += " PUMP"; rMessage = getPumpStatus(); } else if("loop".equals(act[1])){ rTitle += " LOOP"; rMessage = getLoopStatus(); } else if("targets".equals(act[1])){ rTitle += " TARGETS"; rMessage = getTargetsStatus(); } } else if ("wizard".equals(act[0])) { ////////////////////////////////////////////// WIZARD Integer carbsBeforeConstraints = SafeParse.stringToInt(act[1]); Integer carbsAfterConstraints = MainApp.getConfigBuilder().applyCarbsConstraints(carbsBeforeConstraints); if(carbsAfterConstraints - carbsBeforeConstraints !=0){ sendError("Carb constraint violation!"); return; } boolean useBG = Boolean.parseBoolean(act[2]); boolean useBolusIOB = Boolean.parseBoolean(act[3]); boolean useBasalIOB = Boolean.parseBoolean(act[4]); NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile(); if (profile == null) { sendError("No profile found!"); return; } BgReading bgReading = GlucoseStatus.actualBg(); if(bgReading==null && useBG){ sendError("No recent BG to base calculation on!"); return; } DecimalFormat format = new DecimalFormat("0.00"); BolusWizard bolusWizard = new BolusWizard(); bolusWizard.doCalc(profile.getDefaultProfile(), carbsAfterConstraints, useBG?bgReading.valueToUnits(profile.getUnits()):0d, 0d, useBolusIOB, useBasalIOB); Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(bolusWizard.calculatedTotalInsulin); if(insulinAfterConstraints - bolusWizard.calculatedTotalInsulin !=0){ sendError("Insulin contraint violation!" + "\nCannot deliver " + format.format(bolusWizard.calculatedTotalInsulin) +"!"); return; } double insulin = bolusWizard.calculatedTotalInsulin; if(bolusWizard.calculatedTotalInsulin < 0) { bolusWizard.calculatedTotalInsulin = 0d; } if(bolusWizard.calculatedTotalInsulin <=0 && bolusWizard.carbs <=0){ rAction = "info"; rTitle = "INFO"; } else { rAction = actionstring; } rMessage += "Carbs: " + bolusWizard.carbs + "g"; rMessage += "\nBolus: " + format.format(bolusWizard.calculatedTotalInsulin) + "U"; rMessage += "\n_____________"; rMessage += "\nCalc (IC:" + DecimalFormatter.to1Decimal(bolusWizard.ic) + ", " + "ISF:" + DecimalFormatter.to1Decimal(bolusWizard.sens) + "): "; rMessage += "\nFrom Carbs: " + format.format(bolusWizard.insulinFromCarbs) + "U"; if(useBG)rMessage += "\nFrom BG: " + format.format(bolusWizard.insulinFromBG) + "U"; if(useBolusIOB)rMessage += "\nBolus IOB: " + format.format(bolusWizard.insulingFromBolusIOB) + "U"; if(useBasalIOB)rMessage += "\nBasal IOB: " + format.format(bolusWizard.insulingFromBasalsIOB) + "U"; lastBolusWizard = bolusWizard; } else return; // send result WearFragment.getPlugin(MainApp.instance()).requestActionConfirmation(rTitle, rMessage, rAction); lastSentTimestamp = System.currentTimeMillis(); lastConfirmActionString = rAction; } @NonNull private static String getPumpStatus() { return MainApp.getConfigBuilder().shortStatus(false); } @NonNull private static String getLoopStatus() { String ret = ""; // decide if enabled/disabled closed/open; what Plugin as APS? final LoopPlugin activeloop = MainApp.getConfigBuilder().getActiveLoop(); if(activeloop != null && activeloop.isEnabled(activeloop.getType())) { if (MainApp.getConfigBuilder().isClosedModeEnabled()) { ret += "CLOSED LOOP\n"; } else { ret += "OPEN LOOP\n"; } final APSInterface aps = MainApp.getConfigBuilder().getActiveAPS(); ret += "APS: " + ((aps==null)?"NO APS SELECTED!":((PluginBase) aps).getName()); if(activeloop.lastRun != null){ if(activeloop.lastRun.lastAPSRun != null) ret += "\nLast Run: " + DateUtil.timeString(activeloop.lastRun.lastAPSRun); if(activeloop.lastRun.lastEnact != null) ret += "\nLast Enact: " + DateUtil.timeString(activeloop.lastRun.lastEnact); } } else { ret += "LOOP DISABLED\n"; } return ret; } @NonNull private static String getTargetsStatus() { String ret = ""; if (!Config.APS){ return "Targets only apply in APS mode!"; } NSProfile profile = MainApp.getConfigBuilder().getActiveProfile().getProfile(); if (profile == null){ return "No profile set :("; } //Check for Temp-Target: TempTargetRangePlugin tempTargetRangePlugin = (TempTargetRangePlugin) MainApp.getSpecificPlugin(TempTargetRangePlugin.class); if (Config.APS && tempTargetRangePlugin != null && tempTargetRangePlugin.isEnabled(PluginBase.GENERAL)) { TempTarget tempTarget = tempTargetRangePlugin.getTempTargetInProgress(new Date().getTime()); if (tempTarget != null) { ret += "Temp Target: " + NSProfile.toUnitsString(tempTarget.low, NSProfile.fromMgdlToUnits(tempTarget.low, profile.getUnits()), profile.getUnits()) + " - " + NSProfile.toUnitsString(tempTarget.high, NSProfile.fromMgdlToUnits(tempTarget.high, profile.getUnits()), profile.getUnits()); ret += "\nuntil: " + DateUtil.timeString(tempTarget.getPlannedTimeEnd()); ret += "\n\n"; } } //Default Range/Target Double maxBgDefault = Constants.MAX_BG_DEFAULT_MGDL; Double minBgDefault = Constants.MIN_BG_DEFAULT_MGDL; Double targetBgDefault = Constants.TARGET_BG_DEFAULT_MGDL; if (!profile.getUnits().equals(Constants.MGDL)) { maxBgDefault = Constants.MAX_BG_DEFAULT_MMOL; minBgDefault = Constants.MIN_BG_DEFAULT_MMOL; targetBgDefault = Constants.TARGET_BG_DEFAULT_MMOL; } ret += "DEFAULT RANGE: "; ret += SP.getDouble("openapsma_min_bg", minBgDefault) + " - " + SP.getDouble("openapsma_max_bg", maxBgDefault); ret += " target: " + SP.getDouble("openapsma_target_bg", targetBgDefault); return ret; } public synchronized static void handleConfirmation(String actionString){ if(!BuildConfig.WEAR_CONTROL) return; //Guard from old or duplicate confirmations if (lastConfirmActionString == null) return; if (!lastConfirmActionString.equals(actionString)) return; if (System.currentTimeMillis() - lastSentTimestamp > TIMEOUT) return; lastConfirmActionString = null; // do the parsing, check constraints and enact! String[] act = actionString.split("\\s+"); if ("fill".equals(act[0])){ Double amount = SafeParse.stringToDouble(act[1]); Double insulinAfterConstraints = MainApp.getConfigBuilder().applyBolusConstraints(amount); if(amount - insulinAfterConstraints != 0){ ToastUtils.showToastInUiThread(MainApp.instance(), "aborting: previously applied constraint changed"); sendError("aborting: previously applied constraint changed"); return; } doFillBolus(amount); } else if ("temptarget".equals(act[0])) { int duration = SafeParse.stringToInt(act[2]); double low = SafeParse.stringToDouble(act[3]); double high = SafeParse.stringToDouble(act[4]); boolean isMGDL = Boolean.parseBoolean(act[1]); if(!isMGDL){ low *= Constants.MMOLL_TO_MGDL; high *= Constants.MMOLL_TO_MGDL; } generateTempTarget(duration, low, high); } else if ("wizard".equals(act[0])){ //use last calculation as confirmed string matches doBolus(lastBolusWizard.calculatedTotalInsulin, lastBolusWizard.carbs); lastBolusWizard = null; } else if ("bolus".equals(act[0])) { double insulin = SafeParse.stringToDouble(act[1]); int carbs = SafeParse.stringToInt(act[2]); doBolus(insulin, carbs); } lastBolusWizard = null; } private static void generateTempTarget(int duration, double low, double high) { TempTarget tempTarget = new TempTarget(); tempTarget.timeStart = new Date(); tempTarget.duration = duration; tempTarget.reason = "WearPlugin"; if(tempTarget.duration != 0) { tempTarget.low = low; tempTarget.high = high; } else { tempTarget.low = 0; tempTarget.high = 0; } tempTarget.setTimeIndex(tempTarget.getTimeIndex()); Dao<TempTarget, Long> dao = null; try { dao = MainApp.getDbHelper().getDaoTempTargets(); dao.createIfNotExists(tempTarget); MainApp.bus().post(new EventTempTargetRangeChange()); //TODO: Nightscout-Treatment for Temp-Target! //ConfigBuilderPlugin.uploadCareportalEntryToNS(data); } catch (SQLException e) { e.printStackTrace(); } } private static void doFillBolus(final Double amount) { //if(1==1)return; Handler handler = new Handler(handlerThread.getLooper()); handler.post(new Runnable() { @Override public void run() { PumpEnactResult result = MainApp.getConfigBuilder().deliverTreatment(amount, 0, null, false); if (!result.success) { sendError(MainApp.sResources.getString(R.string.treatmentdeliveryerror) + "\n" + result.comment); } } }); } private static void doBolus(final Double amount, final Integer carbs) { //if(1==1)return; Handler handler = new Handler(handlerThread.getLooper()); handler.post(new Runnable() { @Override public void run() { PumpEnactResult result = MainApp.getConfigBuilder().deliverTreatment(amount, carbs, null, true); if (!result.success) { sendError(MainApp.sResources.getString(R.string.treatmentdeliveryerror) + "\n" + result.comment); } } }); } private synchronized static void sendError(String errormessage){ WearFragment.getPlugin(MainApp.instance()).requestActionConfirmation("ERROR", errormessage, "error"); lastSentTimestamp = System.currentTimeMillis(); lastConfirmActionString = null; lastBolusWizard = null; } }