Merge pull request #1870 from MilosKozak/tidepool

Tidepool
This commit is contained in:
Milos Kozak 2019-07-14 19:40:25 +02:00 committed by GitHub
commit f5640eec92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 2143 additions and 67 deletions

View file

@ -237,7 +237,11 @@ dependencies {
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0' implementation 'androidx.percentlayout:percentlayout:1.0.0'
implementation "com.wdullaer:materialdatetimepicker:2.3.0" implementation "com.wdullaer:materialdatetimepicker:2.3.0"
// Otto bus will be replaced by rx
implementation "com.squareup:otto:1.3.7" implementation "com.squareup:otto:1.3.7"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "com.j256.ormlite:ormlite-core:${ormLiteVersion}" implementation "com.j256.ormlite:ormlite-core:${ormLiteVersion}"
implementation "com.j256.ormlite:ormlite-android:${ormLiteVersion}" implementation "com.j256.ormlite:ormlite-android:${ormLiteVersion}"
implementation("com.github.tony19:logback-android-classic:1.1.1-6") { implementation("com.github.tony19:logback-android-classic:1.1.1-6") {
@ -286,6 +290,16 @@ dependencies {
androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}" androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}"
androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// new for tidepool
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation "com.squareup.retrofit2:retrofit:2.4.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.4.0"
implementation "com.squareup.retrofit2:converter-gson:2.4.0"
} }

View file

@ -4,6 +4,7 @@ import android.app.Application;
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 androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.PluralsRes; import androidx.annotation.PluralsRes;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@ -50,6 +51,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessRec
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin;
import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin; import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin;
import info.nightscout.androidaps.plugins.general.wear.WearPlugin; import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin;
@ -202,6 +204,8 @@ public class MainApp extends Application {
pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(StatuslinePlugin.initPlugin(this));
pluginsList.add(PersistentNotificationPlugin.getPlugin()); pluginsList.add(PersistentNotificationPlugin.getPlugin());
pluginsList.add(NSClientPlugin.getPlugin()); pluginsList.add(NSClientPlugin.getPlugin());
if (engineeringMode)
pluginsList.add(TidepoolPlugin.INSTANCE);
pluginsList.add(MaintenancePlugin.initPlugin(this)); pluginsList.add(MaintenancePlugin.initPlugin(this));
pluginsList.add(ConfigBuilderPlugin.getPlugin()); pluginsList.add(ConfigBuilderPlugin.getPlugin());

View file

@ -15,12 +15,15 @@ import android.text.TextUtils;
import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Config;
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.bus.RxBus;
import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPreferenceChange;
import info.nightscout.androidaps.events.EventRefreshGui; import info.nightscout.androidaps.events.EventRefreshGui;
import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PluginType;
import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin;
import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin;
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin;
import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader;
import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin;
@ -63,6 +66,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
MainApp.bus().post(new EventPreferenceChange(key)); MainApp.bus().post(new EventPreferenceChange(key));
RxBus.INSTANCE.send(new EventPreferenceChange(key));
if (key.equals("language")) { if (key.equals("language")) {
String lang = sharedPreferences.getString("language", "en"); String lang = sharedPreferences.getString("language", "en");
LocaleHelper.setLocale(getApplicationContext(), lang); LocaleHelper.setLocale(getApplicationContext(), lang);
@ -182,6 +186,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginType.INSULIN); addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginType.INSULIN);
addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL); addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(TidepoolPlugin.INSTANCE, PluginType.GENERAL);
addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL); addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL);
addPreferencesFromResource(R.xml.pref_others); addPreferencesFromResource(R.xml.pref_others);
@ -203,6 +208,13 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
} }
initSummary(getPreferenceScreen()); initSummary(getPreferenceScreen());
final Preference tidepoolTestLogin = findPreference(MainApp.gs(R.string.key_tidepool_test_login));
if (tidepoolTestLogin != null)
tidepoolTestLogin.setOnPreferenceClickListener(preference -> {
TidepoolUploader.INSTANCE.testLogin(getActivity());
return false;
});
} }
@Override @Override

View file

@ -19,7 +19,7 @@ public class NonOverlappingIntervals<T extends Interval> extends Intervals<T> {
rawData = other.rawData.clone(); rawData = other.rawData.clone();
} }
protected synchronized void merge() { public synchronized void merge() {
for (int index = 0; index < rawData.size() - 1; index++) { for (int index = 0; index < rawData.size() - 1; index++) {
Interval i = rawData.valueAt(index); Interval i = rawData.valueAt(index);
long startOfNewer = rawData.valueAt(index + 1).start(); long startOfNewer = rawData.valueAt(index + 1).start();

View file

@ -400,6 +400,19 @@ public class Profile {
return getValuesList(isf_v, null, new DecimalFormat("0.0"), getUnits() + MainApp.gs(R.string.profile_per_unit)); return getValuesList(isf_v, null, new DecimalFormat("0.0"), getUnits() + MainApp.gs(R.string.profile_per_unit));
} }
public ProfileValue[] getIsfs() {
if (isf_v == null)
isf_v = convertToSparseArray(ic);
ProfileValue[] ret = new ProfileValue[isf_v.size()];
for (Integer index = 0; index < isf_v.size(); index++) {
Integer tas = (int) isf_v.keyAt(index);
double value = isf_v.valueAt(index);
ret[index] = new ProfileValue(tas, value);
}
return ret;
}
public double getIc() { public double getIc() {
return getIcTimeFromMidnight(secondsFromMidnight()); return getIcTimeFromMidnight(secondsFromMidnight());
} }
@ -420,6 +433,19 @@ public class Profile {
return getValuesList(ic_v, null, new DecimalFormat("0.0"), MainApp.gs(R.string.profile_carbs_per_unit)); return getValuesList(ic_v, null, new DecimalFormat("0.0"), MainApp.gs(R.string.profile_carbs_per_unit));
} }
public ProfileValue[] getIcs() {
if (ic_v == null)
ic_v = convertToSparseArray(ic);
ProfileValue[] ret = new ProfileValue[ic_v.size()];
for (Integer index = 0; index < ic_v.size(); index++) {
Integer tas = (int) ic_v.keyAt(index);
double value = ic_v.valueAt(index);
ret[index] = new ProfileValue(tas, value);
}
return ret;
}
public double getBasal() { public double getBasal() {
return getBasalTimeFromMidnight(secondsFromMidnight()); return getBasalTimeFromMidnight(secondsFromMidnight());
} }
@ -441,8 +467,8 @@ public class Profile {
return getValuesList(basal_v, null, new DecimalFormat("0.00"), MainApp.gs(R.string.profile_ins_units_per_hout)); return getValuesList(basal_v, null, new DecimalFormat("0.00"), MainApp.gs(R.string.profile_ins_units_per_hout));
} }
public class BasalValue { public class ProfileValue {
public BasalValue(int timeAsSeconds, double value) { public ProfileValue(int timeAsSeconds, double value) {
this.timeAsSeconds = timeAsSeconds; this.timeAsSeconds = timeAsSeconds;
this.value = value; this.value = value;
} }
@ -451,15 +477,15 @@ public class Profile {
public double value; public double value;
} }
public synchronized BasalValue[] getBasalValues() { public synchronized ProfileValue[] getBasalValues() {
if (basal_v == null) if (basal_v == null)
basal_v = convertToSparseArray(basal); basal_v = convertToSparseArray(basal);
BasalValue[] ret = new BasalValue[basal_v.size()]; ProfileValue[] ret = new ProfileValue[basal_v.size()];
for (Integer index = 0; index < basal_v.size(); index++) { for (Integer index = 0; index < basal_v.size(); index++) {
Integer tas = (int) basal_v.keyAt(index); Integer tas = (int) basal_v.keyAt(index);
double value = basal_v.valueAt(index); double value = basal_v.valueAt(index);
ret[index] = new BasalValue(tas, value); ret[index] = new ProfileValue(tas, value);
} }
return ret; return ret;
} }
@ -500,6 +526,49 @@ public class Profile {
return getValueToTime(targetHigh_v, timeAsSeconds); return getValueToTime(targetHigh_v, timeAsSeconds);
} }
public class TargetValue {
public TargetValue(int timeAsSeconds, double low, double high) {
this.timeAsSeconds = timeAsSeconds;
this.low = low;
this.high = high;
}
public int timeAsSeconds;
public double low;
public double high;
}
public TargetValue[] getTargets() {
if (targetLow_v == null)
targetLow_v = convertToSparseArray(targetLow);
if (targetHigh_v == null)
targetHigh_v = convertToSparseArray(targetHigh);
TargetValue[] ret = new TargetValue[targetLow_v.size()];
for (Integer index = 0; index < targetLow_v.size(); index++) {
Integer tas = (int) targetLow_v.keyAt(index);
double low = targetLow_v.valueAt(index);
double high = targetHigh_v.valueAt(index);
ret[index] = new TargetValue(tas, low, high);
}
return ret;
}
public ProfileValue[] getSingleTargets() {
if (targetLow_v == null)
targetLow_v = convertToSparseArray(targetLow);
if (targetHigh_v == null)
targetHigh_v = convertToSparseArray(targetHigh);
ProfileValue[] ret = new ProfileValue[targetLow_v.size()];
for (Integer index = 0; index < targetLow_v.size(); index++) {
Integer tas = (int) targetLow_v.keyAt(index);
double target = (targetLow_v.valueAt(index) + targetHigh_v.valueAt(index)) / 2;
ret[index] = new ProfileValue(tas, target);
}
return ret;
}
public String getTargetList() { public String getTargetList() {
if (targetLow_v == null) if (targetLow_v == null)
targetLow_v = convertToSparseArray(targetLow); targetLow_v = convertToSparseArray(targetLow);

View file

@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.data.OverlappingIntervals; import info.nightscout.androidaps.data.OverlappingIntervals;
import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.data.ProfileStore;
@ -410,6 +411,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
if (L.isEnabled(L.DATABASE)) if (L.isEnabled(L.DATABASE))
log.debug("Firing EventNewBg"); log.debug("Firing EventNewBg");
MainApp.bus().post(new EventNewBG(bgReading)); MainApp.bus().post(new EventNewBG(bgReading));
RxBus.INSTANCE.send(new EventNewBG(bgReading));
scheduledBgPost = null; scheduledBgPost = null;
} }
} }
@ -596,7 +598,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
} }
} }
// -------------------- TREATMENT HANDLING ------------------- // -------------------- TEMPTARGET HANDLING -------------------
public static void updateEarliestDataChange(long newDate) { public static void updateEarliestDataChange(long newDate) {
if (earliestDataChange == null) { if (earliestDataChange == null) {
@ -627,6 +629,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList<TempTarget>(); return new ArrayList<TempTarget>();
} }
public List<TempTarget> getTemptargetsDataFromTime(long from, long to, boolean ascending) {
try {
Dao<TempTarget, Long> daoTempTargets = getDaoTempTargets();
List<TempTarget> tempTargets;
QueryBuilder<TempTarget, Long> queryBuilder = daoTempTargets.queryBuilder();
queryBuilder.orderBy("date", ascending);
Where where = queryBuilder.where();
where.between("date", from, to);
PreparedQuery<TempTarget> preparedQuery = queryBuilder.prepare();
tempTargets = daoTempTargets.query(preparedQuery);
return tempTargets;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return new ArrayList<TempTarget>();
}
public boolean createOrUpdate(TempTarget tempTarget) { public boolean createOrUpdate(TempTarget tempTarget) {
try { try {
TempTarget old; TempTarget old;
@ -950,6 +969,22 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList<TemporaryBasal>(); return new ArrayList<TemporaryBasal>();
} }
public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long from, long to, boolean ascending) {
try {
List<TemporaryBasal> tempbasals;
QueryBuilder<TemporaryBasal, Long> queryBuilder = getDaoTemporaryBasal().queryBuilder();
queryBuilder.orderBy("date", ascending);
Where where = queryBuilder.where();
where.between("date", from, to);
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
tempbasals = getDaoTemporaryBasal().query(preparedQuery);
return tempbasals;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return new ArrayList<TemporaryBasal>();
}
private static void scheduleTemporaryBasalChange() { private static void scheduleTemporaryBasalChange() {
class PostRunnable implements Runnable { class PostRunnable implements Runnable {
public void run() { public void run() {
@ -1354,6 +1389,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList<>(); return new ArrayList<>();
} }
public List<CareportalEvent> getCareportalEvents(long start, long end, boolean ascending) {
try {
List<CareportalEvent> careportalEvents;
QueryBuilder<CareportalEvent, Long> queryBuilder = getDaoCareportalEvents().queryBuilder();
queryBuilder.orderBy("date", ascending);
Where where = queryBuilder.where();
where.between("date", start, end);
PreparedQuery<CareportalEvent> preparedQuery = queryBuilder.prepare();
careportalEvents = getDaoCareportalEvents().query(preparedQuery);
preprocessOpenAPSOfflineEvents(careportalEvents);
return careportalEvents;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return new ArrayList<>();
}
public void preprocessOpenAPSOfflineEvents(List<CareportalEvent> list) { public void preprocessOpenAPSOfflineEvents(List<CareportalEvent> list) {
OverlappingIntervals offlineEvents = new OverlappingIntervals(); OverlappingIntervals offlineEvents = new OverlappingIntervals();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
@ -1507,6 +1559,24 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return new ArrayList<>(); return new ArrayList<>();
} }
public List<ProfileSwitch> getProfileSwitchEventsFromTime(long from, long to, boolean ascending) {
try {
Dao<ProfileSwitch, Long> daoProfileSwitch = getDaoProfileSwitch();
List<ProfileSwitch> profileSwitches;
QueryBuilder<ProfileSwitch, Long> queryBuilder = daoProfileSwitch.queryBuilder();
queryBuilder.orderBy("date", ascending);
queryBuilder.limit(100L);
Where where = queryBuilder.where();
where.between("date", from, to);
PreparedQuery<ProfileSwitch> preparedQuery = queryBuilder.prepare();
profileSwitches = daoProfileSwitch.query(preparedQuery);
return profileSwitches;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return new ArrayList<>();
}
public boolean createOrUpdate(ProfileSwitch profileSwitch) { public boolean createOrUpdate(ProfileSwitch profileSwitch) {
try { try {
ProfileSwitch old; ProfileSwitch old;

View file

@ -53,7 +53,9 @@ 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 deviceID(); String manufacter();
String model();
String serialNumber();
// Pump capabilities // Pump capabilities
PumpDescription getPumpDescription(); PumpDescription getPumpDescription();

View file

@ -5,6 +5,7 @@ import java.util.List;
import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.IobTotal;
import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.MealData;
import info.nightscout.androidaps.data.NonOverlappingIntervals;
import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ExtendedBolus;
import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.ProfileSwitch;
@ -42,7 +43,7 @@ public interface TreatmentsInterface {
// basal that can be faked by extended boluses // basal that can be faked by extended boluses
boolean isTempBasalInProgress(); boolean isTempBasalInProgress();
TemporaryBasal getTempBasalFromHistory(long time); TemporaryBasal getTempBasalFromHistory(long time);
Intervals<TemporaryBasal> getTemporaryBasalsFromHistory(); NonOverlappingIntervals<TemporaryBasal> getTemporaryBasalsFromHistory();
boolean isInHistoryExtendedBoluslInProgress(); boolean isInHistoryExtendedBoluslInProgress();
ExtendedBolus getExtendedBolusFromHistory(long time); ExtendedBolus getExtendedBolusFromHistory(long time);

View file

@ -87,6 +87,7 @@ public class L {
public static final String DATAFOOD = "DATAFOOD"; public static final String DATAFOOD = "DATAFOOD";
public static final String DATATREATMENTS = "DATATREATMENTS"; public static final String DATATREATMENTS = "DATATREATMENTS";
public static final String NSCLIENT = "NSCLIENT"; public static final String NSCLIENT = "NSCLIENT";
public static final String TIDEPOOL = "TIDEPOOL";
public static final String CONSTRAINTS = "CONSTRAINTS"; public static final String CONSTRAINTS = "CONSTRAINTS";
public static final String PUMP = "PUMP"; public static final String PUMP = "PUMP";
public static final String PUMPQUEUE = "PUMPQUEUE"; public static final String PUMPQUEUE = "PUMPQUEUE";
@ -114,6 +115,7 @@ public class L {
logElements.add(new LogElement(EVENTS, false, true)); logElements.add(new LogElement(EVENTS, false, 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(OVERVIEW, true)); logElements.add(new LogElement(OVERVIEW, true));
logElements.add(new LogElement(PROFILE, true)); logElements.add(new LogElement(PROFILE, true));
logElements.add(new LogElement(PUMP, true)); logElements.add(new LogElement(PUMP, true));

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.plugins.bus
import info.nightscout.androidaps.events.Event
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
// Use object so we have a singleton instance
object RxBus {
private val publisher = PublishSubject.create<Event>()
fun send(event: Event) {
publisher.onNext(event)
}
// Listen should return an Observable and not the publisher
// Using ofType we filter only events that match that class type
fun <T> toObservable(eventType: Class<T>): Observable<T> =
publisher
.subscribeOn(Schedulers.io())
.ofType(eventType)
}

View file

@ -232,26 +232,32 @@ public class ConfigBuilderPlugin extends PluginBase {
return commandQueue; return commandQueue;
} }
@Nullable
public BgSourceInterface getActiveBgSource() { public BgSourceInterface getActiveBgSource() {
return activeBgSource; return activeBgSource;
} }
@Nullable
public ProfileInterface getActiveProfileInterface() { public ProfileInterface getActiveProfileInterface() {
return activeProfile; return activeProfile;
} }
@Nullable
public InsulinInterface getActiveInsulin() { public InsulinInterface getActiveInsulin() {
return activeInsulin; return activeInsulin;
} }
@Nullable
public APSInterface getActiveAPS() { public APSInterface getActiveAPS() {
return activeAPS; return activeAPS;
} }
@Nullable
public PumpInterface getActivePump() { public PumpInterface getActivePump() {
return activePump; return activePump;
} }
@Nullable
public SensitivityInterface getActiveSensitivity() { public SensitivityInterface getActiveSensitivity() {
return activeSensitivity; return activeSensitivity;
} }

View file

@ -22,6 +22,7 @@ import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.Constants;
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.bus.RxBus;
import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventAppExit;
import info.nightscout.androidaps.events.EventChargingState; import info.nightscout.androidaps.events.EventChargingState;
import info.nightscout.androidaps.events.EventNetworkChange; import info.nightscout.androidaps.events.EventNetworkChange;
@ -213,6 +214,7 @@ public class NSClientPlugin extends PluginBase {
SP.putBoolean(R.string.key_nsclientinternal_paused, newState); SP.putBoolean(R.string.key_nsclientinternal_paused, newState);
paused = newState; paused = newState;
MainApp.bus().post(new EventPreferenceChange(R.string.key_nsclientinternal_paused)); MainApp.bus().post(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
RxBus.INSTANCE.send(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
} }
public UploadQueue queue() { public UploadQueue queue() {

View file

@ -10,6 +10,7 @@ import com.squareup.otto.Bus;
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.bus.RxBus;
import info.nightscout.androidaps.events.EventChargingState; import info.nightscout.androidaps.events.EventChargingState;
import info.nightscout.androidaps.events.EventNetworkChange; import info.nightscout.androidaps.events.EventNetworkChange;
import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPreferenceChange;
@ -99,6 +100,7 @@ class NsClientReceiverDelegate {
if (newAllowedState != allowed) { if (newAllowedState != allowed) {
allowed = newAllowedState; allowed = newAllowedState;
bus.post(new EventPreferenceChange(R.string.key_nsclientinternal_paused)); bus.post(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
RxBus.INSTANCE.send(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
} }
} }

View file

@ -0,0 +1,57 @@
package info.nightscout.androidaps.plugins.general.tidepool
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.fragment.app.Fragment
import info.nightscout.androidaps.R
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolDoUpload
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolResetData
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolUpdateGUI
import info.nightscout.androidaps.utils.SP
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.tidepool_fragment.*
class TidepoolFragment : Fragment() {
private var disposable: CompositeDisposable = CompositeDisposable()
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.tidepool_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tidepool_login.setOnClickListener { TidepoolUploader.doLogin(false) }
tidepool_uploadnow.setOnClickListener { RxBus.send(EventTidepoolDoUpload()) }
tidepool_removeall.setOnClickListener { RxBus.send(EventTidepoolResetData()) }
tidepool_resertstart.setOnClickListener { SP.putLong(R.string.key_tidepool_last_end, 0) }
disposable.add(RxBus
.toObservable(EventTidepoolUpdateGUI::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
TidepoolPlugin.updateLog()
tidepool_log.text = TidepoolPlugin.textLog
tidepool_status.text = TidepoolUploader.connectionStatus.name
tidepool_log.text = TidepoolPlugin.textLog
tidepool_logscrollview.fullScroll(ScrollView.FOCUS_DOWN)
}, {})
)
}
override fun onStop() {
super.onStop()
disposable.clear()
}
}

View file

@ -0,0 +1,147 @@
package info.nightscout.androidaps.plugins.general.tidepool
import android.text.Html
import android.text.Spanned
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R
import info.nightscout.androidaps.db.BgReading
import info.nightscout.androidaps.events.EventNetworkChange
import info.nightscout.androidaps.events.EventNewBG
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.tidepool.comm.TidepoolUploader
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolDoUpload
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolResetData
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolUpdateGUI
import info.nightscout.androidaps.plugins.general.tidepool.utils.RateLimit
import info.nightscout.androidaps.receivers.ChargingStateReceiver
import info.nightscout.androidaps.receivers.NetworkChangeReceiver
import info.nightscout.androidaps.utils.SP
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.ToastUtils
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.slf4j.LoggerFactory
import java.util.*
object TidepoolPlugin : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.pluginName(R.string.tidepool)
.shortName(R.string.tidepool_shortname)
.fragmentClass(TidepoolFragment::class.qualifiedName)
.preferencesId(R.xml.pref_tidepool)
.description(R.string.description_tidepool)
) {
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
private var disposable: CompositeDisposable = CompositeDisposable()
private val listLog = ArrayList<EventTidepoolStatus>()
@Suppress("DEPRECATION") // API level 24 to replace call
var textLog: Spanned = Html.fromHtml("")
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
override fun onStart() {
super.onStart()
disposable += RxBus
.toObservable(EventTidepoolDoUpload::class.java)
.observeOn(Schedulers.io())
.subscribe({ doUpload() }, {})
disposable += RxBus
.toObservable(EventTidepoolResetData::class.java)
.observeOn(Schedulers.io())
.subscribe({
if (TidepoolUploader.connectionStatus != TidepoolUploader.ConnectionStatus.CONNECTED) {
log.debug("Not connected for delete Dataset")
} else {
TidepoolUploader.deleteDataSet()
SP.putLong(R.string.key_tidepool_last_end, 0)
TidepoolUploader.doLogin()
}
}, {})
disposable += RxBus
.toObservable(EventTidepoolStatus::class.java)
.observeOn(Schedulers.io())
.subscribe({ event -> addToLog(event) }, {})
disposable += RxBus
.toObservable(EventNewBG::class.java)
.observeOn(Schedulers.io())
.filter { it.bgReading != null } // better would be optional in API level >24
.map { it.bgReading }
.subscribe { bgReading ->
if (bgReading!!.date < TidepoolUploader.getLastEnd())
TidepoolUploader.setLastEnd(bgReading.date)
if (isEnabled(PluginType.GENERAL)
&& (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging())
&& (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || NetworkChangeReceiver.isWifiConnected())
&& RateLimit.rateLimit("tidepool-new-data-upload", T.mins(4).secs().toInt()))
doUpload()
}
disposable += RxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ event ->
if (event.isChanged(R.string.key_tidepool_dev_servers)
|| event.isChanged(R.string.key_tidepool_username)
|| event.isChanged(R.string.key_tidepool_password)
)
TidepoolUploader.resetInstance()
}, {})
disposable += RxBus
.toObservable(EventNetworkChange::class.java)
.observeOn(Schedulers.io())
.subscribe({}, {}) // TODO start upload on wifi connect
}
override fun onStop() {
disposable.clear()
super.onStop()
}
private fun doUpload() {
if (TidepoolUploader.connectionStatus == TidepoolUploader.ConnectionStatus.DISCONNECTED)
TidepoolUploader.doLogin(true)
else
TidepoolUploader.doUpload()
}
@Synchronized
private fun addToLog(ev: EventTidepoolStatus) {
synchronized(listLog) {
listLog.add(ev)
// remove the first line if log is too large
if (listLog.size >= Constants.MAX_LOG_LINES) {
listLog.removeAt(0)
}
}
RxBus.send(EventTidepoolUpdateGUI())
}
@Synchronized
fun updateLog() {
try {
val newTextLog = StringBuilder()
synchronized(listLog) {
for (log in listLog) {
newTextLog.append(log.toPreparedHtml())
}
}
@Suppress("DEPRECATION") // API level 24 to replace call
textLog = Html.fromHtml(newTextLog.toString())
} catch (e: OutOfMemoryError) {
ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, "Out of memory!\nStop using this phone !!!", R.raw.error)
}
}
}

View file

@ -0,0 +1,32 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import info.nightscout.androidaps.logging.L
import okhttp3.Interceptor
import okhttp3.Response
import okio.Buffer
import org.slf4j.LoggerFactory
import java.io.IOException
class InfoInterceptor(tag: String) : Interceptor {
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
private var tag = "interceptor"
init {
this.tag = tag
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
request?.body()?.let {
if (L.isEnabled(L.TIDEPOOL)) {
log.debug("Interceptor Body size: " + it.contentLength())
val requestBuffer = Buffer()
it.writeTo(requestBuffer)
log.debug("Interceptor Body: " + requestBuffer.readUtf8())
}
}
return chain.proceed(request)
}
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthReplyMessage
import info.nightscout.androidaps.plugins.general.tidepool.messages.DatasetReplyMessage
import okhttp3.Headers
class Session(val authHeader: String?,
private val sessionTokenHeader: String,
val service: TidepoolApiService?) {
internal var token: String? = null
internal var authReply: AuthReplyMessage? = null
internal var datasetReply: DatasetReplyMessage? = null
internal var start: Long = 0
internal var end: Long = 0
@Volatile
internal var iterations: Int = 0
fun populateHeaders(headers: Headers) {
if (this.token == null) {
this.token = headers.get(sessionTokenHeader)
}
}
fun populateBody(obj: Any?) {
if (obj == null) return
if (obj is AuthReplyMessage) {
authReply = obj
} else if (obj is List<*>) {
val list = obj as? List<*>?
list?.getOrNull(0)?.let {
if (it is DatasetReplyMessage) {
datasetReply = it
}
}
} else if (obj is DatasetReplyMessage) {
datasetReply = obj
}
}
}

View file

@ -0,0 +1,48 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthReplyMessage
import info.nightscout.androidaps.plugins.general.tidepool.messages.DatasetReplyMessage
import info.nightscout.androidaps.plugins.general.tidepool.messages.UploadReplyMessage
import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.http.*
const val SESSION_TOKEN_HEADER: String = "x-tidepool-session-token"
interface TidepoolApiService {
@Headers(
"User-Agent: AAPS- " + BuildConfig.VERSION_NAME,
"X-Tidepool-Client-Name: info.nightscout.androidaps" + BuildConfig.APPLICATION_ID,
"X-Tidepool-Client-Version: 0.1.0"
)
@POST("/auth/login")
fun getLogin(@Header("Authorization") secret: String): Call<AuthReplyMessage>
@DELETE("/v1/users/{userId}/data")
fun deleteAllData(@Header(SESSION_TOKEN_HEADER) token: String, @Path("userId") id: String): Call<DatasetReplyMessage>
@DELETE("/v1/datasets/{dataSetId}")
fun deleteDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("dataSetId") id: String): Call<DatasetReplyMessage>
@GET("/v1/users/{userId}/data_sets")
fun getOpenDataSets(@Header(SESSION_TOKEN_HEADER) token: String,
@Path("userId") id: String,
@Query("client.name") clientName: String,
@Query("size") size: Int): Call<List<DatasetReplyMessage>>
@GET("/v1/datasets/{dataSetId}")
fun getDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("dataSetId") id: String): Call<DatasetReplyMessage>
@POST("/v1/users/{userId}/data_sets")
fun openDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("userId") id: String, @Body body: RequestBody): Call<DatasetReplyMessage>
@POST("/v1/datasets/{sessionId}/data")
fun doUpload(@Header(SESSION_TOKEN_HEADER) token: String, @Path("sessionId") id: String, @Body body: RequestBody): Call<UploadReplyMessage>
@PUT("/v1/datasets/{sessionId}")
fun closeDataSet(@Header(SESSION_TOKEN_HEADER) token: String, @Path("sessionId") id: String, @Body body: RequestBody): Call<DatasetReplyMessage>
}

View file

@ -0,0 +1,35 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus
import org.slf4j.LoggerFactory
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
internal class TidepoolCallback<T>(private val session: Session, val name: String, val onSuccess: () -> Unit, val onFail: () -> Unit) : Callback<T> {
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful && response.body() != null) {
if (L.isEnabled(L.TIDEPOOL)) log.debug("$name success")
session.populateBody(response.body())
session.populateHeaders(response.headers())
onSuccess()
} else {
val msg = name + " was not successful: " + response.code() + " " + response.message()
if (L.isEnabled(L.TIDEPOOL)) log.debug(msg)
RxBus.send(EventTidepoolStatus(msg))
onFail()
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
val msg = "$name Failed: $t"
if (L.isEnabled(L.TIDEPOOL)) log.debug(msg)
RxBus.send(EventTidepoolStatus(msg))
onFail()
}
}

View file

@ -0,0 +1,314 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import android.content.Context
import android.os.PowerManager
import android.os.SystemClock
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus
import info.nightscout.androidaps.plugins.general.tidepool.messages.*
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.OKDialog
import info.nightscout.androidaps.utils.SP
import info.nightscout.androidaps.utils.T
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.logging.HttpLoggingInterceptor
import org.slf4j.LoggerFactory
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object TidepoolUploader {
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
private var wl: PowerManager.WakeLock? = null
private const val INTEGRATION_BASE_URL = "https://int-api.tidepool.org"
private const val PRODUCTION_BASE_URL = "https://api.tidepool.org"
internal const val VERSION = "0.0.1"
private var retrofit: Retrofit? = null
private var session: Session? = null
enum class ConnectionStatus {
DISCONNECTED, CONNECTING, CONNECTED, FAILED
}
val PUMPTYPE = "Tandem"
var connectionStatus: ConnectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED
fun getRetrofitInstance(): Retrofit? {
if (retrofit == null) {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(InfoInterceptor(TidepoolUploader::class.java.name))
.build()
retrofit = Retrofit.Builder()
.baseUrl(if (SP.getBoolean(R.string.key_tidepool_dev_servers, false)) INTEGRATION_BASE_URL else PRODUCTION_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
fun createSession(): Session {
val service = getRetrofitInstance()?.create(TidepoolApiService::class.java)
return Session(AuthRequestMessage.getAuthRequestHeader(), SESSION_TOKEN_HEADER, service)
}
// TODO: call on preference change
fun resetInstance() {
retrofit = null
if (L.isEnabled(L.TIDEPOOL))
log.debug("Instance reset")
connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED
}
@Synchronized
fun doLogin(doUpload: Boolean = false) {
if (connectionStatus == TidepoolUploader.ConnectionStatus.CONNECTED || connectionStatus == TidepoolUploader.ConnectionStatus.CONNECTING) {
if (L.isEnabled(L.TIDEPOOL))
log.debug("Already connected")
return
}
// TODO failure backoff
extendWakeLock(30000)
session = createSession()
val authHeader = session?.authHeader
if (authHeader != null) {
connectionStatus = TidepoolUploader.ConnectionStatus.CONNECTING
RxBus.send(EventTidepoolStatus(("Connecting")))
val call = session?.service?.getLogin(authHeader)
call?.enqueue(TidepoolCallback<AuthReplyMessage>(session!!, "Login", {
startSession(session!!, doUpload)
}, {
connectionStatus = TidepoolUploader.ConnectionStatus.FAILED
loginFailed()
}))
return
} else {
if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot do login as user credentials have not been set correctly")
connectionStatus = TidepoolUploader.ConnectionStatus.FAILED
RxBus.send(EventTidepoolStatus(("Invalid credentials")))
releaseWakeLock()
return
}
}
fun testLogin(rootContext: Context) {
val session = createSession()
session.authHeader?.let {
val call = session.service?.getLogin(it)
call?.enqueue(TidepoolCallback<AuthReplyMessage>(session, "Login", {
OKDialog.show(rootContext, MainApp.gs(R.string.tidepool), "Successfully logged into Tidepool.", null)
}, {
OKDialog.show(rootContext, MainApp.gs(R.string.tidepool), "Failed to log into Tidepool.\nCheck that your user name and password are correct.", null)
}))
}
?: OKDialog.show(rootContext, MainApp.gs(R.string.tidepool), "Cannot do login as user credentials have not been set correctly", null)
}
private fun loginFailed() {
releaseWakeLock()
}
private fun startSession(session: Session, doUpload: Boolean = false) {
extendWakeLock(30000)
if (session.authReply?.userid != null) {
// See if we already have an open data set to write to
val datasetCall = session.service!!.getOpenDataSets(session.token!!,
session.authReply!!.userid!!, BuildConfig.APPLICATION_ID, 1)
datasetCall.enqueue(TidepoolCallback<List<DatasetReplyMessage>>(session, "Get Open Datasets", {
if (session.datasetReply == null) {
RxBus.send(EventTidepoolStatus(("Creating new dataset")))
val call = session.service.openDataSet(session.token!!, session.authReply!!.userid!!, OpenDatasetRequestMessage().getBody())
call.enqueue(TidepoolCallback<DatasetReplyMessage>(session, "Open New Dataset", {
connectionStatus = TidepoolUploader.ConnectionStatus.CONNECTED
RxBus.send(EventTidepoolStatus(("New dataset OK")))
if (doUpload) doUpload()
else
releaseWakeLock()
}, {
RxBus.send(EventTidepoolStatus(("New dataset FAILED")))
connectionStatus = TidepoolUploader.ConnectionStatus.FAILED
releaseWakeLock()
}))
} else {
if (L.isEnabled(L.TIDEPOOL))
log.debug("Existing Dataset: " + session.datasetReply!!.getUploadId())
// TODO: Wouldn't need to do this if we could block on the above `call.enqueue`.
// ie, do the openDataSet conditionally, and then do `doUpload` either way.
connectionStatus = TidepoolUploader.ConnectionStatus.CONNECTED
RxBus.send(EventTidepoolStatus(("Appending to existing dataset")))
if (doUpload) doUpload()
else
releaseWakeLock()
}
}, {
connectionStatus = TidepoolUploader.ConnectionStatus.FAILED
RxBus.send(EventTidepoolStatus(("Open dataset FAILED")))
releaseWakeLock()
}))
} else {
log.error("Got login response but cannot determine userId - cannot proceed")
connectionStatus = TidepoolUploader.ConnectionStatus.FAILED
RxBus.send(EventTidepoolStatus(("Error userId")))
releaseWakeLock()
}
}
@Synchronized
fun doUpload() {
session.let { session ->
if (session == null) {
log.error("Session is null, cannot proceed")
releaseWakeLock()
return
}
extendWakeLock(60000)
session.iterations++
val chunk = UploadChunk.getNext(session)
when {
chunk == null -> {
log.error("Upload chunk is null, cannot proceed")
releaseWakeLock()
}
chunk.length == 2 -> {
if (L.isEnabled(L.TIDEPOOL)) log.debug("Empty dataset - marking as succeeded")
RxBus.send(EventTidepoolStatus(("No data to upload")))
releaseWakeLock()
unploadNext()
}
else -> {
val body = RequestBody.create(MediaType.parse("application/json"), chunk)
RxBus.send(EventTidepoolStatus(("Uploading")))
val call = session.service!!.doUpload(session.token!!, session.datasetReply!!.getUploadId()!!, body)
call.enqueue(TidepoolCallback<UploadReplyMessage>(session, "Data Upload", {
setLastEnd(session.end)
RxBus.send(EventTidepoolStatus(("Upload completed OK")))
releaseWakeLock()
unploadNext()
}, {
RxBus.send(EventTidepoolStatus(("Upload FAILED")))
releaseWakeLock()
}))
}
}
}
}
private fun unploadNext() {
if (getLastEnd() < DateUtil.now() - T.mins(1).msecs()) {
SystemClock.sleep(3000)
if (L.isEnabled(L.TIDEPOOL))
log.debug("Restarting doUpload. Last: " + DateUtil.dateAndTimeString(getLastEnd()))
doUpload()
}
}
fun deleteDataSet() {
if (session?.datasetReply?.id != null) {
extendWakeLock(60000)
val call = session!!.service?.deleteDataSet(session!!.token!!, session!!.datasetReply!!.id!!)
call?.enqueue(TidepoolCallback(session!!, "Delete Dataset", {
connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED
RxBus.send(EventTidepoolStatus(("Dataset removed OK")))
releaseWakeLock()
}, {
connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED
RxBus.send(EventTidepoolStatus(("Dataset remove FAILED")))
releaseWakeLock()
}))
} else {
log.error("Got login response but cannot determine datasetId - cannot proceed")
}
}
fun deleteAllData() {
val session = this.session
val token = session?.token
val userid = session?.authReply?.userid
try {
requireNotNull(session)
requireNotNull(token)
requireNotNull(userid)
extendWakeLock(60000)
val call = session.service?.deleteAllData(token, userid)
call?.enqueue(TidepoolCallback(session, "Delete all data", {
connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED
RxBus.send(EventTidepoolStatus(("All data removed OK")))
releaseWakeLock()
}, {
connectionStatus = TidepoolUploader.ConnectionStatus.DISCONNECTED
RxBus.send(EventTidepoolStatus(("All data remove FAILED")))
releaseWakeLock()
}))
} catch (e: IllegalArgumentException) {
log.error("Got login response but cannot determine userId - cannot proceed")
}
}
fun getLastEnd(): Long {
val result = SP.getLong(R.string.key_tidepool_last_end, 0)
return Math.max(result, DateUtil.now() - T.months(2).msecs())
}
fun setLastEnd(time: Long) {
if (time > getLastEnd()) {
SP.putLong(R.string.key_tidepool_last_end, time)
val friendlyEnd = DateUtil.dateAndTimeString(time)
RxBus.send(EventTidepoolStatus(("Marking uploaded data up to $friendlyEnd")))
if (L.isEnabled(L.TIDEPOOL)) log.debug("Updating last end to: " + DateUtil.dateAndTimeString(time))
} else {
if (L.isEnabled(L.TIDEPOOL)) log.debug("Cannot set last end to: " + DateUtil.dateAndTimeString(time) + " vs " + DateUtil.dateAndTimeString(getLastEnd()))
}
}
@Synchronized
private fun extendWakeLock(ms: Long) {
if (wl == null) {
val pm = MainApp.instance().getSystemService(Context.POWER_SERVICE) as PowerManager
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:TidepoolUploader")
wl?.acquire(ms)
} else {
releaseWakeLock() // lets not get too messy
wl?.acquire(ms)
}
}
@Synchronized
private fun releaseWakeLock() {
wl?.let {
if (it.isHeld) {
try {
it.release()
} catch (e: Exception) {
log.error("Error releasing wakelock: $e")
}
}
}
}
}

View file

@ -0,0 +1,130 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.tidepool.elements.*
import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus
import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.SP
import info.nightscout.androidaps.utils.T
import org.slf4j.LoggerFactory
import java.util.*
object UploadChunk {
private val MAX_UPLOAD_SIZE = T.days(7).msecs() // don't change this
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
fun getNext(session: Session?): String? {
if (session == null)
return null
session.start = TidepoolUploader.getLastEnd()
session.end = Math.min(session.start + MAX_UPLOAD_SIZE, DateUtil.now())
val result = get(session.start, session.end)
if (result.length < 3) {
if (L.isEnabled(L.TIDEPOOL)) log.debug("No records in this time period, setting start to best end time")
TidepoolUploader.setLastEnd(Math.max(session.end, getOldestRecordTimeStamp()))
}
return result
}
operator fun get(start: Long, end: Long): String {
if (L.isEnabled(L.TIDEPOOL)) log.debug("Syncing data between: " + DateUtil.dateAndTimeString(start) + " -> " + DateUtil.dateAndTimeString(end))
if (end <= start) {
if (L.isEnabled(L.TIDEPOOL)) log.debug("End is <= start: " + DateUtil.dateAndTimeString(start) + " " + DateUtil.dateAndTimeString(end))
return ""
}
if (end - start > MAX_UPLOAD_SIZE) {
if (L.isEnabled(L.TIDEPOOL)) log.debug("More than max range - rejecting")
return ""
}
val records = LinkedList<BaseElement>()
if (SP.getBoolean(R.string.key_tidepool_upload_bolus, true))
records.addAll(getTreatments(start, end))
if (SP.getBoolean(R.string.key_tidepool_upload_bg, true))
records.addAll(getBloodTests(start, end))
if (SP.getBoolean(R.string.key_tidepool_upload_tbr, true))
records.addAll(getBasals(start, end))
if (SP.getBoolean(R.string.key_tidepool_upload_cgm, true))
records.addAll(getBgReadings(start, end))
if (SP.getBoolean(R.string.key_tidepool_upload_profile, true))
records.addAll(getProfiles(start, end))
return GsonInstance.defaultGsonInstance().toJson(records)
}
// numeric limits must match max time windows
private fun getOldestRecordTimeStamp(): Long {
// TODO we could make sure we include records older than the first bg record for completeness
val start: Long = 0
val end = DateUtil.now()
val bgReadingList = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)
return if (bgReadingList.size > 0)
bgReadingList[0].date
else -1
}
private fun getTreatments(start: Long, end: Long): List<BaseElement> {
val result = LinkedList<BaseElement>()
val treatments = TreatmentsPlugin.getPlugin().service.getTreatmentDataFromTime(start, end, true)
for (treatment in treatments) {
if (treatment.carbs > 0) {
result.add(WizardElement(treatment))
} else if (treatment.insulin > 0) {
result.add(BolusElement(treatment))
}
}
return result
}
private fun getBloodTests(start: Long, end: Long): List<BloodGlucoseElement> {
val readings = MainApp.getDbHelper().getCareportalEvents(start, end, true)
val selection = BloodGlucoseElement.fromCareportalEvents(readings)
if (selection.isNotEmpty())
RxBus.send(EventTidepoolStatus("${selection.size} BGs selected for upload"))
return selection
}
internal fun getBgReadings(start: Long, end: Long): List<SensorGlucoseElement> {
val readings = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)
val selection = SensorGlucoseElement.fromBgReadings(readings)
if (selection.isNotEmpty())
RxBus.send(EventTidepoolStatus("${selection.size} CGMs selected for upload"))
return selection
}
private fun getBasals(start: Long, end: Long): List<BasalElement> {
val tbrs = TreatmentsPlugin.getPlugin().temporaryBasalsFromHistory
tbrs.merge()
val selection = BasalElement.fromTemporaryBasals(tbrs, start, end) // TODO do not upload running TBR
if (selection.isNotEmpty())
RxBus.send(EventTidepoolStatus("${selection.size} TBRs selected for upload"))
return selection
}
private fun getProfiles(start: Long, end: Long): List<ProfileElement> {
val pss = MainApp.getDbHelper().getProfileSwitchEventsFromTime(start, end, true)
val selection = LinkedList<ProfileElement>()
for (ps in pss) {
ProfileElement.newInstanceOrNull(ps)?.let { selection.add(it) }
}
if (selection.size > 0)
RxBus.send(EventTidepoolStatus("${selection.size} ProfileSwitches selected for upload"))
return selection
}
}

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.data.Intervals
import info.nightscout.androidaps.db.TemporaryBasal
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions
import java.util.*
class BasalElement(tbr: TemporaryBasal)
: BaseElement(tbr.date, UUID.nameUUIDFromBytes(("AAPS-basal" + tbr.date).toByteArray()).toString()) {
internal var timestamp: Long = 0 // not exposed
@Expose
internal var deliveryType = "automated"
@Expose
internal var duration: Long = 0
@Expose
internal var rate = -1.0
@Expose
internal var scheduleName = "AAPS"
@Expose
internal var clockDriftOffset: Long = 0
@Expose
internal var conversionOffset: Long = 0
init {
type = "basal"
timestamp = tbr.date
rate = tbr.tempBasalConvertedToAbsolute(tbr.date, ProfileFunctions.getInstance().getProfile(tbr.date))
duration = tbr.end() - tbr.start()
}
companion object {
internal fun fromTemporaryBasals(tbrList: Intervals<TemporaryBasal>, start: Long, end: Long): List<BasalElement> {
val results = LinkedList<BasalElement>()
for (tbr in tbrList.list) {
if (tbr.date >= start && tbr.date <= end && tbr.durationInMinutes != 0)
results.add(BasalElement(tbr))
}
return results
}
}
}

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.utils.DateUtil
open class BaseElement(timestamp: Long, uuid: String) {
@Expose
var deviceTime: String = ""
@Expose
var time: String = ""
@Expose
var timezoneOffset: Int = 0
@Expose
var type: String? = null
@Expose
var origin: Origin? = null
init {
deviceTime = DateUtil.toISONoZone(timestamp)
time = DateUtil.toISOAsUTC(timestamp)
timezoneOffset = DateUtil.getTimeZoneOffsetMinutes(timestamp) // TODO
origin = Origin(uuid)
}
inner class Origin internal constructor(@field:Expose
internal var id: String)
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.data.Profile
import info.nightscout.androidaps.db.CareportalEvent
import info.nightscout.androidaps.utils.JsonHelper
import org.json.JSONObject
import java.util.*
class BloodGlucoseElement(careportalEvent: CareportalEvent)
: BaseElement(careportalEvent.date, UUID.nameUUIDFromBytes(("AAPS-bg" + careportalEvent.date).toByteArray()).toString()) {
@Expose
var subType: String = "manual"
@Expose
var units: String = "mg/dL"
@Expose
var value: Int = 0
init {
type = "cbg"
subType = "manual" // TODO
val json = if (careportalEvent.json != null) JSONObject(careportalEvent.json) else JSONObject()
value = Profile.toMgdl(JsonHelper.safeGetDouble(json, "glucose"), JsonHelper.safeGetString(json, "units", Constants.MGDL)).toInt()
}
companion object {
fun fromCareportalEvents(careportalList: List<CareportalEvent>): List<BloodGlucoseElement> {
val results = LinkedList<BloodGlucoseElement>()
for (bt in careportalList) {
if (bt.eventType == CareportalEvent.MBG || bt.eventType == CareportalEvent.BGCHECK) {
val bge = BloodGlucoseElement(bt)
if (bge.value > 0)
results.add(BloodGlucoseElement(bt))
}
}
return results
}
}
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.plugins.treatments.Treatment
import java.util.*
class BolusElement(treatment: Treatment)
: BaseElement(treatment.date, UUID.nameUUIDFromBytes(("AAPS-bolus" + treatment.date).toByteArray()).toString()) {
@Expose
var subType = "normal"
@Expose
var normal: Double = 0.0
@Expose
var expectedNormal: Double = 0.0
init {
type = "bolus"
normal = treatment.insulin
expectedNormal = treatment.insulin
}
}

View file

@ -0,0 +1,111 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.data.Profile
import info.nightscout.androidaps.db.ProfileSwitch
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader
import info.nightscout.androidaps.utils.InstanceId
import java.util.*
import kotlin.collections.ArrayList
class ProfileElement private constructor(ps: ProfileSwitch)
: BaseElement(ps.date, UUID.nameUUIDFromBytes(("AAPS-profile" + ps.date).toByteArray()).toString()) {
@Expose
internal var activeSchedule = "Normal"
@Expose
internal var basalSchedules: BasalProfile = BasalProfile()
@Expose
internal var units: Units = Units()
@Expose
internal var bgTargets: TargetProfile = TargetProfile()
@Expose
internal var carbRatios: IcProfile = IcProfile()
@Expose
internal var insulinSensitivities: IsfProfile = IsfProfile()
@Expose
internal var deviceId: String = TidepoolUploader.PUMPTYPE + ":" + (ConfigBuilderPlugin.getPlugin().activePump?.serialNumber()
?: InstanceId.instanceId())
@Expose
internal var deviceSerialNumber: String = ConfigBuilderPlugin.getPlugin().activePump?.serialNumber()
?: InstanceId.instanceId()
@Expose
internal var clockDriftOffset: Long = 0
@Expose
internal var conversionOffset: Long = 0
init {
type = "pumpSettings"
val profile: Profile? = ps.profileObject
checkNotNull(profile)
for (br in profile.basalValues)
basalSchedules.Normal.add(BasalRate(br.timeAsSeconds * 1000, br.value))
for (target in profile.singleTargets)
bgTargets.Normal.add(Target(target.timeAsSeconds * 1000, Profile.toMgdl(target.value, profile.units)))
for (ic in profile.ics)
carbRatios.Normal.add(Ratio(ic.timeAsSeconds * 1000, ic.value))
for (isf in profile.isfs)
insulinSensitivities.Normal.add(Ratio(isf.timeAsSeconds * 1000, Profile.toMgdl(isf.value, profile.units)))
}
inner class BasalProfile internal constructor(
@field:Expose
internal var Normal: ArrayList<BasalRate> = ArrayList() // must be the same var name as activeSchedule
)
inner class BasalRate internal constructor(
@field:Expose
internal var start: Int,
@field:Expose
internal var rate: Double
)
inner class Units internal constructor(
@field:Expose
internal var carb: String = "grams",
@field:Expose
internal var bg: String = "mg/dL"
)
inner class TargetProfile internal constructor(
@field:Expose
internal var Normal: ArrayList<Target> = ArrayList() // must be the same var name as activeSchedule
)
inner class Target internal constructor(
@field:Expose
internal var start: Int,
@field:Expose
internal var target: Double
)
inner class IcProfile internal constructor(
@field:Expose
internal var Normal: ArrayList<Ratio> = ArrayList() // must be the same var name as activeSchedule
)
inner class IsfProfile internal constructor(
@field:Expose
internal var Normal: ArrayList<Ratio> = ArrayList() // must be the same var name as activeSchedule
)
inner class Ratio internal constructor(
@field:Expose
internal var start: Int,
@field:Expose
internal var amount: Double
)
companion object {
@JvmStatic
fun newInstanceOrNull(ps: ProfileSwitch): ProfileElement? = try {
ProfileElement(ps)
} catch (e: Throwable) {
null
}
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.db.BgReading
import java.util.*
class SensorGlucoseElement(bgReading: BgReading)
: BaseElement(bgReading.date, UUID.nameUUIDFromBytes(("AAPS-cgm" + bgReading.date).toByteArray()).toString()) {
@Expose
internal var units: String = "mg/dL"
@Expose
internal var value: Int = 0
init {
this.type = "cbg"
value = bgReading.value.toInt()
}
companion object {
internal fun fromBgReadings(bgReadingList: List<BgReading>): List<SensorGlucoseElement> {
val results = LinkedList<SensorGlucoseElement>()
for (bgReading in bgReadingList) {
results.add(SensorGlucoseElement(bgReading))
}
return results
}
}
}

View file

@ -0,0 +1,68 @@
package info.nightscout.androidaps.plugins.general.tidepool.elements
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.plugins.treatments.Treatment
import java.util.*
class WizardElement(treatment: Treatment)
: BaseElement(treatment.date, UUID.nameUUIDFromBytes(("AAPS-wizard" + treatment.date).toByteArray()).toString()) {
@Expose
var units = "mg/dL"
@Expose
var carbInput: Double = 0.toDouble()
@Expose
var insulinCarbRatio: Double = 0.toDouble()
@Expose
var bolus: BolusElement? = null
init {
type = "wizard"
carbInput = treatment.carbs
insulinCarbRatio = treatment.ic
if (treatment.insulin > 0) {
bolus = BolusElement(treatment)
} else {
val fake = Treatment()
fake.insulin = 0.0001
fake.date = treatment.date
bolus = BolusElement(fake) // fake insulin record
}
}
}
/* TODO fill the rest
{
"type": "wizard",
"bgInput": 16.152676653942503,
"bgTarget": {
"low": 3.6079861941795968,
"high": 6.938434988806917
},
"bolus": "22239d4d592b48ae920b28971cceb48b",
"carbInput": 57,
"insulinCarbRatio": 24,
"insulinOnBoard": 24.265,
"insulinSensitivity": 4.329583433015516,
"recommended": {
"carb": 2.5,
"correction": 2.25,
"net": 0
},
"units": "mmol/L",
"_active": true,
"_groupId": "abcdef",
"_schemaVersion": 0,
"_version": 0,
"clockDriftOffset": 0,
"conversionOffset": 0,
"createdTime": "2018-05-14T08:17:14.353Z",
"deviceId": "DevId0987654321",
"deviceTime": "2018-05-14T18:17:09",
"guid": "18d90ea0-5915-4e95-a8b2-cb22819ce696",
"id": "087c94ccdae84eb5a76b8205a244ec6b",
"time": "2018-05-14T08:17:09.353Z",
"timezoneOffset": 600,
"uploadId": "SampleUploadId"
}
*/

View file

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

View file

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

View file

@ -0,0 +1,32 @@
package info.nightscout.androidaps.plugins.general.tidepool.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.LocaleHelper
import org.slf4j.LoggerFactory
import java.text.SimpleDateFormat
class EventTidepoolStatus(val status: String) : Event() {
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
var date: Long = DateUtil.now()
init {
if (L.isEnabled(L.TIDEPOOL))
log.debug("New status: $status")
}
private var timeFormat = SimpleDateFormat("HH:mm:ss", LocaleHelper.getLocale())
fun toPreparedHtml(): StringBuilder {
val stringBuilder = StringBuilder()
stringBuilder.append(timeFormat.format(date))
stringBuilder.append(" <b>")
stringBuilder.append(status)
stringBuilder.append("</b> ")
stringBuilder.append("<br>")
return stringBuilder
}
}

View file

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

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
class AuthReplyMessage {
@Expose
@SerializedName("emailVerified")
internal var emailVerified: Boolean? = null
@Expose
@SerializedName("emails")
internal var emailList: List<String>? = null
@Expose
@SerializedName("termsAccepted")
internal var termsDate: String? = null
@Expose
@SerializedName("userid")
internal var userid: String? = null
@Expose
@SerializedName("username")
internal var username: String? = null
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.SP
import info.nightscout.androidaps.utils.StringUtils
import okhttp3.Credentials
class AuthRequestMessage : BaseMessage() {
companion object {
fun getAuthRequestHeader(): String? {
val username = SP.getString(R.string.key_tidepool_username, null)
val password = SP.getString(R.string.key_tidepool_password, null)
return if (StringUtils.emptyString(username) || StringUtils.emptyString(password)) null else Credentials.basic(username.trim { it <= ' ' }, password)
}
}
}

View file

@ -0,0 +1,16 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
import info.nightscout.androidaps.plugins.general.tidepool.utils.GsonInstance
import okhttp3.MediaType
import okhttp3.RequestBody
open class BaseMessage {
private fun toS(): String {
return GsonInstance.defaultGsonInstance().toJson(this) ?: "null"
}
fun getBody(): RequestBody {
return RequestBody.create(MediaType.parse("application/json"), this.toS())
}
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
import com.google.gson.annotations.Expose
class CloseDatasetRequestMessage : BaseMessage() {
@Expose
internal var dataState = "closed"
}

View file

@ -0,0 +1,42 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
class DatasetReplyMessage {
internal var data: Data? = null
// openDataSet and others return this in the root of the json reply it seems
internal var id: String? = null
internal var uploadId: String? = null
inner class Data {
internal var createdTime: String? = null
internal var deviceId: String? = null
internal var id: String? = null
internal var time: String? = null
internal var timezone: String? = null
internal var timezoneOffset: Int = 0
internal var type: String? = null
internal var uploadId: String? = null
internal var client: Client? = null
internal var computerTime: String? = null
internal var dataSetType: String? = null
internal var deviceManufacturers: List<String>? = null
internal var deviceModel: String? = null
internal var deviceSerialNumber: String? = null
internal var deviceTags: List<String>? = null
internal var timeProcessing: String? = null
internal var version: String? = null
// meta
}
inner class Client {
internal var name: String? = null
internal var version: String? = null
}
fun getUploadId(): String? {
return data?.uploadId ?: uploadId
}
}

View file

@ -0,0 +1,57 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
import com.google.gson.annotations.Expose
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.general.tidepool.comm.TidepoolUploader
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.InstanceId
import info.nightscout.androidaps.utils.T
import java.util.*
class OpenDatasetRequestMessage : BaseMessage() {
@Expose
var deviceId: String = TidepoolUploader.PUMPTYPE + ":" + (ConfigBuilderPlugin.getPlugin().activePump?.serialNumber()
?: InstanceId.instanceId())
@Expose
var time = DateUtil.toISOAsUTC(DateUtil.now())
@Expose
var timezoneOffset = (DateUtil.getTimeZoneOffsetMs() / T.mins(1).msecs()).toInt()
@Expose
var type = "upload"
//public String byUser;
@Expose
var client = ClientInfo()
@Expose
var computerTime = DateUtil.toISONoZone(DateUtil.now())
@Expose
var dataSetType = "continuous"
@Expose
var deviceManufacturers = arrayOf(TidepoolUploader.PUMPTYPE)
@Expose
var deviceModel = TidepoolUploader.PUMPTYPE
@Expose
var deviceTags = arrayOf("bgm", "cgm", "insulin-pump")
@Expose
var deduplicator = Deduplicator()
@Expose
var timeProcessing = "none"
@Expose
var timezone = TimeZone.getDefault().id
@Expose
var version = BuildConfig.VERSION_NAME
inner class ClientInfo {
@Expose
val name = BuildConfig.APPLICATION_ID
@Expose
val version = TidepoolUploader.VERSION
}
inner class Deduplicator {
@Expose
val name = "org.tidepool.deduplicator.dataset.delete.origin"
}
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.general.tidepool.messages
class UploadReplyMessage {
internal var data: List<String>? = null
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.general.tidepool.utils
import com.google.gson.Gson
import com.google.gson.GsonBuilder
object GsonInstance {
private var gson_instance: Gson? = null
fun defaultGsonInstance(): Gson {
if (gson_instance == null) {
gson_instance = GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create()
}
return gson_instance as Gson
}
}

View file

@ -0,0 +1,31 @@
package info.nightscout.androidaps.plugins.general.tidepool.utils
import info.nightscout.androidaps.logging.L
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.T
import org.slf4j.LoggerFactory
import java.util.*
object RateLimit {
private val rateLimits = HashMap<String, Long>()
private val log = LoggerFactory.getLogger(L.TIDEPOOL)
// return true if below rate limit
@Synchronized
fun rateLimit(name: String, seconds: Int): Boolean {
// check if over limit
rateLimits[name]?.let {
if (DateUtil.now() - it < T.secs(seconds.toLong()).msecs()) {
if (L.isEnabled(L.TIDEPOOL))
log.debug("$name rate limited: $seconds seconds")
return false
}
}
// not over limit
rateLimits[name] = DateUtil.now()
return true
}
}

View file

@ -65,6 +65,7 @@ import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.InstanceId;
import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SP;
/** /**
* Created by mike on 05.08.2016. * Created by mike on 05.08.2016.
@ -1312,10 +1313,20 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
} }
@Override @Override
public String deviceID() { public String manufacter() {
return "Roche";
}
@Override
public String model() {
return "Combo"; return "Combo";
} }
@Override
public String serialNumber() {
return InstanceId.INSTANCE.instanceId(); // TODO replace by real serial
}
@Override @Override
public PumpDescription getPumpDescription() { public PumpDescription getPumpDescription() {
return pumpDescription; return pumpDescription;

View file

@ -12,13 +12,13 @@ import info.nightscout.androidaps.utils.Round;
/** /**
* Created by andy on 02/05/2018. * Created by andy on 02/05/2018.
* <p> *
* Most of this defintions is intended for VirtualPump only, but they can be used by other plugins. * Most of this defintions is intended for VirtualPump only, but they can be used by other plugins.
*/ */
public enum PumpType { public enum PumpType {
GenericAAPS("Generic AAPS", 0.1d, null, // GenericAAPS("Generic AAPS", "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 +26,7 @@ public enum PumpType {
// Cellnovo // Cellnovo
Cellnovo1("Cellnovo", 0.05d, null, // Cellnovo1("Cellnovo", "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,105 +34,107 @@ public enum PumpType {
// Accu-Chek // Accu-Chek
AccuChekCombo("Accu-Chek Combo", 0.1d, null, // AccuChekCombo("Accu-Chek Combo", "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", 0.1d, null, // AccuChekSpirit("Accu-Chek Spirit", "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", 0.05d, DoseStepSize.InsightBolus, // AccuChekInsight("Accu-Chek Insight", "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", 0.01d, null, // AccuChekInsightBluetooth("Accu-Chek Insight", "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", 0.05d, null, // AnimasBolus? AnimasVibe("Animas Vibe","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, //
0.025d, 5d, 0d, null, PumpCapability.VirtualPumpCapabilities), // 0.025d, 5d, 0d, null, PumpCapability.VirtualPumpCapabilities), //
AnimasPing("Animas Ping", AnimasVibe), AnimasPing("Animas Ping", "Ping", AnimasVibe),
// Dana // Dana
DanaR("DanaR", 0.05d, null, // DanaR("DanaR", "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", 0.05d, null, // DanaRKorean("DanaR Korean", "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", 0.05d, null, // DanaRS("DanaRS", "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, //
0.04d, 0.01d, null, PumpCapability.DanaWithHistoryCapabilities), 0.04d, 0.01d, null, PumpCapability.DanaWithHistoryCapabilities),
DanaRv2("DanaRv2", DanaRS), DanaRv2("DanaRv2", "DanaRv2", DanaRS),
// Insulet // Insulet
Insulet_Omnipod("Insulet Omnipod", 0.05d, null, // Insulet_Omnipod("Insulet Omnipod", "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", 0.05d, null, // Medtronic_512_712("Medtronic 512/712", "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.VirtualPumpCapabilities), // TODO
Medtronic_515_715("Medtronic 515/715", Medtronic_512_712), Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712),
Medtronic_522_722("Medtronic 522/722", Medtronic_512_712), Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712),
Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", 0.05d, null, // Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", "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.VirtualPumpCapabilities), //
Medtronic_554_754_Veo("Medtronic 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", 0.025d, null, // Medtronic_640G("Medtronic 640G", "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", 0.01d, null, // TandemTSlim("Tandem t:slim", "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, //
0.1d, 0.001d, null, PumpCapability.VirtualPumpCapabilities), 0.1d, 0.001d, null, PumpCapability.VirtualPumpCapabilities),
TandemTFlex("Tandem t:flex", TandemTSlim), // TandemTFlex("Tandem t:flex", "t:flex", TandemTSlim), //
TandemTSlimG4("Tandem t:slim G4", TandemTSlim), // TandemTSlimG4("Tandem t:slim G4", "t:slim G4", TandemTSlim), //
TandemTSlimX2("Tandem t:slim X2", TandemTSlim), // TandemTSlimX2("Tandem t:slim X2", "t:slim X2", TandemTSlim), //
; ;
private String description; private String description;
private String manufacter;
private String model;
private double bolusSize; private double bolusSize;
private DoseStepSize specialBolusSize; private DoseStepSize specialBolusSize;
private DoseSettings extendedBolusSettings; private DoseSettings extendedBolusSettings;
@ -157,29 +159,36 @@ public enum PumpType {
} }
PumpType(String description, PumpType parent) { PumpType(String description, String model, PumpType parent)
{
this.description = description; this.description = description;
this.parent = parent; this.parent = parent;
parent.model = model;
} }
PumpType(String description, PumpType parent, PumpCapability pumpCapability) { PumpType(String description, String model, PumpType parent, PumpCapability pumpCapability)
{
this.description = description; this.description = description;
this.parent = parent; this.parent = parent;
this.pumpCapability = pumpCapability; this.pumpCapability = pumpCapability;
parent.model = model;
} }
PumpType(String description, double bolusSize, DoseStepSize specialBolusSize, // PumpType(String description, String manufacter, 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, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability); {
this(description, manufacter, model, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability);
} }
PumpType(String description, double bolusSize, DoseStepSize specialBolusSize, // PumpType(String description, String manufacter, 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.model = model;
this.bolusSize = bolusSize; this.bolusSize = bolusSize;
this.specialBolusSize = specialBolusSize; this.specialBolusSize = specialBolusSize;
this.extendedBolusSettings = extendedBolusSettings; this.extendedBolusSettings = extendedBolusSettings;
@ -198,6 +207,14 @@ public enum PumpType {
return description; return description;
} }
public String getManufacter() {
return isParentSet() ? parent.manufacter : manufacter;
}
public String getModel() {
return isParentSet() ? parent.model : model;
}
public PumpCapability getPumpCapability() { public PumpCapability getPumpCapability() {
if (isParentSet()) if (isParentSet())

View file

@ -376,7 +376,12 @@ public abstract class AbstractDanaRPlugin extends PluginBase implements PumpInte
} }
@Override @Override
public String deviceID() { public String manufacter() {
return "SOOIL";
};
@Override
public String serialNumber() {
return DanaRPump.getInstance().serialNumber; return DanaRPump.getInstance().serialNumber;
} }

View file

@ -360,6 +360,11 @@ public class DanaRPlugin extends AbstractDanaRPlugin {
return result; return result;
} }
@Override
public String model() {
return "DanaR";
}
private PumpEnactResult cancelRealTempBasal() { private PumpEnactResult cancelRealTempBasal() {
PumpEnactResult result = new PumpEnactResult(); PumpEnactResult result = new PumpEnactResult();
TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());

View file

@ -364,6 +364,11 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin {
return result; return result;
} }
@Override
public String model() {
return "DanaRKorean";
}
private PumpEnactResult cancelRealTempBasal() { private PumpEnactResult cancelRealTempBasal() {
PumpEnactResult result = new PumpEnactResult(); PumpEnactResult result = new PumpEnactResult();
TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());

View file

@ -769,7 +769,17 @@ public class DanaRSPlugin extends PluginBase implements PumpInterface, DanaRInte
} }
@Override @Override
public String deviceID() { public String manufacter() {
return "SOOIL";
}
@Override
public String model() {
return "DanaRS";
}
@Override
public String serialNumber() {
return DanaRPump.getInstance().serialNumber; return DanaRPump.getInstance().serialNumber;
} }

View file

@ -402,6 +402,11 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin {
} }
} }
@Override
public String model() {
return "DanaRv2";
}
@Override @Override
public PumpEnactResult loadEvents() { public PumpEnactResult loadEvents() {
return sExecutionService.loadEvents(); return sExecutionService.loadEvents();

View file

@ -417,8 +417,8 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con
MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED));
List<BasalProfileBlock> profileBlocks = new ArrayList<>(); List<BasalProfileBlock> profileBlocks = new ArrayList<>();
for (int i = 0; i < profile.getBasalValues().length; i++) { for (int i = 0; i < profile.getBasalValues().length; i++) {
Profile.BasalValue basalValue = profile.getBasalValues()[i]; Profile.ProfileValue basalValue = profile.getBasalValues()[i];
Profile.BasalValue nextValue = null; Profile.ProfileValue nextValue = null;
if (profile.getBasalValues().length > i + 1) if (profile.getBasalValues().length > i + 1)
nextValue = profile.getBasalValues()[i + 1]; nextValue = profile.getBasalValues()[i + 1];
BasalProfileBlock profileBlock = new BasalProfileBlock(); BasalProfileBlock profileBlock = new BasalProfileBlock();
@ -471,8 +471,8 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con
if (activeBasalProfile != BasalProfile.PROFILE_1) return false; if (activeBasalProfile != BasalProfile.PROFILE_1) return false;
for (int i = 0; i < profileBlocks.size(); i++) { for (int i = 0; i < profileBlocks.size(); i++) {
BasalProfileBlock profileBlock = profileBlocks.get(i); BasalProfileBlock profileBlock = profileBlocks.get(i);
Profile.BasalValue basalValue = profile.getBasalValues()[i]; Profile.ProfileValue basalValue = profile.getBasalValues()[i];
Profile.BasalValue nextValue = null; Profile.ProfileValue nextValue = null;
if (profile.getBasalValues().length > i + 1) if (profile.getBasalValues().length > i + 1)
nextValue = profile.getBasalValues()[i + 1]; nextValue = profile.getBasalValues()[i + 1];
if (profileBlock.getDuration() * 60 != (nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds) if (profileBlock.getDuration() * 60 != (nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds)
@ -945,8 +945,18 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con
} }
@Override @Override
public String deviceID() { public String manufacter() {
if (connectionService == null || alertService == null) return null; return "Roche";
}
@Override
public String model() {
return "Insight";
}
@Override
public String serialNumber() {
if (connectionService == null || alertService == null) return "Unknown";
return connectionService.getPumpSystemIdentification().getSerialNumber(); return connectionService.getPumpSystemIdentification().getSerialNumber();
} }

View file

@ -23,6 +23,7 @@ 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.treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.InstanceId;
/** /**
@ -236,10 +237,20 @@ public class MDIPlugin extends PluginBase implements PumpInterface {
} }
@Override @Override
public String deviceID() { public String manufacter() {
return "AndroidAPS";
}
@Override
public String model() {
return "MDI"; return "MDI";
} }
@Override
public String serialNumber() {
return InstanceId.INSTANCE.instanceId();
}
@Override @Override
public PumpDescription getPumpDescription() { public PumpDescription getPumpDescription() {
return pumpDescription; return pumpDescription;
@ -247,7 +258,7 @@ public class MDIPlugin extends PluginBase implements PumpInterface {
@Override @Override
public String shortStatus(boolean veryShort) { public String shortStatus(boolean veryShort) {
return deviceID(); return model();
} }
@Override @Override

View file

@ -39,6 +39,7 @@ import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui; import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.InstanceId;
import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SP;
@ -437,8 +438,18 @@ public class VirtualPumpPlugin extends PluginBase implements PumpInterface {
} }
@Override @Override
public String deviceID() { public String manufacter() {
return "VirtualPump"; return pumpDescription.pumpType.getManufacter();
}
@Override
public String model() {
return pumpDescription.pumpType.getModel();
}
@Override
public String serialNumber() {
return InstanceId.INSTANCE.instanceId();
} }
@Override @Override

View file

@ -16,9 +16,11 @@ import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.Iob; import info.nightscout.androidaps.data.Iob;
import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.Source;
import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.InsulinInterface;
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.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface;
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries;
@ -142,6 +144,15 @@ public class Treatment implements DataPointWithLabelInterface {
return null; return null;
} }
public double getIc() {
JSONObject bw = getBoluscalc();
if (bw == null || !bw.has("ic")) {
Profile profile = ProfileFunctions.getInstance().getProfile(date);
return profile.getIc(date);
}
return JsonHelper.safeGetDouble(bw, "ic");
}
/* /*
* mealBolus, _id and isSMB cannot be known coming from pump. Only compare rest * mealBolus, _id and isSMB cannot be known coming from pump. Only compare rest
* TODO: remove debug toasts * TODO: remove debug toasts

View file

@ -500,6 +500,23 @@ public class TreatmentService extends OrmLiteBaseService<DatabaseHelper> {
return new ArrayList<>(); return new ArrayList<>();
} }
public List<Treatment> getTreatmentDataFromTime(long from, long to, boolean ascending) {
try {
Dao<Treatment, Long> daoTreatments = getDao();
List<Treatment> treatments;
QueryBuilder<Treatment, Long> queryBuilder = daoTreatments.queryBuilder();
queryBuilder.orderBy("date", ascending);
Where where = queryBuilder.where();
where.between("date", from, to);
PreparedQuery<Treatment> preparedQuery = queryBuilder.prepare();
treatments = daoTreatments.query(preparedQuery);
return treatments;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return new ArrayList<>();
}
@Nullable @Nullable
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {

View file

@ -477,7 +477,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
} }
@Override @Override
public Intervals<TemporaryBasal> getTemporaryBasalsFromHistory() { public NonOverlappingIntervals<TemporaryBasal> getTemporaryBasalsFromHistory() {
synchronized (tempBasals) { synchronized (tempBasals) {
return new NonOverlappingIntervals<>(tempBasals); return new NonOverlappingIntervals<>(tempBasals);
} }

View file

@ -386,10 +386,10 @@ public class CommandQueue {
} }
// Compare with pump limits // Compare with pump limits
Profile.BasalValue[] basalValues = profile.getBasalValues(); Profile.ProfileValue[] basalValues = profile.getBasalValues();
PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump();
for (Profile.BasalValue basalValue : basalValues) { for (Profile.ProfileValue basalValue : basalValues) {
if (basalValue.value < pump.getPumpDescription().basalMinimumRate) { if (basalValue.value < pump.getPumpDescription().basalMinimumRate) {
Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.gs(R.string.basalvaluebelowminimum), Notification.URGENT); Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.gs(R.string.basalvaluebelowminimum), Notification.URGENT);
MainApp.bus().post(new EventNewNotification(notification)); MainApp.bus().post(new EventNewNotification(notification));

View file

@ -10,12 +10,15 @@ import info.nightscout.androidaps.events.EventChargingState;
public class ChargingStateReceiver extends BroadcastReceiver { public class ChargingStateReceiver extends BroadcastReceiver {
private static EventChargingState lastEvent;
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
EventChargingState event = grabChargingState(context); EventChargingState event = grabChargingState(context);
if (event != null) if (event != null)
MainApp.bus().post(event); MainApp.bus().post(event);
lastEvent = event;
} }
public EventChargingState grabChargingState(Context context) { public EventChargingState grabChargingState(Context context) {
@ -32,4 +35,7 @@ public class ChargingStateReceiver extends BroadcastReceiver {
return event; return event;
} }
static public boolean isCharging() {
return lastEvent != null && lastEvent.isCharging;
}
} }

View file

@ -21,6 +21,8 @@ public class NetworkChangeReceiver extends BroadcastReceiver {
private static Logger log = LoggerFactory.getLogger(L.CORE); private static Logger log = LoggerFactory.getLogger(L.CORE);
private static EventNetworkChange lastEvent = null;
@Override @Override
public void onReceive(final Context context, final Intent intent) { public void onReceive(final Context context, final Intent intent) {
EventNetworkChange event = grabNetworkStatus(context); EventNetworkChange event = grabNetworkStatus(context);
@ -61,6 +63,11 @@ public class NetworkChangeReceiver extends BroadcastReceiver {
log.debug("NETCHANGE: Disconnected."); log.debug("NETCHANGE: Disconnected.");
} }
lastEvent = event;
return event; return event;
} }
public static boolean isWifiConnected() {
return lastEvent != null && lastEvent.wifiConnected;
}
} }

View file

@ -12,6 +12,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPreferenceChange;
import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.setupwizard.events.EventSWUpdate; import info.nightscout.androidaps.setupwizard.events.EventSWUpdate;
@ -98,6 +99,7 @@ public class SWItem {
if (L.isEnabled(L.CORE)) if (L.isEnabled(L.CORE))
log.debug("Firing EventPreferenceChange"); log.debug("Firing EventPreferenceChange");
MainApp.bus().post(new EventPreferenceChange(preferenceId)); MainApp.bus().post(new EventPreferenceChange(preferenceId));
RxBus.INSTANCE.send(new EventPreferenceChange(preferenceId));
MainApp.bus().post(new EventSWUpdate()); MainApp.bus().post(new EventSWUpdate());
scheduledEventPost = null; scheduledEventPost = null;
} }

View file

@ -188,7 +188,7 @@ class BolusWizard @JvmOverloads constructor(val profile: Profile,
calculatedTotalInsulin = 0.0 calculatedTotalInsulin = 0.0
} }
val bolusStep = ConfigBuilderPlugin.getPlugin().activePump.pumpDescription.bolusStep val bolusStep = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.bolusStep ?: 0.1
calculatedTotalInsulin = Round.roundTo(calculatedTotalInsulin, bolusStep) calculatedTotalInsulin = Round.roundTo(calculatedTotalInsulin, bolusStep)
insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(calculatedTotalInsulin)).value() insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(calculatedTotalInsulin)).value()
@ -278,7 +278,7 @@ class BolusWizard @JvmOverloads constructor(val profile: Profile,
val pump1 = ConfigBuilderPlugin.getPlugin().activePump val pump1 = ConfigBuilderPlugin.getPlugin().activePump
if (pump1.pumpDescription.tempBasalStyle == PumpDescription.ABSOLUTE) { if (pump1?.pumpDescription?.tempBasalStyle == PumpDescription.ABSOLUTE) {
ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalAbsolute(0.0, 120, true, profile, object : Callback() { ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalAbsolute(0.0, 120, true, profile, object : Callback() {
override fun run() { override fun run() {
if (!result.success) { if (!result.success) {
@ -318,7 +318,7 @@ class BolusWizard @JvmOverloads constructor(val profile: Profile,
detailedBolusInfo.boluscalc = nsJSON() detailedBolusInfo.boluscalc = nsJSON()
detailedBolusInfo.source = Source.USER detailedBolusInfo.source = Source.USER
detailedBolusInfo.notes = notes detailedBolusInfo.notes = notes
if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getPlugin().activePump.pumpDescription.storesCarbInfo) { if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.storesCarbInfo == true) {
ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() { override fun run() {
if (!result.success) { if (!result.success) {

View file

@ -8,6 +8,8 @@ import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISODateTimeFormat;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
@ -71,6 +73,18 @@ public class DateUtil {
return toISOString(new Date(date), FORMAT_DATE_ISO_OUT, TimeZone.getTimeZone("UTC")); return toISOString(new Date(date), FORMAT_DATE_ISO_OUT, TimeZone.getTimeZone("UTC"));
} }
public static String toISOAsUTC(final long timestamp) {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("UTC"));
return format.format(timestamp);
}
public static String toISONoZone(final long timestamp) {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
format.setTimeZone(TimeZone.getDefault());
return format.format(timestamp);
}
public static Date toDate(Integer seconds) { public static Date toDate(Integer seconds) {
Calendar calendar = new GregorianCalendar(); Calendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, 0); // Set january to be sure we miss DST changing calendar.set(Calendar.MONTH, 0); // Set january to be sure we miss DST changing
@ -187,4 +201,80 @@ public class DateUtil {
long diff = Math.abs(date - now()); long diff = Math.abs(date - now());
return diff < T.mins(2).msecs(); return diff < T.mins(2).msecs();
} }
public static long getTimeZoneOffsetMs() {
return new GregorianCalendar().getTimeZone().getRawOffset();
}
public static int getTimeZoneOffsetMinutes(final long timestamp) {
return TimeZone.getDefault().getOffset(timestamp) / 60000;
}
public static String niceTimeScalar(long t) {
String unit = MainApp.gs(R.string.unit_second);
t = t / 1000;
if (t != 1) unit = MainApp.gs(R.string.unit_seconds);
if (t > 59) {
unit = MainApp.gs(R.string.unit_minute);
t = t / 60;
if (t != 1) unit = MainApp.gs(R.string.unit_minutes);
if (t > 59) {
unit = MainApp.gs(R.string.unit_hour);
t = t / 60;
if (t != 1) unit = MainApp.gs(R.string.unit_hours);
if (t > 24) {
unit = MainApp.gs(R.string.unit_day);
t = t / 24;
if (t != 1) unit = MainApp.gs(R.string.unit_days);
if (t > 28) {
unit = MainApp.gs(R.string.unit_week);
t = t / 7;
if (t != 1) unit = MainApp.gs(R.string.unit_weeks);
}
}
}
}
//if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character
return qs((double) t, 0) + " " + unit;
}
// singletons to avoid repeated allocation
private static DecimalFormatSymbols dfs;
private static DecimalFormat df;
public static String qs(double x, int digits) {
if (digits == -1) {
digits = 0;
if (((int) x != x)) {
digits++;
if ((((int) x * 10) / 10 != x)) {
digits++;
if ((((int) x * 100) / 100 != x)) digits++;
}
}
}
if (dfs == null) {
final DecimalFormatSymbols local_dfs = new DecimalFormatSymbols();
local_dfs.setDecimalSeparator('.');
dfs = local_dfs; // avoid race condition
}
final DecimalFormat this_df;
// use singleton if on ui thread otherwise allocate new as DecimalFormat is not thread safe
if (Thread.currentThread().getId() == 1) {
if (df == null) {
final DecimalFormat local_df = new DecimalFormat("#", dfs);
local_df.setMinimumIntegerDigits(1);
df = local_df; // avoid race condition
}
this_df = df;
} else {
this_df = new DecimalFormat("#", dfs);
}
this_df.setMaximumFractionDigits(digits);
return this_df.format(x);
}
} }

View file

@ -121,7 +121,7 @@ public class FabricPrivacy {
.replace(".net/", ":"); .replace(".net/", ":");
MainApp.getFirebaseAnalytics().setUserProperty("Mode", BuildConfig.APPLICATION_ID + "-" + closedLoopEnabled); MainApp.getFirebaseAnalytics().setUserProperty("Mode", BuildConfig.APPLICATION_ID + "-" + closedLoopEnabled);
MainApp.getFirebaseAnalytics().setUserProperty("Language", LocaleHelper.getLanguage(MainApp.instance())); MainApp.getFirebaseAnalytics().setUserProperty("Language", LocaleHelper.getLanguage());
MainApp.getFirebaseAnalytics().setUserProperty("Version", BuildConfig.VERSION); MainApp.getFirebaseAnalytics().setUserProperty("Version", BuildConfig.VERSION);
MainApp.getFirebaseAnalytics().setUserProperty("HEAD", BuildConfig.HEAD); MainApp.getFirebaseAnalytics().setUserProperty("HEAD", BuildConfig.HEAD);
MainApp.getFirebaseAnalytics().setUserProperty("Remote", remote); MainApp.getFirebaseAnalytics().setUserProperty("Remote", remote);

View file

@ -0,0 +1,10 @@
package info.nightscout.androidaps.utils
import com.google.firebase.iid.FirebaseInstanceId
object InstanceId {
fun instanceId(): String {
var id = FirebaseInstanceId.getInstance().id
return id
}
}

View file

@ -21,17 +21,17 @@ public class LocaleHelper {
private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language"; private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language";
public static void onCreate(Context context) { public static void onCreate(Context context) {
String lang = getPersistedData(context, Locale.getDefault().getLanguage()); String lang = getPersistedData(Locale.getDefault().getLanguage());
setLocale(context, lang); setLocale(context, lang);
} }
public static void onCreate(Context context, String defaultLanguage) { public static void onCreate(Context context, String defaultLanguage) {
String lang = getPersistedData(context, defaultLanguage); String lang = getPersistedData(defaultLanguage);
setLocale(context, lang); setLocale(context, lang);
} }
public static String getLanguage(Context context) { public static String getLanguage() {
return getPersistedData(context, Locale.getDefault().getLanguage()); return getPersistedData(Locale.getDefault().getLanguage());
} }
public static void setLocale(Context context, String language) { public static void setLocale(Context context, String language) {
@ -39,7 +39,7 @@ public class LocaleHelper {
updateResources(context, language); updateResources(context, language);
} }
private static String getPersistedData(Context context, String defaultLanguage) { private static String getPersistedData(String defaultLanguage) {
return SP.getString(SELECTED_LANGUAGE, defaultLanguage); return SP.getString(SELECTED_LANGUAGE, defaultLanguage);
} }
@ -62,4 +62,8 @@ public class LocaleHelper {
resources.updateConfiguration(configuration, resources.getDisplayMetrics()); resources.updateConfiguration(configuration, resources.getDisplayMetrics());
} }
public static Locale getLocale() {
return new Locale(getPersistedData("en"));
}
} }

View file

@ -21,6 +21,10 @@ public class StringUtils {
return string; return string;
} }
public static boolean emptyString(final String str) {
return str == null || str.length() == 0;
}
public static String formatInsulin(double insulin) { public static String formatInsulin(double insulin) {
return String.format(MainApp.gs(R.string.formatinsulinunits), insulin); return String.format(MainApp.gs(R.string.formatinsulinunits), insulin);
} }

View file

@ -43,6 +43,12 @@ public class T {
return t; return t;
} }
public static T months(long month) {
T t = new T();
t.time = month * 31 * 24 * 60 * 60 * 1000L;
return t;
}
public long msecs() { public long msecs() {
return time; return time;
} }

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tidepool_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginTop="32dp"
android:text="-"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/tidepool_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Login"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tidepool_status" />
<Button
android:id="@+id/tidepool_uploadnow"
android:layout_width="113dp"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:text="Upload now"
app:layout_constraintStart_toEndOf="@+id/tidepool_login"
app:layout_constraintTop_toBottomOf="@+id/tidepool_status" />
<Button
android:id="@+id/tidepool_removeall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="8dp"
android:text="Remove all"
app:layout_constraintStart_toEndOf="@+id/tidepool_uploadnow"
app:layout_constraintTop_toBottomOf="@+id/tidepool_status" />
<Button
android:id="@+id/tidepool_resertstart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="8dp"
android:text="Reset start"
app:layout_constraintStart_toEndOf="@+id/tidepool_removeall"
app:layout_constraintTop_toBottomOf="@+id/tidepool_status" />
<ScrollView
android:id="@+id/tidepool_logscrollview"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tidepool_login"
app:layout_constraintVertical_bias="0.023">
<TextView
android:id="@+id/tidepool_log"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-- logs --" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1317,6 +1317,45 @@
<string name="tomato">Tomato (MiaoMiao)</string> <string name="tomato">Tomato (MiaoMiao)</string>
<string name="tomato_short">Tomato</string> <string name="tomato_short">Tomato</string>
<string name="unit_second">second</string>
<string name="unit_minute">minute</string>
<string name="unit_hour">hour</string>
<string name="unit_day">day"</string>
<string name="unit_week">week"</string>
<string name="unit_seconds">seconds</string>
<string name="unit_minutes">minutes</string>
<string name="unit_hours">hours</string>
<string name="unit_days">days"</string>
<string name="unit_weeks">weeks"</string>
<string name="key_tidepool_username" translatable="false">tidepool_username</string>
<string name="key_tidepool_password" translatable="false">tidepool_password</string>
<string name="key_tidepool_dev_servers" translatable="false">tidepool_dev_servers</string>
<string name="key_tidepool_test_login" translatable="false">tidepool_test_login</string>
<string name="key_tidepool_only_while_charging" translatable="false">tidepool_only_while_charging</string>
<string name="key_tidepool_only_while_unmetered" translatable="false">tidepool_only_while_unmetered</string>
<string name="summary_tidepool_username">Your Tidepool login user name, normally your email address</string>
<string name="title_tidepool_username">Login User Name</string>
<string name="summary_tidepool_password">Your Tidepool login password</string>
<string name="title_tidepool_password">Login Password</string>
<string name="title_tidepool_test_login">Test Tidepool Login</string>
<string name="summary_tidepool_dev_servers">If enabled, uploads will go to https://int-app.tidepool.org instead of the regular https://app.tidepool.org/</string>
<string name="title_tidepool_dev_servers">Use Integration (test) servers</string>
<string name="tidepool">Tidepool</string>
<string name="tidepool_shortname">TDP</string>
<string name="description_tidepool">Uploads data to Tidepool</string>
<string name="key_tidepool_last_end" translatable="false">tidepool_last_end</string>
<string name="tidepool_upload_cgm">Upload CGM data</string>
<string name="key_tidepool_upload_cgm" translatable="false">tidepool_upload_cgm</string>
<string name="key_tidepool_upload_bolus" translatable="false">tidepool_upload_bolus</string>
<string name="tidepool_upload_bolus">Upload treatments (insulin, carbs)</string>
<string name="key_tidepool_upload_tbr" translatable="false">tidepool_upload_tbr</string>
<string name="tidepool_upload_tbr">Upload temporary basals</string>
<string name="key_tidepool_upload_profile" translatable="false">tidepool_upload_profile</string>
<string name="tidepool_upload_profile">Upload profile switches, temp targets</string>
<string name="key_tidepool_upload_bg" translatable="false">tidepool_upload_bg</string>
<string name="tidepool_upload_bg">Upload BG tests</string>
<string name="key_instanceid" translatable="false">instanceid</string>
<string name="key_smbmaxminutes" translatable="false">smbmaxminutes</string> <string name="key_smbmaxminutes" translatable="false">smbmaxminutes</string>
<string name="dst_plugin_name" translatable="false">Dayligh Saving time</string> <string name="dst_plugin_name" translatable="false">Dayligh Saving time</string>
<string name="dst_in_24h_warning">Dayligh Saving time change in 24h or less</string> <string name="dst_in_24h_warning">Dayligh Saving time change in 24h or less</string>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="tidepool_upload_screen"
android:title="@string/tidepool">
<EditTextPreference
android:key="@string/key_tidepool_username"
android:summary="@string/summary_tidepool_username"
android:title="@string/title_tidepool_username" />
<EditTextPreference
android:inputType="textPassword"
android:key="@string/key_tidepool_password"
android:summary="@string/summary_tidepool_password"
android:title="@string/title_tidepool_password" />
<Preference
android:key="@string/key_tidepool_test_login"
android:title="@string/title_tidepool_test_login" />
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/key_tidepool_upload_cgm"
android:title="@string/tidepool_upload_cgm" />
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/key_tidepool_upload_bolus"
android:title="@string/tidepool_upload_bolus" />
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/key_tidepool_upload_bg"
android:title="@string/tidepool_upload_bg" />
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/key_tidepool_upload_tbr"
android:title="@string/tidepool_upload_tbr" />
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/key_tidepool_upload_profile"
android:title="@string/tidepool_upload_profile" />
<CheckBoxPreference
android:defaultValue="true"
android:enabled="false"
android:key="@string/key_tidepool_dev_servers"
android:summary="@string/summary_tidepool_dev_servers"
android:title="@string/title_tidepool_dev_servers" />
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/key_tidepool_only_while_charging"
android:summary="Upload data only when charging"
android:title="Only when charging" />
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/key_tidepool_only_while_unmetered"
android:summary="Upload data only when connected to an unmetered network like Wifi"
android:title="Only on Wifi" />
</PreferenceCategory>
</PreferenceScreen>

View file

@ -0,0 +1,37 @@
package info.nightscout.androidaps.plugins.general.tidepool.comm
import info.nightscout.androidaps.plugins.general.tidepool.messages.AuthReplyMessage
import info.nightscout.androidaps.plugins.general.tidepool.messages.DatasetReplyMessage
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.modules.junit4.PowerMockRunner
@RunWith(PowerMockRunner::class)
class SessionTest {
@Test
fun populateBody() {
val session = Session("", "", null)
assertNull(session.authReply)
// test authReply
val authReplyMessage = AuthReplyMessage()
session.populateBody(authReplyMessage)
assertEquals(authReplyMessage, session.authReply)
// test datasetReply
val datasetReplyMessage = DatasetReplyMessage()
assertNull(session.datasetReply)
session.populateBody(datasetReplyMessage)
assertEquals(datasetReplyMessage, session.datasetReply)
// test datasetReply as array
val list: List<DatasetReplyMessage> = listOf(datasetReplyMessage)
session.datasetReply = null
assertNull(session.datasetReply)
session.populateBody(list)
assertEquals(datasetReplyMessage, session.datasetReply)
}
}