diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java index 86e7ae74b7..3bf4042512 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.java @@ -1,9 +1,13 @@ package info.nightscout.androidaps.plugins.general.automation; +import java.util.ArrayList; +import java.util.List; + import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.general.automation.actions.Action; public class AutomationPlugin extends PluginBase { @@ -15,6 +19,8 @@ public class AutomationPlugin extends PluginBase { return plugin; } + List actions = new ArrayList<>(); + private AutomationPlugin() { super(new PluginDescription() .mainType(PluginType.GENERAL) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java new file mode 100644 index 0000000000..5523edf15f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Action.java @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.general.automation.actions; + +public abstract class Action { + + abstract void doAction(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/AutomationEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/AutomationEvent.java new file mode 100644 index 0000000000..904cc78a4e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/AutomationEvent.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.general.automation.actions; + +import java.util.ArrayList; +import java.util.List; + +public class AutomationEvent { + + Trigger trigger; + List actions = new ArrayList<>(); + + AutomationEvent() { + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Trigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Trigger.java index fbb80d7d65..cfff99f6ab 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Trigger.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/Trigger.java @@ -5,17 +5,26 @@ import org.json.JSONObject; abstract class Trigger { + protected static final int ISLOWER = -2; + protected static final int ISEQUALORLOWER = -1; + protected static final int ISEQUAL = 0; + protected static final int ISEQUALORGREATER = 1; + protected static final int ISGREATER = 2; + + protected static final int NOTAVAILABLE = 10; + Trigger() { } - Trigger(String js) { - fromJSON(js); - } - abstract boolean shouldRun(); + abstract String toJSON(); + abstract Trigger fromJSON(String data); + void notifyAboutRun(long time) { + } + static Trigger instantiate(JSONObject object) { try { String type = object.getString("type"); @@ -27,6 +36,6 @@ abstract class Trigger { } return null; - } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerBg.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerBg.java new file mode 100644 index 0000000000..4e945cacfa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerBg.java @@ -0,0 +1,84 @@ +package info.nightscout.androidaps.plugins.general.automation.actions; + +import org.json.JSONException; +import org.json.JSONObject; + +import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.ConfigBuilder.ProfileFunctions; +import info.nightscout.utils.JsonHelper; + +public class TriggerBg extends Trigger { + + double threshold; + int comparator = ISEQUAL; + String units = ProfileFunctions.getInstance().getProfileUnits(); + + @Override + synchronized boolean shouldRun() { + GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); + + if (glucoseStatus == null && comparator == NOTAVAILABLE) + return true; + if (glucoseStatus == null) + return false; + + switch (comparator) { + case ISLOWER: + return glucoseStatus.glucose < Profile.toMgdl(threshold, units); + case ISEQUALORLOWER: + return glucoseStatus.glucose <= Profile.toMgdl(threshold, units); + case ISEQUAL: + return glucoseStatus.glucose == Profile.toMgdl(threshold, units); + case ISEQUALORGREATER: + return glucoseStatus.glucose >= Profile.toMgdl(threshold, units); + case ISGREATER: + return glucoseStatus.glucose > Profile.toMgdl(threshold, units); + } + return false; + } + + @Override + synchronized String toJSON() { + JSONObject o = new JSONObject(); + try { + o.put("type", TriggerBg.class.getName()); + JSONObject data = new JSONObject(); + data.put("threshold", threshold); + data.put("comparator", comparator); + data.put("units", units); + o.put("data", data.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + return o.toString(); + } + + @Override + Trigger fromJSON(String data) { + try { + JSONObject d = new JSONObject(data); + threshold = JsonHelper.safeGetDouble(d, "threshold"); + comparator = JsonHelper.safeGetInt(d, "comparator"); + units = JsonHelper.safeGetString(d, "units"); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + TriggerBg threshold(double threshold) { + this.threshold = threshold; + return this; + } + + TriggerBg comparator(int comparator) { + this.comparator = comparator; + return this; + } + + TriggerBg units(String units) { + this.units = units; + return this; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerTime.java new file mode 100644 index 0000000000..8281edb906 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerTime.java @@ -0,0 +1,188 @@ +package info.nightscout.androidaps.plugins.general.automation.actions; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Calendar; + +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.JsonHelper; +import info.nightscout.utils.T; + +public class TriggerTime extends Trigger { + + long lastRun; + + // Single execution + long runAt; + + // Recurring + boolean recurring; + boolean monday = true; + boolean tuesday = true; + boolean wednesday = true; + boolean thursday = true; + boolean friday = true; + boolean saturday = true; + boolean sunday = true; + int hour; + int minute; + + long validTo; + + @Override + boolean shouldRun() { + if (recurring) { + if (validTo != 0 && DateUtil.now() > validTo) + return false; + Calendar c = Calendar.getInstance(); + int scheduledDayOfWeek = c.get(Calendar.DAY_OF_WEEK); + + Calendar scheduledCal = DateUtil.gregorianCalendar(); + scheduledCal.set(Calendar.HOUR_OF_DAY, hour); + scheduledCal.set(Calendar.MINUTE, minute); + scheduledCal.set(Calendar.SECOND, 0); + long scheduled = scheduledCal.getTimeInMillis(); + + if (monday && scheduledDayOfWeek == Calendar.MONDAY || + tuesday && scheduledDayOfWeek == Calendar.TUESDAY || + wednesday && scheduledDayOfWeek == Calendar.WEDNESDAY || + thursday && scheduledDayOfWeek == Calendar.THURSDAY || + friday && scheduledDayOfWeek == Calendar.FRIDAY || + saturday && scheduledDayOfWeek == Calendar.SATURDAY || + sunday && scheduledDayOfWeek == Calendar.SUNDAY) { + if (DateUtil.now() >= scheduled && DateUtil.now() - scheduled < T.mins(5).msecs()) { + if (lastRun < scheduled) + return true; + } + } + return false; + } else { + long now = DateUtil.now(); + if (now >= runAt && now - runAt < T.mins(5).msecs()) + return true; + return false; + } + } + + @Override + String toJSON() { + JSONObject object = new JSONObject(); + JSONObject data = new JSONObject(); + try { + data.put("lastRun", lastRun); + data.put("runAt", runAt); + data.put("recurring", recurring); + data.put("monday", monday); + data.put("tuesday", tuesday); + data.put("wednesday", wednesday); + data.put("thursday", thursday); + data.put("friday", friday); + data.put("saturday", saturday); + data.put("sunday", sunday); + data.put("hour", hour); + data.put("minute", minute); + data.put("validTo", validTo); + object.put("type", TriggerTime.class.getName()); + object.put("data", data.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + return object.toString(); + } + + @Override + Trigger fromJSON(String data) { + JSONObject o; + try { + o = new JSONObject(data); + lastRun = JsonHelper.safeGetLong(o, "lastRun"); + runAt = JsonHelper.safeGetLong(o, "runAt"); + recurring = JsonHelper.safeGetBoolean(o, "recurring"); + monday = JsonHelper.safeGetBoolean(o, "monday"); + tuesday = JsonHelper.safeGetBoolean(o, "tuesday"); + wednesday = JsonHelper.safeGetBoolean(o, "wednesday"); + thursday = JsonHelper.safeGetBoolean(o, "thursday"); + friday = JsonHelper.safeGetBoolean(o, "friday"); + saturday = JsonHelper.safeGetBoolean(o, "saturday"); + sunday = JsonHelper.safeGetBoolean(o, "sunday"); + hour = JsonHelper.safeGetInt(o, "hour"); + minute = JsonHelper.safeGetInt(o, "minute"); + validTo = JsonHelper.safeGetLong(o, "validTo"); + } catch (JSONException e) { + e.printStackTrace(); + } + return this; + } + + @Override + void notifyAboutRun(long time) { + lastRun = time; + } + + TriggerTime lastRun(long lastRun) { + this.lastRun = lastRun; + return this; + } + + TriggerTime runAt(long runAt) { + this.runAt = runAt; + return this; + } + + TriggerTime recurring(boolean recurring) { + this.recurring = recurring; + return this; + } + + TriggerTime monday(boolean monday) { + this.monday = monday; + return this; + } + + TriggerTime tuesday(boolean tuesday) { + this.tuesday = tuesday; + return this; + } + + TriggerTime wednesday(boolean wednesday) { + this.wednesday = wednesday; + return this; + } + + TriggerTime thursday(boolean thursday) { + this.thursday = thursday; + return this; + } + + TriggerTime friday(boolean friday) { + this.friday = friday; + return this; + } + + TriggerTime saturday(boolean saturday) { + this.saturday = saturday; + return this; + } + + TriggerTime sunday(boolean sunday) { + this.sunday = sunday; + return this; + } + + TriggerTime validTo(long validTo) { + this.validTo = validTo; + return this; + } + + TriggerTime hour(int hour) { + this.hour = hour; + return this; + } + + TriggerTime minute(int minute) { + this.minute = minute; + return this; + } + +} diff --git a/app/src/main/java/info/nightscout/utils/DateUtil.java b/app/src/main/java/info/nightscout/utils/DateUtil.java index c5d5eed51c..0cf3a6c87a 100644 --- a/app/src/main/java/info/nightscout/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/utils/DateUtil.java @@ -183,4 +183,8 @@ public class DateUtil { public static long roundDateToSec(long date) { return date - date % 1000; } + + public static GregorianCalendar gregorianCalendar() { + return new GregorianCalendar(); + } } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerBgTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerBgTest.java new file mode 100644 index 0000000000..7215ee8d3e --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerBgTest.java @@ -0,0 +1,105 @@ +package info.nightscout.androidaps.plugins.general.automation.actions; + +import com.squareup.otto.Bus; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Assert; +import org.junit.Before; +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 java.util.ArrayList; +import java.util.List; + +import info.AAPSMocker; +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.plugins.ConfigBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSgv; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.T; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MainApp.class, Bus.class, ProfileFunctions.class, DateUtil.class}) +public class TriggerBgTest { + + @Test + public void shouldRunTest() { + when(MainApp.getDbHelper().getBgreadingsDataFromTime(anyLong(), anyBoolean())).thenReturn(generateOneCurrentRecordBgData()); + + TriggerBg t = new TriggerBg().units(Constants.MMOL).threshold(4.1d).comparator(Trigger.ISEQUAL); + Assert.assertFalse(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(214).comparator(Trigger.ISEQUAL); + Assert.assertTrue(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(214).comparator(Trigger.ISEQUALORGREATER); + Assert.assertTrue(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(214).comparator(Trigger.ISEQUALORLOWER); + Assert.assertTrue(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(215).comparator(Trigger.ISEQUAL); + Assert.assertFalse(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(215).comparator(Trigger.ISEQUALORLOWER); + Assert.assertTrue(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(215).comparator(Trigger.ISEQUALORGREATER); + Assert.assertFalse(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(213).comparator(Trigger.ISEQUALORGREATER); + Assert.assertTrue(t.shouldRun()); + t = new TriggerBg().units(Constants.MGDL).threshold(213).comparator(Trigger.ISEQUALORLOWER); + Assert.assertFalse(t.shouldRun()); + + when(MainApp.getDbHelper().getBgreadingsDataFromTime(anyLong(), anyBoolean())).thenReturn(new ArrayList<>()); + t = new TriggerBg().units(Constants.MGDL).threshold(213).comparator(Trigger.ISEQUALORLOWER); + Assert.assertFalse(t.shouldRun()); + t = new TriggerBg().comparator(Trigger.NOTAVAILABLE); + Assert.assertTrue(t.shouldRun()); + } + + String bgJson = "{\"data\":\"{\\\"comparator\\\":0,\\\"threshold\\\":4.1,\\\"units\\\":\\\"mmol\\\"}\",\"type\":\"info.nightscout.androidaps.plugins.general.automation.actions.TriggerBg\"}"; + + @Test + public void toJSONTest() { + TriggerBg t = new TriggerBg().units(Constants.MMOL).threshold(4.1d).comparator(Trigger.ISEQUAL); + Assert.assertEquals(bgJson, t.toJSON()); + } + + @Test + public void fromJSONTest() throws JSONException { + TriggerBg t = new TriggerBg().units(Constants.MMOL).threshold(4.1d).comparator(Trigger.ISEQUAL); + + TriggerBg t2 = (TriggerBg) Trigger.instantiate(new JSONObject(t.toJSON())); + Assert.assertEquals(Trigger.ISEQUAL, t2.comparator); + Assert.assertEquals(4.1d, t2.threshold, 0.01d); + Assert.assertEquals(Constants.MMOL, t2.units); + } + + @Before + public void mock() { + AAPSMocker.mockMainApp(); + AAPSMocker.mockBus(); + AAPSMocker.mockDatabaseHelper(); + AAPSMocker.mockProfileFunctions(); + + PowerMockito.mockStatic(DateUtil.class); + when(DateUtil.now()).thenReturn(1514766900000L + T.mins(1).msecs()); + + } + + List generateOneCurrentRecordBgData() { + List list = new ArrayList<>(); + try { + list.add(new BgReading(new NSSgv(new JSONObject("{\"mgdl\":214,\"mills\":1514766900000,\"direction\":\"Flat\"}")))); + } catch (JSONException e) { + e.printStackTrace(); + } + return list; + } + +} diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerTimeTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerTimeTest.java new file mode 100644 index 0000000000..c8a53fa30a --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/TriggerTimeTest.java @@ -0,0 +1,83 @@ +package info.nightscout.androidaps.plugins.general.automation.actions; + +import com.squareup.otto.Bus; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Assert; +import org.junit.Before; +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 java.util.GregorianCalendar; + +import info.AAPSMocker; +import info.nightscout.androidaps.MainApp; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.T; + +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MainApp.class, Bus.class, DateUtil.class, GregorianCalendar.class}) +public class TriggerTimeTest { + + long now = 1514766900000L; + + @Test + public void shouldRunTest() { + + // scheduled 1 min before + TriggerTime t = new TriggerTime().runAt(now - T.mins(1).msecs()); + Assert.assertTrue(t.shouldRun()); + // scheduled 1 min in the future + t = new TriggerTime().runAt(now + T.mins(1).msecs()); + Assert.assertFalse(t.shouldRun()); + + // limit by validTo + t = new TriggerTime().recurring(true).hour(1).minute(34).validTo(1); + Assert.assertFalse(t.shouldRun()); + + // scheduled 1 min before + t = new TriggerTime().recurring(true).hour(1).minute(34); + Assert.assertTrue(t.shouldRun()); + + // already run + t = new TriggerTime().recurring(true).hour(1).minute(34).lastRun(now - 1); + Assert.assertFalse(t.shouldRun()); + + } + + String timeJson = "{\"data\":\"{\\\"saturday\\\":true,\\\"runAt\\\":1514766840000,\\\"lastRun\\\":0,\\\"recurring\\\":false,\\\"thursday\\\":true,\\\"minute\\\":0,\\\"sunday\\\":true,\\\"tuesday\\\":true,\\\"hour\\\":0,\\\"wednesday\\\":true,\\\"friday\\\":true,\\\"monday\\\":true,\\\"validTo\\\":0}\",\"type\":\"info.nightscout.androidaps.plugins.general.automation.actions.TriggerTime\"}"; + + @Test + public void toJSONTest() { + TriggerTime t = new TriggerTime().runAt(now - T.mins(1).msecs()); + Assert.assertEquals(timeJson, t.toJSON()); + } + + @Test + public void fromJSONTest() throws JSONException { + TriggerTime t = new TriggerTime().runAt(now - T.mins(1).msecs()); + + TriggerTime t2 = (TriggerTime) Trigger.instantiate(new JSONObject(t.toJSON())); + Assert.assertEquals(now - T.mins(1).msecs(), t2.runAt); + Assert.assertEquals(false, t2.recurring); + } + + @Before + public void mock() { + AAPSMocker.mockMainApp(); + AAPSMocker.mockBus(); + + PowerMockito.mockStatic(DateUtil.class); + when(DateUtil.now()).thenReturn(now); + + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(now); + when(DateUtil.gregorianCalendar()).thenReturn(calendar); + } +}