Merge branch 'dev' into iobfix

This commit is contained in:
Milos Kozak 2019-07-26 00:39:40 +02:00
commit 595bf0314c
389 changed files with 36055 additions and 399 deletions

View file

@ -14,7 +14,7 @@ android:
- extra-google-google_play_services - extra-google-google_play_services
before_install: before_install:
- yes | sdkmanager "platforms;android-28" #- yes | sdkmanager "platforms;android-28"
script: script:
# Unit Test # Unit Test

View file

@ -215,6 +215,7 @@ allprojects {
flatDir { flatDir {
dirs 'libs' dirs 'libs'
} }
maven { url 'https://jitpack.io' }
} }
} }
@ -274,6 +275,8 @@ dependencies {
implementation "com.jakewharton:butterknife:${butterknifeVersion}" implementation "com.jakewharton:butterknife:${butterknifeVersion}"
annotationProcessor "com.jakewharton:butterknife-compiler:${butterknifeVersion}" annotationProcessor "com.jakewharton:butterknife-compiler:${butterknifeVersion}"
implementation 'com.github.DavidProdinger:weekdays-selector:1.0.4'
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"
testImplementation "org.json:json:20140107" testImplementation "org.json:json:20140107"
testImplementation "org.mockito:mockito-core:2.8.47" testImplementation "org.mockito:mockito-core:2.8.47"
@ -282,9 +285,17 @@ dependencies {
testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}" testImplementation "org.powermock:powermock-module-junit4-rule:${powermockVersion}"
testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}" testImplementation "org.powermock:powermock-module-junit4:${powermockVersion}"
testImplementation "joda-time:joda-time:2.9.9" testImplementation "joda-time:joda-time:2.9.9"
testImplementation "com.google.truth:truth:0.39" testImplementation("com.google.truth:truth:0.39") {
testImplementation 'org.robolectric:robolectric:4.2.1' exclude group: "com.google.guava", module: "guava"
}
testImplementation("org.robolectric:robolectric:4.2.1") {
exclude group: "com.google.guava", module: "guava"
}
testImplementation "org.skyscreamer:jsonassert:1.5.0" testImplementation "org.skyscreamer:jsonassert:1.5.0"
testImplementation "org.hamcrest:hamcrest-all:1.3"
testImplementation("uk.org.lidalia:slf4j-test:1.2.0") {
exclude group: "com.google.guava", module: "guava"
}
androidTestImplementation "org.mockito:mockito-core:2.8.47" androidTestImplementation "org.mockito:mockito-core:2.8.47"
androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}" androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}"

View file

@ -19,6 +19,7 @@
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" /> <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="com.dexcom.cgm.EXTERNAL_PERMISSION" /> <uses-permission android:name="com.dexcom.cgm.EXTERNAL_PERMISSION" />
@ -161,6 +162,9 @@
<service <service
android:name=".services.DataService" android:name=".services.DataService"
android:exported="false" /> android:exported="false" />
<service
android:name=".services.LocationService"
android:exported="false" />
<service <service
android:name=".plugins.pump.danaR.services.DanaRExecutionService" android:name=".plugins.pump.danaR.services.DanaRExecutionService"
android:enabled="true" android:enabled="true"
@ -284,6 +288,23 @@
android:theme="@style/AppTheme" /> android:theme="@style/AppTheme" />
<activity android:name=".activities.RequestDexcomPermissionActivity" /> <activity android:name=".activities.RequestDexcomPermissionActivity" />
<!-- Medtronic service and activities -->
<service
android:name=".plugins.pump.medtronic.service.RileyLinkMedtronicService"
android:enabled="true"
android:exported="true" />
<activity android:name=".plugins.pump.common.dialog.RileyLinkBLEScanActivity">
<intent-filter>
<action android:name="info.nightscout.androidaps.plugins.PumpCommon.dialog.RileyLinkBLEScanActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity"
android:label="@string/title_activity_rileylink_settings"
android:theme="@style/Theme.AppCompat.NoTitle" />
<activity android:name=".plugins.pump.medtronic.dialog.MedtronicHistoryActivity" />
<uses-library android:name="org.apache.http.legacy" android:required="false"/> <uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application> </application>

View file

@ -31,7 +31,7 @@ public class Constants {
public static final long remoteBolusMinDistance = 15 * 60 * 1000L; public static final long remoteBolusMinDistance = 15 * 60 * 1000L;
// Circadian Percentage Profile // Circadian Percentage Profile
public static final int CPP_MIN_PERCENTAGE = 50; public static final int CPP_MIN_PERCENTAGE = 30;
public static final int CPP_MAX_PERCENTAGE = 200; public static final int CPP_MAX_PERCENTAGE = 200;
public static final int CPP_MIN_TIMESHIFT = -6; public static final int CPP_MIN_TIMESHIFT = -6;
public static final int CPP_MAX_TIMESHIFT = 23; public static final int CPP_MAX_TIMESHIFT = 23;

View file

@ -1,9 +1,14 @@
package info.nightscout.androidaps; package info.nightscout.androidaps;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.PluralsRes; import androidx.annotation.PluralsRes;
@ -40,6 +45,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugi
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin;
import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin; import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin;
import info.nightscout.androidaps.plugins.general.actions.ActionsFragment; import info.nightscout.androidaps.plugins.general.actions.ActionsFragment;
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin;
import info.nightscout.androidaps.plugins.general.food.FoodPlugin; import info.nightscout.androidaps.plugins.general.food.FoodPlugin;
import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils; import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils;
@ -63,12 +69,15 @@ import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin;
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin;
import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin; import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin;
import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin;
import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin;
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin; import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
@ -86,6 +95,7 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.androidaps.receivers.DataReceiver;
import info.nightscout.androidaps.receivers.KeepAliveReceiver; import info.nightscout.androidaps.receivers.KeepAliveReceiver;
import info.nightscout.androidaps.receivers.NSAlarmReceiver; import info.nightscout.androidaps.receivers.NSAlarmReceiver;
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver;
import info.nightscout.androidaps.services.Intents; import info.nightscout.androidaps.services.Intents;
import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.FabricPrivacy;
import io.fabric.sdk.android.Fabric; import io.fabric.sdk.android.Fabric;
@ -113,6 +123,8 @@ public class MainApp extends Application {
private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver(); private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver();
private static DBAccessReceiver dbAccessReciever = new DBAccessReceiver(); private static DBAccessReceiver dbAccessReciever = new DBAccessReceiver();
private LocalBroadcastManager lbm; private LocalBroadcastManager lbm;
BroadcastReceiver btReceiver;
TimeDateOrTZChangeReceiver timeDateOrTZChangeReceiver;
public static boolean devBranch; public static boolean devBranch;
public static boolean engineeringMode; public static boolean engineeringMode;
@ -154,6 +166,7 @@ public class MainApp extends Application {
//trigger here to see the new version on app start after an update //trigger here to see the new version on app start after an update
triggerCheckVersion(); triggerCheckVersion();
//setBTReceiver();
if (pluginsList == null) { if (pluginsList == null) {
pluginsList = new ArrayList<>(); pluginsList = new ArrayList<>();
@ -175,6 +188,7 @@ public class MainApp extends Application {
if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin()); if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin());
pluginsList.add(CareportalPlugin.getPlugin()); pluginsList.add(CareportalPlugin.getPlugin());
if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin()); if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin());
if (Config.PUMPDRIVERS && engineeringMode) pluginsList.add(MedtronicPumpPlugin.getPlugin());
if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin()); if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin());
pluginsList.add(VirtualPumpPlugin.getPlugin()); pluginsList.add(VirtualPumpPlugin.getPlugin());
if (Config.APS) pluginsList.add(LoopPlugin.getPlugin()); if (Config.APS) pluginsList.add(LoopPlugin.getPlugin());
@ -207,6 +221,8 @@ public class MainApp extends Application {
if (engineeringMode) if (engineeringMode)
pluginsList.add(TidepoolPlugin.INSTANCE); pluginsList.add(TidepoolPlugin.INSTANCE);
pluginsList.add(MaintenancePlugin.initPlugin(this)); pluginsList.add(MaintenancePlugin.initPlugin(this));
if (engineeringMode)
pluginsList.add(AutomationPlugin.INSTANCE);
pluginsList.add(ConfigBuilderPlugin.getPlugin()); pluginsList.add(ConfigBuilderPlugin.getPlugin());
@ -254,6 +270,10 @@ public class MainApp extends Application {
//register dbaccess //register dbaccess
lbm.registerReceiver(dbAccessReciever, new IntentFilter(Intents.ACTION_DATABASE)); lbm.registerReceiver(dbAccessReciever, new IntentFilter(Intents.ACTION_DATABASE));
this.timeDateOrTZChangeReceiver = new TimeDateOrTZChangeReceiver();
this.timeDateOrTZChangeReceiver.registerBroadcasts(this);
} }
private void startKeepAliveService() { private void startKeepAliveService() {
@ -439,5 +459,19 @@ public class MainApp extends Application {
sDatabaseHelper.close(); sDatabaseHelper.close();
sDatabaseHelper = null; sDatabaseHelper = null;
} }
if (btReceiver != null) {
unregisterReceiver(btReceiver);
}
if (timeDateOrTZChangeReceiver!=null) {
unregisterReceiver(timeDateOrTZChangeReceiver);
}
}
public static int dpToPx(int dp) {
float scale = sResources.getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
} }
} }

View file

@ -36,6 +36,7 @@ import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin;
import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin;
import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin;
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin;
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
@ -48,6 +49,7 @@ import info.nightscout.androidaps.plugins.source.SourceDexcomPlugin;
import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.LocaleHelper;
import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.OKDialog;
import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin;
public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
MyPreferenceFragment myPreferenceFragment; MyPreferenceFragment myPreferenceFragment;
@ -170,6 +172,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP); addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP);
addPreferencesFromResourceIfEnabled(LocalInsightPlugin.getPlugin(), PluginType.PUMP); addPreferencesFromResourceIfEnabled(LocalInsightPlugin.getPlugin(), PluginType.PUMP);
addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP); addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP);
addPreferencesFromResourceIfEnabled(MedtronicPumpPlugin.getPlugin(), PluginType.PUMP);
if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE) if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE)
|| DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE) || DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE)
@ -188,6 +191,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL); addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(TidepoolPlugin.INSTANCE, PluginType.GENERAL); addPreferencesFromResourceIfEnabled(TidepoolPlugin.INSTANCE, PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL); addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(AutomationPlugin.INSTANCE, PluginType.GENERAL);
addPreferencesFromResource(R.xml.pref_others); addPreferencesFromResource(R.xml.pref_others);
addPreferencesFromResource(R.xml.pref_datachoices); addPreferencesFromResource(R.xml.pref_datachoices);

View file

@ -45,6 +45,11 @@ public class PumpEnactResult {
return this; return this;
} }
public PumpEnactResult comment(int comment) {
this.comment = MainApp.gs(comment);
return this;
}
public PumpEnactResult duration(int duration) { public PumpEnactResult duration(int duration) {
this.duration = duration; this.duration = duration;
return this; return this;

View file

@ -21,6 +21,8 @@ import org.slf4j.LoggerFactory;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -536,6 +538,24 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return tddList; return tddList;
} }
public List<TDD> getTDDsForLastXDays(int days) {
List<TDD> tddList;
GregorianCalendar gc = new GregorianCalendar();
gc.add(Calendar.DAY_OF_YEAR, (-1) * days);
try {
QueryBuilder<TDD, String> queryBuilder = getDaoTDD().queryBuilder();
queryBuilder.orderBy("date", false);
Where<TDD, String> where = queryBuilder.where();
where.ge("date", gc.getTimeInMillis());
PreparedQuery<TDD> preparedQuery = queryBuilder.prepare();
tddList = getDaoTDD().query(preparedQuery);
} catch (SQLException e) {
log.error("Unhandled exception", e);
tddList = new ArrayList<>();
}
return tddList;
}
// ------------- DbRequests handling ------------------- // ------------- DbRequests handling -------------------
@ -871,6 +891,31 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
log.debug("TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); log.debug("TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
return false; return false;
} }
// search by date (in case its standard record that has become pump record)
QueryBuilder<TemporaryBasal, Long> queryBuilder2 = getDaoTemporaryBasal().queryBuilder();
Where where2 = queryBuilder2.where();
where2.eq("date", tempBasal.date);
PreparedQuery<TemporaryBasal> preparedQuery2 = queryBuilder2.prepare();
List<TemporaryBasal> trList2 = getDaoTemporaryBasal().query(preparedQuery2);
if (trList2.size() > 0) {
old = trList2.get(0);
old.copyFromPump(tempBasal);
old.source = Source.PUMP;
if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
getDaoTemporaryBasal().update(old);
updateEarliestDataChange(tempBasal.date);
scheduleTemporaryBasalChange();
return false;
}
getDaoTemporaryBasal().create(tempBasal); getDaoTemporaryBasal().create(tempBasal);
if (L.isEnabled(L.DATABASE)) if (L.isEnabled(L.DATABASE))
log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString());
@ -1111,6 +1156,29 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return null; return null;
} }
public TemporaryBasal findTempBasalByPumpId(Long pumpId) {
try {
QueryBuilder<TemporaryBasal, Long> queryBuilder = null;
queryBuilder = getDaoTemporaryBasal().queryBuilder();
queryBuilder.orderBy("date", false);
Where where = queryBuilder.where();
where.eq("pumpId", pumpId);
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
List<TemporaryBasal> list = getDaoTemporaryBasal().query(preparedQuery);
if (list.size() > 0)
return list.get(0);
else
return null;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return null;
}
// ------------ ExtendedBolus handling --------------- // ------------ ExtendedBolus handling ---------------
public boolean createOrUpdate(ExtendedBolus extendedBolus) { public boolean createOrUpdate(ExtendedBolus extendedBolus) {

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.db;
public interface DbObjectBase {
long getDate();
long getPumpId();
}

View file

@ -6,9 +6,8 @@ import com.j256.ormlite.table.DatabaseTable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Objects;
import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
/** /**
* Created by mike on 20.09.2017. * Created by mike on 20.09.2017.
@ -45,4 +44,16 @@ public class TDD {
this.basal = basal; this.basal = basal;
this.total = total; this.total = total;
} }
@Override
public String toString() {
return "TDD [" +
"date=" + date +
"date(str)=" + DateTimeUtil.toStringFromTimeInMillis(date) +
", bolus=" + bolus +
", basal=" + basal +
", total=" + total +
']';
}
} }

View file

@ -9,6 +9,9 @@ import org.slf4j.LoggerFactory;
import java.util.Objects; import java.util.Objects;
import info.nightscout.androidaps.Constants; 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.interfaces.Interval; import info.nightscout.androidaps.interfaces.Interval;
import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DateUtil;
@ -191,4 +194,11 @@ public class TempTarget implements Interval {
'}'; '}';
} }
public String friendlyDescription(String units) {
return Profile.toTargetRangeString(low, high, Constants.MGDL, units) +
units +
"@" + MainApp.gs(R.string.mins, durationInMinutes) +
(reason != null && !reason.equals("") ? "(" + reason + ")" : "");
}
} }

View file

@ -28,7 +28,7 @@ import info.nightscout.androidaps.utils.SP;
*/ */
@DatabaseTable(tableName = DatabaseHelper.DATABASE_TEMPORARYBASALS) @DatabaseTable(tableName = DatabaseHelper.DATABASE_TEMPORARYBASALS)
public class TemporaryBasal implements Interval { public class TemporaryBasal implements Interval, DbObjectBase {
private static Logger log = LoggerFactory.getLogger(L.DATABASE); private static Logger log = LoggerFactory.getLogger(L.DATABASE);
@DatabaseField(id = true) @DatabaseField(id = true)
@ -157,6 +157,14 @@ public class TemporaryBasal implements Interval {
netExtendedRate = t.netExtendedRate; netExtendedRate = t.netExtendedRate;
} }
public void copyFromPump(TemporaryBasal t) {
durationInMinutes = t.durationInMinutes;
isAbsolute = t.isAbsolute;
percentRate = t.percentRate;
absoluteRate = t.absoluteRate;
pumpId = t.pumpId;
}
// -------- Interval interface --------- // -------- Interval interface ---------
Long cuttedEnd = null; Long cuttedEnd = null;
@ -490,4 +498,13 @@ public class TemporaryBasal implements Interval {
} }
} }
@Override
public long getDate() {
return this.date;
}
@Override
public long getPumpId() {
return this.pumpId;
}
} }

View file

@ -0,0 +1,4 @@
package info.nightscout.androidaps.events;
public class EventCustomActionsChanged extends Event {
}

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.events;
import android.location.Location;
public class EventLocationChange extends Event {
public Location location;
public EventLocationChange(Location location) {
this.location = location;
}
}

View file

@ -1,8 +1,11 @@
package info.nightscout.androidaps.interfaces; package info.nightscout.androidaps.interfaces;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.utils.SP;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -42,6 +45,28 @@ public abstract class PluginBase {
pluginSwitcher.invoke(); pluginSwitcher.invoke();
} }
protected void confirmPumpPluginActivation(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity activity) {
boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false);
if (allowHardwarePump || activity == null) {
pluginSwitcher.invoke();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.allow_hardware_pump_text)
.setPositiveButton(R.string.yes, (dialog, id) -> {
pluginSwitcher.invoke();
SP.putBoolean("allow_hardware_pump", true);
if (L.isEnabled(L.PUMP))
log.debug("First time HW pump allowed!");
})
.setNegativeButton(R.string.cancel, (dialog, id) -> {
pluginSwitcher.cancel();
if (L.isEnabled(L.PUMP))
log.debug("User does not allow switching to HW pump!");
});
builder.create().show();
}
}
// public PluginType getType() { // public PluginType getType() {
// return mainType; // return mainType;
// } // }

View file

@ -7,8 +7,10 @@ import java.util.List;
import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction;
import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
/** /**
* Created by mike on 04.06.2016. * Created by mike on 04.06.2016.
@ -53,8 +55,8 @@ public interface PumpInterface {
// Status to be passed to NS // Status to be passed to NS
JSONObject getJSONStatus(Profile profile, String profileName); JSONObject getJSONStatus(Profile profile, String profileName);
String manufacter(); ManufacturerType manufacturer();
String model(); PumpType model();
String serialNumber(); String serialNumber();
// Pump capabilities // Pump capabilities
@ -73,4 +75,10 @@ public interface PumpInterface {
void executeCustomAction(CustomActionType customActionType); void executeCustomAction(CustomActionType customActionType);
/**
* This method will be called when time or Timezone changes, and pump driver can then do a specific action (for
* example update clock on pump).
*/
void timeDateOrTimeZoneChanged();
} }

View file

@ -32,6 +32,7 @@ public interface TreatmentsInterface {
List<Treatment> getTreatmentsFromHistory(); List<Treatment> getTreatmentsFromHistory();
List<Treatment> getTreatments5MinBackFromHistory(long time); List<Treatment> getTreatments5MinBackFromHistory(long time);
List<Treatment> getTreatmentsFromHistoryAfterTimestamp(long timestamp);
long getLastBolusTime(); long getLastBolusTime();
// real basals (not faked by extended bolus) // real basals (not faked by extended bolus)

View file

@ -77,6 +77,7 @@ public class L {
public static final String CORE = "CORE"; public static final String CORE = "CORE";
public static final String AUTOSENS = "AUTOSENS"; public static final String AUTOSENS = "AUTOSENS";
public static final String AUTOMATION = "AUTOMATION";
public static final String EVENTS = "EVENTS"; public static final String EVENTS = "EVENTS";
public static final String GLUCOSE = "GLUCOSE"; public static final String GLUCOSE = "GLUCOSE";
public static final String BGSOURCE = "BGSOURCE"; public static final String BGSOURCE = "BGSOURCE";
@ -97,11 +98,13 @@ public class L {
public static final String PROFILE = "PROFILE"; public static final String PROFILE = "PROFILE";
public static final String CONFIGBUILDER = "CONFIGBUILDER"; public static final String CONFIGBUILDER = "CONFIGBUILDER";
public static final String UI = "UI"; public static final String UI = "UI";
public static final String LOCATION = "LOCATION";
public static final String SMS = "SMS"; public static final String SMS = "SMS";
private static void initialize() { private static void initialize() {
logElements = new ArrayList<>(); logElements = new ArrayList<>();
logElements.add(new LogElement(APS, true)); logElements.add(new LogElement(APS, true));
logElements.add(new LogElement(AUTOMATION, true));
logElements.add(new LogElement(AUTOSENS, false)); logElements.add(new LogElement(AUTOSENS, false));
logElements.add(new LogElement(BGSOURCE, true)); logElements.add(new LogElement(BGSOURCE, true));
logElements.add(new LogElement(GLUCOSE, false)); logElements.add(new LogElement(GLUCOSE, false));
@ -113,6 +116,7 @@ public class L {
logElements.add(new LogElement(DATASERVICE, true)); logElements.add(new LogElement(DATASERVICE, true));
logElements.add(new LogElement(DATATREATMENTS, true)); logElements.add(new LogElement(DATATREATMENTS, true));
logElements.add(new LogElement(EVENTS, false, true)); logElements.add(new LogElement(EVENTS, false, true));
logElements.add(new LogElement(LOCATION, true));
logElements.add(new LogElement(NOTIFICATION, true)); logElements.add(new LogElement(NOTIFICATION, true));
logElements.add(new LogElement(NSCLIENT, true)); logElements.add(new LogElement(NSCLIENT, true));
logElements.add(new LogElement(TIDEPOOL, true)); logElements.add(new LogElement(TIDEPOOL, true));

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.common;
public enum ManufacturerType {
AndroidAPS("AndroidAPS"),
Medtronic("Medtronic"),
Sooil("SOOIL"),
Tandem("Tandem"),
Insulet("Insulet"),
Animas("Animas"), Cellnovo("Cellnovo"), Roche("Roche");
private String description;
ManufacturerType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View file

@ -26,6 +26,7 @@ import info.nightscout.androidaps.activities.HistoryBrowseActivity;
import info.nightscout.androidaps.activities.TDDStatsActivity; import info.nightscout.androidaps.activities.TDDStatsActivity;
import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ExtendedBolus;
import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.db.TemporaryBasal;
import info.nightscout.androidaps.events.EventCustomActionsChanged;
import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.events.EventExtendedBolusChange;
import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventInitializationChanged;
import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.events.EventRefreshOverview;
@ -133,6 +134,11 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
updateGUI(); updateGUI();
} }
@Subscribe
public void onStatusEvent(final EventCustomActionsChanged ev) {
updateGUI();
}
@Override @Override
protected void updateGUI() { protected void updateGUI() {
Activity activity = getActivity(); Activity activity = getActivity();
@ -247,6 +253,9 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
for (CustomAction customAction : customActions) { for (CustomAction customAction : customActions) {
if (!customAction.isEnabled())
continue;
SingleClickButton btn = new SingleClickButton(getContext(), null, android.R.attr.buttonStyle); SingleClickButton btn = new SingleClickButton(getContext(), null, android.R.attr.buttonStyle);
btn.setText(MainApp.gs(customAction.getName())); btn.setText(MainApp.gs(customAction.getName()));
@ -264,10 +273,8 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
this.pumpCustomActions.put(MainApp.gs(customAction.getName()), customAction); this.pumpCustomActions.put(MainApp.gs(customAction.getName()), customAction);
this.pumpCustomButtons.add(btn); this.pumpCustomButtons.add(btn);
} }
} }
} }
@ -283,7 +290,6 @@ public class ActionsFragment extends SubscriberFragment implements View.OnClickL
} }
pumpCustomButtons.clear(); pumpCustomButtons.clear();
pumpCustomActions.clear();
} }

View file

@ -12,31 +12,36 @@ public class CustomAction {
private String iconName; private String iconName;
private CustomActionType customActionType; private CustomActionType customActionType;
private int iconResourceId; private int iconResourceId;
private boolean enabled = true;
public CustomAction(int nameResourceId, CustomActionType actionType) { public CustomAction(int nameResourceId, CustomActionType actionType) {
this.name = nameResourceId; this(nameResourceId, actionType, R.drawable.icon_actions_profileswitch, true);
this.customActionType = actionType;
this.iconResourceId = R.drawable.icon_actions_profileswitch;
} }
public CustomAction(int nameResourceId, CustomActionType actionType, int iconResourceId) { public CustomAction(int nameResourceId, CustomActionType actionType, int iconResourceId) {
this(nameResourceId, actionType, iconResourceId, true);
}
public CustomAction(int nameResourceId, CustomActionType actionType, boolean enabled) {
this(nameResourceId, actionType, R.drawable.icon_actions_profileswitch, enabled);
}
public CustomAction(int nameResourceId, CustomActionType actionType, int iconResourceId, boolean enabled) {
this.name = nameResourceId; this.name = nameResourceId;
this.customActionType = actionType; this.customActionType = actionType;
this.iconResourceId = iconResourceId; this.iconResourceId = iconResourceId;
this.enabled = enabled;
} }
public int getName() { public int getName() {
return name; return name;
} }
public CustomActionType getCustomActionType() { public CustomActionType getCustomActionType() {
return customActionType; return customActionType;
} }
@ -44,4 +49,15 @@ public class CustomAction {
public int getIconResourceId() { public int getIconResourceId() {
return iconResourceId; return iconResourceId;
} }
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
} }

View file

@ -103,7 +103,7 @@ public class FillDialog extends DialogFragment implements OnClickListener {
Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value();
double bolusstep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep; double bolusstep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep;
editInsulin = view.findViewById(R.id.fill_insulinamount); editInsulin = view.findViewById(R.id.fill_insulinamount);
editInsulin.setParams(0d, 0d, maxInsulin, bolusstep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); editInsulin.setParams(0d, 0d, maxInsulin, bolusstep, DecimalFormatter.pumpSupportedBolusFormat(), false, view.findViewById(R.id.ok), textWatcher);
Button preset1Button = view.findViewById(R.id.fill_preset_button1); Button preset1Button = view.findViewById(R.id.fill_preset_button1);
@ -184,7 +184,7 @@ public class FillDialog extends DialogFragment implements OnClickListener {
confirmMessage.add(""); confirmMessage.add("");
confirmMessage.add(MainApp.gs(R.string.bolus) + ": " + "<font color='" + MainApp.gc(R.color.colorCarbsButton) + "'>" + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints) + "U" + "</font>"); confirmMessage.add(MainApp.gs(R.string.bolus) + ": " + "<font color='" + MainApp.gc(R.color.colorCarbsButton) + "'>" + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints) + "U" + "</font>");
if (Math.abs(insulinAfterConstraints - insulin) > 0.01d) if (Math.abs(insulinAfterConstraints - insulin) > 0.01d)
confirmMessage.add("<font color='" + MainApp.gc(R.color.low) + "'>" + MainApp.gs(R.string.bolusconstraintapplied) + "</font>"); confirmMessage.add(MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), insulin, insulinAfterConstraints));
} }
if (pumpSiteChangeCheckbox.isChecked()) if (pumpSiteChangeCheckbox.isChecked())

View file

@ -42,12 +42,12 @@ public class NewExtendedBolusDialog extends DialogFragment implements View.OnCli
Double maxInsulin = MainApp.getConstraintChecker().getMaxExtendedBolusAllowed().value(); Double maxInsulin = MainApp.getConstraintChecker().getMaxExtendedBolusAllowed().value();
editInsulin = (NumberPicker) view.findViewById(R.id.overview_newextendedbolus_insulin); editInsulin = (NumberPicker) view.findViewById(R.id.overview_newextendedbolus_insulin);
editInsulin.setParams(0d, 0d, maxInsulin, 0.1d, new DecimalFormat("0.00"), false); editInsulin.setParams(0d, 0d, maxInsulin, 0.1d, new DecimalFormat("0.00"), false, view.findViewById(R.id.ok));
double extendedDurationStep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().extendedBolusDurationStep; double extendedDurationStep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().extendedBolusDurationStep;
double extendedMaxDuration = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().extendedBolusMaxDuration; double extendedMaxDuration = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().extendedBolusMaxDuration;
editDuration = (NumberPicker) view.findViewById(R.id.overview_newextendedbolus_duration); editDuration = (NumberPicker) view.findViewById(R.id.overview_newextendedbolus_duration);
editDuration.setParams(extendedDurationStep, extendedDurationStep, extendedMaxDuration, extendedDurationStep, new DecimalFormat("0"), false); editDuration.setParams(extendedDurationStep, extendedDurationStep, extendedMaxDuration, extendedDurationStep, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
view.findViewById(R.id.ok).setOnClickListener(this); view.findViewById(R.id.ok).setOnClickListener(this);
view.findViewById(R.id.cancel).setOnClickListener(this); view.findViewById(R.id.cancel).setOnClickListener(this);

View file

@ -66,17 +66,17 @@ public class NewTempBasalDialog extends DialogFragment implements View.OnClickLi
basalPercent = (NumberPicker) view.findViewById(R.id.overview_newtempbasal_basalpercentinput); basalPercent = (NumberPicker) view.findViewById(R.id.overview_newtempbasal_basalpercentinput);
double maxTempPercent = pumpDescription.maxTempPercent; double maxTempPercent = pumpDescription.maxTempPercent;
double tempPercentStep = pumpDescription.tempPercentStep; double tempPercentStep = pumpDescription.tempPercentStep;
basalPercent.setParams(100d, 0d, maxTempPercent, tempPercentStep, new DecimalFormat("0"), true); basalPercent.setParams(100d, 0d, maxTempPercent, tempPercentStep, new DecimalFormat("0"), true, view.findViewById(R.id.ok));
Profile profile = ProfileFunctions.getInstance().getProfile(); Profile profile = ProfileFunctions.getInstance().getProfile();
Double currentBasal = profile != null ? profile.getBasal() : 0d; Double currentBasal = profile != null ? profile.getBasal() : 0d;
basalAbsolute = (NumberPicker) view.findViewById(R.id.overview_newtempbasal_basalabsoluteinput); basalAbsolute = (NumberPicker) view.findViewById(R.id.overview_newtempbasal_basalabsoluteinput);
basalAbsolute.setParams(currentBasal, 0d, pumpDescription.maxTempAbsolute, pumpDescription.tempAbsoluteStep, new DecimalFormat("0.00"), true); basalAbsolute.setParams(currentBasal, 0d, pumpDescription.maxTempAbsolute, pumpDescription.tempAbsoluteStep, new DecimalFormat("0.00"), true, view.findViewById(R.id.ok));
double tempDurationStep = pumpDescription.tempDurationStep; double tempDurationStep = pumpDescription.tempDurationStep;
double tempMaxDuration = pumpDescription.tempMaxDuration; double tempMaxDuration = pumpDescription.tempMaxDuration;
duration = (NumberPicker) view.findViewById(R.id.overview_newtempbasal_duration); duration = (NumberPicker) view.findViewById(R.id.overview_newtempbasal_duration);
duration.setParams(tempDurationStep, tempDurationStep, tempMaxDuration, tempDurationStep, new DecimalFormat("0"), false); duration.setParams(tempDurationStep, tempDurationStep, tempMaxDuration, tempDurationStep, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
if ((pumpDescription.tempBasalStyle & PumpDescription.PERCENT) == PumpDescription.PERCENT && (pumpDescription.tempBasalStyle & PumpDescription.ABSOLUTE) == PumpDescription.ABSOLUTE) { if ((pumpDescription.tempBasalStyle & PumpDescription.PERCENT) == PumpDescription.PERCENT && (pumpDescription.tempBasalStyle & PumpDescription.ABSOLUTE) == PumpDescription.ABSOLUTE) {
// Both allowed // Both allowed

View file

@ -0,0 +1,90 @@
package info.nightscout.androidaps.plugins.general.automation;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.general.automation.actions.Action;
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
public class AutomationEvent {
private Trigger trigger = new TriggerConnector();
private List<Action> actions = new ArrayList<>();
private String title;
public void setTitle(String title) {
this.title = title;
}
public void setTrigger(Trigger trigger) {
this.trigger = trigger;
}
public Trigger getTrigger() {
return trigger;
}
public List<Action> getActions() {
return actions;
}
public TriggerConnector getPreconditions() {
TriggerConnector trigger = new TriggerConnector(TriggerConnector.Type.AND);
for (Action action : actions) {
if (action.precondition != null)
trigger.add(action.precondition);
}
return trigger;
}
public void addAction(Action action) {
actions.add(action);
}
public String getTitle() {
return title;
}
public String toJSON() {
JSONObject o = new JSONObject();
try {
// title
o.put("title", title);
// trigger
o.put("trigger", trigger.toJSON());
// actions
JSONArray array = new JSONArray();
for (Action a : actions) {
array.put(a.toJSON());
}
o.put("actions", array);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
public AutomationEvent fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
// title
title = d.optString("title", "");
// trigger
trigger = Trigger.instantiate(d.getString("trigger"));
// actions
JSONArray array = d.getJSONArray("actions");
actions.clear();
for (int i = 0; i < array.length(); i++) {
actions.add(Action.instantiate(new JSONObject(array.getString(i))));
}
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
}

View file

@ -0,0 +1,73 @@
package info.nightscout.androidaps.plugins.general.automation
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationDataChanged
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.automation_fragment.*
class AutomationFragment : Fragment() {
private var disposable: CompositeDisposable = CompositeDisposable()
private var eventListAdapter: EventListAdapter? = null
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.automation_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
eventListAdapter = EventListAdapter(AutomationPlugin.automationEvents, fragmentManager)
automation_eventListView.layoutManager = LinearLayoutManager(context)
automation_eventListView.adapter = eventListAdapter
automation_fabAddEvent.setOnClickListener {
val dialog = EditEventDialog()
val args = Bundle()
args.putString("event", AutomationEvent().toJSON())
args.putInt("position", -1) // New event
dialog.arguments = args
fragmentManager?.let { dialog.show(it, "EditEventDialog") }
}
disposable += RxBus
.toObservable(EventAutomationUpdateGui::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
eventListAdapter?.notifyDataSetChanged()
val sb = StringBuilder()
for (l in AutomationPlugin.executionLog) {
sb.append(l)
sb.append("\n")
}
automation_logView.text = sb.toString()
}, {})
disposable += RxBus
.toObservable(EventAutomationDataChanged::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
eventListAdapter?.notifyDataSetChanged()
}, {})
}
override fun onStop() {
super.onStop()
disposable.clear()
}
}

View file

@ -0,0 +1,213 @@
package info.nightscout.androidaps.plugins.general.automation
import android.content.Intent
import android.os.Handler
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventChargingState
import info.nightscout.androidaps.events.EventLocationChange
import info.nightscout.androidaps.events.EventNetworkChange
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.actions.*
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationDataChanged
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import info.nightscout.androidaps.plugins.general.automation.triggers.*
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.services.LocationService
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.SP
import info.nightscout.androidaps.utils.T
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.slf4j.LoggerFactory
import java.util.*
object AutomationPlugin : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(AutomationFragment::class.qualifiedName)
.pluginName(R.string.automation)
.shortName(R.string.automation_short)
.preferencesId(R.xml.pref_automation)
.description(R.string.automation_description)) {
private val log = LoggerFactory.getLogger(L.AUTOMATION)
private var disposable: CompositeDisposable = CompositeDisposable()
private const val key_AUTOMATION_EVENTS = "AUTOMATION_EVENTS"
val automationEvents = ArrayList<AutomationEvent>()
var executionLog: MutableList<String> = ArrayList()
private val loopHandler = Handler()
private lateinit var refreshLoop: Runnable
init {
refreshLoop = Runnable {
processActions()
loopHandler.postDelayed(refreshLoop, T.mins(1).msecs())
}
}
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
override fun onStart() {
val context = MainApp.instance().applicationContext
context.startService(Intent(context, LocationService::class.java))
super.onStart()
loadFromSP()
loopHandler.postDelayed(refreshLoop, T.mins(1).msecs())
disposable += RxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ e ->
if (e.isChanged(R.string.key_location)) {
val ctx = MainApp.instance().applicationContext
ctx.stopService(Intent(ctx, LocationService::class.java))
ctx.startService(Intent(ctx, LocationService::class.java))
}
}, {})
disposable += RxBus
.toObservable(EventAutomationDataChanged::class.java)
.observeOn(Schedulers.io())
.subscribe({ storeToSP() }, {})
disposable += RxBus
.toObservable(EventLocationChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ e ->
e?.let {
log.debug("Grabbed location: $it.location.latitude $it.location.longitude Provider: $it.location.provider")
processActions()
}
}, {})
disposable += RxBus
.toObservable(EventChargingState::class.java)
.observeOn(Schedulers.io())
.subscribe({ processActions() }, {})
disposable += RxBus
.toObservable(EventNetworkChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ processActions() }, {})
disposable += RxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(Schedulers.io())
.subscribe({ processActions() }, {})
}
override fun onStop() {
disposable.clear()
loopHandler.removeCallbacks(refreshLoop)
val context = MainApp.instance().applicationContext
context.stopService(Intent(context, LocationService::class.java))
}
private fun storeToSP() {
val array = JSONArray()
try {
for (event in automationEvents) {
array.put(JSONObject(event.toJSON()))
}
} catch (e: JSONException) {
e.printStackTrace()
}
SP.putString(key_AUTOMATION_EVENTS, array.toString())
}
private fun loadFromSP() {
automationEvents.clear()
val data = SP.getString(key_AUTOMATION_EVENTS, "")
if (data != "") {
try {
val array = JSONArray(data)
for (i in 0 until array.length()) {
val o = array.getJSONObject(i)
val event = AutomationEvent().fromJSON(o.toString())
automationEvents.add(event)
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
@Synchronized
private fun processActions() {
if (!isEnabled(PluginType.GENERAL))
return
if (L.isEnabled(L.AUTOMATION))
log.debug("processActions")
for (event in automationEvents) {
if (event.trigger.shouldRun() && event.preconditions.shouldRun()) {
val actions = event.actions
for (action in actions) {
action.doAction(object : Callback() {
override fun run() {
val sb = StringBuilder()
sb.append(DateUtil.timeString(DateUtil.now()))
sb.append(" ")
sb.append(if (result.success) "" else "X")
sb.append(" ")
sb.append(event.title)
sb.append(": ")
sb.append(action.shortDescription())
sb.append(": ")
sb.append(result.comment)
executionLog.add(sb.toString())
if (L.isEnabled(L.AUTOMATION))
log.debug("Executed: $sb")
RxBus.send(EventAutomationUpdateGui())
}
})
}
event.trigger.executed(DateUtil.now())
}
}
storeToSP() // save last run time
}
fun getActionDummyObjects(): List<Action> {
return listOf(
//ActionLoopDisable(),
//ActionLoopEnable(),
//ActionLoopResume(),
//ActionLoopSuspend(),
ActionStartTempTarget(),
ActionStopTempTarget(),
ActionNotification(),
ActionProfileSwitchPercent()
)
}
fun getTriggerDummyObjects(): List<Trigger> {
return listOf(
TriggerTime(),
TriggerRecurringTime(),
TriggerBg(),
TriggerDelta(),
TriggerIob(),
TriggerCOB(),
TriggerProfilePercent(),
TriggerTempTarget(),
TriggerWifiSsid(),
TriggerLocation(),
TriggerAutosensValue(),
TriggerBolusAgo()
)
}
}

View file

@ -0,0 +1,120 @@
package info.nightscout.androidaps.plugins.general.automation;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.HashSet;
import java.util.List;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.general.automation.actions.Action;
import info.nightscout.androidaps.plugins.general.automation.dialogs.EditEventDialog;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
class EventListAdapter extends RecyclerView.Adapter<EventListAdapter.ViewHolder> {
private final List<AutomationEvent> mEventList;
private final FragmentManager mFragmentManager;
EventListAdapter(List<AutomationEvent> events, FragmentManager fragmentManager) {
this.mEventList = events;
this.mFragmentManager = fragmentManager;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.automation_event_item, parent, false);
return new ViewHolder(v, parent.getContext());
}
private void addImage(@DrawableRes int res, Context context, LinearLayout layout) {
ImageView iv = new ImageView(context);
iv.setImageResource(res);
iv.setLayoutParams(new LinearLayout.LayoutParams(MainApp.dpToPx(24), MainApp.dpToPx(24)));
layout.addView(iv);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final AutomationEvent event = mEventList.get(position);
holder.eventTitle.setText(event.getTitle());
holder.iconLayout.removeAllViews();
// trigger icons
HashSet<Integer> triggerIcons = new HashSet<>();
TriggerConnector.fillIconSet((TriggerConnector) event.getTrigger(), triggerIcons);
for (int res : triggerIcons) {
addImage(res, holder.context, holder.iconLayout);
}
// arrow icon
ImageView iv = new ImageView(holder.context);
iv.setImageResource(R.drawable.ic_arrow_forward_white_24dp);
iv.setLayoutParams(new LinearLayout.LayoutParams(MainApp.dpToPx(24), MainApp.dpToPx(24)));
iv.setPadding(MainApp.dpToPx(4), 0, MainApp.dpToPx(4), 0);
holder.iconLayout.addView(iv);
// action icons
HashSet<Integer> actionIcons = new HashSet<>();
for (Action action : event.getActions()) {
if (action.icon().isPresent())
actionIcons.add(action.icon().get());
}
for (int res : actionIcons) {
addImage(res, holder.context, holder.iconLayout);
}
// remove event
holder.iconTrash.setOnClickListener(v -> {
mEventList.remove(event);
notifyDataSetChanged();
});
// edit event
holder.rootLayout.setOnClickListener(v -> {
//EditEventDialog dialog = EditEventDialog.Companion.newInstance(event, false);
EditEventDialog dialog = new EditEventDialog();
Bundle args = new Bundle();
args.putString("event", event.toJSON());
args.putInt("position", position);
dialog.setArguments(args);
if (mFragmentManager != null)
dialog.show(mFragmentManager, "EditEventDialog");
});
}
@Override
public int getItemCount() {
return mEventList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
final RelativeLayout rootLayout;
final LinearLayout iconLayout;
final TextView eventTitle;
final Context context;
final ImageView iconTrash;
ViewHolder(View view, Context context) {
super(view);
this.context = context;
eventTitle = view.findViewById(R.id.viewEventTitle);
rootLayout = view.findViewById(R.id.rootLayout);
iconLayout = view.findViewById(R.id.iconLayout);
iconTrash = view.findViewById(R.id.iconTrash);
}
}
}

View file

@ -0,0 +1,104 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import android.widget.LinearLayout;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import javax.annotation.Nullable;
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
import info.nightscout.androidaps.queue.Callback;
/*
Action ideas:
* cancel temp target
* change preference setting
* enable/disable plugin
* create notification
* create ugly alarm
* create profile switch
* set/cancel tbr
* set/cancel extended bolus
* run bolus wizard
Trigger ideas:
* location (close to)
* connected to specific wifi
* internet available/not available
* nsclient connected/disconnected
* iob
* cob
* autosens value
* delta, short delta, long delta
* last bolus ago
* is tbr running
* bolus wizard result
* loop is enabled, disabled, suspended, running
*/
public abstract class Action {
public Trigger precondition = null;
public abstract int friendlyName();
public abstract String shortDescription();
public abstract void doAction(Callback callback);
public void generateDialog(LinearLayout root) {
}
public boolean hasDialog() {
return false;
}
public String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", this.getClass().getName());
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
public abstract Optional<Integer> icon();
public Action fromJSON(String data) {
return this;
}
@Nullable
public static Action instantiate(JSONObject object) {
try {
String type = object.getString("type");
JSONObject data = object.optJSONObject("data");
Class clazz = Class.forName(type);
return ((Action) clazz.newInstance()).fromJSON(data != null ? data.toString() : "");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) {
e.printStackTrace();
}
return null;
}
public void apply(Action a) {
try {
JSONObject object = new JSONObject(a.toJSON());
String type = object.getString("type");
JSONObject data = object.getJSONObject("data");
if (type.equals(getClass().getName())) {
fromJSON(data.toString());
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,49 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import com.google.common.base.Optional;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.queue.Callback;
public class ActionLoopDisable extends Action {
@Override
public int friendlyName() {
return R.string.disableloop;
}
@Override
public String shortDescription() {
return MainApp.gs(R.string.disableloop);
}
@Override
public void doAction(Callback callback) {
if (LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) {
LoopPlugin.getPlugin().setPluginEnabled(PluginType.LOOP, false);
ConfigBuilderPlugin.getPlugin().storeSettings("ActionLoopDisable");
ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelTempBasal(true, new Callback() {
@Override
public void run() {
RxBus.INSTANCE.send(new EventRefreshOverview("ActionLoopDisable"));
if (callback != null)
callback.result(result).run();
}
});
} else {
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.alreadydisabled)).run();
}
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_stop_24dp);
}
}

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import com.google.common.base.Optional;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.queue.Callback;
public class ActionLoopEnable extends Action {
@Override
public int friendlyName() {
return R.string.enableloop;
}
@Override
public String shortDescription() {
return MainApp.gs(R.string.enableloop);
}
@Override
public void doAction(Callback callback) {
if (!LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) {
LoopPlugin.getPlugin().setPluginEnabled(PluginType.LOOP, true);
ConfigBuilderPlugin.getPlugin().storeSettings("ActionLoopEnable");
RxBus.INSTANCE.send(new EventRefreshOverview("ActionLoopEnable"));
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
} else {
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.alreadyenabled)).run();
}
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_play_circle_outline_24dp);
}
}

View file

@ -0,0 +1,45 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import com.google.common.base.Optional;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.queue.Callback;
public class ActionLoopResume extends Action {
@Override
public int friendlyName() {
return R.string.resumeloop;
}
@Override
public String shortDescription() {
return MainApp.gs(R.string.resumeloop);
}
@Override
public void doAction(Callback callback) {
if (LoopPlugin.getPlugin().isSuspended()) {
LoopPlugin.getPlugin().suspendTo(0);
ConfigBuilderPlugin.getPlugin().storeSettings("ActionLoopResume");
NSUpload.uploadOpenAPSOffline(0);
RxBus.INSTANCE.send(new EventRefreshOverview("ActionLoopResume"));
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
} else {
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.notsuspended)).run();
}
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_replay_24dp);
}
}

View file

@ -0,0 +1,91 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import android.widget.LinearLayout;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.utils.JsonHelper;
public class ActionLoopSuspend extends Action {
public InputDuration minutes = new InputDuration(0, InputDuration.TimeUnit.MINUTES);
@Override
public int friendlyName() {
return R.string.suspendloop;
}
@Override
public String shortDescription() {
return MainApp.gs(R.string.suspendloopforXmin, minutes.getMinutes());
}
@Override
public void doAction(Callback callback) {
if (!LoopPlugin.getPlugin().isSuspended()) {
LoopPlugin.getPlugin().suspendLoop(minutes.getMinutes());
RxBus.INSTANCE.send(new EventRefreshOverview("ActionLoopSuspend"));
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
} else {
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.alreadysuspended)).run();
}
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_pause_circle_outline_24dp);
}
@Override
public String toJSON() {
JSONObject o = new JSONObject();
JSONObject data = new JSONObject();
try {
data.put("minutes", minutes.getMinutes());
o.put("type", this.getClass().getName());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
public Action fromJSON(String data) {
try {
JSONObject o = new JSONObject(data);
minutes.setMinutes(JsonHelper.safeGetInt(o, "minutes"));
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public boolean hasDialog() {
return true;
}
@Override
public void generateDialog(LinearLayout root) {
new LayoutBuilder()
.add(new LabelWithElement(MainApp.gs(R.string.careportal_newnstreatment_duration_min_label), "", minutes))
.build(root);
}
}

View file

@ -0,0 +1,91 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import android.widget.LinearLayout;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.plugins.bus.RxBus;
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;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.utils.JsonHelper;
public class ActionNotification extends Action {
public InputString text = new InputString();
@Override
public int friendlyName() {
return R.string.notification;
}
@Override
public String shortDescription() {
return MainApp.gs(R.string.notification_message, text.getValue());
}
@Override
public void doAction(Callback callback) {
Notification notification = new Notification(Notification.USERMESSAGE, text.getValue(), Notification.URGENT);
RxBus.INSTANCE.send(new EventNewNotification(notification));
NSUpload.uploadError(text.getValue());
RxBus.INSTANCE.send(new EventRefreshOverview("ActionNotification"));
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_notifications);
}
@Override
public String toJSON() {
JSONObject o = new JSONObject();
JSONObject data = new JSONObject();
try {
data.put("text", text.getValue());
o.put("type", this.getClass().getName());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
public Action fromJSON(String data) {
try {
JSONObject o = new JSONObject(data);
text.setValue(JsonHelper.safeGetString(o, "text"));
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public boolean hasDialog() {
return true;
}
@Override
public void generateDialog(LinearLayout root) {
new LayoutBuilder()
.add(new LabelWithElement(MainApp.gs(R.string.message_short), "", text))
.build(root);
}
}

View file

@ -0,0 +1,95 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import android.widget.LinearLayout;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration;
import info.nightscout.androidaps.plugins.general.automation.elements.InputPercent;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerProfilePercent;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.utils.JsonHelper;
public class ActionProfileSwitchPercent extends Action {
InputPercent pct = new InputPercent();
InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES);
public ActionProfileSwitchPercent() {
precondition = new TriggerProfilePercent().comparator(Comparator.Compare.IS_EQUAL).setValue(100);
}
@Override
public int friendlyName() {
return R.string.profilepercentage;
}
@Override
public String shortDescription() {
if (duration.getMinutes() == 0)
return MainApp.gs(R.string.startprofileforever, (int) pct.getValue());
else
return MainApp.gs(R.string.startprofile, (int) pct.getValue(), duration.getMinutes());
}
@Override
public void doAction(Callback callback) {
ProfileFunctions.doProfileSwitch((int) duration.getValue(), (int) pct.getValue(), 0);
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
}
@Override
public void generateDialog(LinearLayout root) {
new LayoutBuilder()
.add(new LabelWithElement(MainApp.gs(R.string.percent_u), "", pct))
.add(new LabelWithElement(MainApp.gs(R.string.careportal_newnstreatment_duration_min_label), "", duration))
.build(root);
}
@Override
public boolean hasDialog() {
return true;
}
@Override
public String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", ActionProfileSwitchPercent.class.getName());
JSONObject data = new JSONObject();
data.put("percentage", pct.getValue());
data.put("durationInMinutes", duration.getMinutes());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
public Action fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
pct.setValue(JsonHelper.safeGetInt(d, "percentage"));
duration.setMinutes(JsonHelper.safeGetInt(d, "durationInMinutes"));
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_actions_profileswitch);
}
}

View file

@ -0,0 +1,119 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import android.widget.LinearLayout;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
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.PumpEnactResult;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.db.TempTarget;
import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorExists;
import info.nightscout.androidaps.plugins.general.automation.elements.InputBg;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerTempTarget;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
public class ActionStartTempTarget extends Action {
String reason = "";
InputBg value = new InputBg();
InputDuration duration = new InputDuration(0, InputDuration.TimeUnit.MINUTES);
private TempTarget tempTarget;
public ActionStartTempTarget() {
precondition = new TriggerTempTarget().comparator(ComparatorExists.Compare.NOT_EXISTS);
}
@Override
public int friendlyName() {
return R.string.starttemptarget;
}
@Override
public String shortDescription() {
tempTarget = new TempTarget()
.date(DateUtil.now())
.duration(duration.getMinutes())
.reason(reason)
.source(Source.USER)
.low(Profile.toMgdl(value.getValue(), value.getUnits()))
.high(Profile.toMgdl(value.getValue(), value.getUnits()));
return MainApp.gs(R.string.starttemptarget) + ": " + (tempTarget == null ? "null" : tempTarget.friendlyDescription(value.getUnits()));
}
@Override
public void doAction(Callback callback) {
tempTarget = new TempTarget()
.date(DateUtil.now())
.duration(duration.getMinutes())
.reason(reason)
.source(Source.USER)
.low(Profile.toMgdl(value.getValue(), value.getUnits()))
.high(Profile.toMgdl(value.getValue(), value.getUnits()));
TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget);
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
}
@Override
public void generateDialog(LinearLayout root) {
int unitResId = value.getUnits().equals(Constants.MGDL) ? R.string.mgdl : R.string.mmol;
new LayoutBuilder()
.add(new LabelWithElement(MainApp.gs(R.string.careportal_temporarytarget), MainApp.gs(unitResId), value))
.add(new LabelWithElement(MainApp.gs(R.string.careportal_newnstreatment_duration_min_label), "", duration))
.build(root);
}
@Override
public boolean hasDialog() {
return true;
}
@Override
public String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", ActionStartTempTarget.class.getName());
JSONObject data = new JSONObject();
data.put("reason", reason);
data.put("value", value.getValue());
data.put("units", value.getUnits());
data.put("durationInMinutes", duration.getMinutes());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
public Action fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
reason = JsonHelper.safeGetString(d, "reason");
value.setUnits(JsonHelper.safeGetString(d, "units"));
value.setValue(JsonHelper.safeGetDouble(d, "value"));
duration.setMinutes(JsonHelper.safeGetInt(d, "durationInMinutes"));
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_cp_cgm_target);
}
}

View file

@ -0,0 +1,77 @@
package info.nightscout.androidaps.plugins.general.automation.actions;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.db.TempTarget;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.queue.Callback;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
public class ActionStopTempTarget extends Action {
String reason = "";
private TempTarget tempTarget;
public ActionStopTempTarget() {
}
@Override
public int friendlyName() {
return R.string.stoptemptarget;
}
@Override
public String shortDescription() {
return MainApp.gs(R.string.stoptemptarget);
}
@Override
public void doAction(Callback callback) {
tempTarget = new TempTarget().date(DateUtil.now()).duration(0).reason(reason).source(Source.USER).low(0).high(0);
TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget);
if (callback != null)
callback.result(new PumpEnactResult().success(true).comment(R.string.ok)).run();
}
@Override
public boolean hasDialog() {
return false;
}
@Override
public String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", ActionStopTempTarget.class.getName());
JSONObject data = new JSONObject();
data.put("reason", reason);
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
public Action fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
reason = JsonHelper.safeGetString(d, "reason");
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_stop_24dp);
}
}

View file

@ -0,0 +1,54 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.actions.Action
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
class ActionListAdapter(private val fragmentManager: FragmentManager, private val actionList: MutableList<Action>) : RecyclerView.Adapter<ActionListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.automation_action_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val action = actionList[position]
holder.bind(action, fragmentManager, this, position, actionList)
}
override fun getItemCount(): Int {
return actionList.size
}
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(action: Action, fragmentManager: FragmentManager, recyclerView: RecyclerView.Adapter<ViewHolder>, position : Int, actionList: MutableList<Action>) {
view.findViewById<LinearLayout>(R.id.automation_layoutText).setOnClickListener {
if (action.hasDialog()) {
val args = Bundle()
args.putInt("actionPosition", position)
args.putString("action", action.toJSON())
val dialog = EditActionDialog()
dialog.arguments = args
dialog.show(fragmentManager, "EditActionDialog")
}
}
view.findViewById<ImageView>(R.id.automation_iconTrash).setOnClickListener {
actionList.remove(action)
recyclerView.notifyDataSetChanged()
RxBus.send(EventAutomationUpdateGui())
}
view.findViewById<TextView>(R.id.automation_viewActionTitle).text = action.shortDescription()
}
}
}

View file

@ -0,0 +1,85 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import androidx.fragment.app.DialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.automation.actions.Action
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationAddAction
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateGui
import kotlinx.android.synthetic.main.automation_dialog_choose_action.*
import kotlinx.android.synthetic.main.okcancel.*
class ChooseActionDialog : DialogFragment() {
var checkedIndex = -1
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// restore checked radio button
savedInstanceState?.let { bundle ->
checkedIndex = bundle.getInt("checkedIndex")
}
dialog.setCanceledOnTouchOutside(false)
return inflater.inflate(R.layout.automation_dialog_choose_action, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
for (a in AutomationPlugin.getActionDummyObjects()) {
val radioButton = RadioButton(context)
radioButton.setText(a.friendlyName())
radioButton.tag = a.javaClass
automation_radioGroup.addView(radioButton)
}
if (checkedIndex != -1)
(automation_radioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true
// OK button
ok.setOnClickListener {
dismiss()
instantiateAction()?.let {
RxBus.send(EventAutomationAddAction(it))
RxBus.send(EventAutomationUpdateGui())
}
}
// Cancel button
cancel.setOnClickListener { dismiss() }
}
override fun onSaveInstanceState(bundle: Bundle) {
bundle.putInt("checkedIndex", determineCheckedIndex())
}
private fun instantiateAction(): Action? {
return getActionClass()?.let {
it.newInstance() as Action
}
}
private fun getActionClass(): Class<*>? {
val radioButtonID = automation_radioGroup.checkedRadioButtonId
val radioButton = automation_radioGroup.findViewById<RadioButton>(radioButtonID)
return radioButton?.let {
it.tag as Class<*>
}
}
private fun determineCheckedIndex(): Int {
for (i in 0 until automation_radioGroup.childCount) {
if ((automation_radioGroup.getChildAt(i) as RadioButton).isChecked)
return i
}
return -1
}
}

View file

@ -0,0 +1,89 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioButton
import androidx.fragment.app.DialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
import kotlinx.android.synthetic.main.automation_dialog_choose_trigger.*
import kotlinx.android.synthetic.main.okcancel.*
class ChooseTriggerDialog : DialogFragment() {
private var checkedIndex = -1
private var clickListener: OnClickListener? = null
interface OnClickListener {
fun onClick(newTriggerObject: Trigger?)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// restore checked radio button
savedInstanceState?.let { bundle ->
checkedIndex = bundle.getInt("checkedIndex")
}
dialog.setCanceledOnTouchOutside(false)
return inflater.inflate(R.layout.automation_dialog_choose_trigger, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
for (t in AutomationPlugin.getTriggerDummyObjects()) {
val radioButton = RadioButton(context)
radioButton.setText(t.friendlyName())
radioButton.tag = t.javaClass
automation_chooseTriggerRadioGroup.addView(radioButton)
}
if (checkedIndex != -1)
(automation_chooseTriggerRadioGroup.getChildAt(checkedIndex) as RadioButton).isChecked = true
// OK button
ok.setOnClickListener {
dismiss()
clickListener?.onClick(instantiateTrigger())
}
// Cancel button
cancel.setOnClickListener { dismiss() }
}
fun setOnClickListener(clickListener: OnClickListener) {
this.clickListener = clickListener
}
override fun onSaveInstanceState(bundle: Bundle) {
bundle.putInt("checkedIndex", determineCheckedIndex())
}
private fun instantiateTrigger(): Trigger? {
return getTriggerClass()?.let {
it.newInstance() as Trigger
}
}
private fun getTriggerClass(): Class<*>? {
val radioButtonID = automation_chooseTriggerRadioGroup.checkedRadioButtonId
val radioButton = automation_chooseTriggerRadioGroup.findViewById<RadioButton>(radioButtonID)
return radioButton?.let {
it.tag as Class<*>
}
}
private fun determineCheckedIndex(): Int {
for (i in 0 until automation_chooseTriggerRadioGroup.childCount) {
if ((automation_chooseTriggerRadioGroup.getChildAt(i) as RadioButton).isChecked)
return i
}
return -1
}
}

View file

@ -0,0 +1,62 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.DialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.actions.Action
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateAction
import kotlinx.android.synthetic.main.automation_dialog_action.*
import kotlinx.android.synthetic.main.okcancel.*
import org.json.JSONObject
class EditActionDialog : DialogFragment() {
private var action: Action? = null
private var actionPosition: Int = -1
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// load data from bundle
(savedInstanceState ?: arguments)?.let { bundle ->
actionPosition = bundle.getInt("actionPosition", -1)
bundle.getString("action")?.let { action = Action.instantiate(JSONObject(it)) }
}
dialog.setCanceledOnTouchOutside(false)
return inflater.inflate(R.layout.automation_dialog_action, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
action?.let {
automation_actionTitle.setText(it.friendlyName())
automation_editActionLayout.removeAllViews()
it.generateDialog(automation_editActionLayout)
}
// OK button
ok.setOnClickListener {
dismiss()
action?.let {
RxBus.send(EventAutomationUpdateAction(it, actionPosition))
}
}
// Cancel button
cancel.setOnClickListener { dismiss() }
}
override fun onSaveInstanceState(bundle: Bundle) {
super.onSaveInstanceState(bundle)
action?.let {
bundle.putInt("actionPosition", actionPosition)
bundle.putString("action", it.toJSON())
}
}
}

View file

@ -0,0 +1,154 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.AutomationEvent
import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin
import info.nightscout.androidaps.plugins.general.automation.events.*
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector
import info.nightscout.androidaps.utils.ToastUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.automation_dialog_event.*
import kotlinx.android.synthetic.main.okcancel.*
class EditEventDialog : DialogFragment() {
private var actionListAdapter: ActionListAdapter? = null
private var event: AutomationEvent = AutomationEvent()
private var position: Int = -1
private var disposable: CompositeDisposable = CompositeDisposable()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// load data from bundle
(savedInstanceState ?: arguments)?.let { bundle ->
position = bundle.getInt("position", -1)
bundle.getString("event")?.let { event = AutomationEvent().fromJSON(it) }
}
dialog.setCanceledOnTouchOutside(false)
return inflater.inflate(R.layout.automation_dialog_event, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
automation_inputEventTitle.setText(event.title)
automation_triggerDescription.text = event.trigger.friendlyDescription()
automation_editTrigger.setOnClickListener {
val args = Bundle()
args.putString("trigger", event.trigger.toJSON())
val dialog = EditTriggerDialog()
dialog.arguments = args
fragmentManager?.let { dialog.show(it, "EditTriggerDialog") }
}
// setup action list view
fragmentManager?.let { actionListAdapter = ActionListAdapter(it, event.actions) }
automation_actionListView.layoutManager = LinearLayoutManager(context)
automation_actionListView.adapter = actionListAdapter
automation_addAction.setOnClickListener { fragmentManager?.let { ChooseActionDialog().show(it, "ChooseActionDialog") } }
// OK button
ok.setOnClickListener {
// check for title
val title = automation_inputEventTitle.text.toString()
if (title.isEmpty()) {
ToastUtils.showToastInUiThread(context, R.string.automation_missing_task_name)
return@setOnClickListener
}
event.title = title
// check for at least one trigger
val con = event.trigger as TriggerConnector
if (con.size() == 0) {
ToastUtils.showToastInUiThread(context, R.string.automation_missing_trigger)
return@setOnClickListener
}
// check for at least one action
if (event.actions.isEmpty()) {
ToastUtils.showToastInUiThread(context, R.string.automation_missing_action)
return@setOnClickListener
}
// store
if (position == -1)
AutomationPlugin.automationEvents.add(event)
else
AutomationPlugin.automationEvents[position] = event
dismiss()
RxBus.send(EventAutomationDataChanged())
}
// Cancel button
cancel.setOnClickListener { dismiss() }
showPreconditions()
disposable.add(RxBus
.toObservable(EventAutomationUpdateGui::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
actionListAdapter?.notifyDataSetChanged()
showPreconditions()
}, {})
)
disposable.add(RxBus
.toObservable(EventAutomationAddAction::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
event.addAction(it.action)
actionListAdapter?.notifyDataSetChanged()
}, {})
)
disposable.add(RxBus
.toObservable(EventAutomationUpdateTrigger::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
event.trigger = it.trigger
automation_triggerDescription.text = event.trigger.friendlyDescription()
}, {})
)
disposable.add(RxBus
.toObservable(EventAutomationUpdateAction::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
event.actions[it.position] = it.action
actionListAdapter?.notifyDataSetChanged()
}, {})
)
}
override fun onDestroyView() {
super.onDestroyView()
disposable.clear()
}
override fun onSaveInstanceState(bundle: Bundle) {
super.onSaveInstanceState(bundle)
bundle.putString("event", event.toJSON())
bundle.putInt("position", position)
}
private fun showPreconditions() {
val forcedTriggers = event.preconditions
if (forcedTriggers.size() > 0) {
automation_forcedTriggerDescription.visibility = View.VISIBLE
automation_forcedTriggerDescriptionLabel.visibility = View.VISIBLE
automation_forcedTriggerDescription.text = forcedTriggers.friendlyDescription()
} else {
automation_forcedTriggerDescription.visibility = View.GONE
automation_forcedTriggerDescriptionLabel.visibility = View.GONE
}
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.automation.events.EventAutomationUpdateTrigger
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
import kotlinx.android.synthetic.main.automation_dialog_edit_trigger.*
import kotlinx.android.synthetic.main.okcancel.*
class EditTriggerDialog : DialogFragment() {
private var trigger: Trigger? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// load data from bundle
(savedInstanceState ?: arguments)?.let { bundle ->
bundle.getString("trigger")?.let { trigger = Trigger.instantiate(it) }
}
dialog.setCanceledOnTouchOutside(false)
return inflater.inflate(R.layout.automation_dialog_edit_trigger, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// display root trigger
trigger?.let { it.generateDialog(automation_layoutTrigger, fragmentManager) }
// OK button
ok.setOnClickListener {
dismiss()
RxBus.send(EventAutomationUpdateTrigger(trigger!!))
}
// Cancel button
cancel.setOnClickListener { dismiss() }
}
override fun onSaveInstanceState(bundle: Bundle) {
super.onSaveInstanceState(bundle)
trigger?.let { bundle.putString("trigger", it.toJSON()) }
}
}

View file

@ -0,0 +1,186 @@
package info.nightscout.androidaps.plugins.general.automation.dialogs;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Spinner;
import androidx.fragment.app.FragmentManager;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger;
import info.nightscout.androidaps.plugins.general.automation.triggers.TriggerConnector;
public class TriggerListAdapter {
private final LinearLayout mRootLayout;
private final FragmentManager mFragmentManager;
private final Context mContext;
private final TriggerConnector mRootConnector;
public TriggerListAdapter(FragmentManager fragmentManager, Context context, LinearLayout rootLayout, TriggerConnector rootTrigger) {
mRootLayout = rootLayout;
mFragmentManager = fragmentManager;
mContext = context;
mRootConnector = rootTrigger;
build(fragmentManager);
}
public Context getContext() {
return mContext;
}
private FragmentManager getFM() {
return mFragmentManager;
}
private void destroy() {
mRootLayout.removeAllViews();
}
private void build(FragmentManager fragmentManager) {
for (int i = 0; i < mRootConnector.size(); ++i) {
final Trigger trigger = mRootConnector.get(i);
// spinner
if (i > 0) {
createSpinner(trigger);
}
// trigger layout
trigger.generateDialog(mRootLayout, fragmentManager);
// buttons
createButtons(fragmentManager, trigger);
}
if (mRootConnector.size() == 0) {
Button buttonAdd = new Button(mContext);
buttonAdd.setText(MainApp.gs(R.string.addnew));
buttonAdd.setOnClickListener(v -> {
ChooseTriggerDialog dialog = new ChooseTriggerDialog();
dialog.setOnClickListener(newTriggerObject -> {
mRootConnector.add(newTriggerObject);
rebuild(fragmentManager);
});
dialog.show(fragmentManager, "ChooseTriggerDialog");
});
mRootLayout.addView(buttonAdd);
}
}
private Spinner createSpinner() {
Spinner spinner = new Spinner(mContext);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_item, TriggerConnector.Type.labels());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerArrayAdapter);
return spinner;
}
private void createSpinner(Trigger trigger) {
final TriggerConnector connector = trigger.getConnector();
final int initialPosition = connector.getConnectorType().ordinal();
Spinner spinner = createSpinner();
spinner.setSelection(initialPosition);
spinner.setBackgroundColor(MainApp.gc(R.color.black_overlay));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, MainApp.dpToPx(8), 0, MainApp.dpToPx(8));
spinner.setLayoutParams(params);
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position != initialPosition) {
// connector type changed
changeConnector(getFM(), trigger, connector, TriggerConnector.Type.values()[position]);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mRootLayout.addView(spinner);
}
private void createButtons(FragmentManager fragmentManager, Trigger trigger) {
// do not create buttons for TriggerConnector
if (trigger instanceof TriggerConnector) {
return;
}
// Button Layout
LinearLayout buttonLayout = new LinearLayout(mContext);
buttonLayout.setOrientation(LinearLayout.HORIZONTAL);
buttonLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mRootLayout.addView(buttonLayout);
// Button [-]
Button buttonRemove = new Button(mContext);
buttonRemove.setText(MainApp.gs(R.string.delete_short));
buttonRemove.setOnClickListener(v -> {
final TriggerConnector connector = trigger.getConnector();
connector.remove(trigger);
connector.simplify().rebuildView(getFM());
});
buttonLayout.addView(buttonRemove);
// Button [+]
Button buttonAdd = new Button(mContext);
buttonAdd.setText(MainApp.gs(R.string.add_short));
buttonAdd.setOnClickListener(v -> {
ChooseTriggerDialog dialog = new ChooseTriggerDialog();
dialog.show(fragmentManager, "ChooseTriggerDialog");
dialog.setOnClickListener(newTriggerObject -> {
TriggerConnector connector = trigger.getConnector();
connector.add(connector.pos(trigger) + 1, newTriggerObject);
connector.simplify().rebuildView(getFM());
});
});
buttonLayout.addView(buttonAdd);
// Button [*]
Button buttonCopy = new Button(mContext);
buttonCopy.setText(MainApp.gs(R.string.copy_short));
buttonCopy.setOnClickListener(v -> {
TriggerConnector connector = trigger.getConnector();
connector.add(connector.pos(trigger) + 1, trigger.duplicate());
connector.simplify().rebuildView(getFM());
});
buttonLayout.addView(buttonCopy);
}
public void rebuild(FragmentManager fragmentManager) {
destroy();
build(fragmentManager);
}
public static void changeConnector(final FragmentManager fragmentManager, final Trigger trigger, final TriggerConnector connector, final TriggerConnector.Type newConnectorType) {
if (connector.size() > 2) {
// split connector
int pos = connector.pos(trigger) - 1;
TriggerConnector newConnector = new TriggerConnector(newConnectorType);
// move trigger from pos and pos+1 into new connector
for (int i = 0; i < 2; ++i) {
Trigger t = connector.get(pos);
newConnector.add(t);
connector.remove(t);
}
connector.add(pos, newConnector);
} else {
connector.changeConnectorType(newConnectorType);
}
connector.simplify().rebuildView(fragmentManager);
}
}

View file

@ -0,0 +1,123 @@
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 Comparator extends Element {
public enum Compare {
IS_LESSER,
IS_EQUAL_OR_LESSER,
IS_EQUAL,
IS_EQUAL_OR_GREATER,
IS_GREATER,
IS_NOT_AVAILABLE;
public @StringRes
int getStringRes() {
switch (this) {
case IS_LESSER:
return R.string.islesser;
case IS_EQUAL_OR_LESSER:
return R.string.isequalorlesser;
case IS_EQUAL:
return R.string.isequal;
case IS_EQUAL_OR_GREATER:
return R.string.isequalorgreater;
case IS_GREATER:
return R.string.isgreater;
case IS_NOT_AVAILABLE:
return R.string.isnotavailable;
default:
return R.string.unknown;
}
}
public <T extends Comparable> boolean check(T obj1, T obj2) {
if (obj1 == null || obj2 == null)
return this.equals(IS_NOT_AVAILABLE);
int comparison = obj1.compareTo(obj2);
switch (this) {
case IS_LESSER:
return comparison < 0;
case IS_EQUAL_OR_LESSER:
return comparison <= 0;
case IS_EQUAL:
return comparison == 0;
case IS_EQUAL_OR_GREATER:
return comparison >= 0;
case IS_GREATER:
return comparison > 0;
default:
return false;
}
}
public static List<String> labels() {
List<String> list = new ArrayList<>();
for (Compare c : Compare.values()) {
list.add(MainApp.gs(c.getStringRes()));
}
return list;
}
}
private Compare compare = Compare.IS_EQUAL;
public Comparator() {
super();
}
public Comparator(Comparator another) {
super();
compare = another.getValue();
}
@Override
public void addToLayout(LinearLayout root) {
Spinner spinner = new Spinner(root.getContext());
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_spinner_item, Compare.labels());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerArrayAdapter);
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
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) {
compare = Compare.values()[position];
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spinner.setSelection(compare.ordinal());
root.addView(spinner);
}
public Compare getValue() {
return compare;
}
public Comparator setValue(Compare compare) {
this.compare = compare;
return this;
}
}

View file

@ -0,0 +1,90 @@
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 ComparatorExists extends Element {
public enum Compare {
EXISTS,
NOT_EXISTS;
public @StringRes
int getStringRes() {
switch (this) {
case EXISTS:
return R.string.exists;
case NOT_EXISTS:
return R.string.notexists;
default:
return R.string.unknown;
}
}
public static List<String> labels() {
List<String> list = new ArrayList<>();
for (Compare c : Compare.values()) {
list.add(MainApp.gs(c.getStringRes()));
}
return list;
}
}
private Compare compare = Compare.EXISTS;
public ComparatorExists() {
super();
}
public ComparatorExists(ComparatorExists another) {
super();
compare = another.getValue();
}
@Override
public void addToLayout(LinearLayout root) {
Spinner spinner = new Spinner(root.getContext());
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_spinner_item, Compare.labels());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerArrayAdapter);
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
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) {
compare = Compare.values()[position];
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spinner.setSelection(compare.ordinal());
root.addView(spinner);
}
public Compare getValue() {
return compare;
}
public ComparatorExists setValue(Compare compare) {
this.compare = compare;
return this;
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.widget.LinearLayout;
public abstract class Element {
public abstract void addToLayout(LinearLayout root);
}

View file

@ -0,0 +1,99 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.LinearLayout;
import java.text.DecimalFormat;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.utils.NumberPicker;
public class InputBg extends Element {
final TextWatcher textWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (units.equals(Constants.MMOL)) {
value = Math.max(value, 4d);
value = Math.min(value, 15d);
} else {
value = Math.max(value, 72d);
value = Math.min(value, 270d);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
private String units = Constants.MGDL;
private double value;
double minValue;
private double maxValue;
private double step;
private DecimalFormat decimalFormat;
public InputBg() {
super();
setUnits(ProfileFunctions.getInstance().getProfileUnits());
}
public InputBg(InputBg another) {
super();
value = another.getValue();
setUnits(another.getUnits());
}
@Override
public void addToLayout(LinearLayout root) {
NumberPicker numberPicker = new NumberPicker(root.getContext(), null);
numberPicker.setParams(value, minValue, maxValue, step, decimalFormat, true, null, textWatcher);
numberPicker.setOnValueChangedListener(value -> this.value = value);
root.addView(numberPicker);
}
public String getUnits() {
return units;
}
public InputBg setUnits(String units) {
// set default initial value
if (units.equals(Constants.MMOL)) {
// mmol
minValue = 2;
maxValue = 30;
step = 0.1;
decimalFormat = new DecimalFormat("0.0");
} else {
// mg/dL
minValue = 40;
maxValue = 540;
step = 1;
decimalFormat = new DecimalFormat("0");
}
// make sure that value is in range
textWatcher.afterTextChanged(null);
this.units = units;
return this;
}
public InputBg setValue(double value) {
this.value = value;
return this;
}
public double getValue() {
return value;
}
}

View file

@ -0,0 +1,28 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.widget.Button;
import android.widget.LinearLayout;
public class InputButton extends Element {
String text;
Runnable runnable;
public InputButton(String text, Runnable runnable) {
this.text = text;
this.runnable = runnable;
}
@Override
public void addToLayout(LinearLayout root) {
Button button = new Button(root.getContext());
button.setText(text);
button.setOnClickListener(view -> {
if (runnable != null)
runnable.run();
});
root.addView(button);
}
}

View file

@ -0,0 +1,151 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import androidx.annotation.StringRes;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.utils.NumberPicker;
public class InputDelta extends Element {
private Comparator.Compare compare = Comparator.Compare.IS_EQUAL;
final TextWatcher textWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
public enum DeltaType {
DELTA,
SHORT_AVERAGE,
LONG_AVERAGE;
public @StringRes
int getStringRes() {
switch (this) {
case DELTA:
return R.string.delta;
case SHORT_AVERAGE:
return R.string.short_avgdelta;
case LONG_AVERAGE:
return R.string.long_avgdelta;
default:
return R.string.unknown;
}
}
public static List<String> labels() {
List<String> list = new ArrayList<>();
for (DeltaType d : DeltaType.values()) {
list.add(MainApp.gs(d.getStringRes()));
}
return list;
}
}
private double value;
double minValue;
double maxValue;
private double step;
private DecimalFormat decimalFormat;
private DeltaType deltaType;
NumberPicker numberPicker;
public InputDelta() {
super();
}
public InputDelta(double value, double minValue, double maxValue, double step, DecimalFormat decimalFormat, DeltaType deltaType) {
super();
this.value = value;
this.minValue = minValue;
this.maxValue = maxValue;
this.step = step;
this.decimalFormat = decimalFormat;
this.deltaType = deltaType;
}
public InputDelta(InputDelta another) {
super();
value = another.getValue();
minValue = another.minValue;
maxValue = another.maxValue;
step = another.step;
decimalFormat = another.decimalFormat;
deltaType = another.deltaType;
}
@Override
public void addToLayout(LinearLayout root) {
Spinner spinner = new Spinner(root.getContext());
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_spinner_item, DeltaType.labels());
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerArrayAdapter);
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) {
deltaType = DeltaType.values()[position];
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
spinner.setSelection(this.deltaType.ordinal());
// root.addView(spinner);
numberPicker = new NumberPicker(root.getContext(), null);
numberPicker.setParams(value, minValue, maxValue, step, decimalFormat, true, null, textWatcher);
numberPicker.setOnValueChangedListener(value -> this.value = value);
LinearLayout l = new LinearLayout(root.getContext());
l.setOrientation(LinearLayout.VERTICAL);
l.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
l.addView(spinner);
l.addView(numberPicker);
root.addView(l);
}
public InputDelta setValue(double value, DeltaType type) {
this.value = value;
this.deltaType = type;
if (numberPicker != null)
numberPicker.setValue(value);
return this;
}
public double getValue() {
return value;
}
public DeltaType getDeltaType() {
return deltaType;
}
}

View file

@ -0,0 +1,77 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.LinearLayout;
import java.text.DecimalFormat;
import info.nightscout.androidaps.utils.NumberPicker;
public class InputDouble extends Element {
final TextWatcher textWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
private double value;
double minValue;
double maxValue;
private double step;
private DecimalFormat decimalFormat;
NumberPicker numberPicker;
public InputDouble() {
super();
}
public InputDouble(double value, double minValue, double maxValue, double step, DecimalFormat decimalFormat) {
super();
this.value = value;
this.minValue = minValue;
this.maxValue = maxValue;
this.step = step;
this.decimalFormat = decimalFormat;
}
public InputDouble(InputDouble another) {
super();
value = another.getValue();
minValue = another.minValue;
maxValue = another.maxValue;
step = another.step;
decimalFormat = another.decimalFormat;
}
@Override
public void addToLayout(LinearLayout root) {
numberPicker = new NumberPicker(root.getContext(), null);
numberPicker.setParams(value, minValue, maxValue, step, decimalFormat, true, null, textWatcher);
numberPicker.setOnValueChangedListener(value -> this.value = value);
root.addView(numberPicker);
}
public InputDouble setValue(double value) {
this.value = value;
if (numberPicker != null)
numberPicker.setValue(value);
return this;
}
public double getValue() {
return value;
}
}

View file

@ -0,0 +1,61 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.widget.LinearLayout;
import java.text.DecimalFormat;
import info.nightscout.androidaps.utils.NumberPicker;
public class InputDuration extends Element {
public enum TimeUnit {
MINUTES,
HOURS
}
private TimeUnit unit;
private int value;
public InputDuration(int value, TimeUnit unit) {
this.unit = unit;
this.value = value;
}
@Override
public void addToLayout(LinearLayout root) {
NumberPicker numberPicker = new NumberPicker(root.getContext(), null);
if (unit.equals(TimeUnit.MINUTES)) {
// Minutes
numberPicker.setParams(0d, 0d, 24 * 60d, 10d, new DecimalFormat("0"), false, null);
} else {
// Hours
numberPicker.setParams(0d, 0d, 24d, 1d, new DecimalFormat("0"), false, null);
}
numberPicker.setValue((double) value);
numberPicker.setOnValueChangedListener(value -> this.value = (int) value);
root.addView(numberPicker);
}
TimeUnit getUnit() {
return unit;
}
public double getValue() {
return value;
}
public void setMinutes(int value) {
if (unit.equals(TimeUnit.MINUTES)) {
this.value = value;
} else {
this.value = value / 60;
}
}
public int getMinutes() {
if (unit.equals(TimeUnit.MINUTES)) {
return value;
} else {
return value * 60;
}
}
}

View file

@ -0,0 +1,57 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.LinearLayout;
import java.text.DecimalFormat;
import info.nightscout.androidaps.utils.NumberPicker;
public class InputInsulin extends Element {
final TextWatcher textWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
value = Math.max(value, -20d);
value = Math.min(value, 20d);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
private double value;
public InputInsulin() {
super();
}
public InputInsulin(InputInsulin another) {
super();
value = another.getValue();
}
@Override
public void addToLayout(LinearLayout root) {
NumberPicker numberPicker = new NumberPicker(root.getContext(), null);
numberPicker.setParams(0d, -20d, 20d, 0.1, new DecimalFormat("0.0"), true, null, textWatcher);
numberPicker.setValue(value);
numberPicker.setOnValueChangedListener(value -> this.value = value);
root.addView(numberPicker);
}
public double getValue() {
return value;
}
public InputInsulin setValue(double value) {
this.value = value;
return this;
}
}

View file

@ -0,0 +1,58 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.LinearLayout;
import java.text.DecimalFormat;
import info.nightscout.androidaps.utils.NumberPicker;
public class InputPercent extends Element {
final TextWatcher textWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
value = Math.max(value, 70d);
value = Math.min(value, 130d);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
};
private double value;
public InputPercent() {
super();
value = 100d;
}
public InputPercent(InputPercent another) {
super();
value = another.getValue();
}
@Override
public void addToLayout(LinearLayout root) {
NumberPicker numberPicker = new NumberPicker(root.getContext(), null);
numberPicker.setParams(100d, 70d, 130d, 5d, new DecimalFormat("0"), true, null, textWatcher);
numberPicker.setValue(value);
numberPicker.setOnValueChangedListener(value -> this.value = value);
root.addView(numberPicker);
}
public double getValue() {
return value;
}
public InputPercent setValue(double value) {
this.value = value;
return this;
}
}

View file

@ -0,0 +1,56 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
public class InputString extends Element {
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
value = s.toString();
}
};
private String value = "";
public InputString() {
super();
}
public InputString(InputString another) {
super();
value = another.getValue();
}
@Override
public void addToLayout(LinearLayout root) {
EditText editText = new EditText(root.getContext());
editText.setText(value);
editText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
editText.addTextChangedListener(textWatcher);
root.addView(editText);
}
public InputString setValue(String value) {
this.value = value;
return this;
}
public String getValue() {
return value;
}
}

View file

@ -0,0 +1,59 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.graphics.Typeface;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import info.nightscout.androidaps.MainApp;
public class LabelWithElement extends Element {
final Element element;
final String textPre;
final String textPost;
public LabelWithElement(String textPre, String textPost, Element element) {
this.element = element;
this.textPre = textPre;
this.textPost = textPost;
}
@Override
public void addToLayout(LinearLayout root) {
// container layout
LinearLayout layout = new LinearLayout(root.getContext());
layout.setOrientation(LinearLayout.HORIZONTAL);
layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
// text view pre element
int px = MainApp.dpToPx(10);
TextView textViewPre = new TextView(root.getContext());
textViewPre.setText(textPre);
textViewPre.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
//textViewPre.setWidth(MainApp.dpToPx(120));
textViewPre.setPadding(px, px, px, px);
textViewPre.setTypeface(textViewPre.getTypeface(), Typeface.BOLD);
layout.addView(textViewPre);
// add element to layout
element.addToLayout(layout);
// text view post element
if (textPost != null) {
px = MainApp.dpToPx(5);
TextView textViewPost = new TextView(root.getContext());
textViewPost.setText(textPost);
textViewPost.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
//textViewPost.setWidth(MainApp.dpToPx(45));
textViewPost.setPadding(px, px, px, px);
textViewPost.setTypeface(textViewPost.getTypeface(), Typeface.BOLD);
layout.addView(textViewPost);
}
// add layout to root layout
root.addView(layout);
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.widget.LinearLayout;
import java.util.ArrayList;
public class LayoutBuilder {
ArrayList<Element> mElements = new ArrayList<>();
public LayoutBuilder add(Element element) {
mElements.add(element);
return this;
}
public LayoutBuilder add(Element element, boolean condition) {
if (condition) mElements.add(element);
return this;
}
public void build(LinearLayout layout) {
for (Element e : mElements) {
e.addToLayout(layout);
}
}
}

View file

@ -0,0 +1,36 @@
package info.nightscout.androidaps.plugins.general.automation.elements;
import android.graphics.Typeface;
import android.widget.LinearLayout;
import android.widget.TextView;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
public class StaticLabel extends Element {
String label;
public StaticLabel(String label) {
super();
this.label = label;
}
public StaticLabel(int resourceId) {
super();
this.label = MainApp.gs(resourceId);
}
@Override
public void addToLayout(LinearLayout root) {
// text view pre element
int px = MainApp.dpToPx(10);
TextView textView = new TextView(root.getContext());
textView.setText(label);
// textViewPre.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView.setPadding(px, px, px, px);
textView.setTypeface(textView.getTypeface(), Typeface.BOLD);
textView.setBackgroundColor(MainApp.gc(R.color.mdtp_line_dark));
// add element to layout
root.addView(textView);
}
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.general.automation.actions.Action
class EventAutomationAddAction(val action: Action) : Event()

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.general.automation.events
import info.nightscout.androidaps.events.Event
class EventAutomationDataChanged : Event()

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.general.automation.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.general.automation.actions.Action
class EventAutomationUpdateAction(val action: Action, val position : Int) : Event() {
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.general.automation.events
import info.nightscout.androidaps.events.Event
class EventAutomationUpdateGui : Event()

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.general.automation.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.general.automation.triggers.Trigger
class EventAutomationUpdateTrigger(val trigger: Trigger) : Event()

View file

@ -0,0 +1,94 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import javax.annotation.Nullable;
public abstract class Trigger {
TriggerConnector connector = null;
long lastRun;
Trigger() {
}
public TriggerConnector getConnector() {
return connector;
}
public abstract boolean shouldRun();
public abstract String toJSON();
/*package*/
abstract Trigger fromJSON(String data);
public abstract int friendlyName();
public abstract String friendlyDescription();
public abstract Optional<Integer> icon();
public void executed(long time) {
lastRun = time;
}
public long getLastRun() {
return lastRun;
}
public abstract Trigger duplicate();
public static Trigger instantiate(String json) {
try {
return instantiate(new JSONObject(json));
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@Nullable
public static Trigger instantiate(JSONObject object) {
try {
String type = object.getString("type");
JSONObject data = object.getJSONObject("data");
Class clazz = Class.forName(type);
return ((Trigger) clazz.newInstance()).fromJSON(data.toString());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | JSONException e) {
e.printStackTrace();
}
return null;
}
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
TextView title = new TextView(root.getContext());
title.setText(friendlyName());
root.addView(title);
}
@Nullable
Activity scanForActivity(Context cont) {
if (cont == null)
return null;
else if (cont instanceof Activity)
return (Activity) cont;
else if (cont instanceof ContextWrapper)
return scanForActivity(((ContextWrapper) cont).getBaseContext());
return null;
}
}

View file

@ -0,0 +1,153 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDouble;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.utils.T;
public class TriggerAutosensValue extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private final int minValue = (int) (SP.getDouble("openapsama_autosens_min", 0.7d) * 100);
private final int maxValue = (int) (SP.getDouble("openapsama_autosens_max", 1.2d) * 100);
private final double step = 1;
private DecimalFormat decimalFormat = new DecimalFormat("1");
private InputDouble value = new InputDouble(100, (double) minValue, (double) maxValue, step, decimalFormat);
private Comparator comparator = new Comparator();
public TriggerAutosensValue() {
super();
}
private TriggerAutosensValue(TriggerAutosensValue triggerAutosensValue) {
super();
value = new InputDouble(triggerAutosensValue.value);
lastRun = triggerAutosensValue.lastRun;
comparator = new Comparator(triggerAutosensValue.comparator);
}
public double getValue() {
return value.getValue();
}
public Comparator getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getLastAutosensData("Automation trigger");
if (autosensData == null)
if (comparator.getValue() == Comparator.Compare.IS_NOT_AVAILABLE)
return true;
else
return false;
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
boolean doRun = comparator.getValue().check((autosensData.autosensResult.ratio), getValue() / 100d);
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerAutosensValue.class.getName());
JSONObject data = new JSONObject();
data.put("value", getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
value.setValue(JsonHelper.safeGetDouble(d, "value"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.autosenslabel;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.autosenscompared, MainApp.gs(comparator.getValue().getStringRes()), getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.as);
}
@Override
public Trigger duplicate() {
return new TriggerAutosensValue(this);
}
TriggerAutosensValue setValue(int requestedValue) {
this.value.setValue(requestedValue);
return this;
}
TriggerAutosensValue lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerAutosensValue comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.autosenslabel))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.autosenslabel) + ": ", "", value))
.build(root);
}
}

View file

@ -0,0 +1,169 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.logging.L;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputBg;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerBg extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private InputBg bg = new InputBg();
private Comparator comparator = new Comparator();
public TriggerBg() {
super();
}
private TriggerBg(TriggerBg triggerBg) {
super();
bg = new InputBg(triggerBg.bg);
comparator = new Comparator(triggerBg.comparator);
lastRun = triggerBg.lastRun;
}
public double getValue() {
return bg.getValue();
}
public Comparator getComparator() {
return comparator;
}
public String getUnits() {
return bg.getUnits();
}
public long getLastRun() {
return lastRun;
}
@Override
public synchronized boolean shouldRun() {
GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
if (glucoseStatus == null && comparator.getValue().equals(Comparator.Compare.IS_NOT_AVAILABLE)) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
if (glucoseStatus == null)
return false;
boolean doRun = comparator.getValue().check(glucoseStatus.glucose, Profile.toMgdl(bg.getValue(), bg.getUnits()));
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerBg.class.getName());
JSONObject data = new JSONObject();
data.put("bg", bg.getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
data.put("units", bg.getUnits());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
bg.setUnits(JsonHelper.safeGetString(d, "units"));
bg.setValue(JsonHelper.safeGetDouble(d, "bg"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.glucose;
}
@Override
public String friendlyDescription() {
if (comparator.getValue().equals(Comparator.Compare.IS_NOT_AVAILABLE))
return MainApp.gs(R.string.glucoseisnotavailable);
else {
return MainApp.gs(bg.getUnits().equals(Constants.MGDL) ? R.string.glucosecomparedmgdl : R.string.glucosecomparedmmol, MainApp.gs(comparator.getValue().getStringRes()), bg.getValue(), bg.getUnits());
}
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_cp_bgcheck);
}
@Override
public Trigger duplicate() {
return new TriggerBg(this);
}
TriggerBg setValue(double value) {
bg.setValue(value);
return this;
}
TriggerBg lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerBg comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
TriggerBg setUnits(String units) {
bg.setUnits(units);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.glucose))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.glucose_u, bg.getUnits()), "", bg))
.build(root);
}
}

View file

@ -0,0 +1,154 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDuration;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerBolusAgo extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private final double step = 1;
private DecimalFormat decimalFormat = new DecimalFormat("1");
public InputDuration minutesAgo = new InputDuration(0, InputDuration.TimeUnit.MINUTES);
private Comparator comparator = new Comparator();
public TriggerBolusAgo() {
super();
}
private TriggerBolusAgo(TriggerBolusAgo triggerBolusAgo) {
super();
minutesAgo = new InputDuration(triggerBolusAgo.minutesAgo.getMinutes(), InputDuration.TimeUnit.MINUTES);
lastRun = triggerBolusAgo.lastRun;
comparator = new Comparator(triggerBolusAgo.comparator);
}
public double getValue() {
return minutesAgo.getValue();
}
public Comparator getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
long lastBolusTime = TreatmentsPlugin.getPlugin().getLastBolusTime(false);
if (lastBolusTime == 0)
if (comparator.getValue() == Comparator.Compare.IS_NOT_AVAILABLE)
return true;
else
return false;
double minutesAgo = (double) (DateUtil.now() - lastBolusTime) / (60 * 1000);
if (L.isEnabled(L.AUTOMATION))
log.debug("LastBolus min ago: " + minutesAgo);
boolean doRun = comparator.getValue().check((minutesAgo), getValue());
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerBolusAgo.class.getName());
JSONObject data = new JSONObject();
data.put("minutesAgo", getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
minutesAgo.setMinutes(JsonHelper.safeGetInt(d, "minutesAgo"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.lastboluslabel;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.lastboluscompared, MainApp.gs(comparator.getValue().getStringRes()), (int) getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_bolus);
}
@Override
public Trigger duplicate() {
return new TriggerBolusAgo(this);
}
TriggerBolusAgo setValue(int requestedValue) {
this.minutesAgo.setMinutes(requestedValue);
return this;
}
TriggerBolusAgo lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerBolusAgo comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.lastboluslabel))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.lastboluslabel) + ": ", "", minutesAgo))
.build(root);
}
}

View file

@ -0,0 +1,153 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDouble;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.SP;
import info.nightscout.androidaps.utils.T;
public class TriggerCOB extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private final int minValue = 0;
private final int maxValue = (int) (SP.getInt(R.string.key_treatmentssafety_maxcarbs, 48));
private final double step = 1;
private DecimalFormat decimalFormat = new DecimalFormat("1");
private InputDouble value = new InputDouble(0, (double) minValue, (double) maxValue, step, decimalFormat);
private Comparator comparator = new Comparator();
public TriggerCOB() {
super();
}
private TriggerCOB(TriggerCOB triggerCOB) {
super();
value = new InputDouble(triggerCOB.value);
lastRun = triggerCOB.lastRun;
comparator = new Comparator(triggerCOB.comparator);
}
public double getValue() {
return value.getValue();
}
public Comparator getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
CobInfo cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "AutomationTriggerCOB");
if (cobInfo == null)
if (comparator.getValue() == Comparator.Compare.IS_NOT_AVAILABLE)
return true;
else
return false;
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
boolean doRun = comparator.getValue().check((cobInfo.displayCob), getValue());
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerCOB.class.getName());
JSONObject data = new JSONObject();
data.put("carbs", getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
value.setValue(JsonHelper.safeGetDouble(d, "carbs"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.triggercoblabel;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.cobcompared, MainApp.gs(comparator.getValue().getStringRes()), getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_cp_bolus_carbs);
}
@Override
public Trigger duplicate() {
return new TriggerCOB(this);
}
TriggerCOB setValue(int requestedValue) {
this.value.setValue(requestedValue);
return this;
}
TriggerCOB lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerCOB comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.triggercoblabel))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.triggercoblabel) + ": ", "", value))
.build(root);
}
}

View file

@ -0,0 +1,277 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.StringRes;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.dialogs.TriggerListAdapter;
import info.nightscout.androidaps.utils.JsonHelper;
public class TriggerConnector extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
public enum Type {
AND,
OR,
XOR;
public boolean apply(boolean a, boolean b) {
switch (this) {
case AND:
return a && b;
case OR:
return a || b;
case XOR:
return a ^ b;
}
return false;
}
public @StringRes
int getStringRes() {
switch (this) {
case OR:
return R.string.or;
case XOR:
return R.string.xor;
default:
case AND:
return R.string.and;
}
}
public static List<String> labels() {
List<String> list = new ArrayList<>();
for (Type t : values()) {
list.add(MainApp.gs(t.getStringRes()));
}
return list;
}
}
public static void fillIconSet(TriggerConnector connector, HashSet<Integer> set) {
for (Trigger t : connector.list) {
if (t instanceof TriggerConnector) {
fillIconSet((TriggerConnector) t, set);
} else {
Optional<Integer> icon = t.icon();
if (icon.isPresent()) {
set.add(icon.get());
}
}
}
}
protected List<Trigger> list = new ArrayList<>();
private Type connectorType;
public TriggerConnector() {
connectorType = Type.AND;
}
public TriggerConnector(Type connectorType) {
this.connectorType = connectorType;
}
public void changeConnectorType(Type type) {
this.connectorType = type;
}
public Type getConnectorType() {
return connectorType;
}
public synchronized void add(Trigger t) {
list.add(t);
t.connector = this;
}
public synchronized void add(int pos, Trigger t) {
list.add(pos, t);
t.connector = this;
}
public synchronized boolean remove(Trigger t) {
return list.remove(t);
}
public int size() {
return list.size();
}
public Trigger get(int i) {
return list.get(i);
}
public int pos(Trigger trigger) {
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) == trigger) return i;
}
return -1;
}
@Override
public synchronized boolean shouldRun() {
boolean result = true;
// check first trigger
if (list.size() > 0)
result = list.get(0).shouldRun();
// check all others
for (int i = 1; i < list.size(); ++i) {
result = connectorType.apply(result, list.get(i).shouldRun());
}
if (result)
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription().replace("\n", " "));
return result;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerConnector.class.getName());
JSONObject data = new JSONObject();
data.put("connectorType", connectorType.toString());
JSONArray array = new JSONArray();
for (Trigger t : list) {
array.put(t.toJSON());
}
data.put("triggerList", array);
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
connectorType = Type.valueOf(JsonHelper.safeGetString(d, "connectorType"));
JSONArray array = d.getJSONArray("triggerList");
list.clear();
for (int i = 0; i < array.length(); i++) {
Trigger newItem = instantiate(new JSONObject(array.getString(i)));
add(newItem);
}
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return connectorType.getStringRes();
}
@Override
public String friendlyDescription() {
int counter = 0;
StringBuilder result = new StringBuilder();
for (Trigger t : list) {
if (counter++ > 0) result.append("\n").append(MainApp.gs(friendlyName())).append("\n");
result.append(t.friendlyDescription());
}
return result.toString();
}
@Override
public Optional<Integer> icon() {
return Optional.absent();
}
@Override
public void executed(long time) {
for (int i = 0; i < list.size(); ++i) {
list.get(i).executed(time);
}
}
@Override
public Trigger duplicate() {
return null;
}
private TriggerListAdapter adapter;
public void rebuildView(FragmentManager fragmentManager) {
if (adapter != null)
adapter.rebuild(fragmentManager);
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
final int padding = MainApp.dpToPx(5);
root.setPadding(padding, padding, padding, padding);
root.setBackgroundResource(R.drawable.border_automation_unit);
LinearLayout triggerListLayout = new LinearLayout(root.getContext());
triggerListLayout.setOrientation(LinearLayout.VERTICAL);
triggerListLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(triggerListLayout);
adapter = new TriggerListAdapter(fragmentManager, root.getContext(), triggerListLayout, this);
}
public TriggerConnector simplify() {
// simplify children
for (int i = 0; i < size(); ++i) {
if (get(i) instanceof TriggerConnector) {
TriggerConnector t = (TriggerConnector) get(i);
t.simplify();
}
}
// drop connector with only 1 element
if (size() == 1 && get(0) instanceof TriggerConnector) {
TriggerConnector c = (TriggerConnector) get(0);
remove(c);
changeConnectorType(c.getConnectorType());
for (Trigger t : c.list) {
add(t);
}
c.list.clear();
return simplify();
}
// merge connectors
if (connector != null && (connector.getConnectorType().equals(connectorType) || size() == 1)) {
final int pos = connector.pos(this);
connector.remove(this);
// move triggers of child connector into parent connector
for (int i = size() - 1; i >= 0; --i) {
connector.add(pos, get(i));
}
list.clear();
return connector.simplify();
}
return this;
}
}

View file

@ -0,0 +1,208 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
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.logging.L;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDelta;
import info.nightscout.androidaps.plugins.general.automation.elements.InputDelta.DeltaType;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerDelta extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private double minValue = 0d;
private double maxValue = 1d;
private double step = 1;
private DecimalFormat decimalFormat = new DecimalFormat("1");
private String units;
private DeltaType deltaType;
private InputDelta value = new InputDelta( (double) minValue,(double) minValue, (double) maxValue, step, decimalFormat, deltaType);
private Comparator comparator = new Comparator();
public TriggerDelta() {
super();
this.units = ProfileFunctions.getInstance().getProfileUnits();
initializer();
}
private TriggerDelta(TriggerDelta triggerDelta) {
super();
this.units = ProfileFunctions.getInstance().getProfileUnits();
initializer();
value = triggerDelta.value;
lastRun = triggerDelta.lastRun;
}
public double getValue() {
deltaType = value.getDeltaType();
return value.getValue();
}
public DeltaType getType() {
return deltaType;
}
public String getUnits() {
return this.units;
}
public Comparator getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData();
if (glucoseStatus == null)
if (comparator.getValue() == Comparator.Compare.IS_NOT_AVAILABLE)
return true;
else
return false;
// Setting type of delta
double delta;
if (deltaType == DeltaType.SHORT_AVERAGE)
delta = glucoseStatus.short_avgdelta;
else if (deltaType == DeltaType.LONG_AVERAGE)
delta = glucoseStatus.long_avgdelta;
else
delta = glucoseStatus.delta;
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
boolean doRun = comparator.getValue().check(delta, Profile.toMgdl(value.getValue(), this.units));
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: delta is " + delta + " " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerDelta.class.getName());
JSONObject data = new JSONObject();
data.put("value", getValue());
data.put("units", units);
data.put("lastRun", lastRun);
data.put("deltaType", getType());
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
units = JsonHelper.safeGetString(d, "units");
deltaType = DeltaType.valueOf(JsonHelper.safeGetString(d, "deltaType", ""));
value.setValue(JsonHelper.safeGetDouble(d, "value"), deltaType);
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.deltalabel;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.deltacompared, MainApp.gs(comparator.getValue().getStringRes()), getValue(), MainApp.gs(deltaType.getStringRes()));
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_auto_delta);
}
@Override
public Trigger duplicate() {
return new TriggerDelta(this);
}
TriggerDelta setValue(double requestedValue, DeltaType requestedType) {
this.value.setValue(requestedValue, requestedType);
this.deltaType = requestedType;
return this;
}
TriggerDelta setUnits(String units) {
this.units = units;
return this;
}
void initializer(){
if (this.units.equals(Constants.MMOL)) {
this.maxValue = 4d;
this.minValue = -4d;
this.step = 0.1d;
this.decimalFormat = new DecimalFormat("0.1");
this.deltaType = DeltaType.DELTA;
} else {
this.maxValue = 72d;
this.minValue = -72d;
this.step = 1d;
this.deltaType = DeltaType.DELTA;
}
value = new InputDelta( (double) minValue,(double) minValue, (double) maxValue, step, decimalFormat, deltaType);
}
TriggerDelta lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerDelta comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.deltalabel))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.deltalabel) + ": ", "", value))
.build(root);
}
}

View file

@ -0,0 +1,146 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.IobTotal;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputInsulin;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerIob extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private InputInsulin insulin = new InputInsulin();
private Comparator comparator = new Comparator();
public TriggerIob() {
super();
}
private TriggerIob(TriggerIob triggerIob) {
super();
insulin = new InputInsulin(triggerIob.insulin);
comparator = new Comparator(triggerIob.comparator);
lastRun = triggerIob.lastRun;
}
public double getValue() {
return insulin.getValue();
}
public Comparator getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
Profile profile = ProfileFunctions.getInstance().getProfile();
if (profile == null)
return false;
IobTotal iob = IobCobCalculatorPlugin.getPlugin().calculateFromTreatmentsAndTempsSynchronized(DateUtil.now(), profile);
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
boolean doRun = comparator.getValue().check(iob.iob, getValue());
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerIob.class.getName());
JSONObject data = new JSONObject();
data.put("insulin", getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
insulin.setValue(JsonHelper.safeGetDouble(d, "insulin"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.iob;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.iobcompared, MainApp.gs(comparator.getValue().getStringRes()), getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_keyboard_capslock);
}
@Override
public Trigger duplicate() {
return new TriggerIob(this);
}
TriggerIob setValue(double threshold) {
insulin.setValue(threshold);
return this;
}
TriggerIob lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerIob comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.iob))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.iob_u), "", insulin))
.build(root);
}
}

View file

@ -0,0 +1,167 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.location.Location;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import info.nightscout.androidaps.MainApp;
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.InputString;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.services.LocationService;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
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"));
InputString name = new InputString();
private Runnable buttonAction = () -> {
Location location = LocationService.getLastLocation();
if (location != null) {
latitude.setValue(location.getLatitude());
longitude.setValue(location.getLongitude());
log.debug(String.format("Grabbed location: %f %f", latitude.getValue(), longitude.getValue()));
}
};
public TriggerLocation() {
super();
}
private TriggerLocation(TriggerLocation triggerLocation) {
super();
latitude = new InputDouble(triggerLocation.latitude.getValue(), -90d, +90d, 0.00001d, new DecimalFormat("0.00000"));
longitude = new InputDouble(triggerLocation.longitude.getValue(), -180d, +180d, 0.00001d, new DecimalFormat("0.00000"));
distance = new InputDouble(200d, 0, 100000, 10d, new DecimalFormat("0"));
lastRun = triggerLocation.lastRun;
}
@Override
public synchronized boolean shouldRun() {
Location location = LocationService.getLastLocation();
if (location == null)
return false;
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
Location a = new Location("Trigger");
a.setLatitude(latitude.getValue());
a.setLongitude(longitude.getValue());
double calculatedDistance = location.distanceTo(a);
if (calculatedDistance < distance.getValue()) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerLocation.class.getName());
JSONObject data = new JSONObject();
data.put("latitude", latitude.getValue());
data.put("longitude", longitude.getValue());
data.put("distance", distance.getValue());
data.put("name", name.getValue());
data.put("lastRun", lastRun);
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
latitude.setValue(JsonHelper.safeGetDouble(d, "latitude"));
longitude.setValue(JsonHelper.safeGetDouble(d, "longitude"));
distance.setValue(JsonHelper.safeGetDouble(d, "distance"));
name.setValue(JsonHelper.safeGetString(d, "name"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.location;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.locationis, name.getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_location_on);
}
@Override
public Trigger duplicate() {
return new TriggerLocation(this);
}
TriggerLocation setLatitude(double value) {
latitude.setValue(value);
return this;
}
TriggerLocation setLongitude(double value) {
longitude.setValue(value);
return this;
}
TriggerLocation setdistance(double value) {
distance.setValue(value);
return this;
}
TriggerLocation lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.location))
.add(new LabelWithElement(MainApp.gs(R.string.name_short), "", name))
.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 InputButton(MainApp.gs(R.string.currentlocation), buttonAction), LocationService.getLastLocation() != null)
.build(root);
}
}

View file

@ -0,0 +1,152 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
import info.nightscout.androidaps.plugins.general.automation.elements.InputPercent;
import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithElement;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerProfilePercent extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private InputPercent pct = new InputPercent();
private Comparator comparator = new Comparator();
public TriggerProfilePercent() {
super();
}
private TriggerProfilePercent(TriggerProfilePercent triggerProfilePercent) {
super();
pct = new InputPercent(triggerProfilePercent.pct);
comparator = new Comparator(triggerProfilePercent.comparator);
lastRun = triggerProfilePercent.lastRun;
}
public double getValue() {
return pct.getValue();
}
public Comparator getComparator() {
return comparator;
}
public long getLastRun() {
return lastRun;
}
@Override
public synchronized boolean shouldRun() {
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
Profile profile = ProfileFunctions.getInstance().getProfile();
if (profile == null && comparator.getValue().equals(Comparator.Compare.IS_NOT_AVAILABLE)) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
if (profile == null)
return false;
boolean doRun = comparator.getValue().check((double) profile.getPercentage(), pct.getValue());
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerProfilePercent.class.getName());
JSONObject data = new JSONObject();
data.put("percentage", pct.getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
pct.setValue(JsonHelper.safeGetDouble(d, "percentage"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.profilepercentage;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.percentagecompared, MainApp.gs(comparator.getValue().getStringRes()), (int) pct.getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.icon_actions_profileswitch);
}
@Override
public Trigger duplicate() {
return new TriggerProfilePercent(this);
}
public TriggerProfilePercent setValue(double value) {
pct.setValue(value);
return this;
}
TriggerProfilePercent lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
public TriggerProfilePercent comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.profilepercentage))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.percent_u), "", pct))
.build(root);
}
}

View file

@ -0,0 +1,317 @@
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.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.FragmentManager;
import com.dpro.widgets.WeekdaysPicker;
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.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
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;
public class TriggerRecurringTime extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
public enum DayOfWeek {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
private static final int[] calendarInts = new int[]{
Calendar.MONDAY,
Calendar.TUESDAY,
Calendar.WEDNESDAY,
Calendar.THURSDAY,
Calendar.FRIDAY,
Calendar.SATURDAY,
Calendar.SUNDAY
};
private static final int[] shortNames = new int[]{
R.string.weekday_monday_short,
R.string.weekday_tuesday_short,
R.string.weekday_wednesday_short,
R.string.weekday_thursday_short,
R.string.weekday_friday_short,
R.string.weekday_saturday_short,
R.string.weekday_sunday_short
};
public int toCalendarInt() {
return calendarInts[ordinal()];
}
@Nullable
public static DayOfWeek fromCalendarInt(int day) {
for (int i = 0; i < calendarInts.length; ++i) {
if (calendarInts[i] == day)
return values()[i];
}
return null;
}
public @StringRes
int getShortName() {
return shortNames[ordinal()];
}
}
private final boolean[] weekdays = new boolean[DayOfWeek.values().length];
// Recurring
private int hour;
private int minute;
private long validTo;
public TriggerRecurringTime() {
super();
setAll(false);
}
private TriggerRecurringTime(TriggerRecurringTime triggerTime) {
super();
lastRun = triggerTime.lastRun;
hour = triggerTime.hour;
minute = triggerTime.minute;
validTo = triggerTime.validTo;
if (weekdays.length >= 0)
System.arraycopy(triggerTime.weekdays, 0, weekdays, 0, weekdays.length);
}
public void setAll(boolean value) {
for (DayOfWeek day : DayOfWeek.values()) {
set(day, value);
}
}
public TriggerRecurringTime set(DayOfWeek day, boolean value) {
weekdays[day.ordinal()] = value;
return this;
}
private boolean isSet(DayOfWeek day) {
return weekdays[day.ordinal()];
}
@Override
public boolean shouldRun() {
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 (isSet(Objects.requireNonNull(DayOfWeek.fromCalendarInt(scheduledDayOfWeek)))) {
if (DateUtil.now() >= scheduled && DateUtil.now() - scheduled < T.mins(5).msecs()) {
if (lastRun < scheduled) {
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();
try {
data.put("lastRun", lastRun);
for (int i = 0; i < weekdays.length; ++i) {
data.put(DayOfWeek.values()[i].name(), weekdays[i]);
}
data.put("hour", hour);
data.put("minute", minute);
data.put("validTo", validTo);
object.put("type", TriggerRecurringTime.class.getName());
object.put("data", data);
} 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");
for (int i = 0; i < weekdays.length; ++i) {
weekdays[i] = JsonHelper.safeGetBoolean(o, DayOfWeek.values()[i].name());
}
hour = JsonHelper.safeGetInt(o, "hour");
minute = JsonHelper.safeGetInt(o, "minute");
validTo = JsonHelper.safeGetLong(o, "validTo");
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.recurringTime;
}
@Override
public String friendlyDescription() {
int counter = 0;
StringBuilder sb = new StringBuilder();
sb.append(MainApp.gs(R.string.every));
sb.append(" ");
for (Integer i : getSelectedDays()) {
if (counter > 0)
sb.append(",");
sb.append(MainApp.gs(Objects.requireNonNull(DayOfWeek.fromCalendarInt(i)).getShortName()));
counter++;
}
sb.append(" ");
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();
sb.append(DateUtil.timeString(scheduled));
if (counter == 0)
return MainApp.gs(R.string.never);
return sb.toString();
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_access_alarm_24dp);
}
@Override
public Trigger duplicate() {
return new TriggerRecurringTime(this);
}
TriggerRecurringTime lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
@SuppressWarnings("SameParameterValue")
TriggerRecurringTime validTo(long validTo) {
this.validTo = validTo;
return this;
}
TriggerRecurringTime hour(int hour) {
this.hour = hour;
return this;
}
TriggerRecurringTime minute(int minute) {
this.minute = minute;
return this;
}
private List<Integer> getSelectedDays() {
List<Integer> selectedDays = new ArrayList<>();
for (int i = 0; i < weekdays.length; ++i) {
DayOfWeek day = DayOfWeek.values()[i];
boolean selected = weekdays[i];
if (selected) selectedDays.add(day.toCalendarInt());
}
return selectedDays;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
TextView label = new TextView(root.getContext());
// TODO: Replace external tool WeekdaysPicker with a self-made GUI element
WeekdaysPicker weekdaysPicker = new WeekdaysPicker(root.getContext());
weekdaysPicker.setEditable(true);
weekdaysPicker.setSelectedDays(getSelectedDays());
weekdaysPicker.setOnWeekdaysChangeListener((view, i, list) -> set(DayOfWeek.fromCalendarInt(i), list.contains(i)));
weekdaysPicker.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(weekdaysPicker);
TextView timeButton = new TextView(root.getContext());
GregorianCalendar runAt = new GregorianCalendar();
//Date runAt = new Date();
runAt.set(Calendar.HOUR_OF_DAY, hour);
runAt.set(Calendar.MINUTE, minute);
timeButton.setText(DateUtil.timeString(runAt.getTimeInMillis()));
timeButton.setOnClickListener(view -> {
TimePickerDialog tpd = TimePickerDialog.newInstance(
(view12, hourOfDay, minute, second) -> {
hour(hourOfDay);
minute(minute);
runAt.set(Calendar.HOUR_OF_DAY, hour);
runAt.set(Calendar.MINUTE, minute);
timeButton.setText(DateUtil.timeString(runAt.getTimeInMillis()));
},
runAt.get(Calendar.HOUR_OF_DAY),
runAt.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.atspecifiedtime, ""));
label.setTypeface(label.getTypeface(), Typeface.BOLD);
label.setPadding(px, px, px, px);
timeButton.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(timeButton);
root.addView(l);
}
}

View file

@ -0,0 +1,131 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.db.TempTarget;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorExists;
import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerTempTarget extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private ComparatorExists comparator = new ComparatorExists();
public TriggerTempTarget() {
super();
}
private TriggerTempTarget(TriggerTempTarget triggerTempTarget) {
super();
comparator = new ComparatorExists(triggerTempTarget.comparator);
lastRun = triggerTempTarget.lastRun;
}
public ComparatorExists getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
TempTarget tt = TreatmentsPlugin.getPlugin().getTempTargetFromHistory();
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
if (tt == null && comparator.getValue() == ComparatorExists.Compare.NOT_EXISTS) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
if (tt != null && comparator.getValue() == ComparatorExists.Compare.EXISTS) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerTempTarget.class.getName());
JSONObject data = new JSONObject();
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(ComparatorExists.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.temptarget;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.temptargetcompared, MainApp.gs(comparator.getValue().getStringRes()));
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_keyboard_tab);
}
@Override
public Trigger duplicate() {
return new TriggerTempTarget(this);
}
TriggerTempTarget lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
public TriggerTempTarget comparator(ComparatorExists.Compare compare) {
this.comparator = new ComparatorExists().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.temptarget))
.add(comparator)
.build(root);
}
}

View file

@ -0,0 +1,186 @@
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.date.DatePickerDialog;
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.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;
public class TriggerTime extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private long runAt;
public TriggerTime() {
runAt = DateUtil.now();
}
private TriggerTime(TriggerTime triggerTime) {
super();
lastRun = triggerTime.lastRun;
runAt = triggerTime.runAt;
}
@Override
public boolean shouldRun() {
long now = DateUtil.now();
if (now >= runAt && now - runAt < T.mins(5).msecs())
if (lastRun < runAt) {
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();
try {
data.put("runAt", runAt);
data.put("lastRun", lastRun);
object.put("type", TriggerTime.class.getName());
object.put("data", data);
} 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");
} catch (JSONException e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.time;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.atspecifiedtime, DateUtil.dateAndTimeString(runAt));
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_access_alarm_24dp);
}
TriggerTime runAt(long runAt) {
this.runAt = runAt;
return this;
}
TriggerTime lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
@Override
public Trigger duplicate() {
return new TriggerTime(this);
}
long getRunAt() {
return runAt;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
TextView label = new TextView(root.getContext());
TextView dateButton = new TextView(root.getContext());
TextView timeButton = new TextView(root.getContext());
dateButton.setText(DateUtil.dateString(runAt));
timeButton.setText(DateUtil.timeString(runAt));
dateButton.setOnClickListener(view -> {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(runAt);
DatePickerDialog dpd = DatePickerDialog.newInstance(
(view1, year, monthOfYear, dayOfMonth) -> {
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, monthOfYear);
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
runAt = calendar.getTimeInMillis();
dateButton.setText(DateUtil.dateString(runAt));
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
);
dpd.setThemeDark(true);
dpd.dismissOnPause(true);
Activity a = scanForActivity(root.getContext());
if (a != null)
dpd.show(a.getFragmentManager(), "DatePickerDialog");
});
timeButton.setOnClickListener(view -> {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(runAt);
TimePickerDialog tpd = TimePickerDialog.newInstance(
(view12, hourOfDay, minute, second) -> {
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
calendar.set(Calendar.MINUTE, minute);
runAt = calendar.getTimeInMillis();
timeButton.setText(DateUtil.timeString(runAt));
},
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.atspecifiedtime, ""));
label.setTypeface(label.getTypeface(), Typeface.BOLD);
label.setPadding(px, px, px, px);
dateButton.setPadding(px, px, px, px);
timeButton.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(dateButton);
l.addView(timeButton);
root.addView(l);
}
}

View file

@ -0,0 +1,149 @@
package info.nightscout.androidaps.plugins.general.automation.triggers;
import android.widget.LinearLayout;
import androidx.fragment.app.FragmentManager;
import com.google.common.base.Optional;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.events.EventNetworkChange;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.elements.Comparator;
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;
import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel;
import info.nightscout.androidaps.receivers.NetworkChangeReceiver;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.T;
public class TriggerWifiSsid extends Trigger {
private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);
private InputString ssid = new InputString();
private Comparator comparator = new Comparator();
public TriggerWifiSsid() {
super();
}
private TriggerWifiSsid(TriggerWifiSsid triggerWifiSsid) {
super();
ssid = new InputString(triggerWifiSsid.ssid);
comparator = new Comparator(triggerWifiSsid.comparator);
lastRun = triggerWifiSsid.lastRun;
}
public String getValue() {
return ssid.getValue();
}
public Comparator getComparator() {
return comparator;
}
@Override
public synchronized boolean shouldRun() {
EventNetworkChange eventNetworkChange = NetworkChangeReceiver.getLastEvent();
if (eventNetworkChange == null)
return false;
if (lastRun > DateUtil.now() - T.mins(5).msecs())
return false;
if (!eventNetworkChange.wifiConnected && comparator.getValue() == Comparator.Compare.IS_NOT_AVAILABLE) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
boolean doRun = eventNetworkChange.wifiConnected && comparator.getValue().check(eventNetworkChange.ssid, getValue());
if (doRun) {
if (L.isEnabled(L.AUTOMATION))
log.debug("Ready for execution: " + friendlyDescription());
return true;
}
return false;
}
@Override
public synchronized String toJSON() {
JSONObject o = new JSONObject();
try {
o.put("type", TriggerWifiSsid.class.getName());
JSONObject data = new JSONObject();
data.put("ssid", getValue());
data.put("lastRun", lastRun);
data.put("comparator", comparator.getValue().toString());
o.put("data", data);
} catch (JSONException e) {
e.printStackTrace();
}
return o.toString();
}
@Override
Trigger fromJSON(String data) {
try {
JSONObject d = new JSONObject(data);
ssid.setValue(JsonHelper.safeGetString(d, "ssid"));
lastRun = JsonHelper.safeGetLong(d, "lastRun");
comparator.setValue(Comparator.Compare.valueOf(JsonHelper.safeGetString(d, "comparator")));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
@Override
public int friendlyName() {
return R.string.ns_wifi_ssids;
}
@Override
public String friendlyDescription() {
return MainApp.gs(R.string.wifissidcompared, MainApp.gs(comparator.getValue().getStringRes()), getValue());
}
@Override
public Optional<Integer> icon() {
return Optional.of(R.drawable.ic_network_wifi);
}
@Override
public Trigger duplicate() {
return new TriggerWifiSsid(this);
}
TriggerWifiSsid setValue(String value) {
ssid.setValue(value);
return this;
}
TriggerWifiSsid lastRun(long lastRun) {
this.lastRun = lastRun;
return this;
}
TriggerWifiSsid comparator(Comparator.Compare compare) {
this.comparator = new Comparator().setValue(compare);
return this;
}
@Override
public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
new LayoutBuilder()
.add(new StaticLabel(R.string.ns_wifi_ssids))
.add(comparator)
.add(new LabelWithElement(MainApp.gs(R.string.ns_wifi_ssids) + ": ", "", ssid))
.build(root);
}
}

View file

@ -266,14 +266,14 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
editBg = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_bginput); editBg = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_bginput);
editTemptarget = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_temptarget); editTemptarget = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_temptarget);
if (profile == null) { if (profile == null) {
editBg.setParams(bg, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false, bgTextWatcher); editBg.setParams(bg, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.ok), bgTextWatcher);
editTemptarget.setParams(bg, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false); editTemptarget.setParams(bg, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.ok));
} else if (units.equals(Constants.MMOL)) { } else if (units.equals(Constants.MMOL)) {
editBg.setParams(bg, 0d, 30d, 0.1d, new DecimalFormat("0.0"), false, bgTextWatcher); editBg.setParams(bg, 0d, 30d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.ok), bgTextWatcher);
editTemptarget.setParams(bg, 0d, 30d, 0.1d, new DecimalFormat("0.0"), false); editTemptarget.setParams(bg, 0d, 30d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.ok));
} else { } else {
editBg.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false, bgTextWatcher); editBg.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), bgTextWatcher);
editTemptarget.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false); editTemptarget.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
} }
sensorRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> { sensorRadioButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
@ -287,16 +287,16 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
Integer maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); Integer maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value();
editCarbs = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_carbsinput); editCarbs = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_carbsinput);
editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); Double maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value();
editInsulin = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_insulininput); editInsulin = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_insulininput);
editInsulin.setParams(0d, 0d, maxInsulin, 0.05d, new DecimalFormat("0.00"), false); editInsulin.setParams(0d, 0d, maxInsulin, 0.05d, new DecimalFormat("0.00"), false, view.findViewById(R.id.ok));
editSplit = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_splitinput); editSplit = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_splitinput);
editSplit.setParams(100d, 0d, 100d, 5d, new DecimalFormat("0"), true); editSplit.setParams(100d, 0d, 100d, 5d, new DecimalFormat("0"), true, view.findViewById(R.id.ok));
editDuration = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_durationinput); editDuration = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_durationinput);
editDuration.setParams(0d, 0d, 24 * 60d, 10d, new DecimalFormat("0"), false); editDuration.setParams(0d, 0d, 24 * 60d, 10d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
TextWatcher percentTextWatcher = new TextWatcher() { TextWatcher percentTextWatcher = new TextWatcher() {
@Override @Override
@ -320,7 +320,7 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
if (profile != null) if (profile != null)
maxPercent = MainApp.getConstraintChecker().getMaxBasalPercentAllowed(profile).value(); maxPercent = MainApp.getConstraintChecker().getMaxBasalPercentAllowed(profile).value();
editPercent = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_percentinput); editPercent = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_percentinput);
editPercent.setParams(0d, -100d, (double) maxPercent, 5d, new DecimalFormat("0"), true, percentTextWatcher); editPercent.setParams(0d, -100d, (double) maxPercent, 5d, new DecimalFormat("0"), true, view.findViewById(R.id.ok), percentTextWatcher);
TextWatcher absoluteTextWatcher = new TextWatcher() { TextWatcher absoluteTextWatcher = new TextWatcher() {
@Override @Override
@ -344,16 +344,16 @@ public class NewNSTreatmentDialog extends DialogFragment implements View.OnClick
if (profile != null) if (profile != null)
maxAbsolute = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value(); maxAbsolute = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value();
editAbsolute = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_absoluteinput); editAbsolute = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_absoluteinput);
editAbsolute.setParams(0d, 0d, maxAbsolute, 0.05d, new DecimalFormat("0.00"), true, absoluteTextWatcher); editAbsolute.setParams(0d, 0d, maxAbsolute, 0.05d, new DecimalFormat("0.00"), true, view.findViewById(R.id.ok), absoluteTextWatcher);
editCarbTime = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_carbtimeinput); editCarbTime = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_carbtimeinput);
editCarbTime.setParams(0d, -60d, 60d, 5d, new DecimalFormat("0"), false); editCarbTime.setParams(0d, -60d, 60d, 5d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
editPercentage = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_percentage); editPercentage = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_percentage);
editPercentage.setParams(100d, (double) Constants.CPP_MIN_PERCENTAGE, (double) Constants.CPP_MAX_PERCENTAGE, 1d, new DecimalFormat("0"), false); editPercentage.setParams(100d, (double) Constants.CPP_MIN_PERCENTAGE, (double) Constants.CPP_MAX_PERCENTAGE, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
editTimeshift = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_timeshift); editTimeshift = (NumberPicker) view.findViewById(R.id.careportal_newnstreatment_timeshift);
editTimeshift.setParams(0d, (double) Constants.CPP_MIN_TIMESHIFT, (double) Constants.CPP_MAX_TIMESHIFT, 1d, new DecimalFormat("0"), false); editTimeshift.setParams(0d, (double) Constants.CPP_MIN_TIMESHIFT, (double) Constants.CPP_MAX_TIMESHIFT, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
ProfileSwitch ps = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(DateUtil.now()); ProfileSwitch ps = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(DateUtil.now());
if (ps != null && ps.isCPP) { if (ps != null && ps.isCPP) {

View file

@ -66,9 +66,9 @@ public class CalibrationDialog extends DialogFragment implements View.OnClickLis
bgNumber = (NumberPicker) view.findViewById(R.id.overview_calibration_bg); bgNumber = (NumberPicker) view.findViewById(R.id.overview_calibration_bg);
if (units.equals(Constants.MMOL)) if (units.equals(Constants.MMOL))
bgNumber.setParams(bg, 0d, 30d, 0.1d, new DecimalFormat("0.0"), false); bgNumber.setParams(bg, 0d, 30d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.ok));
else else
bgNumber.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false); bgNumber.setParams(bg, 0d, 500d, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok));
unitsView = (TextView) view.findViewById(R.id.overview_calibration_units); unitsView = (TextView) view.findViewById(R.id.overview_calibration_units);
unitsView.setText(units); unitsView.setText(units);

View file

@ -130,15 +130,15 @@ public class NewCarbsDialog extends DialogFragment implements OnClickListener, C
startHypoTTCheckbox = view.findViewById(R.id.newcarbs_hypo_tt); startHypoTTCheckbox = view.findViewById(R.id.newcarbs_hypo_tt);
editTime = view.findViewById(R.id.newcarbs_time); editTime = view.findViewById(R.id.newcarbs_time);
editTime.setParams(0d, -12 * 60d, 12 * 60d, 5d, new DecimalFormat("0"), false, textWatcher); editTime.setParams(0d, -12 * 60d, 12 * 60d, 5d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
editDuration = view.findViewById(R.id.new_carbs_duration); editDuration = view.findViewById(R.id.new_carbs_duration);
editDuration.setParams(0d, 0d, 10d, 1d, new DecimalFormat("0"), false, textWatcher); editDuration.setParams(0d, 0d, 10d, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value();
editCarbs = view.findViewById(R.id.newcarb_carbsamount); editCarbs = view.findViewById(R.id.newcarb_carbsamount);
editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, textWatcher); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
Button fav1Button = view.findViewById(R.id.newcarbs_plus1); Button fav1Button = view.findViewById(R.id.newcarbs_plus1);
fav1Button.setOnClickListener(this); fav1Button.setOnClickListener(this);

View file

@ -126,12 +126,12 @@ public class NewInsulinDialog extends DialogFragment implements OnClickListener
editLayout = view.findViewById(R.id.newinsulin_time_layout); editLayout = view.findViewById(R.id.newinsulin_time_layout);
editLayout.setVisibility(View.GONE); editLayout.setVisibility(View.GONE);
editTime = view.findViewById(R.id.newinsulin_time); editTime = view.findViewById(R.id.newinsulin_time);
editTime.setParams(0d, -12 * 60d, 12 * 60d, 5d, new DecimalFormat("0"), false, textWatcher); editTime.setParams(0d, -12 * 60d, 12 * 60d, 5d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); maxInsulin = MainApp.getConstraintChecker().getMaxBolusAllowed().value();
editInsulin = view.findViewById(R.id.newinsulin_amount); editInsulin = view.findViewById(R.id.newinsulin_amount);
editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, view.findViewById(R.id.ok), textWatcher);
Button plus1Button = view.findViewById(R.id.newinsulin_plus05); Button plus1Button = view.findViewById(R.id.newinsulin_plus05);
plus1Button.setOnClickListener(this); plus1Button.setOnClickListener(this);
@ -226,7 +226,7 @@ public class NewInsulinDialog extends DialogFragment implements OnClickListener
} }
if (Math.abs(insulinAfterConstraints - insulin) > pump.getPumpDescription().pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) if (Math.abs(insulinAfterConstraints - insulin) > pump.getPumpDescription().pumpType.determineCorrectBolusStepSize(insulinAfterConstraints))
actions.add("<font color='" + MainApp.gc(R.color.warning) + "'>" + MainApp.gs(R.string.bolusconstraintapplied) + "</font>"); actions.add(MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), insulin, insulinAfterConstraints));
int eatingSoonTTDuration = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration); int eatingSoonTTDuration = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration);
eatingSoonTTDuration = eatingSoonTTDuration > 0 ? eatingSoonTTDuration : Constants.defaultEatingSoonTTDuration; eatingSoonTTDuration = eatingSoonTTDuration > 0 ? eatingSoonTTDuration : Constants.defaultEatingSoonTTDuration;

View file

@ -101,8 +101,8 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene
editCarbs = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_carbsamount); editCarbs = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_carbsamount);
editInsulin = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_insulinamount); editInsulin = (NumberPicker) view.findViewById(R.id.treatments_newtreatment_insulinamount);
editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, textWatcher); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); editInsulin.setParams(0d, 0d, maxInsulin, ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, view.findViewById(R.id.ok), textWatcher);
recordOnlyCheckbox = (CheckBox) view.findViewById(R.id.newtreatment_record_only); recordOnlyCheckbox = (CheckBox) view.findViewById(R.id.newtreatment_record_only);
@ -142,7 +142,7 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene
confirmMessage += "<br/><font color='" + MainApp.gc(R.color.warning) + "'>" + MainApp.gs(R.string.bolusrecordedonly) + "</font>"; confirmMessage += "<br/><font color='" + MainApp.gc(R.color.warning) + "'>" + MainApp.gs(R.string.bolusrecordedonly) + "</font>";
} }
if (Math.abs(insulinAfterConstraints - insulin) > pump.getPumpDescription().pumpType.determineCorrectBolusStepSize(insulinAfterConstraints) || !Objects.equals(carbsAfterConstraints, carbs)) if (Math.abs(insulinAfterConstraints - insulin) > pump.getPumpDescription().pumpType.determineCorrectBolusStepSize(insulinAfterConstraints) || !Objects.equals(carbsAfterConstraints, carbs))
confirmMessage += "<br/><font color='" + MainApp.gc(R.color.warning) + "'>" + MainApp.gs(R.string.bolusconstraintapplied) + "</font>"; confirmMessage += "<br/>" + MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), insulin, insulinAfterConstraints);
} }
if (carbsAfterConstraints > 0) if (carbsAfterConstraints > 0)
confirmMessage += "<br/>" + MainApp.gs(R.string.carbs) + ": " + "<font color='" + MainApp.gc(R.color.carbs) + "'>" + carbsAfterConstraints + "g" + "</font>"; confirmMessage += "<br/>" + MainApp.gs(R.string.carbs) + ": " + "<font color='" + MainApp.gc(R.color.carbs) + "'>" + carbsAfterConstraints + "g" + "</font>";

View file

@ -238,11 +238,11 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
Integer maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value(); Integer maxCarbs = MainApp.getConstraintChecker().getMaxCarbsAllowed().value();
Double maxCorrection = MainApp.getConstraintChecker().getMaxBolusAllowed().value(); Double maxCorrection = MainApp.getConstraintChecker().getMaxBolusAllowed().value();
editBg.setParams(0d, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false, textWatcher); editBg.setParams(0d, 0d, 500d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.ok), textWatcher);
editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, textWatcher); editCarbs.setParams(0d, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
double bolusstep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep; double bolusstep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep;
editCorr.setParams(0d, -maxCorrection, maxCorrection, bolusstep, DecimalFormatter.pumpSupportedBolusFormat(), false, textWatcher); editCorr.setParams(0d, -maxCorrection, maxCorrection, bolusstep, DecimalFormatter.pumpSupportedBolusFormat(), false, view.findViewById(R.id.ok), textWatcher);
editCarbTime.setParams(0d, -60d, 60d, 5d, new DecimalFormat("0"), false); editCarbTime.setParams(0d, -60d, 60d, 5d, new DecimalFormat("0"), false, view.findViewById(R.id.ok), textWatcher);
initDialog(); initDialog();
setCancelable(true); setCancelable(true);
@ -392,7 +392,9 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com
c_cob = cobInfo.displayCob; c_cob = cobInfo.displayCob;
} }
wizard = new BolusWizard(specificProfile, profileName, tempTarget, carbsAfterConstraint, c_cob, c_bg, corrAfterConstraint, 100d, bgCheckbox.isChecked(), cobCheckbox.isChecked(), bolusIobCheckbox.isChecked(), basalIobCheckbox.isChecked(), superbolusCheckbox.isChecked(), ttCheckbox.isChecked(), bgtrendCheckbox.isChecked(), notesEdit.getText().toString(), SafeParse.stringToInt(editCarbTime.getText())); int carbTime = SafeParse.stringToInt(editCarbTime.getText());
wizard = new BolusWizard(specificProfile, profileName, tempTarget, carbsAfterConstraint, c_cob, c_bg, corrAfterConstraint, 100d, bgCheckbox.isChecked(), cobCheckbox.isChecked(), bolusIobCheckbox.isChecked(), basalIobCheckbox.isChecked(), superbolusCheckbox.isChecked(), ttCheckbox.isChecked(), bgtrendCheckbox.isChecked(), notesEdit.getText().toString(), carbTime);
bg.setText(c_bg + " ISF: " + DecimalFormatter.to1Decimal(wizard.getSens())); bg.setText(c_bg + " ISF: " + DecimalFormatter.to1Decimal(wizard.getSens()));
bgInsulin.setText(StringUtils.formatInsulin(wizard.getInsulinFromBG())); bgInsulin.setText(StringUtils.formatInsulin(wizard.getInsulinFromBG()));

View file

@ -1,4 +1,3 @@
package info.nightscout.androidaps.plugins.general.overview.notifications; package info.nightscout.androidaps.plugins.general.overview.notifications;
import java.util.Date; import java.util.Date;
@ -75,6 +74,8 @@ public class Notification {
public static final int DST_IN_24H = 50; public static final int DST_IN_24H = 50;
public static final int DISKFULL = 51; public static final int DISKFULL = 51;
public static final int OLDVERSION = 52; public static final int OLDVERSION = 52;
public static final int USERMESSAGE = 53;
public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54;
public int id; public int id;
@ -85,6 +86,7 @@ public class Notification {
public NSAlarm nsAlarm = null; public NSAlarm nsAlarm = null;
public Integer soundId = null; public Integer soundId = null;
public Notification() { public Notification() {
} }

View file

@ -790,7 +790,7 @@ public class SmsCommunicatorPlugin extends PluginBase {
passCode += Character.toString((char) (startChar2 + Math.random() * ('z' - 'a' + 1))); passCode += Character.toString((char) (startChar2 + Math.random() * ('z' - 'a' + 1)));
int startChar3 = Math.random() > 0.5 ? 'a' : 'A'; int startChar3 = Math.random() > 0.5 ? 'a' : 'A';
passCode += Character.toString((char) (startChar3 + Math.random() * ('z' - 'a' + 1))); passCode += Character.toString((char) (startChar3 + Math.random() * ('z' - 'a' + 1)));
passCode.replace('l', 'k').replace('I', 'J'); passCode = passCode.replace('l', 'k').replace('I', 'J');
return passCode; return passCode;
} }

View file

@ -518,6 +518,20 @@ public class IobCobCalculatorPlugin extends PluginBase {
return new CobInfo(displayCob, futureCarbs); return new CobInfo(displayCob, futureCarbs);
} }
public double slowAbsorptionPercentage(int timeInMinutes) {
double sum = 0;
int count = 0;
int valuesToProcess = timeInMinutes / 5;
synchronized (dataLock) {
for (int i = autosensDataTable.size() - 1; i >= 0 && count < valuesToProcess; i--) {
if (autosensDataTable.valueAt(i).failoverToMinAbsorbtionRate)
sum++;
count++;
}
}
return sum /count;
}
@Nullable @Nullable
public AutosensData getLastAutosensData(String reason) { public AutosensData getLastAutosensData(String reason) {
if (autosensDataTable.size() < 1) { if (autosensDataTable.size() < 1) {

View file

@ -78,7 +78,7 @@ public class LocalProfileFragment extends SubscriberFragment {
PumpDescription pumpDescription = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription(); PumpDescription pumpDescription = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription();
View layout = inflater.inflate(R.layout.localprofile_fragment, container, false); View layout = inflater.inflate(R.layout.localprofile_fragment, container, false);
diaView = (NumberPicker) layout.findViewById(R.id.localprofile_dia); diaView = (NumberPicker) layout.findViewById(R.id.localprofile_dia);
diaView.setParams(LocalProfilePlugin.getPlugin().dia, 2d, 48d, 0.1d, new DecimalFormat("0.0"), false, textWatch); diaView.setParams(LocalProfilePlugin.getPlugin().dia, 2d, 48d, 0.1d, new DecimalFormat("0.0"), false, layout.findViewById(R.id.localprofile_save), textWatch);
mgdlView = (RadioButton) layout.findViewById(R.id.localprofile_mgdl); mgdlView = (RadioButton) layout.findViewById(R.id.localprofile_mgdl);
mmolView = (RadioButton) layout.findViewById(R.id.localprofile_mmol); mmolView = (RadioButton) layout.findViewById(R.id.localprofile_mmol);
icView = new TimeListEdit(getContext(), layout, R.id.localprofile_ic, MainApp.gs(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.getPlugin().ic, null, 0.5, 50d, 0.1d, new DecimalFormat("0.0"), save); icView = new TimeListEdit(getContext(), layout, R.id.localprofile_ic, MainApp.gs(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.getPlugin().ic, null, 0.5, 50d, 0.1d, new DecimalFormat("0.0"), save);
@ -124,7 +124,7 @@ public class LocalProfileFragment extends SubscriberFragment {
LocalProfilePlugin.getPlugin().loadSettings(); LocalProfilePlugin.getPlugin().loadSettings();
mgdlView.setChecked(LocalProfilePlugin.getPlugin().mgdl); mgdlView.setChecked(LocalProfilePlugin.getPlugin().mgdl);
mmolView.setChecked(LocalProfilePlugin.getPlugin().mmol); mmolView.setChecked(LocalProfilePlugin.getPlugin().mmol);
diaView.setParams(LocalProfilePlugin.getPlugin().dia, 5d, 12d, 0.1d, new DecimalFormat("0.0"), false, textWatch); diaView.setParams(LocalProfilePlugin.getPlugin().dia, 5d, 12d, 0.1d, new DecimalFormat("0.0"), false, view.findViewById(R.id.localprofile_save), textWatch);
icView = new TimeListEdit(getContext(), layout, R.id.localprofile_ic, MainApp.gs(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.getPlugin().ic, null, 0.5, 50d, 0.1d, new DecimalFormat("0.0"), save); icView = new TimeListEdit(getContext(), layout, R.id.localprofile_ic, MainApp.gs(R.string.nsprofileview_ic_label) + ":", LocalProfilePlugin.getPlugin().ic, null, 0.5, 50d, 0.1d, new DecimalFormat("0.0"), save);
isfView = new TimeListEdit(getContext(), layout, R.id.localprofile_isf, MainApp.gs(R.string.nsprofileview_isf_label) + ":", LocalProfilePlugin.getPlugin().isf, null, 0.5, 500d, 0.1d, new DecimalFormat("0.0"), save); isfView = new TimeListEdit(getContext(), layout, R.id.localprofile_isf, MainApp.gs(R.string.nsprofileview_isf_label) + ":", LocalProfilePlugin.getPlugin().isf, null, 0.5, 500d, 0.1d, new DecimalFormat("0.0"), save);
basalView = new TimeListEdit(getContext(), layout, R.id.localprofile_basal, MainApp.gs(R.string.nsprofileview_basal_label) + ": " + getSumLabel(), LocalProfilePlugin.getPlugin().basal, null, pumpDescription.basalMinimumRate, 10, 0.01d, new DecimalFormat("0.00"), save); basalView = new TimeListEdit(getContext(), layout, R.id.localprofile_basal, MainApp.gs(R.string.nsprofileview_basal_label) + ": " + getSumLabel(), LocalProfilePlugin.getPlugin().basal, null, pumpDescription.basalMinimumRate, 10, 0.01d, new DecimalFormat("0.00"), save);

View file

@ -39,6 +39,7 @@ import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
@ -163,32 +164,9 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
} }
@Override @Override
public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity context) { public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity activity) {
boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false); confirmPumpPluginActivation(pluginSwitcher, activity);
if (allowHardwarePump || context == null) {
pluginSwitcher.invoke();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.allow_hardware_pump_text)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
pluginSwitcher.invoke();
SP.putBoolean("allow_hardware_pump", true);
if (L.isEnabled(L.PUMP))
log.debug("First time HW pump allowed!");
} }
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
pluginSwitcher.cancel();
if (L.isEnabled(L.PUMP))
log.debug("User does not allow switching to HW pump!");
}
});
builder.create().show();
}
}
@Override @Override
public boolean isInitialized() { public boolean isInitialized() {
@ -1313,13 +1291,13 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
} }
@Override @Override
public String manufacter() { public ManufacturerType manufacturer() {
return "Roche"; return ManufacturerType.Roche;
} }
@Override @Override
public String model() { public PumpType model() {
return "Combo"; return PumpType.AccuChekCombo;
} }
@Override @Override
@ -1407,4 +1385,10 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
return false; return false;
} }
@Override
public void timeDateOrTimeZoneChanged() {
}
} }

View file

@ -0,0 +1,452 @@
package info.nightscout.androidaps.plugins.pump.common;
import java.util.Date;
import androidx.fragment.app.FragmentActivity;
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import com.squareup.otto.Subscribe;
import info.nightscout.androidaps.BuildConfig;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.db.ExtendedBolus;
import info.nightscout.androidaps.db.TemporaryBasal;
import info.nightscout.androidaps.events.EventAppExit;
import info.nightscout.androidaps.interfaces.ConstraintsInterface;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PluginDescription;
import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress;
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.DecimalFormatter;
/**
* Created by andy on 23.04.18.
*/
// When using this class, make sure that your first step is to create mConnection (see MedtronicPumpPlugin)
public abstract class PumpPluginAbstract extends PluginBase implements PumpInterface, ConstraintsInterface {
private static final Logger LOG = LoggerFactory.getLogger(L.PUMP);
protected static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult().success(false)
.enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump_driver));
protected static final PumpEnactResult OPERATION_NOT_YET_SUPPORTED = new PumpEnactResult().success(false)
.enacted(false).comment(MainApp.gs(R.string.pump_operation_not_yet_supported_by_pump));
protected PumpDescription pumpDescription = new PumpDescription();
protected PumpStatus pumpStatus;
protected ServiceConnection serviceConnection = null;
protected boolean serviceRunning = false;
// protected boolean isInitialized = false;
protected PumpDriverState pumpState = PumpDriverState.NotInitialized;
protected boolean displayConnectionMessages = false;
protected PumpPluginAbstract(PluginDescription pluginDescription, PumpType pumpType) {
super(pluginDescription);
pumpDescription.setPumpDescription(pumpType);
initPumpStatusData();
}
public abstract void initPumpStatusData();
@Override
protected void onStart() {
Context context = MainApp.instance().getApplicationContext();
Intent intent = new Intent(context, getServiceClass());
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
serviceRunning = true;
MainApp.bus().register(this);
onStartCustomActions();
super.onStart();
}
@Override
protected void onStop() {
Context context = MainApp.instance().getApplicationContext();
context.unbindService(serviceConnection);
serviceRunning = false;
MainApp.bus().unregister(this);
}
/**
* If we need to run any custom actions in onStart (triggering events, etc)
*/
public abstract void onStartCustomActions();
@Override
public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity activity) {
confirmPumpPluginActivation(pluginSwitcher, activity);
}
/**
* Service class (same one you did serviceConnection for)
*
* @return
*/
public abstract Class getServiceClass();
@SuppressWarnings("UnusedParameters")
@Subscribe
public void onStatusEvent(final EventAppExit e) {
MainApp.instance().getApplicationContext().unbindService(serviceConnection);
}
public PumpStatus getPumpStatusData() {
return pumpStatus;
}
public boolean isInitialized() {
return PumpDriverState.isInitialized(pumpState);
}
public boolean isSuspended() {
return pumpState == PumpDriverState.Suspended;
}
public boolean isBusy() {
return pumpState == PumpDriverState.Busy;
}
public boolean isConnected() {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("isConnected [PumpPluginAbstract].");
return PumpDriverState.isConnected(pumpState);
}
public boolean isConnecting() {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("isConnecting [PumpPluginAbstract].");
return pumpState == PumpDriverState.Connecting;
}
public void connect(String reason) {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("connect (reason={}) [PumpPluginAbstract] - default (empty) implementation.", reason);
}
public void disconnect(String reason) {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("disconnect (reason={}) [PumpPluginAbstract] - default (empty) implementation.", reason);
}
public void stopConnecting() {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("stopConnecting [PumpPluginAbstract] - default (empty) implementation.");
}
@Override
public boolean isHandshakeInProgress() {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("isHandshakeInProgress [PumpPluginAbstract] - default (empty) implementation.");
return false;
}
@Override
public void finishHandshaking() {
if (displayConnectionMessages && isLoggingEnabled())
LOG.warn("finishHandshaking [PumpPluginAbstract] - default (empty) implementation.");
}
public void getPumpStatus() {
if (isLoggingEnabled())
LOG.warn("getPumpStatus [PumpPluginAbstract] - Not implemented.");
}
// Upload to pump new basal profile
public PumpEnactResult setNewBasalProfile(Profile profile) {
if (isLoggingEnabled())
LOG.warn("setNewBasalProfile [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
public boolean isThisProfileSet(Profile profile) {
if (isLoggingEnabled())
LOG.warn("isThisProfileSet [PumpPluginAbstract] - Not implemented.");
return true;
}
public long lastDataTime() {
if (isLoggingEnabled())
LOG.warn("lastDataTime [PumpPluginAbstract].");
return pumpStatus.lastConnection;
}
public double getBaseBasalRate() {
if (isLoggingEnabled())
LOG.warn("getBaseBasalRate [PumpPluginAbstract] - Not implemented.");
return 0.0d;
} // base basal rate, not temp basal
public void stopBolusDelivering() {
if (isLoggingEnabled())
LOG.warn("stopBolusDelivering [PumpPluginAbstract] - Not implemented.");
}
@Override
public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile,
boolean enforceNew) {
if (isLoggingEnabled())
LOG.warn("setTempBasalAbsolute [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
@Override
public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile,
boolean enforceNew) {
if (isLoggingEnabled())
LOG.warn("setTempBasalPercent [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) {
if (isLoggingEnabled())
LOG.warn("setExtendedBolus [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
// some pumps might set a very short temp close to 100% as cancelling a temp can be noisy
// when the cancel request is requested by the user (forced), the pump should always do a real cancel
public PumpEnactResult cancelTempBasal(boolean enforceNew) {
if (isLoggingEnabled())
LOG.warn("cancelTempBasal [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
public PumpEnactResult cancelExtendedBolus() {
if (isLoggingEnabled())
LOG.warn("cancelExtendedBolus [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
// Status to be passed to NS
// public JSONObject getJSONStatus(Profile profile, String profileName) {
// return pumpDriver.getJSONStatus(profile, profileName);
// }
public String deviceID() {
if (isLoggingEnabled())
LOG.warn("deviceID [PumpPluginAbstract] - Not implemented.");
return "FakeDevice";
}
// Pump capabilities
public PumpDescription getPumpDescription() {
return pumpDescription;
}
// Short info for SMS, Wear etc
public boolean isFakingTempsByExtendedBoluses() {
if (isLoggingEnabled())
LOG.warn("isFakingTempsByExtendedBoluses [PumpPluginAbstract] - Not implemented.");
return false;
}
@Override
public PumpEnactResult loadTDDs() {
if (isLoggingEnabled())
LOG.warn("loadTDDs [PumpPluginAbstract] - Not implemented.");
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
}
@Override
public JSONObject getJSONStatus(Profile profile, String profileName) {
long now = System.currentTimeMillis();
if ((pumpStatus.lastConnection + 5 * 60 * 1000L) < System.currentTimeMillis()) {
return null;
}
JSONObject pump = new JSONObject();
JSONObject battery = new JSONObject();
JSONObject status = new JSONObject();
JSONObject extended = new JSONObject();
try {
battery.put("percent", pumpStatus.batteryRemaining);
status.put("status", pumpStatus.pumpStatusType != null ? pumpStatus.pumpStatusType.getStatus() : "normal");
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
try {
extended.put("ActiveProfile", profileName);
} catch (Exception e) {
}
TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());
if (tb != null) {
extended.put("TempBasalAbsoluteRate",
tb.tempBasalConvertedToAbsolute(System.currentTimeMillis(), profile));
extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date));
extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes());
}
ExtendedBolus eb = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis());
if (eb != null) {
extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate());
extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date));
extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes());
}
status.put("timestamp", DateUtil.toISOString(new Date()));
pump.put("battery", battery);
pump.put("status", status);
pump.put("extended", extended);
pump.put("reservoir", pumpStatus.reservoirRemainingUnits);
pump.put("clock", DateUtil.toISOString(new Date()));
} catch (JSONException e) {
LOG.error("Unhandled exception", e);
}
return pump;
}
// FIXME i18n, null checks: iob, TDD
@Override
public String shortStatus(boolean veryShort) {
String ret = "";
if (pumpStatus.lastConnection != 0) {
Long agoMsec = System.currentTimeMillis() - pumpStatus.lastConnection;
int agoMin = (int)(agoMsec / 60d / 1000d);
ret += "LastConn: " + agoMin + " min ago\n";
}
if (pumpStatus.lastBolusTime != null && pumpStatus.lastBolusTime.getTime() != 0) {
ret += "LastBolus: " + DecimalFormatter.to2Decimal(pumpStatus.lastBolusAmount) + "U @" + //
android.text.format.DateFormat.format("HH:mm", pumpStatus.lastBolusTime) + "\n";
}
TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin()
.getRealTempBasalFromHistory(System.currentTimeMillis());
if (activeTemp != null) {
ret += "Temp: " + activeTemp.toStringFull() + "\n";
}
ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(
System.currentTimeMillis());
if (activeExtendedBolus != null) {
ret += "Extended: " + activeExtendedBolus.toString() + "\n";
}
// if (!veryShort) {
// ret += "TDD: " + DecimalFormatter.to0Decimal(pumpStatus.dailyTotalUnits) + " / "
// + pumpStatus.maxDailyTotalUnits + " U\n";
// }
ret += "IOB: " + pumpStatus.iob + "U\n";
ret += "Reserv: " + DecimalFormatter.to0Decimal(pumpStatus.reservoirRemainingUnits) + "U\n";
ret += "Batt: " + pumpStatus.batteryRemaining + "\n";
return ret;
}
@Override
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
try {
if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) {
// neither carbs nor bolus requested
if (isLoggingEnabled())
LOG.error("deliverTreatment: Invalid input");
return new PumpEnactResult().success(false).enacted(false).bolusDelivered(0d).carbsDelivered(0d)
.comment(MainApp.gs(R.string.danar_invalidinput));
} else if (detailedBolusInfo.insulin > 0) {
// bolus needed, ask pump to deliver it
return deliverBolus(detailedBolusInfo);
} else {
// no bolus required, carb only treatment
TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true);
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
bolusingEvent.t = new Treatment();
bolusingEvent.t.isSMB = detailedBolusInfo.isSMB;
bolusingEvent.percent = 100;
MainApp.bus().post(bolusingEvent);
if (isLoggingEnabled())
LOG.debug("deliverTreatment: Carb only treatment.");
return new PumpEnactResult().success(true).enacted(true).bolusDelivered(0d)
.carbsDelivered(detailedBolusInfo.carbs).comment(MainApp.gs(R.string.virtualpump_resultok));
}
} finally {
triggerUIChange();
}
}
public boolean isLoggingEnabled() {
return L.isEnabled(L.PUMP);
}
protected abstract PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo);
protected abstract void triggerUIChange();
public static PumpEnactResult getOperationNotSupportedWithCustomText(int resourceId) {
return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(resourceId));
}
}

View file

@ -0,0 +1,90 @@
package info.nightscout.androidaps.plugins.pump.common.data;
import java.util.Date;
import org.joda.time.LocalDateTime;
import info.nightscout.androidaps.data.ProfileStore;
import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
/**
* Created by andy on 4/28/18.
*/
public abstract class PumpStatus {
// connection
public LocalDateTime lastDataTime;
public long lastConnection = 0L;
public long previousConnection = 0L; // here should be stored last connection of previous session (so needs to be
// read before lastConnection is modified for first time).
// last bolus
public Date lastBolusTime;
public Double lastBolusAmount;
// other pump settings
public String activeProfileName = "0";
public double reservoirRemainingUnits = 0d;
public String reservoirFullUnits = "???";
public int batteryRemaining = 0; // percent, so 0-100
public Double batteryVoltage = null;
// iob
public String iob = null;
// TDD
public Double dailyTotalUnits;
public String maxDailyTotalUnits;
public boolean validBasalRateProfileSelectedOnPump = true;
public PumpType pumpType = PumpType.GenericAAPS;
public ProfileStore profileStore;
public String units; // Constants.MGDL or Constants.MMOL
public PumpStatusType pumpStatusType = PumpStatusType.Running;
public Double[] basalsByHour;
public double currentBasal = 0;
public int tempBasalInProgress = 0;
public int tempBasalRatio = 0;
public int tempBasalRemainMin = 0;
public Date tempBasalStart;
protected PumpDescription pumpDescription;
public PumpStatus(PumpDescription pumpDescription) {
this.pumpDescription = pumpDescription;
this.initSettings();
}
public abstract void initSettings();
public void setLastCommunicationToNow() {
this.lastDataTime = LocalDateTime.now();
this.lastConnection = System.currentTimeMillis();
}
public abstract String getErrorInfo();
public abstract void refreshConfiguration();
public PumpType getPumpType() {
return pumpType;
}
public void setPumpType(PumpType pumpType) {
this.pumpType = pumpType;
}
// public Date last_bolus_time;
// public double last_bolus_amount = 0;
}

View file

@ -22,7 +22,7 @@ public enum PumpCapability {
DanaCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, TDD, ManualTDDLoad), // DanaCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, TDD, ManualTDDLoad), //
DanaWithHistoryCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, StoreCarbInfo, TDD, ManualTDDLoad), // DanaWithHistoryCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill, StoreCarbInfo, TDD, ManualTDDLoad), //
InsightCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill,TDD,BasalRate30min), // InsightCapabilities(Bolus, ExtendedBolus, TempBasal, BasalProfileSet, Refill,TDD,BasalRate30min), //
MedtronicCapabilities(Bolus, TempBasal, BasalProfileSet, Refill, TDD), //
// BasalRates (separately grouped) // BasalRates (separately grouped)
BasalRate_Duration15minAllowed, // BasalRate_Duration15minAllowed, //

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.pump.common.defs;
/**
* Created by andy on 10/15/18.
*/
public enum PumpDriverState {
NotInitialized, //
Connecting, //
Connected, //
Initialized, //
Ready,
Busy, //
Suspended, //
;
public static boolean isConnected(PumpDriverState pumpState) {
return pumpState == Connected || pumpState == Initialized || pumpState == Busy || pumpState == Suspended;
}
public static boolean isInitialized(PumpDriverState pumpState) {
return pumpState == Initialized || pumpState == Busy || pumpState == Suspended;
}
}

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.plugins.pump.common.defs;
/**
* Created by andy on 5/12/18.
*/
public enum PumpStatusType {
Running("normal"), //
Suspended("suspended") //
;
private String statusString;
PumpStatusType(String statusString) {
this.statusString = statusString;
}
public String getStatus() {
return statusString;
}
}

View file

@ -6,6 +6,7 @@ import java.util.Map;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.pump.common.data.DoseSettings; import info.nightscout.androidaps.plugins.pump.common.data.DoseSettings;
import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.Round;
@ -18,7 +19,7 @@ import info.nightscout.androidaps.utils.Round;
public enum PumpType { public enum PumpType {
GenericAAPS("Generic AAPS", "AndroidAPS", "VirutalPump", 0.1d, null, // GenericAAPS("Generic AAPS", ManufacturerType.AndroidAPS, "VirutalPump", 0.1d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Percent, // PumpTempBasalType.Percent, //
new DoseSettings(10, 30, 24 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(10, 30, 24 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, //
@ -26,7 +27,7 @@ public enum PumpType {
// Cellnovo // Cellnovo
Cellnovo1("Cellnovo", "Cellnovo", "Cellnovo", 0.05d, null, // Cellnovo1("Cellnovo", ManufacturerType.Cellnovo, "Cellnovo", 0.05d, null, //
new DoseSettings(0.05d, 30, 24 * 60, 1d, null), new DoseSettings(0.05d, 30, 24 * 60, 1d, null),
PumpTempBasalType.Percent, PumpTempBasalType.Percent,
new DoseSettings(5, 30, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration30minAllowed, // new DoseSettings(5, 30, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration30minAllowed, //
@ -34,32 +35,32 @@ public enum PumpType {
// Accu-Chek // Accu-Chek
AccuChekCombo("Accu-Chek Combo", "Roche", "Combo", 0.1d, null, // AccuChekCombo("Accu-Chek Combo", ManufacturerType.Roche, "Combo", 0.1d, null, //
new DoseSettings(0.1d, 15, 12 * 60, 0.1d), // new DoseSettings(0.1d, 15, 12 * 60, 0.1d), //
PumpTempBasalType.Percent, PumpTempBasalType.Percent,
new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, //
0.01d, 0.01d, DoseStepSize.ComboBasal, PumpCapability.ComboCapabilities), // 0.01d, 0.01d, DoseStepSize.ComboBasal, PumpCapability.ComboCapabilities), //
AccuChekSpirit("Accu-Chek Spirit", "Roche", "Spirit", 0.1d, null, // AccuChekSpirit("Accu-Chek Spirit", ManufacturerType.Roche, "Spirit", 0.1d, null, //
new DoseSettings(0.1d, 15, 12 * 60, 0.1d), // new DoseSettings(0.1d, 15, 12 * 60, 0.1d), //
PumpTempBasalType.Percent, PumpTempBasalType.Percent,
new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, //
0.01d, 0.1d, null, PumpCapability.VirtualPumpCapabilities), // 0.01d, 0.1d, null, PumpCapability.VirtualPumpCapabilities), //
AccuChekInsight("Accu-Chek Insight", "Roche", "Insight", 0.05d, DoseStepSize.InsightBolus, // AccuChekInsight("Accu-Chek Insight", ManufacturerType.Roche, "Insight", 0.05d, DoseStepSize.InsightBolus, //
new DoseSettings(0.05d, 15, 24 * 60, 0.05d), // new DoseSettings(0.05d, 15, 24 * 60, 0.05d), //
PumpTempBasalType.Percent, PumpTempBasalType.Percent,
new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
0.02d, 0.01d, null, PumpCapability.InsightCapabilities), // 0.02d, 0.01d, null, PumpCapability.InsightCapabilities), //
AccuChekInsightBluetooth("Accu-Chek Insight", "Roche", "Insight", 0.01d, null, // AccuChekInsightBluetooth("Accu-Chek Insight", ManufacturerType.Roche, "Insight", 0.01d, null, //
new DoseSettings(0.01d, 15, 24 * 60, 0.05d), // new DoseSettings(0.01d, 15, 24 * 60, 0.05d), //
PumpTempBasalType.Percent, PumpTempBasalType.Percent,
new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
0.02d, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities), // 0.02d, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities), //
// Animas // Animas
AnimasVibe("Animas Vibe","Animas", "Vibe", 0.05d, null, // AnimasBolus? AnimasVibe("Animas Vibe",ManufacturerType.Animas, "Vibe", 0.05d, null, // AnimasBolus?
new DoseSettings(0.05d, 30, 12 * 60, 0.05d), // new DoseSettings(0.05d, 30, 12 * 60, 0.05d), //
PumpTempBasalType.Percent, // PumpTempBasalType.Percent, //
new DoseSettings(10, 30, 24 * 60, 0d, 300d), PumpCapability.BasalRate_Duration30minAllowed, // new DoseSettings(10, 30, 24 * 60, 0d, 300d), PumpCapability.BasalRate_Duration30minAllowed, //
@ -68,19 +69,19 @@ public enum PumpType {
AnimasPing("Animas Ping", "Ping", AnimasVibe), AnimasPing("Animas Ping", "Ping", AnimasVibe),
// Dana // Dana
DanaR("DanaR", "SOOIL", "DanaR", 0.05d, null, // DanaR("DanaR", ManufacturerType.Sooil, "DanaR", 0.05d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Percent, // PumpTempBasalType.Percent, //
new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, // new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, //
0.04d, 0.01d, null, PumpCapability.DanaCapabilities), 0.04d, 0.01d, null, PumpCapability.DanaCapabilities),
DanaRKorean("DanaR Korean", "SOOIL", "DanaRKorean", 0.05d, null, // DanaRKorean("DanaR Korean", ManufacturerType.Sooil, "DanaRKorean", 0.05d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Percent, // PumpTempBasalType.Percent, //
new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, // new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, //
0.1d, 0.01d, null, PumpCapability.DanaCapabilities), 0.1d, 0.01d, null, PumpCapability.DanaCapabilities),
DanaRS("DanaRS", "SOOIL", "DanaRS", 0.05d, null, // DanaRS("DanaRS", ManufacturerType.Sooil, "DanaRS", 0.05d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Percent, // PumpTempBasalType.Percent, //
new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minAllowed, //
@ -90,38 +91,38 @@ public enum PumpType {
// Insulet // Insulet
Insulet_Omnipod("Insulet Omnipod", "Insulet", "Omnipod", 0.05d, null, // Insulet_Omnipod("Insulet Omnipod", ManufacturerType.Insulet, "Omnipod", 0.05d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Absolute, // PumpTempBasalType.Absolute, //
new DoseSettings(0.05d, 30, 12 * 60, 0d, 30.0d), PumpCapability.BasalRate_Duration30minAllowed, // cannot exceed max basal rate 30u/hr new DoseSettings(0.05d, 30, 12 * 60, 0d, 30.0d), PumpCapability.BasalRate_Duration30minAllowed, // cannot exceed max basal rate 30u/hr
0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), 0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities),
// Medtronic // Medtronic
Medtronic_512_712("Medtronic 512/712", "Medtronic", "512/712", 0.05d, null, // Medtronic_512_712("Medtronic 512/712", ManufacturerType.Medtronic, "512/712", 0.05d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Absolute, // PumpTempBasalType.Absolute, //
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), // TODO 0.05d, 0.05d, null, PumpCapability.MedtronicCapabilities), //
Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712), Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712),
Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712), Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712),
Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", "Medtronic", "523/723 (Revel)", 0.05d, null, // Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", ManufacturerType.Medtronic, "523/723 (Revel)", 0.05d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Absolute, // PumpTempBasalType.Absolute, //
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), // 0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.MedtronicCapabilities), //
Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", "554/754 (Veo)", Medtronic_523_723_Revel), // TODO Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", "554/754 (Veo)", Medtronic_523_723_Revel), // TODO
Medtronic_640G("Medtronic 640G", "Medtronic", "640G", 0.025d, null, // Medtronic_640G("Medtronic 640G", ManufacturerType.Medtronic, "640G", 0.025d, null, //
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
PumpTempBasalType.Absolute, // PumpTempBasalType.Absolute, //
new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), // 0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), //
// Tandem // Tandem
TandemTSlim("Tandem t:slim", "Tandem", "t:slim", 0.01d, null, // TandemTSlim("Tandem t:slim", ManufacturerType.Tandem, "t:slim", 0.01d, null, //
new DoseSettings(0.01d, 15, 8 * 60, 0.4d), new DoseSettings(0.01d, 15, 8 * 60, 0.4d),
PumpTempBasalType.Percent, PumpTempBasalType.Percent,
new DoseSettings(1, 15, 8 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // new DoseSettings(1, 15, 8 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
@ -130,10 +131,14 @@ public enum PumpType {
TandemTFlex("Tandem t:flex", "t:flex", TandemTSlim), // TandemTFlex("Tandem t:flex", "t:flex", TandemTSlim), //
TandemTSlimG4("Tandem t:slim G4", "t:slim G4", TandemTSlim), // TandemTSlimG4("Tandem t:slim G4", "t:slim G4", TandemTSlim), //
TandemTSlimX2("Tandem t:slim X2", "t:slim X2", TandemTSlim), // TandemTSlimX2("Tandem t:slim X2", "t:slim X2", TandemTSlim), //
// MDI
MDI("MDI", ManufacturerType.AndroidAPS, "MDI")
; ;
private String description; private String description;
private String manufacter; private ManufacturerType manufacturer;
private String model; private String model;
private double bolusSize; private double bolusSize;
private DoseStepSize specialBolusSize; private DoseStepSize specialBolusSize;
@ -163,9 +168,18 @@ public enum PumpType {
{ {
this.description = description; this.description = description;
this.parent = parent; this.parent = parent;
parent.model = model; this.model = model;
} }
PumpType(String description, ManufacturerType manufacturer, String model)
{
this.description = description;
this.manufacturer = manufacturer;
this.model = model;
}
PumpType(String description, String model, PumpType parent, PumpCapability pumpCapability) PumpType(String description, String model, PumpType parent, PumpCapability pumpCapability)
{ {
this.description = description; this.description = description;
@ -174,20 +188,20 @@ public enum PumpType {
parent.model = model; parent.model = model;
} }
PumpType(String description, String manufacter, String model, double bolusSize, DoseStepSize specialBolusSize, // PumpType(String description, ManufacturerType manufacturer, String model, double bolusSize, DoseStepSize specialBolusSize, //
DoseSettings extendedBolusSettings, // DoseSettings extendedBolusSettings, //
PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, // PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, //
double baseBasalMinValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) double baseBasalMinValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability)
{ {
this(description, manufacter, model, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability); this(description, manufacturer, model, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability);
} }
PumpType(String description, String manufacter, String model, double bolusSize, DoseStepSize specialBolusSize, // PumpType(String description, ManufacturerType manufacturer, String model, double bolusSize, DoseStepSize specialBolusSize, //
DoseSettings extendedBolusSettings, // DoseSettings extendedBolusSettings, //
PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, // PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, //
double baseBasalMinValue, Double baseBasalMaxValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) { double baseBasalMinValue, Double baseBasalMaxValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) {
this.description = description; this.description = description;
this.manufacter = manufacter; this.manufacturer = manufacturer;
this.model = model; this.model = model;
this.bolusSize = bolusSize; this.bolusSize = bolusSize;
this.specialBolusSize = specialBolusSize; this.specialBolusSize = specialBolusSize;
@ -207,8 +221,8 @@ public enum PumpType {
return description; return description;
} }
public String getManufacter() { public ManufacturerType getManufacturer() {
return isParentSet() ? parent.manufacter : manufacter; return isParentSet() ? parent.manufacturer : manufacturer;
} }
public String getModel() { public String getModel() {

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.pump.common.dialog;
/**
* Created by andy on 5/19/18.
*/
public interface RefreshableInterface {
void refreshData();
}

View file

@ -0,0 +1,423 @@
package info.nightscout.androidaps.plugins.pump.common.dialog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelUuid;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
import info.nightscout.androidaps.plugins.pump.common.utils.LocationHelper;
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpConfigurationChanged;
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
import info.nightscout.androidaps.utils.SP;
// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example)
public class RileyLinkBLEScanActivity extends AppCompatActivity {
private static final Logger LOG = LoggerFactory.getLogger(RileyLinkBLEScanActivity.class);
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary.
private static final int REQUEST_ENABLE_BT = 30242; // arbitrary
private static String TAG = "RileyLinkBLEScanActivity";
// Stops scanning after 30 seconds.
private static final long SCAN_PERIOD = 30000;
public boolean mScanning;
public ScanSettings settings;
public List<ScanFilter> filters;
public ListView listBTScan;
public Toolbar toolbarBTScan;
public Context mContext = this;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScanner mLEScanner;
private LeDeviceListAdapter mLeDeviceListAdapter;
private Handler mHandler;
private String actionTitleStart, actionTitleStop;
private MenuItem menuItem;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.rileylink_scan_activity);
// Initializes Bluetooth adapter.
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mHandler = new Handler();
mLeDeviceListAdapter = new LeDeviceListAdapter();
listBTScan = (ListView)findViewById(R.id.rileylink_listBTScan);
listBTScan.setAdapter(mLeDeviceListAdapter);
listBTScan.setOnItemClickListener((parent, view, position, id) -> {
// stop scanning if still active
if (mScanning) {
mScanning = false;
mLEScanner.stopScan(mScanCallback2);
}
TextView textview = (TextView)view.findViewById(R.id.rileylink_device_address);
String bleAddress = textview.getText().toString();
SP.putString(RileyLinkConst.Prefs.RileyLinkAddress, bleAddress);
RileyLinkUtil.getRileyLinkSelectPreference().setSummary(bleAddress);
MedtronicPumpStatus pumpStatus = MedtronicUtil.getPumpStatus();
pumpStatus.verifyConfiguration(); // force reloading of address
MainApp.bus().post(new EventMedtronicPumpConfigurationChanged());
finish();
});
toolbarBTScan = (Toolbar)findViewById(R.id.rileylink_toolbarBTScan);
toolbarBTScan.setTitle(R.string.rileylink_scanner_title);
setSupportActionBar(toolbarBTScan);
prepareForScanning();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_rileylink_ble_scan, menu);
actionTitleStart = MainApp.gs(R.string.rileylink_scanner_scan_scan);
actionTitleStop = MainApp.gs(R.string.rileylink_scanner_scan_stop);
menuItem = menu.getItem(0);
menuItem.setTitle(actionTitleStart);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.rileylink_miScan: {
scanLeDevice(menuItem.getTitle().equals(actionTitleStart));
return true;
}
default:
return super.onOptionsItemSelected(item);
}
}
public void prepareForScanning() {
// https://developer.android.com/training/permissions/requesting.html
// http://developer.radiusnetworks.com/2015/09/29/is-your-beacon-app-ready-for-android-6.html
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.rileylink_scanner_ble_not_supported, Toast.LENGTH_SHORT).show();
} else {
// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// your code that requires permission
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION },
PERMISSION_REQUEST_COARSE_LOCATION);
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Toast.makeText(this, R.string.rileylink_scanner_ble_not_enabled, Toast.LENGTH_SHORT).show();
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Will request that GPS be enabled for devices running Marshmallow or newer.
if (!LocationHelper.isLocationEnabled(this)) {
LocationHelper.requestLocationForBluetooth(this);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
filters = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
ParcelUuid.fromString(GattAttributes.SERVICE_RADIO)).build());
}
}
// disable currently selected RL, so that we can discover it
RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnect);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
if (resultCode == RESULT_OK) {
// User allowed Bluetooth to turn on
} else if (resultCode == RESULT_CANCELED) {
// Error, or user said "NO"
finish();
}
}
}
private ScanCallback mScanCallback2 = new ScanCallback() {
@Override
public void onScanResult(int callbackType, final ScanResult scanRecord) {
Log.d(TAG, scanRecord.toString());
runOnUiThread(() -> {
if (addDevice(scanRecord))
mLeDeviceListAdapter.notifyDataSetChanged();
});
}
@Override
public void onBatchScanResults(final List<ScanResult> results) {
runOnUiThread(() -> {
boolean added = false;
for (ScanResult result : results) {
if (addDevice(result))
added = true;
}
if (added)
mLeDeviceListAdapter.notifyDataSetChanged();
});
}
private boolean addDevice(ScanResult result) {
BluetoothDevice device = result.getDevice();
List<ParcelUuid> serviceUuids = result.getScanRecord().getServiceUuids();
if (serviceUuids == null || serviceUuids.size() == 0) {
Log.v(TAG, "Device " + device.getAddress() + " has no serviceUuids (Not RileyLink).");
} else if (serviceUuids.size() > 1) {
Log.v(TAG, "Device " + device.getAddress() + " has too many serviceUuids (Not RileyLink).");
} else {
String uuid = serviceUuids.get(0).getUuid().toString().toLowerCase();
if (uuid.equals(GattAttributes.SERVICE_RADIO)) {
Log.i(TAG, "Found RileyLink with address: " + device.getAddress());
mLeDeviceListAdapter.addDevice(result);
return true;
} else {
Log.v(TAG, "Device " + device.getAddress() + " has incorrect uuid (Not RileyLink).");
}
}
return false;
}
private String getDeviceDebug(BluetoothDevice device) {
return "BluetoothDevice [name=" + device.getName() + ", address=" + device.getAddress() + //
", type=" + device.getType(); // + ", alias=" + device.getAlias();
}
@Override
public void onScanFailed(int errorCode) {
Log.e("Scan Failed", "Error Code: " + errorCode);
Toast.makeText(mContext, MainApp.gs(R.string.rileylink_scanner_scanning_error, errorCode),
Toast.LENGTH_LONG).show();
}
};
private void scanLeDevice(final boolean enable) {
if (mLEScanner == null)
return;
if (enable) {
mLeDeviceListAdapter.clear();
mLeDeviceListAdapter.notifyDataSetChanged();
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(() -> {
if (mScanning) {
mScanning = false;
mLEScanner.stopScan(mScanCallback2);
LOG.debug("scanLeDevice: Scanning Stop");
Toast.makeText(mContext, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show();
menuItem.setTitle(actionTitleStart);
}
}, SCAN_PERIOD);
mScanning = true;
mLEScanner.startScan(filters, settings, mScanCallback2);
LOG.debug("scanLeDevice: Scanning Start");
Toast.makeText(this, R.string.rileylink_scanner_scanning, Toast.LENGTH_SHORT).show();
menuItem.setTitle(actionTitleStop);
} else {
if (mScanning) {
mScanning = false;
mLEScanner.stopScan(mScanCallback2);
LOG.debug("scanLeDevice: Scanning Stop");
Toast.makeText(this, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show();
menuItem.setTitle(actionTitleStart);
}
}
}
private class LeDeviceListAdapter extends BaseAdapter {
private ArrayList<BluetoothDevice> mLeDevices;
private Map<BluetoothDevice, Integer> rileyLinkDevices;
private LayoutInflater mInflator;
String currentlySelectedAddress;
public LeDeviceListAdapter() {
super();
mLeDevices = new ArrayList<>();
rileyLinkDevices = new HashMap<>();
mInflator = RileyLinkBLEScanActivity.this.getLayoutInflater();
currentlySelectedAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, "");
}
public void addDevice(ScanResult result) {
if (!mLeDevices.contains(result.getDevice())) {
mLeDevices.add(result.getDevice());
}
rileyLinkDevices.put(result.getDevice(), result.getRssi());
notifyDataSetChanged();
}
public void clear() {
mLeDevices.clear();
rileyLinkDevices.clear();
notifyDataSetChanged();
}
@Override
public int getCount() {
return mLeDevices.size();
}
@Override
public Object getItem(int i) {
return mLeDevices.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.rileylink_scan_item, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView)view.findViewById(R.id.rileylink_device_address);
viewHolder.deviceName = (TextView)view.findViewById(R.id.rileylink_device_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)view.getTag();
}
BluetoothDevice device = mLeDevices.get(i);
String deviceName = device.getName();
if (StringUtils.isBlank(deviceName)) {
deviceName = "RileyLink";
}
deviceName += " [" + rileyLinkDevices.get(device).intValue() + "]";
if (currentlySelectedAddress.equals(device.getAddress())) {
// viewHolder.deviceName.setTextColor(getColor(R.color.secondary_text_light));
// viewHolder.deviceAddress.setTextColor(getColor(R.color.secondary_text_light));
deviceName += " (" + getResources().getString(R.string.rileylink_scanner_selected_device) + ")";
}
viewHolder.deviceName.setText(deviceName);
viewHolder.deviceAddress.setText(device.getAddress());
return view;
}
}
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
}
}

View file

@ -0,0 +1,439 @@
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.content.Context;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyScanResults;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyTrial;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
import info.nightscout.androidaps.utils.SP;
/**
* This is abstract class for RileyLink Communication, this one needs to be extended by specific "Pump" class.
* <p>
* Created by andy on 5/10/18.
*/
public abstract class RileyLinkCommunicationManager {
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM);
private static final int SCAN_TIMEOUT = 1500;
private static final int ALLOWED_PUMP_UNREACHABLE = 10 * 60 * 1000; // 10 minutes
protected final RFSpy rfspy;
protected final Context context;
protected int receiverDeviceAwakeForMinutes = 1; // override this in constructor of specific implementation
protected String receiverDeviceID; // String representation of receiver device (ex. Pump (xxxxxx) or Pod (yyyyyy))
protected long lastGoodReceiverCommunicationTime = 0;
protected PumpStatus pumpStatus;
protected RileyLinkServiceData rileyLinkServiceData;
private long nextWakeUpRequired = 0L;
// internal flag
private boolean showPumpMessages = true;
private int timeoutCount = 0;
public RileyLinkCommunicationManager(Context context, RFSpy rfspy) {
this.context = context;
this.rfspy = rfspy;
this.rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData();
RileyLinkUtil.setRileyLinkCommunicationManager(this);
configurePumpSpecificSettings();
}
protected abstract void configurePumpSpecificSettings();
// All pump communications go through this function.
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, Class<E> clazz)
throws RileyLinkCommunicationException {
if (showPumpMessages) {
if (isLogEnabled())
LOG.info("Sent:" + ByteUtil.shortHexString(msg.getTxData()));
}
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), timeout_ms);
RadioResponse radioResponse = rfSpyResponse.getRadioResponse();
E response = createResponseMessage(rfSpyResponse.getRadioResponse().getPayload(), clazz);
if (response.isValid()) {
// Mark this as the last time we heard from the pump.
rememberLastGoodDeviceCommunicationTime();
} else {
LOG.warn("isDeviceReachable. Response is invalid ! [interrupted={}, timeout={}, unknownCommand={}, invalidParam={}]", rfSpyResponse.wasInterrupted(),
rfSpyResponse.wasTimeout(), rfSpyResponse.isUnknownCommand(), rfSpyResponse.isInvalidParam());
if (rfSpyResponse.wasTimeout()) {
timeoutCount++;
long diff = System.currentTimeMillis() - pumpStatus.lastConnection;
if (diff > ALLOWED_PUMP_UNREACHABLE) {
LOG.warn("We reached max time that Pump can be unreachable. Starting Tuning.");
ServiceTaskExecutor.startTask(new WakeAndTuneTask());
timeoutCount = 0;
}
throw new RileyLinkCommunicationException(RileyLinkBLEError.Timeout);
} else if (rfSpyResponse.wasInterrupted()) {
throw new RileyLinkCommunicationException(RileyLinkBLEError.Interrupted);
}
}
if (showPumpMessages) {
if (isLogEnabled())
LOG.info("Received:" + ByteUtil.shortHexString(rfSpyResponse.getRadioResponse().getPayload()));
}
return response;
}
public abstract <E extends RLMessage> E createResponseMessage(byte[] payload, Class<E> clazz);
public void wakeUp(boolean force) {
wakeUp(receiverDeviceAwakeForMinutes, force);
}
public int getNotConnectedCount() {
return rfspy != null ? rfspy.notConnectedCount : 0;
}
// FIXME change wakeup
// TODO we might need to fix this. Maybe make pump awake for shorter time (battery factor for pump) - Andy
public void wakeUp(int duration_minutes, boolean force) {
// If it has been longer than n minutes, do wakeup. Otherwise assume pump is still awake.
// **** FIXME: this wakeup doesn't seem to work well... must revisit
// receiverDeviceAwakeForMinutes = duration_minutes;
MedtronicUtil.setPumpDeviceState(PumpDeviceState.WakingUp);
if (force)
nextWakeUpRequired = 0L;
if (System.currentTimeMillis() > nextWakeUpRequired) {
if (isLogEnabled())
LOG.info("Waking pump...");
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)200,
(byte)0, (byte)0, 25000, (byte)0);
if (isLogEnabled())
LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
// FIXME wakeUp successful !!!!!!!!!!!!!!!!!!
nextWakeUpRequired = System.currentTimeMillis() + (receiverDeviceAwakeForMinutes * 60 * 1000);
} else {
if (isLogEnabled())
LOG.trace("Last pump communication was recent, not waking pump.");
}
// long lastGoodPlus = getLastGoodReceiverCommunicationTime() + (receiverDeviceAwakeForMinutes * 60 * 1000);
//
// if (System.currentTimeMillis() > lastGoodPlus || force) {
// LOG.info("Waking pump...");
//
// byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.PowerOn);
// RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, (byte)
// 0, (byte) 0, 15000, (byte) 0);
// LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
// } else {
// LOG.trace("Last pump communication was recent, not waking pump.");
// }
}
public void setRadioFrequencyForPump(double freqMHz) {
rfspy.setBaseFrequency(freqMHz);
}
public double tuneForDevice() {
return scanForDevice(RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies());
}
/**
* If user changes pump and one pump is running in US freq, and other in WW, then previously set frequency would be
* invalid,
* so we would need to retune. This checks that saved frequency is correct range.
*
* @param frequency
* @return
*/
public boolean isValidFrequency(double frequency) {
double[] scanFrequencies = RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies();
if (scanFrequencies.length == 1) {
return RileyLinkUtil.isSame(scanFrequencies[0], frequency);
} else {
return (scanFrequencies[0] <= frequency && scanFrequencies[scanFrequencies.length - 1] >= frequency);
}
}
/**
* Do device connection, with wakeup
*
* @return
*/
public abstract boolean tryToConnectToDevice();
public double scanForDevice(double[] frequencies) {
if (isLogEnabled())
LOG.info("Scanning for receiver ({})", receiverDeviceID);
wakeUp(receiverDeviceAwakeForMinutes, false);
FrequencyScanResults results = new FrequencyScanResults();
for (int i = 0; i < frequencies.length; i++) {
int tries = 3;
FrequencyTrial trial = new FrequencyTrial();
trial.frequencyMHz = frequencies[i];
rfspy.setBaseFrequency(frequencies[i]);
int sumRSSI = 0;
for (int j = 0; j < tries; j++) {
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData);
RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)0,
(byte)0, (byte)0, 1250, (byte)0);
if (resp.wasTimeout()) {
LOG.error("scanForPump: Failed to find pump at frequency {}", frequencies[i]);
} else if (resp.looksLikeRadioPacket()) {
RadioResponse radioResponse = new RadioResponse();
try {
radioResponse.init(resp.getRaw());
if (radioResponse.isValid()) {
int rssi = calculateRssi(radioResponse.rssi);
sumRSSI += rssi;
trial.rssiList.add(rssi);
trial.successes++;
} else {
LOG.warn("Failed to parse radio response: " + ByteUtil.shortHexString(resp.getRaw()));
trial.rssiList.add(-99);
}
} catch (RileyLinkCommunicationException rle) {
LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw()));
trial.rssiList.add(-99);
}
} else {
LOG.error("scanForPump: raw response is " + ByteUtil.shortHexString(resp.getRaw()));
trial.rssiList.add(-99);
}
trial.tries++;
}
sumRSSI += -99.0 * (trial.tries - trial.successes);
trial.averageRSSI2 = (double)(sumRSSI) / (double)(trial.tries);
trial.calculateAverage();
results.trials.add(trial);
}
results.dateTime = System.currentTimeMillis();
StringBuilder stringBuilder = new StringBuilder("Scan results:\n");
for (int k = 0; k < results.trials.size(); k++) {
FrequencyTrial one = results.trials.get(k);
stringBuilder.append(String.format("Scan Result[%s]: Freq=%s, avg RSSI = %s\n", "" + k, ""
+ one.frequencyMHz, "" + one.averageRSSI + ", RSSIs =" + one.rssiList));
}
LOG.info(stringBuilder.toString());
results.sort(); // sorts in ascending order
FrequencyTrial bestTrial = results.trials.get(results.trials.size() - 1);
results.bestFrequencyMHz = bestTrial.frequencyMHz;
if (bestTrial.successes > 0) {
rfspy.setBaseFrequency(results.bestFrequencyMHz);
if (isLogEnabled())
LOG.debug("Best frequency found: " + results.bestFrequencyMHz);
return results.bestFrequencyMHz;
} else {
LOG.error("No pump response during scan.");
return 0.0;
}
}
private int calculateRssi(int rssiIn) {
int rssiOffset = 73;
int outRssi = 0;
if (rssiIn >= 128) {
outRssi = ((rssiIn - 256) / 2) - rssiOffset;
} else {
outRssi = (rssiIn / 2) - rssiOffset;
}
return outRssi;
}
public abstract byte[] createPumpMessageContent(RLMessageType type);
private int tune_tryFrequency(double freqMHz) {
rfspy.setBaseFrequency(freqMHz);
// RLMessage msg = makeRLMessage(RLMessageType.ReadSimpleData);
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData);
RadioPacket pkt = new RadioPacket(pumpMsgContent);
RFSpyResponse resp = rfspy.transmitThenReceive(pkt, (byte)0, (byte)0, (byte)0, (byte)0, SCAN_TIMEOUT, (byte)0);
if (resp.wasTimeout()) {
LOG.warn("tune_tryFrequency: no pump response at frequency {}", freqMHz);
} else if (resp.looksLikeRadioPacket()) {
RadioResponse radioResponse = new RadioResponse();
try {
radioResponse.init(resp.getRaw());
if (radioResponse.isValid()) {
LOG.warn("tune_tryFrequency: saw response level {} at frequency {}", radioResponse.rssi, freqMHz);
return calculateRssi(radioResponse.rssi);
} else {
LOG.warn("tune_tryFrequency: invalid radio response:"
+ ByteUtil.shortHexString(radioResponse.getPayload()));
}
} catch (RileyLinkCommunicationException e) {
LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw()));
}
}
return 0;
}
public double quickTuneForPump(double startFrequencyMHz) {
double betterFrequency = startFrequencyMHz;
double stepsize = 0.05;
for (int tries = 0; tries < 4; tries++) {
double evenBetterFrequency = quickTunePumpStep(betterFrequency, stepsize);
if (evenBetterFrequency == 0.0) {
// could not see the pump at all.
// Try again at larger step size
stepsize += 0.05;
} else {
if ((int)(evenBetterFrequency * 100) == (int)(betterFrequency * 100)) {
// value did not change, so we're done.
break;
}
betterFrequency = evenBetterFrequency; // and go again.
}
}
if (betterFrequency == 0.0) {
// we've failed... caller should try a full scan for pump
if (isLogEnabled())
LOG.error("quickTuneForPump: failed to find pump");
} else {
rfspy.setBaseFrequency(betterFrequency);
if (betterFrequency != startFrequencyMHz) {
if (isLogEnabled())
LOG.info("quickTuneForPump: new frequency is {}MHz", betterFrequency);
} else {
if (isLogEnabled())
LOG.info("quickTuneForPump: pump frequency is the same: {}MHz", startFrequencyMHz);
}
}
return betterFrequency;
}
private double quickTunePumpStep(double startFrequencyMHz, double stepSizeMHz) {
if (isLogEnabled())
LOG.info("Doing quick radio tune for receiver ({})", receiverDeviceID);
wakeUp(false);
int startRssi = tune_tryFrequency(startFrequencyMHz);
double lowerFrequency = startFrequencyMHz - stepSizeMHz;
int lowerRssi = tune_tryFrequency(lowerFrequency);
double higherFrequency = startFrequencyMHz + stepSizeMHz;
int higherRssi = tune_tryFrequency(higherFrequency);
if ((higherRssi == 0.0) && (lowerRssi == 0.0) && (startRssi == 0.0)) {
// we can't see the pump at all...
return 0.0;
}
if (higherRssi > startRssi) {
// need to move higher
return higherFrequency;
} else if (lowerRssi > startRssi) {
// need to move lower.
return lowerFrequency;
}
return startFrequencyMHz;
}
protected void rememberLastGoodDeviceCommunicationTime() {
lastGoodReceiverCommunicationTime = System.currentTimeMillis();
SP.putLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, lastGoodReceiverCommunicationTime);
pumpStatus.setLastCommunicationToNow();
}
private long getLastGoodReceiverCommunicationTime() {
// If we have a value of zero, we need to load from prefs.
if (lastGoodReceiverCommunicationTime == 0L) {
lastGoodReceiverCommunicationTime = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L);
// Might still be zero, but that's fine.
}
double minutesAgo = (System.currentTimeMillis() - lastGoodReceiverCommunicationTime) / (1000.0 * 60.0);
if (isLogEnabled())
LOG.trace("Last good pump communication was " + minutesAgo + " minutes ago.");
return lastGoodReceiverCommunicationTime;
}
public PumpStatus getPumpStatus() {
return pumpStatus;
}
public void clearNotConnectedCount() {
if (rfspy != null) {
rfspy.notConnectedCount = 0;
}
}
private boolean isLogEnabled() {
return L.isEnabled(L.PUMPCOMM);
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
import info.nightscout.androidaps.R;
/**
* Created by andy on 16/05/2018.
*/
public class RileyLinkConst {
static final String Prefix = "AAPS.RileyLink.";
public class Intents {
public static final String RileyLinkReady = Prefix + "RileyLink_Ready";
public static final String RileyLinkGattFailed = Prefix + "RileyLink_Gatt_Failed";
public static final String BluetoothConnected = Prefix + "Bluetooth_Connected";
public static final String BluetoothReconnected = Prefix + "Bluetooth_Reconnected";
public static final String BluetoothDisconnected = Prefix + "Bluetooth_Disconnected";
public static final String RileyLinkDisconnected = Prefix + "RileyLink_Disconnected";
public static final String RileyLinkNewAddressSet = Prefix + "NewAddressSet";
public static final String INTENT_NEW_rileylinkAddressKey = Prefix + "INTENT_NEW_rileylinkAddressKey";
public static final String INTENT_NEW_pumpIDKey = Prefix + "INTENT_NEW_pumpIDKey";
public static final String RileyLinkDisconnect = Prefix + "RileyLink_Disconnect";
}
public class Prefs {
//public static final String PrefPrefix = "pref_rileylink_";
//public static final String RileyLinkAddress = PrefPrefix + "mac_address"; // pref_rileylink_mac_address
public static final int RileyLinkAddress = R.string.pref_key_rileylink_mac_address;
public static final String LastGoodDeviceCommunicationTime = Prefix + "lastGoodDeviceCommunicationTime";
public static final String LastGoodDeviceFrequency = Prefix + "LastGoodDeviceFrequency";
}
public class IPC {
// needs to br renamed (and maybe removed)
public static final String MSG_PUMP_quickTune = Prefix + "MSG_PUMP_quickTune";
public static final String MSG_PUMP_tunePump = Prefix + "MSG_PUMP_tunePump";
public static final String MSG_PUMP_fetchHistory = Prefix + "MSG_PUMP_fetchHistory";
public static final String MSG_PUMP_fetchSavedHistory = Prefix + "MSG_PUMP_fetchSavedHistory";
public static final String MSG_ServiceCommand = Prefix + "MSG_ServiceCommand";
}
}

View file

@ -0,0 +1,333 @@
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import android.content.Context;
import android.content.Intent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6b;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6bGeoff;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.BleAdvertisedData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceNotification;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask;
import info.nightscout.androidaps.plugins.pump.common.ui.RileyLinkSelectPreference;
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange;
/**
* Created by andy on 17/05/2018.
*/
public class RileyLinkUtil {
private static final Logger LOG = LoggerFactory.getLogger(L.PUMP);
protected static List<RLHistoryItem> historyRileyLink = new ArrayList<>();
protected static RileyLinkCommunicationManager rileyLinkCommunicationManager;
static ServiceTask currentTask;
private static Context context;
private static RileyLinkBLE rileyLinkBLE;
private static RileyLinkServiceData rileyLinkServiceData;
private static RileyLinkService rileyLinkService;
private static RileyLinkTargetFrequency rileyLinkTargetFrequency;
private static RileyLinkTargetDevice targetDevice;
private static RileyLinkEncodingType encoding;
private static RileyLinkSelectPreference rileyLinkSelectPreference;
private static Encoding4b6b encoding4b6b;
private static RileyLinkFirmwareVersion firmwareVersion;
public static void setContext(Context contextIn) {
RileyLinkUtil.context = contextIn;
}
public static RileyLinkEncodingType getEncoding() {
return encoding;
}
public static void setEncoding(RileyLinkEncodingType encoding) {
RileyLinkUtil.encoding = encoding;
if (encoding == RileyLinkEncodingType.FourByteSixByteLocal) {
RileyLinkUtil.encoding4b6b = new Encoding4b6bGeoff();
}
}
public static void sendBroadcastMessage(String message) {
if (context != null) {
Intent intent = new Intent(message);
LocalBroadcastManager.getInstance(RileyLinkUtil.context).sendBroadcast(intent);
}
}
public static void setServiceState(RileyLinkServiceState newState) {
setServiceState(newState, null);
}
public static RileyLinkError getError() {
if (RileyLinkUtil.rileyLinkServiceData != null)
return RileyLinkUtil.rileyLinkServiceData.errorCode;
else
return null;
}
public static RileyLinkServiceState getServiceState() {
return workWithServiceState(null, null, false);
}
public static void setServiceState(RileyLinkServiceState newState, RileyLinkError errorCode) {
workWithServiceState(newState, errorCode, true);
}
private static synchronized RileyLinkServiceState workWithServiceState(RileyLinkServiceState newState,
RileyLinkError errorCode, boolean set) {
if (set) {
RileyLinkUtil.rileyLinkServiceData.serviceState = newState;
RileyLinkUtil.rileyLinkServiceData.errorCode = errorCode;
if (L.isEnabled(L.PUMP))
LOG.info("RileyLink State Changed: {} {}", newState, errorCode == null ? "" : " - Error State: "
+ errorCode.name());
RileyLinkUtil.historyRileyLink.add(new RLHistoryItem(RileyLinkUtil.rileyLinkServiceData.serviceState,
RileyLinkUtil.rileyLinkServiceData.errorCode, targetDevice));
MainApp.bus().post(new EventMedtronicDeviceStatusChange(newState, errorCode));
return null;
} else {
return (RileyLinkUtil.rileyLinkServiceData == null || RileyLinkUtil.rileyLinkServiceData.serviceState == null) ? //
RileyLinkServiceState.NotStarted
: RileyLinkUtil.rileyLinkServiceData.serviceState;
}
}
public static RileyLinkBLE getRileyLinkBLE() {
return RileyLinkUtil.rileyLinkBLE;
}
public static void setRileyLinkBLE(RileyLinkBLE rileyLinkBLEIn) {
RileyLinkUtil.rileyLinkBLE = rileyLinkBLEIn;
}
public static RileyLinkServiceData getRileyLinkServiceData() {
return RileyLinkUtil.rileyLinkServiceData;
}
public static void setRileyLinkServiceData(RileyLinkServiceData rileyLinkServiceData) {
RileyLinkUtil.rileyLinkServiceData = rileyLinkServiceData;
}
public static boolean hasPumpBeenTunned() {
return RileyLinkUtil.rileyLinkServiceData.tuneUpDone;
}
public static RileyLinkService getRileyLinkService() {
return RileyLinkUtil.rileyLinkService;
}
public static void setRileyLinkService(RileyLinkService rileyLinkService) {
RileyLinkUtil.rileyLinkService = rileyLinkService;
}
public static RileyLinkCommunicationManager getRileyLinkCommunicationManager() {
return RileyLinkUtil.rileyLinkCommunicationManager;
}
public static void setRileyLinkCommunicationManager(RileyLinkCommunicationManager rileyLinkCommunicationManager) {
RileyLinkUtil.rileyLinkCommunicationManager = rileyLinkCommunicationManager;
}
public static boolean sendNotification(ServiceNotification notification, Integer clientHashcode) {
return false;
}
// FIXME remove ?
public static void setCurrentTask(ServiceTask task) {
if (currentTask == null) {
currentTask = task;
} else {
//LOG.error("setCurrentTask: Cannot replace current task");
}
}
public static void finishCurrentTask(ServiceTask task) {
if (task != currentTask) {
//LOG.error("finishCurrentTask: task does not match");
}
// hack to force deep copy of transport contents
ServiceTransport transport = task.getServiceTransport().clone();
if (transport.hasServiceResult()) {
sendServiceTransportResponse(transport, transport.getServiceResult());
}
currentTask = null;
}
public static void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) {
// get the key (hashcode) of the client who requested this
Integer clientHashcode = transport.getSenderHashcode();
// make a new bundle to send as the message data
transport.setServiceResult(serviceResult);
// FIXME
// transport.setTransportType(RT2Const.IPC.MSG_ServiceResult);
// rileyLinkIPCConnection.sendTransport(transport, clientHashcode);
}
public static RileyLinkTargetFrequency getRileyLinkTargetFrequency() {
return RileyLinkUtil.rileyLinkTargetFrequency;
}
public static void setRileyLinkTargetFrequency(RileyLinkTargetFrequency rileyLinkTargetFrequency) {
RileyLinkUtil.rileyLinkTargetFrequency = rileyLinkTargetFrequency;
}
public static boolean isSame(Double d1, Double d2) {
double diff = d1 - d2;
return (Math.abs(diff) <= 0.000001);
}
@Deprecated
public static BleAdvertisedData parseAdertisedData(byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
String name = null;
if (advertisedData == null) {
return new BleAdvertisedData(uuids, name);
}
ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
while (buffer.remaining() > 2) {
byte length = buffer.get();
if (length == 0)
break;
byte type = buffer.get();
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
while (length >= 2) {
uuids
.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
length -= 2;
}
break;
case 0x06: // Partial list of 128-bit UUIDs
case 0x07: // Complete list of 128-bit UUIDs
while (length >= 16) {
long lsb = buffer.getLong();
long msb = buffer.getLong();
uuids.add(new UUID(msb, lsb));
length -= 16;
}
break;
case 0x09:
byte[] nameBytes = new byte[length - 1];
buffer.get(nameBytes);
try {
name = new String(nameBytes, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
break;
default:
buffer.position(buffer.position() + length - 1);
break;
}
}
return new BleAdvertisedData(uuids, name);
}
public static List<RLHistoryItem> getRileyLinkHistory() {
return historyRileyLink;
}
public static RileyLinkTargetDevice getTargetDevice() {
return targetDevice;
}
public static void setTargetDevice(RileyLinkTargetDevice targetDevice) {
RileyLinkUtil.targetDevice = targetDevice;
}
public static void setRileyLinkSelectPreference(RileyLinkSelectPreference rileyLinkSelectPreference) {
RileyLinkUtil.rileyLinkSelectPreference = rileyLinkSelectPreference;
}
public static RileyLinkSelectPreference getRileyLinkSelectPreference() {
return rileyLinkSelectPreference;
}
public static Encoding4b6b getEncoding4b6b() {
return RileyLinkUtil.encoding4b6b;
}
public static void setFirmwareVersion(RileyLinkFirmwareVersion firmwareVersion) {
RileyLinkUtil.firmwareVersion = firmwareVersion;
}
public static RileyLinkFirmwareVersion getFirmwareVersion() {
return firmwareVersion;
}
}

View file

@ -0,0 +1,438 @@
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble;
import android.os.SystemClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.Reset;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SendAndListen;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetHardwareEncoding;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetPreamble;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.UpdateRegister;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RXFilterMode;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil;
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst;
import info.nightscout.androidaps.utils.SP;
/**
* Created by geoff on 5/26/16.
*/
public class RFSpy {
public static final long RILEYLINK_FREQ_XTAL = 24000000;
public static final int EXPECTED_MAX_BLUETOOTH_LATENCY_MS = 7500; // 1500
private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM);
public int notConnectedCount = 0;
private RileyLinkBLE rileyLinkBle;
private RFSpyReader reader;
private RileyLinkTargetFrequency selectedTargetFrequency;
private UUID radioServiceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO);
private UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
private UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION);
private UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT);
private RileyLinkFirmwareVersion firmwareVersion;
private String bleVersion; // We don't use it so no need of sofisticated logic
Double currentFrequencyMHz;
public RFSpy(RileyLinkBLE rileyLinkBle) {
this.rileyLinkBle = rileyLinkBle;
reader = new RFSpyReader(rileyLinkBle);
}
public RileyLinkFirmwareVersion getRLVersionCached() {
return firmwareVersion;
}
public String getBLEVersionCached() {
return bleVersion;
}
// Call this after the RL services are discovered.
// Starts an async task to read when data is available
public void startReader() {
rileyLinkBle.registerRadioResponseCountNotification(new Runnable() {
@Override
public void run() {
newDataIsAvailable();
}
});
reader.start();
}
// Here should go generic RL initialisation + protocol adjustments depending on
// firmware version
public void initializeRileyLink() {
bleVersion = getVersion();
firmwareVersion = getFirmwareVersion();
RileyLinkUtil.setFirmwareVersion(firmwareVersion);
}
// Call this from the "response count" notification handler.
public void newDataIsAvailable() {
// pass the message to the reader (which should be internal to RFSpy)
reader.newDataIsAvailable();
}
// This gets the version from the BLE113, not from the CC1110.
// I.e., this gets the version from the BLE interface, not from the radio.
public String getVersion() {
BLECommOperationResult result = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID, radioVersionUUID);
if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) {
String version = StringUtil.fromBytes(result.value);
if (isLogEnabled())
LOG.debug("BLE Version: " + version);
return version;
} else {
LOG.error("getVersion failed with code: " + result.resultCode);
return "(null)";
}
}
public RileyLinkFirmwareVersion getFirmwareVersion() {
if (isLogEnabled())
LOG.debug("Firmware Version. Get Version - Start");
for (int i = 0; i < 5; i++) {
// We have to call raw version of communication to get firmware version
// So that we can adjust other commands accordingly afterwords
byte[] getVersionRaw = getByteArray(RileyLinkCommandType.GetVersion.code);
byte[] response = writeToDataRaw(getVersionRaw, 5000);
if (isLogEnabled())
LOG.debug("Firmware Version. GetVersion [response={}]", ByteUtil.shortHexString(response));
if (response != null) { // && response[0] == (byte) 0xDD) {
String versionString = StringUtil.fromBytes(response);
RileyLinkFirmwareVersion version = RileyLinkFirmwareVersion.getByVersionString(StringUtil
.fromBytes(response));
if (isLogEnabled())
LOG.trace("Firmware Version string: {}, resolved to {}.", versionString, version);
if (version != RileyLinkFirmwareVersion.UnknownVersion)
return version;
SystemClock.sleep(1000);
}
}
LOG.error("Firmware Version can't be determined. Checking with BLE Version [{}].", bleVersion);
if (bleVersion.contains(" 2.")) {
return RileyLinkFirmwareVersion.Version_2_0;
}
return RileyLinkFirmwareVersion.UnknownVersion;
}
private byte[] writeToDataRaw(byte[] bytes, int responseTimeout_ms) {
SystemClock.sleep(100);
// FIXME drain read queue?
byte[] junkInBuffer = reader.poll(0);
while (junkInBuffer != null) {
LOG.warn(ThreadUtil.sig() + "writeToData: draining read queue, found this: "
+ ByteUtil.shortHexString(junkInBuffer));
junkInBuffer = reader.poll(0);
}
// prepend length, and send it.
byte[] prepended = ByteUtil.concat(new byte[]{(byte) (bytes.length)}, bytes);
LOG.debug("writeToData (raw={})", ByteUtil.shortHexString(prepended));
BLECommOperationResult writeCheck = rileyLinkBle.writeCharacteristic_blocking(radioServiceUUID, radioDataUUID,
prepended);
if (writeCheck.resultCode != BLECommOperationResult.RESULT_SUCCESS) {
LOG.error("BLE Write operation failed, code=" + writeCheck.resultCode);
return null; // will be a null (invalid) response
}
SystemClock.sleep(100);
// Log.i(TAG,ThreadUtil.sig()+String.format(" writeToData:(timeout %d) %s",(responseTimeout_ms),ByteUtil.shortHexString(prepended)));
byte[] rawResponse = reader.poll(responseTimeout_ms);
return rawResponse;
}
// The caller has to know how long the RFSpy will be busy with what was sent to it.
private RFSpyResponse writeToData(RileyLinkCommand command, int responseTimeout_ms) {
byte[] bytes = command.getRaw();
byte[] rawResponse = writeToDataRaw(bytes, responseTimeout_ms);
RFSpyResponse resp = new RFSpyResponse(command, rawResponse);
if (rawResponse == null) {
LOG.error("writeToData: No response from RileyLink");
notConnectedCount++;
} else {
if (resp.wasInterrupted()) {
LOG.error("writeToData: RileyLink was interrupted");
} else if (resp.wasTimeout()) {
LOG.error("writeToData: RileyLink reports timeout");
notConnectedCount++;
} else if (resp.isOK()) {
LOG.warn("writeToData: RileyLink reports OK");
resetNotConnectedCount();
} else {
if (resp.looksLikeRadioPacket()) {
// RadioResponse radioResp = resp.getRadioResponse();
// byte[] responsePayload = radioResp.getPayload();
if (isLogEnabled())
LOG.trace("writeToData: received radio response. Will decode at upper level");
resetNotConnectedCount();
}
// Log.i(TAG, "writeToData: raw response is " + ByteUtil.shortHexString(rawResponse));
}
}
return resp;
}
private void resetNotConnectedCount() {
this.notConnectedCount = 0;
}
private byte[] getByteArray(byte... input) {
return input;
}
private byte[] getCommandArray(RileyLinkCommandType command, byte[] body) {
int bodyLength = body == null ? 0 : body.length;
byte[] output = new byte[bodyLength + 1];
output[0] = command.code;
if (body != null) {
for (int i = 0; i < body.length; i++) {
output[i + 1] = body[i];
}
}
return output;
}
public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms,
byte listenChannel, int timeout_ms, byte retryCount) {
return transmitThenReceive(pkt, sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms, retryCount, null);
}
public RFSpyResponse transmitThenReceive(RadioPacket pkt, int timeout_ms) {
return transmitThenReceive(pkt, (byte) 0, (byte) 0, (byte) 0, (byte) 0, timeout_ms, (byte) 0);
}
public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms,
byte listenChannel, int timeout_ms, byte retryCount, Integer extendPreamble_ms) {
int sendDelay = repeatCount * delay_ms;
int receiveDelay = timeout_ms * (retryCount + 1);
SendAndListen command = new SendAndListen(sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms,
retryCount, extendPreamble_ms, pkt);
return writeToData(command, sendDelay + receiveDelay + EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
}
public RFSpyResponse updateRegister(CC111XRegister reg, int val) {
RFSpyResponse resp = writeToData(new UpdateRegister(reg, (byte) val), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
return resp;
}
public void setBaseFrequency(double freqMHz) {
int value = (int) (freqMHz * 1000000 / ((double) (RILEYLINK_FREQ_XTAL) / Math.pow(2.0, 16.0)));
updateRegister(CC111XRegister.freq0, (byte) (value & 0xff));
updateRegister(CC111XRegister.freq1, (byte) ((value >> 8) & 0xff));
updateRegister(CC111XRegister.freq2, (byte) ((value >> 16) & 0xff));
LOG.info("Set frequency to {} MHz", freqMHz);
this.currentFrequencyMHz = freqMHz;
configureRadioForRegion(RileyLinkUtil.getRileyLinkTargetFrequency());
}
private void configureRadioForRegion(RileyLinkTargetFrequency frequency) {
// we update registers only on first run, or if region changed
switch (frequency) {
case Medtronic_WorldWide: {
// updateRegister(CC111X_MDMCFG4, (byte) 0x59);
setRXFilterMode(RXFilterMode.Wide);
// updateRegister(CC111X_MDMCFG3, (byte) 0x66);
// updateRegister(CC111X_MDMCFG2, (byte) 0x33);
updateRegister(CC111XRegister.mdmcfg1, 0x62);
updateRegister(CC111XRegister.mdmcfg0, 0x1A);
updateRegister(CC111XRegister.deviatn, 0x13);
setMedtronicEncoding();
}
break;
case Medtronic_US: {
// updateRegister(CC111X_MDMCFG4, (byte) 0x99);
setRXFilterMode(RXFilterMode.Narrow);
// updateRegister(CC111X_MDMCFG3, (byte) 0x66);
// updateRegister(CC111X_MDMCFG2, (byte) 0x33);
updateRegister(CC111XRegister.mdmcfg1, 0x61);
updateRegister(CC111XRegister.mdmcfg0, 0x7E);
updateRegister(CC111XRegister.deviatn, 0x15);
setMedtronicEncoding();
}
break;
case Omnipod: {
RFSpyResponse r = null;
// RL initialization for Omnipod is a copy/paste from OmniKit implementation.
// Last commit from original repository: 5c3beb4144
// so if something is terribly wrong, please check git diff PodCommsSession.swift since that commit
r = updateRegister(CC111XRegister.pktctrl1, 0x20);
r = updateRegister(CC111XRegister.agcctrl0, 0x00);
r = updateRegister(CC111XRegister.fsctrl1, 0x06);
r = updateRegister(CC111XRegister.mdmcfg4, 0xCA);
r = updateRegister(CC111XRegister.mdmcfg3, 0xBC);
r = updateRegister(CC111XRegister.mdmcfg2, 0x06);
r = updateRegister(CC111XRegister.mdmcfg1, 0x70);
r = updateRegister(CC111XRegister.mdmcfg0, 0x11);
r = updateRegister(CC111XRegister.deviatn, 0x44);
r = updateRegister(CC111XRegister.mcsm0, 0x18);
r = updateRegister(CC111XRegister.foccfg, 0x17);
r = updateRegister(CC111XRegister.fscal3, 0xE9);
r = updateRegister(CC111XRegister.fscal2, 0x2A);
r = updateRegister(CC111XRegister.fscal1, 0x00);
r = updateRegister(CC111XRegister.fscal0, 0x1F);
r = updateRegister(CC111XRegister.test1, 0x31);
r = updateRegister(CC111XRegister.test0, 0x09);
r = updateRegister(CC111XRegister.paTable0, 0x84);
r = updateRegister(CC111XRegister.sync1, 0xA5);
r = updateRegister(CC111XRegister.sync0, 0x5A);
r = setRileyLinkEncoding(RileyLinkEncodingType.Manchester);
r = setPreamble(0x6665);
}
break;
default:
LOG.warn("No region configuration for RfSpy and {}", frequency.name());
break;
}
this.selectedTargetFrequency = frequency;
}
private void setMedtronicEncoding() {
RileyLinkEncodingType encoding = RileyLinkEncodingType.FourByteSixByteLocal;
if (RileyLinkFirmwareVersion.isSameVersion(this.firmwareVersion, RileyLinkFirmwareVersion.Version2AndHigher)) {
if (SP.getString(MedtronicConst.Prefs.Encoding, "None").equals(MainApp.gs(R.string.medtronic_pump_encoding_4b6b_rileylink))) {
encoding = RileyLinkEncodingType.FourByteSixByteRileyLink;
}
}
setRileyLinkEncoding(encoding);
if (isLogEnabled())
LOG.debug("Set Encoding for Medtronic: " + encoding.name());
}
private RFSpyResponse setPreamble(int preamble) {
RFSpyResponse resp = null;
try {
resp = writeToData(new SetPreamble(preamble), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
} catch (Exception e) {
e.toString();
}
return resp;
}
public RFSpyResponse setRileyLinkEncoding(RileyLinkEncodingType encoding) {
RFSpyResponse resp = writeToData(new SetHardwareEncoding(encoding), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
if (resp.isOK()) {
reader.setRileyLinkEncodingType(encoding);
RileyLinkUtil.setEncoding(encoding);
}
return resp;
}
private void setRXFilterMode(RXFilterMode mode) {
byte drate_e = (byte) 0x9; // exponent of symbol rate (16kbps)
byte chanbw = mode.value;
updateRegister(CC111XRegister.mdmcfg4, (byte) (chanbw | drate_e));
}
/**
* Reset RileyLink Configuration (set all updateRegisters)
*/
public void resetRileyLinkConfiguration() {
if (this.currentFrequencyMHz != null)
this.setBaseFrequency(this.currentFrequencyMHz);
}
public RFSpyResponse resetRileyLink() {
RFSpyResponse resp = null;
try {
resp = writeToData(new Reset(), EXPECTED_MAX_BLUETOOTH_LATENCY_MS);
if (isLogEnabled())
LOG.debug("Reset command send, response: {}", resp);
} catch (Exception e) {
e.toString();
}
return resp;
}
private boolean isLogEnabled() {
return L.isEnabled(L.PUMPBTCOMM);
}
}

Some files were not shown because too many files have changed in this diff Show more