From 9b1acf6958356ceacadcb498fafbd793feb0f45d Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sat, 1 Jun 2019 18:40:33 +0200 Subject: [PATCH] add missing classes. convert to use AAPS data --- app/build.gradle | 5 + .../dexdrip/Models/BloodTest.java | 550 ++++++++++++++++++ .../eveningoutpost/dexdrip/Models/JoH.java | 57 ++ .../dexdrip/UtilityModels/Inevitable.java | 120 ++++ .../UtilityModels/PersistentStore.java | 138 +++++ .../dexdrip/UtilityModels/StatusItem.java | 98 ++++ .../dexdrip/store/FastStore.java | 53 ++ .../dexdrip/store/KeyStore.java | 28 + .../dexdrip/tidepool/EBolus.java | 6 + .../dexdrip/tidepool/ESensorGlucose.java | 13 +- .../dexdrip/tidepool/EWizard.java | 17 +- .../dexdrip/tidepool/TidepoolEntry.java | 15 +- .../dexdrip/tidepool/UploadChunk.java | 83 +-- .../dexdrip/utils/LogSlider.java | 13 + .../dexdrip/utils/NamedSliderProcessor.java | 11 + .../info/nightscout/androidaps/MainApp.java | 2 + .../androidaps/db/DatabaseHelper.java | 35 +- .../general/tidepool/TidepoolPlugin.java | 39 ++ .../plugins/treatments/TreatmentService.java | 17 + .../receivers/ChargingStateReceiver.java | 6 + .../info/nightscout/androidaps/utils/T.java | 6 + app/src/main/res/values/strings.xml | 18 + app/src/main/res/xml/pref_tidepool.xml | 49 ++ 23 files changed, 1322 insertions(+), 57 deletions(-) create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java create mode 100644 app/src/main/res/xml/pref_tidepool.xml diff --git a/app/build.gradle b/app/build.gradle index b374db47b3..86f6e22678 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -283,8 +283,13 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' + implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' + // you will want to install the android studio lombok plugin + compileOnly 'org.projectlombok:lombok:1.16.20' + // compileOnly 'javax.annotation:javax.annotation-api:1.3.2' + annotationProcessor "org.projectlombok:lombok:1.16.20" } task unzip(type: Copy) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java new file mode 100644 index 0000000000..a106e8e48a --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/BloodTest.java @@ -0,0 +1,550 @@ +package com.eveningoutpost.dexdrip.Models; + +import android.provider.BaseColumns; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; +import com.google.gson.annotations.Expose; + +/** + * Created by jamorham on 11/12/2016. + */ + +@Table(name = "BloodTest", id = BaseColumns._ID) +public class BloodTest extends Model { + + public static final long STATE_VALID = 1 << 0; + public static final long STATE_CALIBRATION = 1 << 1; + public static final long STATE_NOTE = 1 << 2; + public static final long STATE_UNDONE = 1 << 3; + public static final long STATE_OVERWRITTEN = 1 << 4; + + private static long highest_timestamp = 0; + private static boolean patched = false; + private final static String TAG = "BloodTest"; + private final static String LAST_BT_AUTO_CALIB_UUID = "last-bt-auto-calib-uuid"; + private final static boolean d = false; + + @Expose + @Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) + public long timestamp; + + @Expose + @Column(name = "mgdl") + public double mgdl; + + @Expose + @Column(name = "created_timestamp") + public long created_timestamp; + + @Expose + @Column(name = "state") + public long state; // bitfield + + @Expose + @Column(name = "source") + public String source; + + @Expose + @Column(name = "uuid", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) + public String uuid; + +/* + public GlucoseReadingRx glucoseReadingRx; + + // patches and saves + public Long saveit() { + fixUpTable(); + return save(); + } + + public void addState(long flag) { + state |= flag; + save(); + } + + public void removeState(long flag) { + state &= ~flag; + save(); + } + + public String toS() { + final Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return gson.toJson(this); + } + + private BloodTestMessage toMessageNative() { + return new BloodTestMessage.Builder() + .timestamp(timestamp) + .mgdl(mgdl) + .created_timestamp(created_timestamp) + .state(state) + .source(source) + .uuid(uuid) + .build(); + } + + public byte[] toMessage() { + final List btl = new ArrayList<>(); + btl.add(this); + return toMultiMessage(btl); + } + + + // static methods + private static final long CLOSEST_READING_MS = 30000; // 30 seconds + + public static BloodTest create(long timestamp_ms, double mgdl, String source) { + return create(timestamp_ms, mgdl, source, null); + } + + public static BloodTest create(long timestamp_ms, double mgdl, String source, String suggested_uuid) { + + if ((timestamp_ms == 0) || (mgdl == 0)) { + UserError.Log.e(TAG, "Either timestamp or mgdl is zero - cannot create reading"); + return null; + } + + if (timestamp_ms < 1487759433000L) { + UserError.Log.d(TAG, "Timestamp really too far in the past @ " + timestamp_ms); + return null; + } + + final long now = JoH.tsl(); + if (timestamp_ms > now) { + if ((timestamp_ms - now) > 600000) { + UserError.Log.wtf(TAG, "Timestamp is > 10 minutes in the future! Something is wrong: " + JoH.dateTimeText(timestamp_ms)); + return null; + } + timestamp_ms = now; // force to now if it showed up to 10 mins in the future + } + + final BloodTest match = getForPreciseTimestamp(timestamp_ms, CLOSEST_READING_MS); + if (match == null) { + final BloodTest bt = new BloodTest(); + bt.timestamp = timestamp_ms; + bt.mgdl = mgdl; + bt.uuid = suggested_uuid == null ? UUID.randomUUID().toString() : suggested_uuid; + bt.created_timestamp = JoH.tsl(); + bt.state = STATE_VALID; + bt.source = source; + bt.saveit(); + if (UploaderQueue.newEntry("insert", bt) != null) { + SyncService.startSyncService(3000); // sync in 3 seconds + } + + if (Pref.getBooleanDefaultFalse("bluetooth_meter_for_calibrations_auto")) { + if ((JoH.msSince(bt.timestamp) < Constants.MINUTE_IN_MS * 5) && (JoH.msSince(bt.timestamp) > 0)) { + UserError.Log.d(TAG, "Blood test value recent enough to send to G5"); + //Ob1G5StateMachine.addCalibration((int) bt.mgdl, timestamp_ms); + NativeCalibrationPipe.addCalibration((int) bt.mgdl, timestamp_ms); + } + } + + return bt; + } else { + UserError.Log.d(TAG, "Not creating new reading as timestamp is too close"); + } + return null; + } + + public static BloodTest createFromCal(double bg, double timeoffset, String source) { + return createFromCal(bg, timeoffset, source, null); + } + + public static BloodTest createFromCal(double bg, double timeoffset, String source, String suggested_uuid) { + final String unit = Pref.getString("units", "mgdl"); + + if (unit.compareTo("mgdl") != 0) { + bg = bg * Constants.MMOLL_TO_MGDL; + } + + if ((bg < 40) || (bg > 400)) { + Log.wtf(TAG, "Invalid out of range bloodtest glucose mg/dl value of: " + bg); + JoH.static_toast_long("Bloodtest out of range: " + bg + " mg/dl"); + return null; + } + + return create((long) (new Date().getTime() - timeoffset), bg, source, suggested_uuid); + } + + public static void pushBloodTestSyncToWatch(BloodTest bt, boolean is_new) { + Log.d(TAG, "pushTreatmentSyncToWatch Add treatment to UploaderQueue."); + if (Pref.getBooleanDefaultFalse("wear_sync")) { + if (UploaderQueue.newEntryForWatch(is_new ? "insert" : "update", bt) != null) { + SyncService.startSyncService(3000); // sync in 3 seconds + } + } + } + + public static BloodTest last() { + final List btl = last(1); + if ((btl != null) && (btl.size() > 0)) { + return btl.get(0); + } else { + return null; + } + } + + public static List last(int num) { + try { + return new Select() + .from(BloodTest.class) + .orderBy("timestamp desc") + .limit(num) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static List lastMatching(int num, String match) { + try { + return new Select() + .from(BloodTest.class) + .where("source like ?", match) + .orderBy("timestamp desc") + .limit(num) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static BloodTest lastValid() { + final List btl = lastValid(1); + if ((btl != null) && (btl.size() > 0)) { + return btl.get(0); + } else { + return null; + } + } + + public static List lastValid(int num) { + try { + return new Select() + .from(BloodTest.class) + .where("state & ? != 0", BloodTest.STATE_VALID) + .orderBy("timestamp desc") + .limit(num) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + + public static BloodTest byUUID(String uuid) { + if (uuid == null) return null; + try { + return new Select() + .from(BloodTest.class) + .where("uuid = ?", uuid) + .executeSingle(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static BloodTest byid(long id) { + try { + return new Select() + .from(BloodTest.class) + .where("_ID = ?", id) + .executeSingle(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + public static byte[] toMultiMessage(List btl) { + if (btl == null) return null; + final List BloodTestMessageList = new ArrayList<>(); + for (BloodTest bt : btl) { + BloodTestMessageList.add(bt.toMessageNative()); + } + return BloodTestMultiMessage.ADAPTER.encode(new BloodTestMultiMessage(BloodTestMessageList)); + } + + private static void processFromMessage(BloodTestMessage btm) { + if ((btm != null) && (btm.uuid != null) && (btm.uuid.length() == 36)) { + boolean is_new = false; + BloodTest bt = byUUID(btm.uuid); + if (bt == null) { + bt = getForPreciseTimestamp(Wire.get(btm.timestamp, BloodTestMessage.DEFAULT_TIMESTAMP), CLOSEST_READING_MS); + if (bt != null) { + UserError.Log.wtf(TAG, "Error matches a different uuid with the same timestamp: " + bt.uuid + " vs " + btm.uuid + " skipping!"); + return; + } + bt = new BloodTest(); + is_new = true; + } else { + if (bt.state != Wire.get(btm.state, BloodTestMessage.DEFAULT_STATE)) { + is_new = true; + } + } + bt.timestamp = Wire.get(btm.timestamp, BloodTestMessage.DEFAULT_TIMESTAMP); + bt.mgdl = Wire.get(btm.mgdl, BloodTestMessage.DEFAULT_MGDL); + bt.created_timestamp = Wire.get(btm.created_timestamp, BloodTestMessage.DEFAULT_CREATED_TIMESTAMP); + bt.state = Wire.get(btm.state, BloodTestMessage.DEFAULT_STATE); + bt.source = Wire.get(btm.source, BloodTestMessage.DEFAULT_SOURCE); + bt.uuid = btm.uuid; + bt.saveit(); // de-dupe by uuid + if (is_new) { // cannot handle updates yet + if (UploaderQueue.newEntry(is_new ? "insert" : "update", bt) != null) { + if (JoH.quietratelimit("start-sync-service", 5)) { + SyncService.startSyncService(3000); // sync in 3 seconds + } + } + } + } else { + UserError.Log.wtf(TAG, "processFromMessage uuid is null or invalid"); + } + } + + public static void processFromMultiMessage(byte[] payload) { + try { + final BloodTestMultiMessage btmm = BloodTestMultiMessage.ADAPTER.decode(payload); + if ((btmm != null) && (btmm.bloodtest_message != null)) { + for (BloodTestMessage btm : btmm.bloodtest_message) { + processFromMessage(btm); + } + Home.staticRefreshBGCharts(); + } + } catch (IOException | NullPointerException | IllegalStateException e) { + UserError.Log.e(TAG, "exception processFromMessage: " + e); + } + } + + public static BloodTest fromJSON(String json) { + if ((json == null) || (json.length() == 0)) { + UserError.Log.d(TAG, "Empty json received in bloodtest fromJson"); + return null; + } + try { + UserError.Log.d(TAG, "Processing incoming json: " + json); + return new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(json, BloodTest.class); + } catch (Exception e) { + UserError.Log.d(TAG, "Got exception parsing bloodtest json: " + e.toString()); + Home.toaststaticnext("Error on Bloodtest sync, probably decryption key mismatch"); + return null; + } + } + + public static BloodTest getForPreciseTimestamp(long timestamp, long precision) { + BloodTest bloodTest = new Select() + .from(BloodTest.class) + .where("timestamp <= ?", (timestamp + precision)) + .where("timestamp >= ?", (timestamp - precision)) + .orderBy("abs(timestamp - " + timestamp + ") asc") + .executeSingle(); + if ((bloodTest != null) && (Math.abs(bloodTest.timestamp - timestamp) < precision)) { + return bloodTest; + } + return null; + } + + public static List latestForGraph(int number, double startTime) { + return latestForGraph(number, (long) startTime, Long.MAX_VALUE); + } + + public static List latestForGraph(int number, long startTime) { + return latestForGraph(number, startTime, Long.MAX_VALUE); + } + + public static List latestForGraph(int number, long startTime, long endTime) { + try { + return new Select() + .from(BloodTest.class) + .where("state & ? != 0", BloodTest.STATE_VALID) + .where("timestamp >= " + Math.max(startTime, 0)) + .where("timestamp <= " + endTime) + .orderBy("timestamp asc") // warn asc! + .limit(number) + .execute(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return new ArrayList<>(); + } + } + + synchronized static void opportunisticCalibration() { + if (Pref.getBooleanDefaultFalse("bluetooth_meter_for_calibrations_auto")) { + final BloodTest bt = lastValid(); + if (bt == null) { + Log.d(TAG, "opportunistic: No blood tests"); + return; + } + if (JoH.msSince(bt.timestamp) > (Constants.HOUR_IN_MS * 8)) { + Log.d(TAG, "opportunistic: Blood test older than 8 hours ago"); + return; + } + + if ((bt.uuid == null) || (bt.uuid.length() < 8)) { + Log.d(TAG, "opportunisitic: invalid uuid"); + return; + } + + if ((bt.uuid != null) && (bt.uuid.length() > 1) && PersistentStore.getString(LAST_BT_AUTO_CALIB_UUID).equals(bt.uuid)) { + Log.d(TAG, "opportunistic: Already processed uuid: " + bt.uuid); + return; + } + + final Calibration calibration = Calibration.lastValid(); + if (calibration == null) { + Log.d(TAG, "opportunistic: No calibrations"); + // TODO do we try to initial calibrate using this? + return; + } + + if (JoH.msSince(calibration.timestamp) < Constants.HOUR_IN_MS) { + Log.d(TAG, "opportunistic: Last calibration less than 1 hour ago"); + return; + } + + if (bt.timestamp <= calibration.timestamp) { + Log.d(TAG, "opportunistic: Blood test isn't more recent than last calibration"); + return; + } + + // get closest bgreading - must be within dexcom period and locked to sensor + final BgReading bgReading = BgReading.getForPreciseTimestamp(bt.timestamp + (AddCalibration.estimatedInterstitialLagSeconds * 1000), BgGraphBuilder.DEXCOM_PERIOD); + if (bgReading == null) { + Log.d(TAG, "opportunistic: No matching bg reading"); + return; + } + + if (bt.timestamp > highest_timestamp) { + Accuracy.create(bt, bgReading, "xDrip Original"); + final CalibrationAbstract plugin = PluggableCalibration.getCalibrationPluginFromPreferences(); + final CalibrationAbstract.CalibrationData cd = (plugin != null) ? plugin.getCalibrationData(bgReading.timestamp) : null; + if (plugin != null) { + BgReading pluginBgReading = plugin.getBgReadingFromBgReading(bgReading, cd); + Accuracy.create(bt, pluginBgReading, plugin.getAlgorithmName()); + } + highest_timestamp = bt.timestamp; + } + + if (!CalibrationRequest.isSlopeFlatEnough(bgReading)) { + Log.d(TAG, "opportunistic: Slope is not flat enough at: " + JoH.dateTimeText(bgReading.timestamp)); + return; + } + + // TODO store evaluation failure for this record in cache for future optimization + + // TODO Check we have prior reading as well perhaps + JoH.clearCache(); + UserError.Log.ueh(TAG, "Opportunistic calibration for Blood Test at " + JoH.dateTimeText(bt.timestamp) + " of " + BgGraphBuilder.unitized_string_with_units_static(bt.mgdl) + " matching sensor slope at: " + JoH.dateTimeText(bgReading.timestamp) + " from source " + bt.source); + final long time_since = JoH.msSince(bt.timestamp); + + + Log.d(TAG, "opportunistic: attempting auto calibration"); + PersistentStore.setString(LAST_BT_AUTO_CALIB_UUID, bt.uuid); + Home.startHomeWithExtra(xdrip.getAppContext(), + Home.BLUETOOTH_METER_CALIBRATION, + BgGraphBuilder.unitized_string_static(bt.mgdl), + Long.toString(time_since), + "auto"); + } + } + + public static String evaluateAccuracy(long period) { + + // CACHE?? + + final List bloodTests = latestForGraph(1000, JoH.tsl() - period, JoH.tsl() - AddCalibration.estimatedInterstitialLagSeconds); + final List difference = new ArrayList<>(); + final List plugin_difference = new ArrayList<>(); + if ((bloodTests == null) || (bloodTests.size() == 0)) return null; + + final boolean show_plugin = true; + final CalibrationAbstract plugin = (show_plugin) ? PluggableCalibration.getCalibrationPluginFromPreferences() : null; + + + for (BloodTest bt : bloodTests) { + final BgReading bgReading = BgReading.getForPreciseTimestamp(bt.timestamp + (AddCalibration.estimatedInterstitialLagSeconds * 1000), BgGraphBuilder.DEXCOM_PERIOD); + + if (bgReading != null) { + final Calibration calibration = bgReading.calibration; + if (calibration == null) { + Log.d(TAG, "Calibration for bgReading is null! @ " + JoH.dateTimeText(bgReading.timestamp)); + continue; + } + final double diff = Math.abs(bgReading.calculated_value - bt.mgdl); + difference.add(diff); + if (d) { + Log.d(TAG, "Evaluate Accuracy: difference: " + JoH.qs(diff)); + } + final CalibrationAbstract.CalibrationData cd = (plugin != null) ? plugin.getCalibrationData(bgReading.timestamp) : null; + if ((plugin != null) && (cd != null)) { + final double plugin_diff = Math.abs(bt.mgdl - plugin.getGlucoseFromBgReading(bgReading, cd)); + plugin_difference.add(plugin_diff); + if (d) + Log.d(TAG, "Evaluate Plugin Accuracy: " + BgGraphBuilder.unitized_string_with_units_static(bt.mgdl) + " @ " + JoH.dateTimeText(bt.timestamp) + " difference: " + JoH.qs(plugin_diff) + "/" + JoH.qs(plugin_diff * Constants.MGDL_TO_MMOLL, 2) + " calibration: " + JoH.qs(cd.slope, 2) + " " + JoH.qs(cd.intercept, 2)); + } + } + } + + if (difference.size() == 0) return null; + double avg = DoubleMath.mean(difference); + Log.d(TAG, "Average accuracy: " + accuracyAsString(avg) + " (" + JoH.qs(avg, 5) + ")"); + + if (plugin_difference.size() > 0) { + double plugin_avg = DoubleMath.mean(plugin_difference); + Log.d(TAG, "Plugin Average accuracy: " + accuracyAsString(plugin_avg) + " (" + JoH.qs(plugin_avg, 5) + ")"); + return accuracyAsString(plugin_avg) + " / " + accuracyAsString(avg); + } + return accuracyAsString(avg); + } + + public static String accuracyAsString(double avg) { + final boolean domgdl = Pref.getString("units", "mgdl").equals("mgdl"); + // +- symbol + return "\u00B1" + (!domgdl ? JoH.qs(avg * Constants.MGDL_TO_MMOLL, 2) + " mmol" : JoH.qs(avg, 1) + " mgdl"); + } + + public static List cleanup(int retention_days) { + return new Delete() + .from(BloodTest.class) + .where("timestamp < ?", JoH.tsl() - (retention_days * Constants.DAY_IN_MS)) + .execute(); + } + + // create the table ourselves without worrying about model versioning and downgrading + private static void fixUpTable() { + if (patched) return; + final String[] patchup = { + "CREATE TABLE BloodTest (_id INTEGER PRIMARY KEY AUTOINCREMENT);", + "ALTER TABLE BloodTest ADD COLUMN timestamp INTEGER;", + "ALTER TABLE BloodTest ADD COLUMN created_timestamp INTEGER;", + "ALTER TABLE BloodTest ADD COLUMN state INTEGER;", + "ALTER TABLE BloodTest ADD COLUMN mgdl REAL;", + "ALTER TABLE BloodTest ADD COLUMN source TEXT;", + "ALTER TABLE BloodTest ADD COLUMN uuid TEXT;", + "CREATE UNIQUE INDEX index_Bloodtest_uuid on BloodTest(uuid);", + "CREATE UNIQUE INDEX index_Bloodtest_timestamp on BloodTest(timestamp);", + "CREATE INDEX index_Bloodtest_created_timestamp on BloodTest(created_timestamp);", + "CREATE INDEX index_Bloodtest_state on BloodTest(state);"}; + + for (String patch : patchup) { + try { + SQLiteUtils.execSql(patch); + // UserError.Log.e(TAG, "Processed patch should not have succeeded!!: " + patch); + } catch (Exception e) { + // UserError.Log.d(TAG, "Patch: " + patch + " generated exception as it should: " + e.toString()); + } + } + patched = true; + } + +*/ +} + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java index df801512fe..3d8593df34 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/JoH.java @@ -1,15 +1,23 @@ package com.eveningoutpost.dexdrip.Models; import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.PowerManager; +import android.util.Base64; import android.util.Log; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.GregorianCalendar; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.utils.DateUtil; public class JoH { @@ -19,6 +27,14 @@ public class JoH { return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString(); } + public static long msSince(long when) { + return (DateUtil.now() - when); + } + + public static long msTill(long when) { + return (when - DateUtil.now()); + } + public static String niceTimeScalar(long t) { String unit = MainApp.gs(R.string.unit_second); t = t / 1000; @@ -122,5 +138,46 @@ public class JoH { return wl; } + public static boolean isLANConnected() { + final ConnectivityManager cm = + (ConnectivityManager) MainApp.instance().getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + final boolean isConnected = activeNetwork != null && + activeNetwork.isConnected(); + return isConnected && ((activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) + || (activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET) + || (activeNetwork.getType() == ConnectivityManager.TYPE_BLUETOOTH)); + } + + private static Gson gson_instance; + public static Gson defaultGsonInstance() { + if (gson_instance == null) { + gson_instance = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + //.registerTypeAdapter(Date.class, new DateTypeAdapter()) + // .serializeSpecialFloatingPointValues() + .create(); + } + return gson_instance; + } + + public static String base64encodeBytes(byte[] input) { + try { + return new String(Base64.encode(input, Base64.NO_WRAP), "UTF-8"); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Got unsupported encoding: " + e); + return "encode-error"; + } + } + + public static byte[] base64decodeBytes(String input) { + try { + return Base64.decode(input.getBytes("UTF-8"), Base64.NO_WRAP); + } catch (UnsupportedEncodingException | IllegalArgumentException e) { + Log.e(TAG, "Got unsupported encoding: " + e); + return new byte[0]; + } + } + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java new file mode 100644 index 0000000000..c3efae80b4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/Inevitable.java @@ -0,0 +1,120 @@ +package com.eveningoutpost.dexdrip.UtilityModels; + +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Log; + +import com.eveningoutpost.dexdrip.Models.JoH; + +import java.util.concurrent.ConcurrentHashMap; + +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.T; + +/** + * Created by jamorham on 07/03/2018. + *

+ * Tasks which are fired from events can be scheduled here and only execute when they become idle + * and are not being rescheduled within their wait window. + */ + +public class Inevitable { + + private static final String TAG = Inevitable.class.getSimpleName(); + private static final int MAX_QUEUE_TIME = (int) T.mins(6).msecs(); + private static final boolean d = true; + + private static final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); + + public static synchronized void task(final String id, long idle_for, Runnable runnable) { + if (idle_for > MAX_QUEUE_TIME) { + throw new RuntimeException(id + " Requested time: " + idle_for + " beyond max queue time"); + } + final Task task = tasks.get(id); + if (task != null) { + // if it already exists then extend the time + task.extendTime(idle_for); + + if (d) + Log.d(TAG, "Extending time for: " + id + " to " + JoH.dateTimeText(task.when)); + } else { + // otherwise create new task + if (runnable == null) return; // extension only if already exists + tasks.put(id, new Task(id, idle_for, runnable)); + + if (d) + Log.d(TAG, "Creating task: " + id + " due: " + JoH.dateTimeText(tasks.get(id).when)); + + // create a thread to wait and execute in background + final Thread t = new Thread(() -> { + final PowerManager.WakeLock wl = JoH.getWakeLock(id, MAX_QUEUE_TIME + 5000); + try { + boolean running = true; + // wait for task to be due or killed + while (running) { + SystemClock.sleep(500); + final Task thisTask = tasks.get(id); + running = thisTask != null && !thisTask.poll(); + } + } finally { + JoH.releaseWakeLock(wl); + } + }); + t.setPriority(Thread.MIN_PRIORITY); + //t.setDaemon(true); + t.start(); + } + } + + public static synchronized void stackableTask(String id, long idle_for, Runnable runnable) { + int stack = 0; + while (tasks.get(id = id + "-" + stack) != null) { + stack++; + } + if (stack > 0) { + Log.d(TAG, "Task stacked to: " + id); + } + task(id, idle_for, runnable); + } + + public static void kill(final String id) { + tasks.remove(id); + } + + public static boolean waiting(final String id) { + return tasks.containsKey(id); + } + + private static class Task { + private long when; + private final Runnable what; + private final String id; + + Task(String id, long offset, Runnable what) { + this.what = what; + this.id = id; + extendTime(offset); + } + + public void extendTime(long offset) { + this.when = DateUtil.now() + offset; + } + + public boolean poll() { + final long till = JoH.msTill(when); + if (till < 1) { + if (d) Log.d(TAG, "Executing task! " + this.id); + tasks.remove(this.id); // early remove to allow overlapping scheduling + what.run(); + return true; + } else if (till > MAX_QUEUE_TIME) { + Log.wtf(TAG, "Task: " + this.id + " In queue too long: " + till); + tasks.remove(this.id); + return true; + } + return false; + } + + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java new file mode 100644 index 0000000000..5e2cfe6fed --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/PersistentStore.java @@ -0,0 +1,138 @@ +package com.eveningoutpost.dexdrip.UtilityModels; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; + +import com.eveningoutpost.dexdrip.Models.JoH; +import com.google.common.primitives.Bytes; + +import info.nightscout.androidaps.MainApp; + +/** + * Created by jamorham on 23/09/2016. + *

+ * This is for internal data which is never backed up, + * separate file means it doesn't clutter prefs + * we can afford to lose it, it is for internal states + * and is alternative to static variables which get + * flushed when classes are destroyed by garbage collection + *

+ * It is suitable for cache type variables where losing + * state will cause problems. Obviously it will be slower than + * pure in-memory state variables. + */ + + +public class PersistentStore { + + private static final String DATA_STORE_INTERNAL = "persist_internal_store"; + private static SharedPreferences prefs; + private static final boolean d = false; // debug flag + + public static String getString(final String name) { + return prefs.getString(name, ""); + } + + static { + try { + prefs = MainApp.instance() + .getSharedPreferences(DATA_STORE_INTERNAL, Context.MODE_PRIVATE); + } catch (NullPointerException e) { + android.util.Log.e("PersistentStore", "Failed to get context on init!!! nothing will work"); + } + } + + public static void setString(final String name, String value) { + prefs.edit().putString(name, value).apply(); + } + + // if string is different to what we have stored then update and return true + public static boolean updateStringIfDifferent(final String name, final String current) { + if (current == null) return false; // can't handle nulls + if (PersistentStore.getString(name).equals(current)) return false; + PersistentStore.setString(name, current); + return true; + } + + public static void appendString(String name, String value) { + setString(name, getString(name) + value); + } + + public static void appendString(String name, String value, String delimiter) { + String current = getString(name); + if (current.length() > 0) current += delimiter; + setString(name, current + value); + } + + public static void appendBytes(String name, byte[] value) { + setBytes(name, Bytes.concat(getBytes(name), value)); + } + + public static byte[] getBytes(String name) { + return JoH.base64decodeBytes(getString(name)); + } + + public static byte getByte(String name) { + return (byte) getLong(name); + } + + public static void setBytes(String name, byte[] value) { + setString(name, JoH.base64encodeBytes(value)); + } + + public static void setByte(String name, byte value) { + setLong(name, value); + } + + public static long getLong(String name) { + return prefs.getLong(name, 0); + } + + public static float getFloat(String name) { + return prefs.getFloat(name, 0); + } + + public static void setLong(String name, long value) { + prefs.edit().putLong(name, value).apply(); + } + + public static void setFloat(String name, float value) { + prefs.edit().putFloat(name, value).apply(); + } + + public static void setDouble(String name, double value) { + setLong(name, Double.doubleToRawLongBits(value)); + } + + public static double getDouble(String name) { + return Double.longBitsToDouble(getLong(name)); + } + + public static boolean getBoolean(String name) { + return prefs.getBoolean(name, false); + } + + public static boolean getBoolean(String name, boolean value) { + return prefs.getBoolean(name, value); + } + + public static void setBoolean(String name, boolean value) { + prefs.edit().putBoolean(name, value).apply(); + } + + public static long incrementLong(String name) { + final long val = getLong(name) + 1; + setLong(name, val); + return val; + } + + public static void setLongZeroIfSet(String name) { + if (getLong(name) > 0) setLong(name, 0); + } + + @SuppressLint("ApplySharedPref") + public static void commit() { + prefs.edit().commit(); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java new file mode 100644 index 0000000000..745b0ae6e4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/StatusItem.java @@ -0,0 +1,98 @@ +package com.eveningoutpost.dexdrip.UtilityModels; + +import android.graphics.Color; +import android.support.annotation.ColorInt; + +import com.google.common.base.MoreObjects; + +import java.util.HashMap; + +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.BAD; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.CRITICAL; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.GOOD; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.NORMAL; +import static com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight.NOTICE; + +/** + * Created by jamorham on 14/01/2017. + *

+ * For representing row items suitable for MegaStatus + */ + +public class StatusItem { + + private static final HashMap colorHints = new HashMap<>(); + + static { + colorHints.put(NORMAL, Color.TRANSPARENT); + colorHints.put(GOOD, Color.parseColor("#003000")); + colorHints.put(BAD, Color.parseColor("#480000")); + colorHints.put(NOTICE, Color.parseColor("#403000")); + colorHints.put(CRITICAL, Color.parseColor("#770000")); + } + + public enum Highlight { + NORMAL, + GOOD, + BAD, + NOTICE, + CRITICAL; + + @ColorInt + public int color() { + return colorHint(this); + } + + } + + public String name; + public String value; + public Highlight highlight; + public String button_name; + public Runnable runnable; + + + public StatusItem(String name, String value) { + this(name, value, NORMAL); + } + + public StatusItem() { + this("line-break", "", NORMAL); + } + + public StatusItem(String name, Highlight highlight) { + this("heading-break", name, highlight); + } + + public StatusItem(String name, Runnable runnable) { + this("button-break", "", NORMAL, name, runnable); + } + + public StatusItem(String name, String value, Highlight highlight) { + this(name, value, highlight, null, null); + } + + public StatusItem(String name, String value, Highlight highlight, String button_name, Runnable runnable) { + this.name = name; + this.value = value; + this.highlight = highlight; + this.button_name = button_name; + this.runnable = runnable; + } + + public StatusItem(String name, Integer value) { + this(name, value, NORMAL); + } + + public StatusItem(String name, Integer value, Highlight highlight) { + this.name = name; + this.value = Integer.toString(value); + this.highlight = highlight; + } + + @ColorInt + public static int colorHint(final Highlight highlight) { + return MoreObjects.firstNonNull(colorHints.get(highlight), Color.TRANSPARENT); + } + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java new file mode 100644 index 0000000000..3cb4e15e88 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/store/FastStore.java @@ -0,0 +1,53 @@ +package com.eveningoutpost.dexdrip.store; + +import java.util.HashMap; + +/** + * Created by jamorham on 08/11/2017. + *

+ * Fast implementation of KeyStore interface + * Uses an in-memory database for short lived data elements + * Content is expired as per normal garbage collection + * Neutral defaults favoured over null return values + * Static creation for fastest shared instance access + */ + +public class FastStore implements KeyStore { + + private static final FastStore mFastStore = new FastStore(); + private static final HashMap stringStore = new HashMap<>(); + private static final HashMap longStore = new HashMap<>(); + + // we trade substitution flexibility at the expense of some object creation + + private FastStore() { + // use getInstance! + } + + public static FastStore getInstance() { + return mFastStore; + } + + // interface methods + + public String getS(String key) { + if (stringStore.containsKey(key)) return stringStore.get(key); + return ""; + } + + public void putS(String key, String value) { + stringStore.put(key, value); + } + + public long getL(String key) { + if (longStore.containsKey(key)) return longStore.get(key); + return 0; + } + + public void putL(String key, long value) { + longStore.put(key, value); + } + +} + + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java b/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java new file mode 100644 index 0000000000..3ea19214f6 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/store/KeyStore.java @@ -0,0 +1,28 @@ +package com.eveningoutpost.dexdrip.store; + +/** + * Created by jamorham on 08/11/2017. + * + * KeyStore is a persistence interface allowing storage and retrieval of primitive data types + * referenced by a String based key. + * + * Implementations may choose how long data is retained for. + * Typical usage might include caching expensive function results + * + */ + +public interface KeyStore { + + // java type erasure prevents us using generics and then implementing multiple generic interfaces + // so storage types we are interested in get their own interface methods + + void putS(String key, String value); + + String getS(String key); + + void putL(String key, long value); + + long getL(String key); + + +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java index 285f0b013b..384942e859 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EBolus.java @@ -2,6 +2,8 @@ package com.eveningoutpost.dexdrip.tidepool; import com.google.gson.annotations.Expose; +import info.nightscout.androidaps.plugins.treatments.Treatment; + // jamorham public class EBolus extends BaseElement { @@ -23,4 +25,8 @@ public class EBolus extends BaseElement { populate(timestamp, uuid); } + public static EBolus fromTreatment(Treatment treatment) { + return new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS"); + } + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java index 857c7905ad..45a4ba7732 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/ESensorGlucose.java @@ -4,6 +4,11 @@ package com.eveningoutpost.dexdrip.tidepool; import com.google.gson.annotations.Expose; +import java.util.LinkedList; +import java.util.List; + +import info.nightscout.androidaps.db.BgReading; + public class ESensorGlucose extends BaseElement { @@ -17,11 +22,11 @@ public class ESensorGlucose extends BaseElement { this.units = "mg/dL"; } -/* + static ESensorGlucose fromBgReading(final BgReading bgReading) { final ESensorGlucose sensorGlucose = new ESensorGlucose(); - sensorGlucose.populate(bgReading.timestamp, bgReading.uuid); - sensorGlucose.value = (int) bgReading.calculated_value; // TODO best glucose? + sensorGlucose.populate(bgReading.date, "uuid-AAPS"); + sensorGlucose.value = (int) bgReading.value; // TODO best glucose? return sensorGlucose; } @@ -33,5 +38,5 @@ public class ESensorGlucose extends BaseElement { } return results; } -*/ + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java index 08de1a390c..64d5b87c34 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/EWizard.java @@ -2,6 +2,9 @@ package com.eveningoutpost.dexdrip.tidepool; import com.google.gson.annotations.Expose; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.treatments.Treatment; + // jamorham public class EWizard extends BaseElement { @@ -18,17 +21,17 @@ public class EWizard extends BaseElement { EWizard() { type = "wizard"; } -/* - public static EWizard fromTreatment(final Treatments treatment) { - final EWizard result = (EWizard)new EWizard().populate(treatment.timestamp, treatment.uuid); + + public static EWizard fromTreatment(final Treatment treatment) { + final EWizard result = (EWizard)new EWizard().populate(treatment.date, "uuid-AAPS"); result.carbInput = treatment.carbs; - result.insulinCarbRatio = Profile.getCarbRatio(treatment.timestamp); + result.insulinCarbRatio = ProfileFunctions.getInstance().getProfile(treatment.date).getIc(); if (treatment.insulin > 0) { - result.bolus = new EBolus(treatment.insulin, treatment.insulin, treatment.timestamp, treatment.uuid); + result.bolus = new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS"); } else { - result.bolus = new EBolus(0.0001,0.0001, treatment.timestamp, treatment.uuid); // fake insulin record + result.bolus = new EBolus(0.0001,0.0001, treatment.date, "uuid-AAPS"); // fake insulin record } return result; } -*/ + } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java index 1cd5ef5e25..26f7901cda 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/TidepoolEntry.java @@ -4,24 +4,25 @@ package com.eveningoutpost.dexdrip.tidepool; // lightweight class entry point -import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.UtilityModels.Pref; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.receivers.ChargingStateReceiver; +import info.nightscout.androidaps.utils.SP; import static com.eveningoutpost.dexdrip.Models.JoH.isLANConnected; -import static com.eveningoutpost.dexdrip.utils.PowerStateReceiver.is_power_connected; public class TidepoolEntry { public static boolean enabled() { - return Pref.getBooleanDefaultFalse("cloud_storage_tidepool_enable"); + return SP.getBoolean(R.string.key_cloud_storage_tidepool_enable, false); } public static void newData() { if (enabled() - && (!Pref.getBooleanDefaultFalse("tidepool_only_while_charging") || is_power_connected()) - && (!Pref.getBooleanDefaultFalse("tidepool_only_while_unmetered") || isLANConnected()) - && JoH.pratelimit("tidepool-new-data-upload", 1200)) { + && (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) + && (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || isLANConnected()) + // && JoH.pratelimit("tidepool-new-data-upload", 1200) + ) { TidepoolUploader.doLogin(false); } } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java index 9d8a580bbe..61b734bc3b 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/tidepool/UploadChunk.java @@ -1,28 +1,34 @@ package com.eveningoutpost.dexdrip.tidepool; -import com.eveningoutpost.dexdrip.Models.APStatus; -import com.eveningoutpost.dexdrip.Models.BgReading; -import com.eveningoutpost.dexdrip.Models.BloodTest; +import android.util.Log; + import com.eveningoutpost.dexdrip.Models.JoH; -import com.eveningoutpost.dexdrip.Models.Profile; -import com.eveningoutpost.dexdrip.Models.Treatments; -import com.eveningoutpost.dexdrip.Models.UserError; -import com.eveningoutpost.dexdrip.UtilityModels.Constants; import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore; -import com.eveningoutpost.dexdrip.UtilityModels.Pref; import com.eveningoutpost.dexdrip.utils.LogSlider; import com.eveningoutpost.dexdrip.utils.NamedSliderProcessor; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.UUID; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +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.SP; +import info.nightscout.androidaps.utils.T; + import static com.eveningoutpost.dexdrip.Models.JoH.dateTimeText; /** * jamorham - * + *

* This class gets the next time slice of all data to upload */ @@ -31,8 +37,8 @@ public class UploadChunk implements NamedSliderProcessor { private static final String TAG = "TidepoolUploadChunk"; private static final String LAST_UPLOAD_END_PREF = "tidepool-last-end"; - private static final long MAX_UPLOAD_SIZE = Constants.DAY_IN_MS * 7; // don't change this - private static final long DEFAULT_WINDOW_OFFSET = Constants.MINUTE_IN_MS * 15; + private static final long MAX_UPLOAD_SIZE = T.days(7).msecs(); // don't change this + private static final long DEFAULT_WINDOW_OFFSET = T.mins(15).msecs(); private static final long MAX_LATENCY_THRESHOLD_MINUTES = 1440; // minutes per day @@ -42,7 +48,7 @@ public class UploadChunk implements NamedSliderProcessor { final String result = get(session.start, session.end); if (result != null && result.length() < 3) { - UserError.Log.d(TAG, "No records in this time period, setting start to best end time"); + Log.d(TAG, "No records in this time period, setting start to best end time"); setLastEnd(Math.max(session.end, getOldestRecordTimeStamp())); } return result; @@ -50,13 +56,13 @@ public class UploadChunk implements NamedSliderProcessor { public static String get(final long start, final long end) { - UserError.Log.uel(TAG, "Syncing data between: " + dateTimeText(start) + " -> " + dateTimeText(end)); + Log.e(TAG, "Syncing data between: " + dateTimeText(start) + " -> " + dateTimeText(end)); if (end <= start) { - UserError.Log.e(TAG, "End is <= start: " + dateTimeText(start) + " " + dateTimeText(end)); + Log.e(TAG, "End is <= start: " + dateTimeText(start) + " " + dateTimeText(end)); return null; } if (end - start > MAX_UPLOAD_SIZE) { - UserError.Log.e(TAG, "More than max range - rejecting"); + Log.e(TAG, "More than max range - rejecting"); return null; } @@ -72,37 +78,37 @@ public class UploadChunk implements NamedSliderProcessor { private static long getWindowSizePreference() { try { - long value = (long) getLatencySliderValue(Pref.getInt("tidepool_window_latency", 0)); - return Math.max(value * Constants.MINUTE_IN_MS, DEFAULT_WINDOW_OFFSET); + long value = (long) getLatencySliderValue(SP.getInt(R.string.key_tidepool_window_latency, 0)); + return Math.max(T.mins(value).msecs(), DEFAULT_WINDOW_OFFSET); } catch (Exception e) { - UserError.Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: " + e); + Log.e(TAG, "Reverting to default of 15 minutes due to Window Size exception: " + e); return DEFAULT_WINDOW_OFFSET; // default } } private static long maxWindow(final long last_end) { - //UserError.Log.d(TAG, "Max window is: " + getWindowSizePreference()); - return Math.min(last_end + MAX_UPLOAD_SIZE, JoH.tsl() - getWindowSizePreference()); + //Log.d(TAG, "Max window is: " + getWindowSizePreference()); + return Math.min(last_end + MAX_UPLOAD_SIZE, DateUtil.now() - getWindowSizePreference()); } public static long getLastEnd() { long result = PersistentStore.getLong(LAST_UPLOAD_END_PREF); - return Math.max(result, JoH.tsl() - Constants.MONTH_IN_MS * 2); + return Math.max(result, DateUtil.now() - T.months(2).msecs()); } public static void setLastEnd(final long when) { if (when > getLastEnd()) { PersistentStore.setLong(LAST_UPLOAD_END_PREF, when); - UserError.Log.d(TAG, "Updating last end to: " + dateTimeText(when)); + Log.d(TAG, "Updating last end to: " + dateTimeText(when)); } else { - UserError.Log.e(TAG, "Cannot set last end to: " + dateTimeText(when) + " vs " + dateTimeText(getLastEnd())); + Log.e(TAG, "Cannot set last end to: " + dateTimeText(when) + " vs " + dateTimeText(getLastEnd())); } } static List getTreatments(final long start, final long end) { List result = new LinkedList<>(); - final List treatments = Treatments.latestForGraph(1800, start, end); - for (Treatments treatment : treatments) { + final List treatments = TreatmentsPlugin.getPlugin().getService().getTreatmentDataFromTime(start, end, true); + for (Treatment treatment : treatments) { if (treatment.carbs > 0) { result.add(EWizard.fromTreatment(treatment)); } else if (treatment.insulin > 0) { @@ -121,46 +127,47 @@ public class UploadChunk implements NamedSliderProcessor { // TODO we could make sure we include records older than the first bg record for completeness final long start = 0; - final long end = JoH.tsl(); + final long end = DateUtil.now(); - final List bgReadingList = BgReading.latestForGraphAsc(1, start, end); + final List bgReadingList = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, false); if (bgReadingList != null && bgReadingList.size() > 0) { - return bgReadingList.get(0).timestamp; + return bgReadingList.get(0).date; } return -1; } static List getBloodTests(final long start, final long end) { - return EBloodGlucose.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); + return new ArrayList<>(); +// return EBloodGlucose.fromBloodTests(BloodTest.latestForGraph(1800, start, end)); } static List getBgReadings(final long start, final long end) { - return ESensorGlucose.fromBgReadings(BgReading.latestForGraphAsc(15000, start, end)); + return ESensorGlucose.fromBgReadings(MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, true)); } static List getBasals(final long start, final long end) { final List basals = new LinkedList<>(); - final List aplist = APStatus.latestForGraph(15000, start, end); + final List aplist = MainApp.getDbHelper().getTemporaryBasalsDataFromTime(start, end, true); EBasal current = null; - for (APStatus apStatus : aplist) { - final double this_rate = Profile.getBasalRate(apStatus.timestamp) * apStatus.basal_percent / 100d; + for (TemporaryBasal temporaryBasal : aplist) { + final double this_rate = temporaryBasal.tempBasalConvertedToAbsolute(temporaryBasal.date, ProfileFunctions.getInstance().getProfile(temporaryBasal.date)); if (current != null) { if (this_rate != current.rate) { - current.duration = apStatus.timestamp - current.timestamp; - UserError.Log.d(TAG, "Adding current: " + current.toS()); + current.duration = temporaryBasal.date - current.timestamp; + Log.d(TAG, "Adding current: " + current.toS()); if (current.isValid()) { basals.add(current); } else { - UserError.Log.e(TAG, "Current basal is invalid: " + current.toS()); + Log.e(TAG, "Current basal is invalid: " + current.toS()); } current = null; } else { - UserError.Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + apStatus.toS()); + Log.d(TAG, "Same rate as previous basal record: " + current.rate + " " + temporaryBasal.toStringFull()); } } if (current == null) { - current = new EBasal(this_rate, apStatus.timestamp, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + apStatus.timestamp).getBytes()).toString()); // start duration is 0 + current = new EBasal(this_rate, temporaryBasal.date, 0, UUID.nameUUIDFromBytes(("tidepool-basal" + temporaryBasal.date).getBytes()).toString()); // start duration is 0 } } return basals; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java new file mode 100644 index 0000000000..014d085ae4 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/LogSlider.java @@ -0,0 +1,13 @@ +package com.eveningoutpost.dexdrip.utils; + +// jamorham + +public class LogSlider { + + // logarithmic slider with positions start - end representing values start - end, calculate value at selected position + public static double calc(int sliderStart, int sliderEnd, double valueStart, double valueEnd, int position) { + valueStart = Math.log(Math.max(1, valueStart)); + valueEnd = Math.log(Math.max(1, valueEnd)); + return Math.exp(valueStart + (valueEnd - valueStart) / (sliderEnd - sliderStart) * (position - sliderStart)); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java b/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java new file mode 100644 index 0000000000..a13e4b05b1 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utils/NamedSliderProcessor.java @@ -0,0 +1,11 @@ +package com.eveningoutpost.dexdrip.utils; + +// jamorham + +// interpolate a slider by name from a supporting class + +public interface NamedSliderProcessor { + + int interpolate(String name, int position); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 720e07dd15..7f1b527d00 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -50,6 +50,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; @@ -204,6 +205,7 @@ public class MainApp extends Application { pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(PersistentNotificationPlugin.getPlugin()); pluginsList.add(NSClientPlugin.getPlugin()); + pluginsList.add(TidepoolPlugin.getPlugin()); pluginsList.add(MaintenancePlugin.initPlugin(this)); pluginsList.add(ConfigBuilderPlugin.getPlugin()); diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index eb51f5e2ba..4bddd714eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -596,7 +596,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } - // -------------------- TREATMENT HANDLING ------------------- + // -------------------- TEMPTARGET HANDLING ------------------- public static void updateEarliestDataChange(long newDate) { if (earliestDataChange == null) { @@ -627,6 +627,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return new ArrayList(); } + public List getTemptargetsDataFromTime(long from, long to, boolean ascending) { + try { + Dao daoTempTargets = getDaoTempTargets(); + List tempTargets; + QueryBuilder queryBuilder = daoTempTargets.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tempTargets = daoTempTargets.query(preparedQuery); + return tempTargets; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList(); + } + public boolean createOrUpdate(TempTarget tempTarget) { try { TempTarget old; @@ -950,6 +967,22 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return new ArrayList(); } + public List getTemporaryBasalsDataFromTime(long from, long to, boolean ascending) { + try { + List tempbasals; + QueryBuilder queryBuilder = getDaoTemporaryBasal().queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tempbasals = getDaoTemporaryBasal().query(preparedQuery); + return tempbasals; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList(); + } + private static void scheduleTemporaryBasalChange() { class PostRunnable implements Runnable { public void run() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java new file mode 100644 index 0000000000..2d00825820 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.plugins.general.tidepool; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.R; +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.source.BGSourceFragment; + +/** + * Created by mike on 28.11.2017. + */ + +public class TidepoolPlugin extends PluginBase { + private static Logger log = LoggerFactory.getLogger(L.DATABASE); + + private static TidepoolPlugin plugin = null; + + public static TidepoolPlugin getPlugin() { + if (plugin == null) + plugin = new TidepoolPlugin(); + return plugin; + } + + private TidepoolPlugin() { + super(new PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment.class.getName()) + .pluginName(R.string.tidepool) + .shortName(R.string.tidepool_shortname) + .preferencesId(R.xml.pref_tidepool) + .description(R.string.description_tidepool) + ); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java index 502b7f38b7..842c01d9ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -500,6 +500,23 @@ public class TreatmentService extends OrmLiteBaseService { return new ArrayList<>(); } + public List getTreatmentDataFromTime(long from, long to, boolean ascending) { + try { + Dao daoTreatments = getDao(); + List treatments; + QueryBuilder queryBuilder = daoTreatments.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery 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) { diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java index b10c2e99e5..349e1771b9 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java @@ -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; + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/T.java b/app/src/main/java/info/nightscout/androidaps/utils/T.java index 2a9bcfc42c..5671f0725c 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/T.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/T.java @@ -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; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67c746535f..798c076d05 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1329,6 +1329,21 @@ tidepool_username tidepool_password tidepool_dev_servers + tidepool_window_latency + tidepool_test_login + tidepool_only_while_charging + tidepool_only_while_unmetered + cloud_storage_tidepool_enable + Upload data to the Tidepool service + Sync to Tidepool + Your Tidepool login user name, normally your email address + Login User Name + Your Tidepool login password + Login Password + Test Tidepool Login + Data Age Mins + If enabled, uploads will go to https://int-app.tidepool.org instead of the regular https://app.tidepool.org/ + Use Integration (test) servers smbmaxminutes Dayligh Saving time @@ -1348,6 +1363,9 @@ old version very old version New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days + Tidepool + TDP + Uploads data to Tidepool %1$d day diff --git a/app/src/main/res/xml/pref_tidepool.xml b/app/src/main/res/xml/pref_tidepool.xml new file mode 100644 index 0000000000..a01b600df1 --- /dev/null +++ b/app/src/main/res/xml/pref_tidepool.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + +