Merge branch 'dev' into medtronic_andy_merge
This commit is contained in:
commit
8c2c2b21ea
|
@ -237,7 +237,11 @@ dependencies {
|
|||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.percentlayout:percentlayout:1.0.0'
|
||||
implementation "com.wdullaer:materialdatetimepicker:2.3.0"
|
||||
|
||||
// Otto bus will be replaced by rx
|
||||
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-android:${ormLiteVersion}"
|
||||
implementation("com.github.tony19:logback-android-classic:1.1.1-6") {
|
||||
|
@ -294,6 +298,16 @@ dependencies {
|
|||
androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}"
|
||||
androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}"
|
||||
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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessRec
|
|||
import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
|
||||
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.wear.WearPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin;
|
||||
|
@ -214,6 +215,8 @@ public class MainApp extends Application {
|
|||
pluginsList.add(StatuslinePlugin.initPlugin(this));
|
||||
pluginsList.add(PersistentNotificationPlugin.getPlugin());
|
||||
pluginsList.add(NSClientPlugin.getPlugin());
|
||||
if (engineeringMode)
|
||||
pluginsList.add(TidepoolPlugin.INSTANCE);
|
||||
pluginsList.add(MaintenancePlugin.initPlugin(this));
|
||||
|
||||
pluginsList.add(ConfigBuilderPlugin.getPlugin());
|
||||
|
|
|
@ -15,12 +15,15 @@ import android.text.TextUtils;
|
|||
import info.nightscout.androidaps.Config;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange;
|
||||
import info.nightscout.androidaps.events.EventRefreshGui;
|
||||
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||
import info.nightscout.androidaps.interfaces.PluginType;
|
||||
import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin;
|
||||
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.aps.loop.LoopPlugin;
|
||||
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin;
|
||||
|
@ -64,6 +67,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
|
|||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
MainApp.bus().post(new EventPreferenceChange(key));
|
||||
RxBus.INSTANCE.send(new EventPreferenceChange(key));
|
||||
if (key.equals("language")) {
|
||||
String lang = sharedPreferences.getString("language", "en");
|
||||
LocaleHelper.setLocale(getApplicationContext(), lang);
|
||||
|
@ -184,6 +188,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
|
|||
addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginType.INSULIN);
|
||||
|
||||
addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL);
|
||||
addPreferencesFromResourceIfEnabled(TidepoolPlugin.INSTANCE, PluginType.GENERAL);
|
||||
addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL);
|
||||
|
||||
addPreferencesFromResource(R.xml.pref_others);
|
||||
|
@ -205,6 +210,13 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre
|
|||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -19,7 +19,7 @@ public class NonOverlappingIntervals<T extends Interval> extends Intervals<T> {
|
|||
rawData = other.rawData.clone();
|
||||
}
|
||||
|
||||
protected synchronized void merge() {
|
||||
public synchronized void merge() {
|
||||
for (int index = 0; index < rawData.size() - 1; index++) {
|
||||
Interval i = rawData.valueAt(index);
|
||||
long startOfNewer = rawData.valueAt(index + 1).start();
|
||||
|
|
|
@ -400,6 +400,19 @@ public class Profile {
|
|||
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() {
|
||||
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));
|
||||
}
|
||||
|
||||
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() {
|
||||
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));
|
||||
}
|
||||
|
||||
public class BasalValue {
|
||||
public BasalValue(int timeAsSeconds, double value) {
|
||||
public class ProfileValue {
|
||||
public ProfileValue(int timeAsSeconds, double value) {
|
||||
this.timeAsSeconds = timeAsSeconds;
|
||||
this.value = value;
|
||||
}
|
||||
|
@ -451,15 +477,15 @@ public class Profile {
|
|||
public double value;
|
||||
}
|
||||
|
||||
public synchronized BasalValue[] getBasalValues() {
|
||||
public synchronized ProfileValue[] getBasalValues() {
|
||||
if (basal_v == null)
|
||||
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++) {
|
||||
Integer tas = (int) basal_v.keyAt(index);
|
||||
double value = basal_v.valueAt(index);
|
||||
ret[index] = new BasalValue(tas, value);
|
||||
ret[index] = new ProfileValue(tas, value);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -500,6 +526,49 @@ public class Profile {
|
|||
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() {
|
||||
if (targetLow_v == null)
|
||||
targetLow_v = convertToSparseArray(targetLow);
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import info.nightscout.androidaps.Constants;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.data.OverlappingIntervals;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.data.ProfileStore;
|
||||
|
@ -412,6 +413,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
if (L.isEnabled(L.DATABASE))
|
||||
log.debug("Firing EventNewBg");
|
||||
MainApp.bus().post(new EventNewBG(bgReading));
|
||||
RxBus.INSTANCE.send(new EventNewBG(bgReading));
|
||||
scheduledBgPost = null;
|
||||
}
|
||||
}
|
||||
|
@ -616,7 +618,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
}
|
||||
}
|
||||
|
||||
// -------------------- TREATMENT HANDLING -------------------
|
||||
// -------------------- TEMPTARGET HANDLING -------------------
|
||||
|
||||
public static void updateEarliestDataChange(long newDate) {
|
||||
if (earliestDataChange == null) {
|
||||
|
@ -647,6 +649,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
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) {
|
||||
try {
|
||||
TempTarget old;
|
||||
|
@ -995,6 +1014,22 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
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() {
|
||||
class PostRunnable implements Runnable {
|
||||
public void run() {
|
||||
|
@ -1422,6 +1457,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
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) {
|
||||
OverlappingIntervals offlineEvents = new OverlappingIntervals();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
|
@ -1575,6 +1627,24 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
|||
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) {
|
||||
try {
|
||||
ProfileSwitch old;
|
||||
|
|
|
@ -53,7 +53,9 @@ public interface PumpInterface {
|
|||
|
||||
// Status to be passed to NS
|
||||
JSONObject getJSONStatus(Profile profile, String profileName);
|
||||
String deviceID();
|
||||
String manufacter();
|
||||
String model();
|
||||
String serialNumber();
|
||||
|
||||
// Pump capabilities
|
||||
PumpDescription getPumpDescription();
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.List;
|
|||
import info.nightscout.androidaps.data.DetailedBolusInfo;
|
||||
import info.nightscout.androidaps.data.IobTotal;
|
||||
import info.nightscout.androidaps.data.MealData;
|
||||
import info.nightscout.androidaps.data.NonOverlappingIntervals;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.db.ExtendedBolus;
|
||||
import info.nightscout.androidaps.db.ProfileSwitch;
|
||||
|
@ -43,7 +44,7 @@ public interface TreatmentsInterface {
|
|||
// basal that can be faked by extended boluses
|
||||
boolean isTempBasalInProgress();
|
||||
TemporaryBasal getTempBasalFromHistory(long time);
|
||||
Intervals<TemporaryBasal> getTemporaryBasalsFromHistory();
|
||||
NonOverlappingIntervals<TemporaryBasal> getTemporaryBasalsFromHistory();
|
||||
|
||||
boolean isInHistoryExtendedBoluslInProgress();
|
||||
ExtendedBolus getExtendedBolusFromHistory(long time);
|
||||
|
|
|
@ -87,6 +87,7 @@ public class L {
|
|||
public static final String DATAFOOD = "DATAFOOD";
|
||||
public static final String DATATREATMENTS = "DATATREATMENTS";
|
||||
public static final String NSCLIENT = "NSCLIENT";
|
||||
public static final String TIDEPOOL = "TIDEPOOL";
|
||||
public static final String CONSTRAINTS = "CONSTRAINTS";
|
||||
public static final String PUMP = "PUMP";
|
||||
public static final String PUMPQUEUE = "PUMPQUEUE";
|
||||
|
@ -114,6 +115,7 @@ public class L {
|
|||
logElements.add(new LogElement(EVENTS, false, true));
|
||||
logElements.add(new LogElement(NOTIFICATION, true));
|
||||
logElements.add(new LogElement(NSCLIENT, true));
|
||||
logElements.add(new LogElement(TIDEPOOL, true));
|
||||
logElements.add(new LogElement(OVERVIEW, true));
|
||||
logElements.add(new LogElement(PROFILE, true));
|
||||
logElements.add(new LogElement(PUMP, true));
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -232,26 +232,32 @@ public class ConfigBuilderPlugin extends PluginBase {
|
|||
return commandQueue;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BgSourceInterface getActiveBgSource() {
|
||||
return activeBgSource;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ProfileInterface getActiveProfileInterface() {
|
||||
return activeProfile;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InsulinInterface getActiveInsulin() {
|
||||
return activeInsulin;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public APSInterface getActiveAPS() {
|
||||
return activeAPS;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PumpInterface getActivePump() {
|
||||
return activePump;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SensitivityInterface getActiveSensitivity() {
|
||||
return activeSensitivity;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import info.nightscout.androidaps.Config;
|
|||
import info.nightscout.androidaps.Constants;
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.events.EventAppExit;
|
||||
import info.nightscout.androidaps.events.EventChargingState;
|
||||
import info.nightscout.androidaps.events.EventNetworkChange;
|
||||
|
@ -213,6 +214,7 @@ public class NSClientPlugin extends PluginBase {
|
|||
SP.putBoolean(R.string.key_nsclientinternal_paused, newState);
|
||||
paused = newState;
|
||||
MainApp.bus().post(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
|
||||
RxBus.INSTANCE.send(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
|
||||
}
|
||||
|
||||
public UploadQueue queue() {
|
||||
|
|
|
@ -10,6 +10,7 @@ import com.squareup.otto.Bus;
|
|||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.events.EventChargingState;
|
||||
import info.nightscout.androidaps.events.EventNetworkChange;
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange;
|
||||
|
@ -99,6 +100,7 @@ class NsClientReceiverDelegate {
|
|||
if (newAllowedState != allowed) {
|
||||
allowed = newAllowedState;
|
||||
bus.post(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
|
||||
RxBus.INSTANCE.send(new EventPreferenceChange(R.string.key_nsclientinternal_paused));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,5 @@
|
|||
package info.nightscout.androidaps.plugins.general.tidepool.events
|
||||
|
||||
import info.nightscout.androidaps.events.Event
|
||||
|
||||
class EventTidepoolDoUpload : Event()
|
|
@ -0,0 +1,5 @@
|
|||
package info.nightscout.androidaps.plugins.general.tidepool.events
|
||||
|
||||
import info.nightscout.androidaps.events.Event
|
||||
|
||||
class EventTidepoolResetData :Event()
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package info.nightscout.androidaps.plugins.general.tidepool.events
|
||||
|
||||
import info.nightscout.androidaps.events.Event
|
||||
|
||||
class EventTidepoolUpdateGUI : Event()
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package info.nightscout.androidaps.plugins.general.tidepool.messages
|
||||
|
||||
class UploadReplyMessage {
|
||||
|
||||
internal var data: List<String>? = null
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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.TreatmentsPlugin;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.InstanceId;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
/**
|
||||
* Created by mike on 05.08.2016.
|
||||
|
@ -1312,10 +1313,20 @@ public class ComboPlugin extends PluginBase implements PumpInterface, Constraint
|
|||
}
|
||||
|
||||
@Override
|
||||
public String deviceID() {
|
||||
public String manufacter() {
|
||||
return "Roche";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "Combo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialNumber() {
|
||||
return InstanceId.INSTANCE.instanceId(); // TODO replace by real serial
|
||||
}
|
||||
|
||||
@Override
|
||||
public PumpDescription getPumpDescription() {
|
||||
return pumpDescription;
|
||||
|
|
|
@ -12,13 +12,13 @@ import info.nightscout.androidaps.utils.Round;
|
|||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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), //
|
||||
PumpTempBasalType.Percent, //
|
||||
new DoseSettings(10, 30, 24 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
|
@ -26,7 +26,7 @@ public enum PumpType {
|
|||
|
||||
// Cellnovo
|
||||
|
||||
Cellnovo1("Cellnovo", 0.05d, null, //
|
||||
Cellnovo1("Cellnovo", "Cellnovo", "Cellnovo", 0.05d, null, //
|
||||
new DoseSettings(0.05d, 30, 24 * 60, 1d, null),
|
||||
PumpTempBasalType.Percent,
|
||||
new DoseSettings(5, 30, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
|
@ -34,105 +34,107 @@ public enum PumpType {
|
|||
|
||||
// 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), //
|
||||
PumpTempBasalType.Percent,
|
||||
new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
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), //
|
||||
PumpTempBasalType.Percent,
|
||||
new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
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), //
|
||||
PumpTempBasalType.Percent,
|
||||
new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
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), //
|
||||
PumpTempBasalType.Percent,
|
||||
new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
0.02d, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities), //
|
||||
|
||||
// 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), //
|
||||
PumpTempBasalType.Percent, //
|
||||
new DoseSettings(10, 30, 24 * 60, 0d, 300d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.025d, 5d, 0d, null, PumpCapability.VirtualPumpCapabilities), //
|
||||
|
||||
AnimasPing("Animas Ping", AnimasVibe),
|
||||
AnimasPing("Animas Ping", "Ping", AnimasVibe),
|
||||
|
||||
// Dana
|
||||
DanaR("DanaR", 0.05d, null, //
|
||||
DanaR("DanaR", "SOOIL", "DanaR", 0.05d, null, //
|
||||
new DoseSettings(0.05d, 30, 8 * 60, 0.05d), //
|
||||
PumpTempBasalType.Percent, //
|
||||
new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, //
|
||||
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), //
|
||||
PumpTempBasalType.Percent, //
|
||||
new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, //
|
||||
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), //
|
||||
PumpTempBasalType.Percent, //
|
||||
new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
0.04d, 0.01d, null, PumpCapability.DanaWithHistoryCapabilities),
|
||||
|
||||
DanaRv2("DanaRv2", DanaRS),
|
||||
DanaRv2("DanaRv2", "DanaRv2", DanaRS),
|
||||
|
||||
|
||||
// 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), //
|
||||
PumpTempBasalType.Absolute, //
|
||||
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),
|
||||
|
||||
// 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), //
|
||||
PumpTempBasalType.Absolute, //
|
||||
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.05d, 0.05d, null, PumpCapability.MedtronicCapabilities), //
|
||||
|
||||
Medtronic_515_715("Medtronic 515/715", Medtronic_512_712),
|
||||
Medtronic_522_722("Medtronic 522/722", Medtronic_512_712),
|
||||
Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712),
|
||||
Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712),
|
||||
|
||||
Medtronic_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), //
|
||||
PumpTempBasalType.Absolute, //
|
||||
new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.MedtronicCapabilities), //
|
||||
|
||||
Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", Medtronic_523_723_Revel), //
|
||||
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), //
|
||||
PumpTempBasalType.Absolute, //
|
||||
new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, //
|
||||
0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), //
|
||||
|
||||
// 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),
|
||||
PumpTempBasalType.Percent,
|
||||
new DoseSettings(1, 15, 8 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
|
||||
0.1d, 0.001d, null, PumpCapability.VirtualPumpCapabilities),
|
||||
|
||||
TandemTFlex("Tandem t:flex", TandemTSlim), //
|
||||
TandemTSlimG4("Tandem t:slim G4", TandemTSlim), //
|
||||
TandemTSlimX2("Tandem t:slim X2", TandemTSlim), //
|
||||
TandemTFlex("Tandem t:flex", "t:flex", TandemTSlim), //
|
||||
TandemTSlimG4("Tandem t:slim G4", "t:slim G4", TandemTSlim), //
|
||||
TandemTSlimX2("Tandem t:slim X2", "t:slim X2", TandemTSlim), //
|
||||
;
|
||||
|
||||
private String description;
|
||||
private String manufacter;
|
||||
private String model;
|
||||
private double bolusSize;
|
||||
private DoseStepSize specialBolusSize;
|
||||
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.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.parent = parent;
|
||||
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, //
|
||||
PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, //
|
||||
double baseBasalMinValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) {
|
||||
this(description, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability);
|
||||
double baseBasalMinValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability 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, //
|
||||
PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, //
|
||||
double baseBasalMinValue, Double baseBasalMaxValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) {
|
||||
this.description = description;
|
||||
this.manufacter = manufacter;
|
||||
this.model = model;
|
||||
this.bolusSize = bolusSize;
|
||||
this.specialBolusSize = specialBolusSize;
|
||||
this.extendedBolusSettings = extendedBolusSettings;
|
||||
|
@ -198,6 +207,14 @@ public enum PumpType {
|
|||
return description;
|
||||
}
|
||||
|
||||
public String getManufacter() {
|
||||
return isParentSet() ? parent.manufacter : manufacter;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return isParentSet() ? parent.model : model;
|
||||
}
|
||||
|
||||
public PumpCapability getPumpCapability() {
|
||||
|
||||
if (isParentSet())
|
||||
|
|
|
@ -376,7 +376,12 @@ public abstract class AbstractDanaRPlugin extends PluginBase implements PumpInte
|
|||
}
|
||||
|
||||
@Override
|
||||
public String deviceID() {
|
||||
public String manufacter() {
|
||||
return "SOOIL";
|
||||
};
|
||||
|
||||
@Override
|
||||
public String serialNumber() {
|
||||
return DanaRPump.getInstance().serialNumber;
|
||||
}
|
||||
|
||||
|
|
|
@ -360,6 +360,11 @@ public class DanaRPlugin extends AbstractDanaRPlugin {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "DanaR";
|
||||
}
|
||||
|
||||
private PumpEnactResult cancelRealTempBasal() {
|
||||
PumpEnactResult result = new PumpEnactResult();
|
||||
TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());
|
||||
|
|
|
@ -364,6 +364,11 @@ public class DanaRKoreanPlugin extends AbstractDanaRPlugin {
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "DanaRKorean";
|
||||
}
|
||||
|
||||
private PumpEnactResult cancelRealTempBasal() {
|
||||
PumpEnactResult result = new PumpEnactResult();
|
||||
TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis());
|
||||
|
|
|
@ -769,7 +769,17 @@ public class DanaRSPlugin extends PluginBase implements PumpInterface, DanaRInte
|
|||
}
|
||||
|
||||
@Override
|
||||
public String deviceID() {
|
||||
public String manufacter() {
|
||||
return "SOOIL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "DanaRS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialNumber() {
|
||||
return DanaRPump.getInstance().serialNumber;
|
||||
}
|
||||
|
||||
|
|
|
@ -402,6 +402,11 @@ public class DanaRv2Plugin extends AbstractDanaRPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "DanaRv2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PumpEnactResult loadEvents() {
|
||||
return sExecutionService.loadEvents();
|
||||
|
|
|
@ -417,8 +417,8 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con
|
|||
MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED));
|
||||
List<BasalProfileBlock> profileBlocks = new ArrayList<>();
|
||||
for (int i = 0; i < profile.getBasalValues().length; i++) {
|
||||
Profile.BasalValue basalValue = profile.getBasalValues()[i];
|
||||
Profile.BasalValue nextValue = null;
|
||||
Profile.ProfileValue basalValue = profile.getBasalValues()[i];
|
||||
Profile.ProfileValue nextValue = null;
|
||||
if (profile.getBasalValues().length > i + 1)
|
||||
nextValue = profile.getBasalValues()[i + 1];
|
||||
BasalProfileBlock profileBlock = new BasalProfileBlock();
|
||||
|
@ -471,8 +471,8 @@ public class LocalInsightPlugin extends PluginBase implements PumpInterface, Con
|
|||
if (activeBasalProfile != BasalProfile.PROFILE_1) return false;
|
||||
for (int i = 0; i < profileBlocks.size(); i++) {
|
||||
BasalProfileBlock profileBlock = profileBlocks.get(i);
|
||||
Profile.BasalValue basalValue = profile.getBasalValues()[i];
|
||||
Profile.BasalValue nextValue = null;
|
||||
Profile.ProfileValue basalValue = profile.getBasalValues()[i];
|
||||
Profile.ProfileValue nextValue = null;
|
||||
if (profile.getBasalValues().length > i + 1)
|
||||
nextValue = profile.getBasalValues()[i + 1];
|
||||
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
|
||||
public String deviceID() {
|
||||
if (connectionService == null || alertService == null) return null;
|
||||
public String manufacter() {
|
||||
return "Roche";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "Insight";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialNumber() {
|
||||
if (connectionService == null || alertService == null) return "Unknown";
|
||||
return connectionService.getPumpSystemIdentification().getSerialNumber();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.treatments.TreatmentsPlugin;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.InstanceId;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -236,10 +237,20 @@ public class MDIPlugin extends PluginBase implements PumpInterface {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String deviceID() {
|
||||
public String manufacter() {
|
||||
return "AndroidAPS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return "MDI";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialNumber() {
|
||||
return InstanceId.INSTANCE.instanceId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PumpDescription getPumpDescription() {
|
||||
return pumpDescription;
|
||||
|
@ -247,7 +258,7 @@ public class MDIPlugin extends PluginBase implements PumpInterface {
|
|||
|
||||
@Override
|
||||
public String shortStatus(boolean veryShort) {
|
||||
return deviceID();
|
||||
return model();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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.treatments.TreatmentsPlugin;
|
||||
import info.nightscout.androidaps.utils.DateUtil;
|
||||
import info.nightscout.androidaps.utils.InstanceId;
|
||||
import info.nightscout.androidaps.utils.SP;
|
||||
|
||||
|
||||
|
@ -437,8 +438,18 @@ public class VirtualPumpPlugin extends PluginBase implements PumpInterface {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String deviceID() {
|
||||
return "VirtualPump";
|
||||
public String manufacter() {
|
||||
return pumpDescription.pumpType.getManufacter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String model() {
|
||||
return pumpDescription.pumpType.getModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialNumber() {
|
||||
return InstanceId.INSTANCE.instanceId();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,9 +17,11 @@ import info.nightscout.androidaps.MainApp;
|
|||
import info.nightscout.androidaps.R;
|
||||
import info.nightscout.androidaps.data.Iob;
|
||||
import info.nightscout.androidaps.db.DbObjectBase;
|
||||
import info.nightscout.androidaps.data.Profile;
|
||||
import info.nightscout.androidaps.db.Source;
|
||||
import info.nightscout.androidaps.interfaces.InsulinInterface;
|
||||
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.graphExtensions.DataPointWithLabelInterface;
|
||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries;
|
||||
|
@ -166,6 +168,15 @@ public class Treatment implements DataPointWithLabelInterface, DbObjectBase {
|
|||
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
|
||||
* TODO: remove debug toasts
|
||||
|
|
|
@ -686,6 +686,23 @@ public class TreatmentService extends OrmLiteBaseService<DatabaseHelper> {
|
|||
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
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
|
|
|
@ -495,7 +495,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
|
|||
}
|
||||
|
||||
@Override
|
||||
public Intervals<TemporaryBasal> getTemporaryBasalsFromHistory() {
|
||||
public NonOverlappingIntervals<TemporaryBasal> getTemporaryBasalsFromHistory() {
|
||||
synchronized (tempBasals) {
|
||||
return new NonOverlappingIntervals<>(tempBasals);
|
||||
}
|
||||
|
|
|
@ -386,10 +386,10 @@ public class CommandQueue {
|
|||
}
|
||||
|
||||
// Compare with pump limits
|
||||
Profile.BasalValue[] basalValues = profile.getBasalValues();
|
||||
Profile.ProfileValue[] basalValues = profile.getBasalValues();
|
||||
PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump();
|
||||
|
||||
for (Profile.BasalValue basalValue : basalValues) {
|
||||
for (Profile.ProfileValue basalValue : basalValues) {
|
||||
if (basalValue.value < pump.getPumpDescription().basalMinimumRate) {
|
||||
Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.gs(R.string.basalvaluebelowminimum), Notification.URGENT);
|
||||
MainApp.bus().post(new EventNewNotification(notification));
|
||||
|
|
|
@ -10,12 +10,15 @@ import info.nightscout.androidaps.events.EventChargingState;
|
|||
|
||||
public class ChargingStateReceiver extends BroadcastReceiver {
|
||||
|
||||
private static EventChargingState lastEvent;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
EventChargingState event = grabChargingState(context);
|
||||
|
||||
if (event != null)
|
||||
MainApp.bus().post(event);
|
||||
lastEvent = event;
|
||||
}
|
||||
|
||||
public EventChargingState grabChargingState(Context context) {
|
||||
|
@ -32,4 +35,7 @@ public class ChargingStateReceiver extends BroadcastReceiver {
|
|||
return event;
|
||||
}
|
||||
|
||||
static public boolean isCharging() {
|
||||
return lastEvent != null && lastEvent.isCharging;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@ public class NetworkChangeReceiver extends BroadcastReceiver {
|
|||
|
||||
private static Logger log = LoggerFactory.getLogger(L.CORE);
|
||||
|
||||
private static EventNetworkChange lastEvent = null;
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
EventNetworkChange event = grabNetworkStatus(context);
|
||||
|
@ -61,6 +63,11 @@ public class NetworkChangeReceiver extends BroadcastReceiver {
|
|||
log.debug("NETCHANGE: Disconnected.");
|
||||
}
|
||||
|
||||
lastEvent = event;
|
||||
return event;
|
||||
}
|
||||
|
||||
public static boolean isWifiConnected() {
|
||||
return lastEvent != null && lastEvent.wifiConnected;
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import java.util.concurrent.ScheduledFuture;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import info.nightscout.androidaps.MainApp;
|
||||
import info.nightscout.androidaps.plugins.bus.RxBus;
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange;
|
||||
import info.nightscout.androidaps.logging.L;
|
||||
import info.nightscout.androidaps.setupwizard.events.EventSWUpdate;
|
||||
|
@ -98,6 +99,7 @@ public class SWItem {
|
|||
if (L.isEnabled(L.CORE))
|
||||
log.debug("Firing EventPreferenceChange");
|
||||
MainApp.bus().post(new EventPreferenceChange(preferenceId));
|
||||
RxBus.INSTANCE.send(new EventPreferenceChange(preferenceId));
|
||||
MainApp.bus().post(new EventSWUpdate());
|
||||
scheduledEventPost = null;
|
||||
}
|
||||
|
|
|
@ -188,7 +188,7 @@ class BolusWizard @JvmOverloads constructor(val profile: Profile,
|
|||
calculatedTotalInsulin = 0.0
|
||||
}
|
||||
|
||||
val bolusStep = ConfigBuilderPlugin.getPlugin().activePump.pumpDescription.bolusStep
|
||||
val bolusStep = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.bolusStep ?: 0.1
|
||||
calculatedTotalInsulin = Round.roundTo(calculatedTotalInsulin, bolusStep)
|
||||
|
||||
insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(calculatedTotalInsulin)).value()
|
||||
|
@ -278,7 +278,7 @@ class BolusWizard @JvmOverloads constructor(val profile: Profile,
|
|||
|
||||
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() {
|
||||
override fun run() {
|
||||
if (!result.success) {
|
||||
|
@ -318,7 +318,7 @@ class BolusWizard @JvmOverloads constructor(val profile: Profile,
|
|||
detailedBolusInfo.boluscalc = nsJSON()
|
||||
detailedBolusInfo.source = Source.USER
|
||||
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() {
|
||||
override fun run() {
|
||||
if (!result.success) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import org.joda.time.format.DateTimeFormatter;
|
|||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
@ -71,6 +73,18 @@ public class DateUtil {
|
|||
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) {
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -121,7 +121,7 @@ public class FabricPrivacy {
|
|||
.replace(".net/", ":");
|
||||
|
||||
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("HEAD", BuildConfig.HEAD);
|
||||
MainApp.getFirebaseAnalytics().setUserProperty("Remote", remote);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -21,17 +21,17 @@ public class LocaleHelper {
|
|||
private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language";
|
||||
|
||||
public static void onCreate(Context context) {
|
||||
String lang = getPersistedData(context, Locale.getDefault().getLanguage());
|
||||
String lang = getPersistedData(Locale.getDefault().getLanguage());
|
||||
setLocale(context, lang);
|
||||
}
|
||||
|
||||
public static void onCreate(Context context, String defaultLanguage) {
|
||||
String lang = getPersistedData(context, defaultLanguage);
|
||||
String lang = getPersistedData(defaultLanguage);
|
||||
setLocale(context, lang);
|
||||
}
|
||||
|
||||
public static String getLanguage(Context context) {
|
||||
return getPersistedData(context, Locale.getDefault().getLanguage());
|
||||
public static String getLanguage() {
|
||||
return getPersistedData(Locale.getDefault().getLanguage());
|
||||
}
|
||||
|
||||
public static void setLocale(Context context, String language) {
|
||||
|
@ -39,7 +39,7 @@ public class LocaleHelper {
|
|||
updateResources(context, language);
|
||||
}
|
||||
|
||||
private static String getPersistedData(Context context, String defaultLanguage) {
|
||||
private static String getPersistedData(String defaultLanguage) {
|
||||
return SP.getString(SELECTED_LANGUAGE, defaultLanguage);
|
||||
}
|
||||
|
||||
|
@ -62,4 +62,8 @@ public class LocaleHelper {
|
|||
|
||||
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
|
||||
}
|
||||
|
||||
public static Locale getLocale() {
|
||||
return new Locale(getPersistedData("en"));
|
||||
}
|
||||
}
|
|
@ -21,6 +21,10 @@ public class StringUtils {
|
|||
return string;
|
||||
}
|
||||
|
||||
public static boolean emptyString(final String str) {
|
||||
return str == null || str.length() == 0;
|
||||
}
|
||||
|
||||
public static String formatInsulin(double insulin) {
|
||||
return String.format(MainApp.gs(R.string.formatinsulinunits), insulin);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,12 @@ public class 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() {
|
||||
return time;
|
||||
}
|
||||
|
|
74
app/src/main/res/layout/tidepool_fragment.xml
Normal file
74
app/src/main/res/layout/tidepool_fragment.xml
Normal 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>
|
|
@ -1317,6 +1317,45 @@
|
|||
<string name="tomato">Tomato (MiaoMiao)</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="dst_plugin_name" translatable="false">Dayligh Saving time</string>
|
||||
<string name="dst_in_24h_warning">Dayligh Saving time change in 24h or less</string>
|
||||
|
|
60
app/src/main/res/xml/pref_tidepool.xml
Normal file
60
app/src/main/res/xml/pref_tidepool.xml
Normal 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>
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue