Merge pull request #2017 from PoweRGbg/TriggerTOD
Trigger for time of day
This commit is contained in:
commit
ac69d1069e
4 changed files with 351 additions and 0 deletions
|
@ -209,6 +209,7 @@ object AutomationPlugin : PluginBase(PluginDescription()
|
||||||
return listOf(
|
return listOf(
|
||||||
TriggerTime(),
|
TriggerTime(),
|
||||||
TriggerRecurringTime(),
|
TriggerRecurringTime(),
|
||||||
|
TriggerTimeRange(),
|
||||||
TriggerBg(),
|
TriggerBg(),
|
||||||
TriggerDelta(),
|
TriggerDelta(),
|
||||||
TriggerIob(),
|
TriggerIob(),
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
package info.nightscout.androidaps.plugins.general.automation.triggers;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.MainApp;
|
||||||
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.logging.L;
|
||||||
|
import info.nightscout.androidaps.utils.DateUtil;
|
||||||
|
import info.nightscout.androidaps.utils.JsonHelper;
|
||||||
|
import info.nightscout.androidaps.utils.T;
|
||||||
|
|
||||||
|
|
||||||
|
// Trigger for time range ( from 10:00AM till 13:00PM )
|
||||||
|
|
||||||
|
public class TriggerTimeRange extends Trigger {
|
||||||
|
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
|
||||||
|
|
||||||
|
// in minutes since midnight 60 means 1AM
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
long timeZoneOffset = DateUtil.getTimeZoneOffsetMs();
|
||||||
|
|
||||||
|
public TriggerTimeRange() {
|
||||||
|
|
||||||
|
start = getMinSinceMidnight(DateUtil.now());
|
||||||
|
end = getMinSinceMidnight(DateUtil.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
private TriggerTimeRange(TriggerTimeRange triggerTimeRange) {
|
||||||
|
super();
|
||||||
|
lastRun = triggerTimeRange.lastRun;
|
||||||
|
start = triggerTimeRange.start;
|
||||||
|
end = triggerTimeRange.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRun() {
|
||||||
|
int currentMinSinceMidnight = getMinSinceMidnight(DateUtil.now());
|
||||||
|
|
||||||
|
if (lastRun > DateUtil.now() - T.mins(5).msecs())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
boolean doRun = false;
|
||||||
|
if ( start < end && start < currentMinSinceMidnight && currentMinSinceMidnight < end)
|
||||||
|
doRun = true;
|
||||||
|
|
||||||
|
// handle cases like 10PM to 6AM
|
||||||
|
else if ( start > end && (start < currentMinSinceMidnight || currentMinSinceMidnight < end))
|
||||||
|
doRun = true;
|
||||||
|
|
||||||
|
if (doRun) {
|
||||||
|
if (L.isEnabled(L.AUTOMATION))
|
||||||
|
log.debug("Ready for execution: " + friendlyDescription());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toJSON() {
|
||||||
|
JSONObject object = new JSONObject();
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
|
||||||
|
// check for too big values
|
||||||
|
if (start > 1440)
|
||||||
|
start = getMinSinceMidnight(start);
|
||||||
|
if (end > 1440)
|
||||||
|
end = getMinSinceMidnight(end);
|
||||||
|
|
||||||
|
try {
|
||||||
|
data.put("start", getMinSinceMidnight(start));
|
||||||
|
data.put("end", getMinSinceMidnight(end));
|
||||||
|
data.put("lastRun", lastRun);
|
||||||
|
object.put("type", TriggerTimeRange.class.getName());
|
||||||
|
object.put("data", data);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
log.debug(object.toString());
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
TriggerTimeRange fromJSON(String data) {
|
||||||
|
JSONObject o;
|
||||||
|
try {
|
||||||
|
o = new JSONObject(data);
|
||||||
|
lastRun = JsonHelper.safeGetLong(o, "lastRun");
|
||||||
|
start = JsonHelper.safeGetInt(o, "start");
|
||||||
|
end = JsonHelper.safeGetInt(o, "end");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int friendlyName() {
|
||||||
|
return R.string.time_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String friendlyDescription() {
|
||||||
|
return MainApp.gs(R.string.timerange_value, DateUtil.timeString(toMilis(start) - timeZoneOffset), DateUtil.timeString(toMilis(end) - timeZoneOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Integer> icon() {
|
||||||
|
return Optional.of(R.drawable.ic_access_alarm_24dp);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerTimeRange period(int start, int end) {
|
||||||
|
this.start = getMinSinceMidnight(start*60000);
|
||||||
|
this.end = getMinSinceMidnight(end*60000);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerTimeRange lastRun(long lastRun) {
|
||||||
|
this.lastRun = lastRun;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Trigger duplicate() {
|
||||||
|
return new TriggerTimeRange(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
long toMilis(long minutesSinceMidnight) {
|
||||||
|
return minutesSinceMidnight*60*1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinSinceMidnight(long time) {
|
||||||
|
// if passed argument is smaller than 1440 ( 24 h * 60 min ) that value is already converted
|
||||||
|
if (0 < time && time < 1441)
|
||||||
|
return (int) time;
|
||||||
|
Calendar calendar = DateUtil.gregorianCalendar();
|
||||||
|
calendar.setTimeInMillis(time);
|
||||||
|
return (calendar.get(Calendar.HOUR_OF_DAY) * 60) + calendar.get(Calendar.MINUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStart(){
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getEnd(){
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
|
||||||
|
TextView label = new TextView(root.getContext());
|
||||||
|
TextView startButton = new TextView(root.getContext());
|
||||||
|
TextView endButton = new TextView(root.getContext());
|
||||||
|
log.debug("Start is: " + start );
|
||||||
|
log.debug("End is: " + end );
|
||||||
|
startButton.setText(DateUtil.timeString(toMilis(start) - timeZoneOffset));
|
||||||
|
endButton.setText(MainApp.gs(R.string.and) + " " + DateUtil.timeString(toMilis(end) - timeZoneOffset));
|
||||||
|
|
||||||
|
startButton.setOnClickListener(view -> {
|
||||||
|
GregorianCalendar calendar = new GregorianCalendar();
|
||||||
|
//setTimeInMillis sets time in milliseconds after
|
||||||
|
// * January 1, 1970, 0:00:00 GMT., but our time contains timezone offsset
|
||||||
|
calendar.setTimeInMillis(toMilis(start) - timeZoneOffset);
|
||||||
|
TimePickerDialog tpd = TimePickerDialog.newInstance(
|
||||||
|
(view12, hourOfDay, minute, second) -> {
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||||
|
calendar.set(Calendar.MINUTE, minute);
|
||||||
|
start = getMinSinceMidnight(calendar.getTimeInMillis());
|
||||||
|
startButton.setText(DateUtil.timeString(toMilis(start) - timeZoneOffset));
|
||||||
|
},
|
||||||
|
calendar.get(Calendar.HOUR_OF_DAY),
|
||||||
|
calendar.get(Calendar.MINUTE),
|
||||||
|
DateFormat.is24HourFormat(root.getContext())
|
||||||
|
);
|
||||||
|
tpd.setThemeDark(true);
|
||||||
|
tpd.dismissOnPause(true);
|
||||||
|
Activity a = scanForActivity(root.getContext());
|
||||||
|
if (a != null)
|
||||||
|
tpd.show(a.getFragmentManager(), "TimePickerDialog");
|
||||||
|
});
|
||||||
|
endButton.setOnClickListener(view -> {
|
||||||
|
GregorianCalendar calendar = new GregorianCalendar();
|
||||||
|
calendar.setTimeInMillis(toMilis(end) - timeZoneOffset);
|
||||||
|
TimePickerDialog tpd = TimePickerDialog.newInstance(
|
||||||
|
(view12, hourOfDay, minute, second) -> {
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||||
|
calendar.set(Calendar.MINUTE, minute);
|
||||||
|
end = getMinSinceMidnight(calendar.getTimeInMillis());
|
||||||
|
endButton.setText(MainApp.gs(R.string.and) + " " + DateUtil.timeString(toMilis(end) - timeZoneOffset));
|
||||||
|
},
|
||||||
|
calendar.get(Calendar.HOUR_OF_DAY),
|
||||||
|
calendar.get(Calendar.MINUTE),
|
||||||
|
DateFormat.is24HourFormat(root.getContext())
|
||||||
|
);
|
||||||
|
tpd.setThemeDark(true);
|
||||||
|
tpd.dismissOnPause(true);
|
||||||
|
Activity a = scanForActivity(root.getContext());
|
||||||
|
if (a != null)
|
||||||
|
tpd.show(a.getFragmentManager(), "TimePickerDialog");
|
||||||
|
});
|
||||||
|
|
||||||
|
int px = MainApp.dpToPx(10);
|
||||||
|
label.setText(MainApp.gs(R.string.between));
|
||||||
|
label.setTypeface(label.getTypeface(), Typeface.BOLD);
|
||||||
|
startButton.setPadding(px, px, px, px);
|
||||||
|
endButton.setPadding(px, px, px, px);
|
||||||
|
|
||||||
|
LinearLayout l = new LinearLayout(root.getContext());
|
||||||
|
l.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
l.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
l.addView(label);
|
||||||
|
l.addView(startButton);
|
||||||
|
l.addView(endButton);
|
||||||
|
root.addView(l);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1583,6 +1583,10 @@
|
||||||
<string name="deliverpartofboluswizard">Bolus wizard performs calculation but only this part of calculated insulin is delivered. Useful with SMB algorithm.</string>
|
<string name="deliverpartofboluswizard">Bolus wizard performs calculation but only this part of calculated insulin is delivered. Useful with SMB algorithm.</string>
|
||||||
<string name="loading">Loading ...</string>
|
<string name="loading">Loading ...</string>
|
||||||
<string name="snooze">Snooze</string>
|
<string name="snooze">Snooze</string>
|
||||||
|
<string name="time_range">Time range</string>
|
||||||
|
<string name="timerange_value">Time is between %1$s and %2$s</string>
|
||||||
|
<string name="between">Between </string>
|
||||||
|
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
<string name="increasingmaxbasal">Increasing max basal value because setting is lower than your max basal in profile</string>
|
<string name="increasingmaxbasal">Increasing max basal value because setting is lower than your max basal in profile</string>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package info.nightscout.androidaps.plugins.general.automation.triggers;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
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.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.utils.DateUtil;
|
||||||
|
import info.nightscout.androidaps.utils.T;
|
||||||
|
|
||||||
|
import static org.powermock.api.mockito.PowerMockito.when;
|
||||||
|
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
@PrepareForTest({MainApp.class, Bus.class, DateUtil.class, GregorianCalendar.class})
|
||||||
|
public class TriggerTimeRangeTest {
|
||||||
|
|
||||||
|
int now = 754;
|
||||||
|
int timeZoneOffset = (int) (DateUtil.getTimeZoneOffsetMs() / 60000);
|
||||||
|
|
||||||
|
String timeJson = "{\"data\":{\"lastRun\":0,\"start\":753,\"end\":784},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTimeRange\"}";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRunTest() {
|
||||||
|
|
||||||
|
TriggerTimeRange t;
|
||||||
|
now = now + timeZoneOffset;
|
||||||
|
// range starts 1 min in the future
|
||||||
|
t = new TriggerTimeRange().period(now + 1 - timeZoneOffset, now + 30 - timeZoneOffset);
|
||||||
|
Assert.assertEquals(false, t.shouldRun());
|
||||||
|
|
||||||
|
// range starts 30 min back
|
||||||
|
t = new TriggerTimeRange().period(now - 30 - timeZoneOffset, now + 30 - timeZoneOffset);
|
||||||
|
Assert.assertEquals(true, t.shouldRun());
|
||||||
|
|
||||||
|
// Period is all day long
|
||||||
|
t = new TriggerTimeRange().period(1, 1440);
|
||||||
|
Assert.assertEquals(true, t.shouldRun());
|
||||||
|
|
||||||
|
// already run
|
||||||
|
t = new TriggerTimeRange().lastRun((long) (now - 1)*60000);
|
||||||
|
Assert.assertFalse(t.shouldRun());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toJSONTest() {
|
||||||
|
TriggerTimeRange t = new TriggerTimeRange().period(now - 1 - timeZoneOffset , now + 30 - timeZoneOffset);
|
||||||
|
Assert.assertEquals(timeJson, t.toJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromJSONTest() throws JSONException {
|
||||||
|
TriggerTimeRange t = new TriggerTimeRange().period(120 , 180);
|
||||||
|
|
||||||
|
TriggerTimeRange t2 = (TriggerTimeRange) Trigger.instantiate(new JSONObject(t.toJSON()));
|
||||||
|
Assert.assertEquals(timeZoneOffset + now - 1, t2.period(753 , 360).getStart());
|
||||||
|
Assert.assertEquals(timeZoneOffset + 360, t2.period(753 , 360).getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void copyConstructorTest() {
|
||||||
|
TriggerTimeRange t = new TriggerTimeRange();
|
||||||
|
t.period(now, now + 30);
|
||||||
|
|
||||||
|
TriggerTimeRange t1 = (TriggerTimeRange) t.duplicate();
|
||||||
|
// Assert.assertEquals(now, t1.getRunAt(), 0.01d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void friendlyNameTest() {
|
||||||
|
Assert.assertEquals(R.string.time_range, new TriggerTimeRange().friendlyName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void friendlyDescriptionTest() {
|
||||||
|
Assert.assertEquals(null, new TriggerTimeRange().friendlyDescription()); //not mocked }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void iconTest() {
|
||||||
|
Assert.assertEquals(Optional.of(R.drawable.ic_access_alarm_24dp), new TriggerTimeRange().icon());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void mock() {
|
||||||
|
AAPSMocker.mockMainApp();
|
||||||
|
AAPSMocker.mockBus();
|
||||||
|
|
||||||
|
PowerMockito.mockStatic(DateUtil.class);
|
||||||
|
when(DateUtil.now()).thenReturn((long) now * 60000);
|
||||||
|
|
||||||
|
GregorianCalendar calendar = new GregorianCalendar();
|
||||||
|
calendar.setTimeInMillis((long) now * 60000);
|
||||||
|
when(DateUtil.gregorianCalendar()).thenReturn(calendar);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue