diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputLocationMode.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputLocationMode.java new file mode 100644 index 0000000000..3fea8b7c33 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputLocationMode.java @@ -0,0 +1,107 @@ +package info.nightscout.androidaps.plugins.general.automation.elements; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.Spinner; + +import androidx.annotation.StringRes; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + +public class InputLocationMode extends Element { + + public enum Mode { + INSIDE, + OUTSIDE, + GOING_IN, + GOING_OUT; + + public @StringRes + int getStringRes() { + switch (this) { + case INSIDE: + return R.string.location_inside; + case OUTSIDE: + return R.string.location_outside; + case GOING_IN: + return R.string.location_going_in; + case GOING_OUT: + return R.string.location_going_out; + default: + return R.string.unknown; + } + } + + public static List labels() { + List list = new ArrayList<>(); + for (Mode c : Mode.values()) { + list.add(MainApp.gs(c.getStringRes())); + } + return list; + } + + public Mode fromString(String wanted){ + for (Mode c : Mode.values()) { + if(c.toString() == wanted) + return c; + } + return null; + } + } + + private Mode mode; + + public InputLocationMode() { + super(); + mode = Mode.INSIDE; + } + + public InputLocationMode(InputLocationMode another) { + super(); + this.mode = another.mode; + } + + @Override + public void addToLayout(LinearLayout root) { + ArrayAdapter adapter = new ArrayAdapter<>(root.getContext(), + R.layout.spinner_centered, Mode.labels()); + Spinner spinner = new Spinner(root.getContext()); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + spinnerParams.setMargins(0, MainApp.dpToPx(4), 0, MainApp.dpToPx(4)); + spinner.setLayoutParams(spinnerParams); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + setValue(Mode.values()[position]); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + spinner.setSelection(this.getValue().ordinal()); + root.addView(spinner); + + } + + public Mode getValue() { + return mode; + } + + public InputLocationMode setValue(Mode mode) { + this.mode = mode; + return this; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java index 19965cfc9a..8070e074be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocation.java @@ -19,6 +19,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.general.automation.elements.InputButton; import info.nightscout.androidaps.plugins.general.automation.elements.InputDouble; +import info.nightscout.androidaps.plugins.general.automation.elements.InputLocationMode; import info.nightscout.androidaps.plugins.general.automation.elements.InputString; import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement; import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder; @@ -28,12 +29,17 @@ import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.JsonHelper; import info.nightscout.androidaps.utils.T; +import static info.nightscout.androidaps.plugins.general.automation.elements.InputLocationMode.Mode.*; + public class TriggerLocation extends Trigger { private static Logger log = LoggerFactory.getLogger(L.AUTOMATION); InputDouble latitude = new InputDouble(0d, -90d, +90d, 0.000001d, new DecimalFormat("0.000000")); InputDouble longitude = new InputDouble(0d, -180d, +180d, 0.000001d, new DecimalFormat("0.000000")); InputDouble distance = new InputDouble(200d, 0, 100000, 10d, new DecimalFormat("0")); + InputLocationMode modeSelected = new InputLocationMode(); + InputLocationMode.Mode lastMode = INSIDE; + InputString name = new InputString(); private Runnable buttonAction = () -> { @@ -54,13 +60,16 @@ public class TriggerLocation extends Trigger { latitude = new InputDouble(triggerLocation.latitude); longitude = new InputDouble(triggerLocation.longitude); distance = new InputDouble(triggerLocation.distance); + modeSelected = new InputLocationMode(triggerLocation.modeSelected); + if (modeSelected.getValue() == GOING_OUT) + lastMode = OUTSIDE; lastRun = triggerLocation.lastRun; name = triggerLocation.name; } @Override public synchronized boolean shouldRun() { - Location location = LocationService.getLastLocation(); + Location location = this.getCurrentLocation(); if (location == null) return false; @@ -72,11 +81,20 @@ public class TriggerLocation extends Trigger { a.setLongitude(longitude.getValue()); double calculatedDistance = location.distanceTo(a); - if (calculatedDistance < distance.getValue()) { +// log.debug("Moded(current/last/wanted): "+(currentMode(calculatedDistance))+"/"+lastMode+"/"+modeSelected.getValue()); +// log.debug("Distance: "+calculatedDistance + "("+distance.getValue()+")"); + + if ((modeSelected.getValue() == INSIDE) && (calculatedDistance <= distance.getValue()) || + ((modeSelected.getValue() == OUTSIDE) && (calculatedDistance > distance.getValue())) || + ((modeSelected.getValue() == GOING_IN) && (calculatedDistance <= distance.getValue()) && (lastMode == OUTSIDE)) || + ((modeSelected.getValue() == GOING_OUT) && (calculatedDistance > distance.getValue()) && (lastMode == INSIDE)) + ) { if (L.isEnabled(L.AUTOMATION)) log.debug("Ready for execution: " + friendlyDescription()); + lastMode = currentMode(calculatedDistance); return true; } + lastMode = currentMode(calculatedDistance); // current mode will be last mode for the next check return false; } @@ -90,6 +108,7 @@ public class TriggerLocation extends Trigger { data.put("longitude", longitude.getValue()); data.put("distance", distance.getValue()); data.put("name", name.getValue()); + data.put("mode", modeSelected.getValue()); data.put("lastRun", lastRun); o.put("data", data); } catch (JSONException e) { @@ -106,7 +125,10 @@ public class TriggerLocation extends Trigger { longitude.setValue(JsonHelper.safeGetDouble(d, "longitude")); distance.setValue(JsonHelper.safeGetDouble(d, "distance")); name.setValue(JsonHelper.safeGetString(d, "name")); - lastRun = JsonHelper.safeGetLong(d, "lastRun"); + modeSelected.setValue(InputLocationMode.Mode.valueOf(JsonHelper.safeGetString(d, "mode"))); + if (modeSelected.getValue() == GOING_OUT) + lastMode = OUTSIDE; + lastRun = DateUtil.now(); // set lastRun to now to give the service 5 mins to get the location properly } catch (Exception e) { log.error("Unhandled exception", e); } @@ -120,7 +142,7 @@ public class TriggerLocation extends Trigger { @Override public String friendlyDescription() { - return MainApp.gs(R.string.locationis, name.getValue()); + return MainApp.gs(R.string.locationis, MainApp.gs(modeSelected.getValue().getStringRes()), " " + name.getValue()); } @Override @@ -154,6 +176,11 @@ public class TriggerLocation extends Trigger { return this; } + TriggerLocation setMode(InputLocationMode.Mode value) { + modeSelected.setValue(value); + return this; + } + @Override public void generateDialog(LinearLayout root, FragmentManager fragmentManager) { new LayoutBuilder() @@ -162,7 +189,21 @@ public class TriggerLocation extends Trigger { .add(new LabelWithElement(MainApp.gs(R.string.latitude_short), "", latitude)) .add(new LabelWithElement(MainApp.gs(R.string.longitude_short), "", longitude)) .add(new LabelWithElement(MainApp.gs(R.string.distance_short), "", distance)) + .add(new LabelWithElement(MainApp.gs(R.string.location_mode), "", modeSelected)) .add(new InputButton(MainApp.gs(R.string.currentlocation), buttonAction), LocationService.getLastLocation() != null) .build(root); } + + // Method to return the actual mode based on the current distance + InputLocationMode.Mode currentMode(double currentDistance){ + if ( currentDistance <= this.distance.getValue() ) + return INSIDE; + else + return OUTSIDE; + } + + static Location getCurrentLocation(){ + return LocationService.getLastLocation(); + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.java index ea7a8c34bd..89f95608b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/data/NSDeviceStatus.java @@ -407,7 +407,7 @@ public class NSDeviceStatus { } Uploader uploader = uploaders.get(device); // check if this is new data - if (clock != 0 && (uploader != null && clock > uploader.clock || uploader == null)) { + if (clock != 0 && battery != null && (uploader != null && clock > uploader.clock || uploader == null)) { if (uploader == null) uploader = new Uploader(); uploader.battery = battery; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index acb7a439b5..d3fd9703ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1411,7 +1411,12 @@ Lon: Dist [m]: Name: - Location is %1$s + %1$s %2$s + When + When you are inside the area + When you are outside the area + When you enter the area named + When you leave the area named Last bolus ago Last bolus time %1$s %2$s min ago COB diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocationTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocationTest.java index 9e1f713339..dcd98fb2e8 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocationTest.java +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerLocationTest.java @@ -19,6 +19,7 @@ import info.AAPSMocker; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.general.automation.elements.InputLocationMode; import info.nightscout.androidaps.services.LocationService; import info.nightscout.androidaps.utils.DateUtil; @@ -39,6 +40,7 @@ public class TriggerLocationTest { PowerMockito.mockStatic(DateUtil.class); PowerMockito.mockStatic(LocationService.class); when(DateUtil.now()).thenReturn(now); + PowerMockito.spy(LocationService.class); PowerMockito.when(LocationService.getLastLocation()).thenReturn(mockedLocation()); @@ -53,11 +55,14 @@ public class TriggerLocationTest { t.latitude.setValue(213); t.longitude.setValue(212); t.distance.setValue(2); + t.modeSelected.setValue(InputLocationMode.Mode.INSIDE); + TriggerLocation t1 = (TriggerLocation) t.duplicate(); - Assert.assertEquals(213d, t.latitude.getValue(), 0.01d); - Assert.assertEquals(212d, t.longitude.getValue(), 0.01d); - Assert.assertEquals(2d, t.distance.getValue(), 0.01d); + Assert.assertEquals(213d, t1.latitude.getValue(), 0.01d); + Assert.assertEquals(212d, t1.longitude.getValue(), 0.01d); + Assert.assertEquals(2d, t1.distance.getValue(), 0.01d); + Assert.assertEquals(InputLocationMode.Mode.INSIDE, t1.modeSelected.getValue()); } @Test @@ -66,6 +71,7 @@ public class TriggerLocationTest { t.latitude.setValue(213); t.longitude.setValue(212); t.distance.setValue(2); +// t.modeSelected.setValue(InputLocationMode.Mode.OUTSIDE); PowerMockito.when(LocationService.getLastLocation()).thenReturn(null); Assert.assertFalse(t.shouldRun()); PowerMockito.when(LocationService.getLastLocation()).thenReturn(mockedLocation()); @@ -76,9 +82,23 @@ public class TriggerLocationTest { t = new TriggerLocation(); t.distance.setValue(-500); Assert.assertFalse(t.shouldRun()); + + //Test of GOING_IN - last mode should be OUTSIDE, and current mode should be INSIDE + t = new TriggerLocation(); + t.distance.setValue(50); + t.lastMode = t.currentMode(55d); + PowerMockito.when(LocationService.getLastLocation()).thenReturn(null); + PowerMockito.when(LocationService.getLastLocation()).thenReturn(mockedLocationOut()); + t.modeSelected.setValue(InputLocationMode.Mode.GOING_IN); + Assert.assertEquals(t.lastMode, InputLocationMode.Mode.OUTSIDE); + Assert.assertEquals(t.currentMode(5d), InputLocationMode.Mode.INSIDE); + Assert.assertTrue(t.shouldRun()); + + //Test of GOING_OUT - last mode should be INSIDE, and current mode should be OUTSIDE + // Currently unavailable due to problems with Location mocking } - String locationJson = "{\"data\":{\"distance\":2,\"lastRun\":0,\"latitude\":213,\"name\":\"\",\"longitude\":212},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerLocation\"}"; + String locationJson = "{\"data\":{\"mode\":\"OUTSIDE\",\"distance\":2,\"lastRun\":0,\"latitude\":213,\"name\":\"\",\"longitude\":212},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerLocation\"}"; @Test public void toJSONTest() { @@ -86,6 +106,7 @@ public class TriggerLocationTest { t.latitude.setValue(213); t.longitude.setValue(212); t.distance.setValue(2); + t.modeSelected = t.modeSelected.setValue(InputLocationMode.Mode.OUTSIDE); Assert.assertEquals(locationJson, t.toJSON()); } @@ -95,11 +116,13 @@ public class TriggerLocationTest { t.latitude.setValue(213); t.longitude.setValue(212); t.distance.setValue(2); + t.modeSelected.setValue(InputLocationMode.Mode.INSIDE); TriggerLocation t2 = (TriggerLocation) Trigger.instantiate(new JSONObject(t.toJSON())); Assert.assertEquals(t.latitude.getValue(), t2.latitude.getValue(), 0.01d); Assert.assertEquals(t.longitude.getValue(), t2.longitude.getValue(), 0.01d); Assert.assertEquals(t.distance.getValue(), t2.distance.getValue(), 0.01d); + Assert.assertEquals(t.modeSelected.getValue(), t2.modeSelected.getValue()); } @Test @@ -139,6 +162,13 @@ public class TriggerLocationTest { Assert.assertEquals(t.distance.getValue(), 2, 0d); } + @Test + public void setModeTest() { + TriggerLocation t = new TriggerLocation(); + t.setMode(InputLocationMode.Mode.INSIDE); + Assert.assertEquals(t.modeSelected.getValue(), InputLocationMode.Mode.INSIDE); + } + @Test public void lastRunTest() { TriggerLocation t = new TriggerLocation(); @@ -153,4 +183,12 @@ public class TriggerLocationTest { newLocation.setAccuracy(1f); return newLocation; } + + public Location mockedLocationOut() { + Location newLocation = new Location("test"); + newLocation.setLatitude(12f); + newLocation.setLongitude(13f); + newLocation.setAccuracy(1f); + return newLocation; + } } \ No newline at end of file