add missing classes. convert to use AAPS data
This commit is contained in:
parent
dd637021f7
commit
9b1acf6958
23 changed files with 1322 additions and 57 deletions
|
@ -283,8 +283,13 @@ dependencies {
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor: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'
|
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) {
|
task unzip(type: Copy) {
|
||||||
|
|
|
@ -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<BloodTest> 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<BloodTest> btl = last(1);
|
||||||
|
if ((btl != null) && (btl.size() > 0)) {
|
||||||
|
return btl.get(0);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BloodTest> 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<BloodTest> 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<BloodTest> btl = lastValid(1);
|
||||||
|
if ((btl != null) && (btl.size() > 0)) {
|
||||||
|
return btl.get(0);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BloodTest> 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<BloodTest> btl) {
|
||||||
|
if (btl == null) return null;
|
||||||
|
final List<BloodTestMessage> 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<BloodTest> latestForGraph(int number, double startTime) {
|
||||||
|
return latestForGraph(number, (long) startTime, Long.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BloodTest> latestForGraph(int number, long startTime) {
|
||||||
|
return latestForGraph(number, startTime, Long.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BloodTest> 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<BloodTest> bloodTests = latestForGraph(1000, JoH.tsl() - period, JoH.tsl() - AddCalibration.estimatedInterstitialLagSeconds);
|
||||||
|
final List<Double> difference = new ArrayList<>();
|
||||||
|
final List<Double> 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<BloodTest> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
package com.eveningoutpost.dexdrip.Models;
|
package com.eveningoutpost.dexdrip.Models;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
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.DecimalFormat;
|
||||||
import java.text.DecimalFormatSymbols;
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
import info.nightscout.androidaps.MainApp;
|
import info.nightscout.androidaps.MainApp;
|
||||||
import info.nightscout.androidaps.R;
|
import info.nightscout.androidaps.R;
|
||||||
|
import info.nightscout.androidaps.utils.DateUtil;
|
||||||
|
|
||||||
public class JoH {
|
public class JoH {
|
||||||
|
|
||||||
|
@ -19,6 +27,14 @@ public class JoH {
|
||||||
return android.text.format.DateFormat.format("yyyy-MM-dd kk:mm:ss", timestamp).toString();
|
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) {
|
public static String niceTimeScalar(long t) {
|
||||||
String unit = MainApp.gs(R.string.unit_second);
|
String unit = MainApp.gs(R.string.unit_second);
|
||||||
t = t / 1000;
|
t = t / 1000;
|
||||||
|
@ -122,5 +138,46 @@ public class JoH {
|
||||||
return wl;
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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<String, Task> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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
|
||||||
|
* <p>
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* For representing row items suitable for MegaStatus
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class StatusItem {
|
||||||
|
|
||||||
|
private static final HashMap<Highlight, Integer> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.eveningoutpost.dexdrip.store;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by jamorham on 08/11/2017.
|
||||||
|
* <p>
|
||||||
|
* 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<String, String> stringStore = new HashMap<>();
|
||||||
|
private static final HashMap<String, Long> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ package com.eveningoutpost.dexdrip.tidepool;
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.treatments.Treatment;
|
||||||
|
|
||||||
// jamorham
|
// jamorham
|
||||||
|
|
||||||
public class EBolus extends BaseElement {
|
public class EBolus extends BaseElement {
|
||||||
|
@ -23,4 +25,8 @@ public class EBolus extends BaseElement {
|
||||||
populate(timestamp, uuid);
|
populate(timestamp, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static EBolus fromTreatment(Treatment treatment) {
|
||||||
|
return new EBolus(treatment.insulin, treatment.insulin, treatment.date, "uuid-AAPS");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,11 @@ package com.eveningoutpost.dexdrip.tidepool;
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
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 {
|
public class ESensorGlucose extends BaseElement {
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,11 +22,11 @@ public class ESensorGlucose extends BaseElement {
|
||||||
this.units = "mg/dL";
|
this.units = "mg/dL";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
static ESensorGlucose fromBgReading(final BgReading bgReading) {
|
static ESensorGlucose fromBgReading(final BgReading bgReading) {
|
||||||
final ESensorGlucose sensorGlucose = new ESensorGlucose();
|
final ESensorGlucose sensorGlucose = new ESensorGlucose();
|
||||||
sensorGlucose.populate(bgReading.timestamp, bgReading.uuid);
|
sensorGlucose.populate(bgReading.date, "uuid-AAPS");
|
||||||
sensorGlucose.value = (int) bgReading.calculated_value; // TODO best glucose?
|
sensorGlucose.value = (int) bgReading.value; // TODO best glucose?
|
||||||
return sensorGlucose;
|
return sensorGlucose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,5 +38,5 @@ public class ESensorGlucose extends BaseElement {
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ package com.eveningoutpost.dexdrip.tidepool;
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions;
|
||||||
|
import info.nightscout.androidaps.plugins.treatments.Treatment;
|
||||||
|
|
||||||
// jamorham
|
// jamorham
|
||||||
|
|
||||||
public class EWizard extends BaseElement {
|
public class EWizard extends BaseElement {
|
||||||
|
@ -18,17 +21,17 @@ public class EWizard extends BaseElement {
|
||||||
EWizard() {
|
EWizard() {
|
||||||
type = "wizard";
|
type = "wizard";
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
public static EWizard fromTreatment(final Treatments treatment) {
|
public static EWizard fromTreatment(final Treatment treatment) {
|
||||||
final EWizard result = (EWizard)new EWizard().populate(treatment.timestamp, treatment.uuid);
|
final EWizard result = (EWizard)new EWizard().populate(treatment.date, "uuid-AAPS");
|
||||||
result.carbInput = treatment.carbs;
|
result.carbInput = treatment.carbs;
|
||||||
result.insulinCarbRatio = Profile.getCarbRatio(treatment.timestamp);
|
result.insulinCarbRatio = ProfileFunctions.getInstance().getProfile(treatment.date).getIc();
|
||||||
if (treatment.insulin > 0) {
|
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 {
|
} 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;
|
return result;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,25 @@ package com.eveningoutpost.dexdrip.tidepool;
|
||||||
|
|
||||||
// lightweight class entry point
|
// lightweight class entry point
|
||||||
|
|
||||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
import info.nightscout.androidaps.R;
|
||||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
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.Models.JoH.isLANConnected;
|
||||||
import static com.eveningoutpost.dexdrip.utils.PowerStateReceiver.is_power_connected;
|
|
||||||
|
|
||||||
public class TidepoolEntry {
|
public class TidepoolEntry {
|
||||||
|
|
||||||
|
|
||||||
public static boolean enabled() {
|
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() {
|
public static void newData() {
|
||||||
if (enabled()
|
if (enabled()
|
||||||
&& (!Pref.getBooleanDefaultFalse("tidepool_only_while_charging") || is_power_connected())
|
&& (!SP.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging())
|
||||||
&& (!Pref.getBooleanDefaultFalse("tidepool_only_while_unmetered") || isLANConnected())
|
&& (!SP.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || isLANConnected())
|
||||||
&& JoH.pratelimit("tidepool-new-data-upload", 1200)) {
|
// && JoH.pratelimit("tidepool-new-data-upload", 1200)
|
||||||
|
) {
|
||||||
TidepoolUploader.doLogin(false);
|
TidepoolUploader.doLogin(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,34 @@
|
||||||
package com.eveningoutpost.dexdrip.tidepool;
|
package com.eveningoutpost.dexdrip.tidepool;
|
||||||
|
|
||||||
|
|
||||||
import com.eveningoutpost.dexdrip.Models.APStatus;
|
import android.util.Log;
|
||||||
import com.eveningoutpost.dexdrip.Models.BgReading;
|
|
||||||
import com.eveningoutpost.dexdrip.Models.BloodTest;
|
|
||||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
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.PersistentStore;
|
||||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
|
||||||
import com.eveningoutpost.dexdrip.utils.LogSlider;
|
import com.eveningoutpost.dexdrip.utils.LogSlider;
|
||||||
import com.eveningoutpost.dexdrip.utils.NamedSliderProcessor;
|
import com.eveningoutpost.dexdrip.utils.NamedSliderProcessor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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;
|
import static com.eveningoutpost.dexdrip.Models.JoH.dateTimeText;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* jamorham
|
* jamorham
|
||||||
*
|
* <p>
|
||||||
* This class gets the next time slice of all data to upload
|
* 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 TAG = "TidepoolUploadChunk";
|
||||||
private static final String LAST_UPLOAD_END_PREF = "tidepool-last-end";
|
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 MAX_UPLOAD_SIZE = T.days(7).msecs(); // don't change this
|
||||||
private static final long DEFAULT_WINDOW_OFFSET = Constants.MINUTE_IN_MS * 15;
|
private static final long DEFAULT_WINDOW_OFFSET = T.mins(15).msecs();
|
||||||
private static final long MAX_LATENCY_THRESHOLD_MINUTES = 1440; // minutes per day
|
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);
|
final String result = get(session.start, session.end);
|
||||||
if (result != null && result.length() < 3) {
|
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()));
|
setLastEnd(Math.max(session.end, getOldestRecordTimeStamp()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -50,13 +56,13 @@ public class UploadChunk implements NamedSliderProcessor {
|
||||||
|
|
||||||
public static String get(final long start, final long end) {
|
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) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
if (end - start > MAX_UPLOAD_SIZE) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,37 +78,37 @@ public class UploadChunk implements NamedSliderProcessor {
|
||||||
|
|
||||||
private static long getWindowSizePreference() {
|
private static long getWindowSizePreference() {
|
||||||
try {
|
try {
|
||||||
long value = (long) getLatencySliderValue(Pref.getInt("tidepool_window_latency", 0));
|
long value = (long) getLatencySliderValue(SP.getInt(R.string.key_tidepool_window_latency, 0));
|
||||||
return Math.max(value * Constants.MINUTE_IN_MS, DEFAULT_WINDOW_OFFSET);
|
return Math.max(T.mins(value).msecs(), DEFAULT_WINDOW_OFFSET);
|
||||||
} catch (Exception e) {
|
} 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
|
return DEFAULT_WINDOW_OFFSET; // default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long maxWindow(final long last_end) {
|
private static long maxWindow(final long last_end) {
|
||||||
//UserError.Log.d(TAG, "Max window is: " + getWindowSizePreference());
|
//Log.d(TAG, "Max window is: " + getWindowSizePreference());
|
||||||
return Math.min(last_end + MAX_UPLOAD_SIZE, JoH.tsl() - getWindowSizePreference());
|
return Math.min(last_end + MAX_UPLOAD_SIZE, DateUtil.now() - getWindowSizePreference());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long getLastEnd() {
|
public static long getLastEnd() {
|
||||||
long result = PersistentStore.getLong(LAST_UPLOAD_END_PREF);
|
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) {
|
public static void setLastEnd(final long when) {
|
||||||
if (when > getLastEnd()) {
|
if (when > getLastEnd()) {
|
||||||
PersistentStore.setLong(LAST_UPLOAD_END_PREF, when);
|
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 {
|
} 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<BaseElement> getTreatments(final long start, final long end) {
|
static List<BaseElement> getTreatments(final long start, final long end) {
|
||||||
List<BaseElement> result = new LinkedList<>();
|
List<BaseElement> result = new LinkedList<>();
|
||||||
final List<Treatments> treatments = Treatments.latestForGraph(1800, start, end);
|
final List<Treatment> treatments = TreatmentsPlugin.getPlugin().getService().getTreatmentDataFromTime(start, end, true);
|
||||||
for (Treatments treatment : treatments) {
|
for (Treatment treatment : treatments) {
|
||||||
if (treatment.carbs > 0) {
|
if (treatment.carbs > 0) {
|
||||||
result.add(EWizard.fromTreatment(treatment));
|
result.add(EWizard.fromTreatment(treatment));
|
||||||
} else if (treatment.insulin > 0) {
|
} 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
|
// TODO we could make sure we include records older than the first bg record for completeness
|
||||||
|
|
||||||
final long start = 0;
|
final long start = 0;
|
||||||
final long end = JoH.tsl();
|
final long end = DateUtil.now();
|
||||||
|
|
||||||
final List<BgReading> bgReadingList = BgReading.latestForGraphAsc(1, start, end);
|
final List<BgReading> bgReadingList = MainApp.getDbHelper().getBgreadingsDataFromTime(start, end, false);
|
||||||
if (bgReadingList != null && bgReadingList.size() > 0) {
|
if (bgReadingList != null && bgReadingList.size() > 0) {
|
||||||
return bgReadingList.get(0).timestamp;
|
return bgReadingList.get(0).date;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<EBloodGlucose> getBloodTests(final long start, final long end) {
|
static List<EBloodGlucose> 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<ESensorGlucose> getBgReadings(final long start, final long end) {
|
static List<ESensorGlucose> 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<EBasal> getBasals(final long start, final long end) {
|
static List<EBasal> getBasals(final long start, final long end) {
|
||||||
final List<EBasal> basals = new LinkedList<>();
|
final List<EBasal> basals = new LinkedList<>();
|
||||||
final List<APStatus> aplist = APStatus.latestForGraph(15000, start, end);
|
final List<TemporaryBasal> aplist = MainApp.getDbHelper().getTemporaryBasalsDataFromTime(start, end, true);
|
||||||
EBasal current = null;
|
EBasal current = null;
|
||||||
for (APStatus apStatus : aplist) {
|
for (TemporaryBasal temporaryBasal : aplist) {
|
||||||
final double this_rate = Profile.getBasalRate(apStatus.timestamp) * apStatus.basal_percent / 100d;
|
final double this_rate = temporaryBasal.tempBasalConvertedToAbsolute(temporaryBasal.date, ProfileFunctions.getInstance().getProfile(temporaryBasal.date));
|
||||||
|
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
if (this_rate != current.rate) {
|
if (this_rate != current.rate) {
|
||||||
current.duration = apStatus.timestamp - current.timestamp;
|
current.duration = temporaryBasal.date - current.timestamp;
|
||||||
UserError.Log.d(TAG, "Adding current: " + current.toS());
|
Log.d(TAG, "Adding current: " + current.toS());
|
||||||
if (current.isValid()) {
|
if (current.isValid()) {
|
||||||
basals.add(current);
|
basals.add(current);
|
||||||
} else {
|
} else {
|
||||||
UserError.Log.e(TAG, "Current basal is invalid: " + current.toS());
|
Log.e(TAG, "Current basal is invalid: " + current.toS());
|
||||||
}
|
}
|
||||||
current = null;
|
current = null;
|
||||||
} else {
|
} 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) {
|
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;
|
return basals;
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
|
@ -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.overview.OverviewPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
|
import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin;
|
import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
|
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
|
||||||
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin;
|
import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin;
|
||||||
|
@ -204,6 +205,7 @@ public class MainApp extends Application {
|
||||||
pluginsList.add(StatuslinePlugin.initPlugin(this));
|
pluginsList.add(StatuslinePlugin.initPlugin(this));
|
||||||
pluginsList.add(PersistentNotificationPlugin.getPlugin());
|
pluginsList.add(PersistentNotificationPlugin.getPlugin());
|
||||||
pluginsList.add(NSClientPlugin.getPlugin());
|
pluginsList.add(NSClientPlugin.getPlugin());
|
||||||
|
pluginsList.add(TidepoolPlugin.getPlugin());
|
||||||
pluginsList.add(MaintenancePlugin.initPlugin(this));
|
pluginsList.add(MaintenancePlugin.initPlugin(this));
|
||||||
|
|
||||||
pluginsList.add(ConfigBuilderPlugin.getPlugin());
|
pluginsList.add(ConfigBuilderPlugin.getPlugin());
|
||||||
|
|
|
@ -596,7 +596,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------- TREATMENT HANDLING -------------------
|
// -------------------- TEMPTARGET HANDLING -------------------
|
||||||
|
|
||||||
public static void updateEarliestDataChange(long newDate) {
|
public static void updateEarliestDataChange(long newDate) {
|
||||||
if (earliestDataChange == null) {
|
if (earliestDataChange == null) {
|
||||||
|
@ -627,6 +627,23 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
||||||
return new ArrayList<TempTarget>();
|
return new ArrayList<TempTarget>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TempTarget> getTemptargetsDataFromTime(long from, long to, boolean ascending) {
|
||||||
|
try {
|
||||||
|
Dao<TempTarget, Long> daoTempTargets = getDaoTempTargets();
|
||||||
|
List<TempTarget> tempTargets;
|
||||||
|
QueryBuilder<TempTarget, Long> queryBuilder = daoTempTargets.queryBuilder();
|
||||||
|
queryBuilder.orderBy("date", ascending);
|
||||||
|
Where where = queryBuilder.where();
|
||||||
|
where.between("date", from, to);
|
||||||
|
PreparedQuery<TempTarget> preparedQuery = queryBuilder.prepare();
|
||||||
|
tempTargets = daoTempTargets.query(preparedQuery);
|
||||||
|
return tempTargets;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Unhandled exception", e);
|
||||||
|
}
|
||||||
|
return new ArrayList<TempTarget>();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean createOrUpdate(TempTarget tempTarget) {
|
public boolean createOrUpdate(TempTarget tempTarget) {
|
||||||
try {
|
try {
|
||||||
TempTarget old;
|
TempTarget old;
|
||||||
|
@ -950,6 +967,22 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
|
||||||
return new ArrayList<TemporaryBasal>();
|
return new ArrayList<TemporaryBasal>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TemporaryBasal> getTemporaryBasalsDataFromTime(long from, long to, boolean ascending) {
|
||||||
|
try {
|
||||||
|
List<TemporaryBasal> tempbasals;
|
||||||
|
QueryBuilder<TemporaryBasal, Long> queryBuilder = getDaoTemporaryBasal().queryBuilder();
|
||||||
|
queryBuilder.orderBy("date", ascending);
|
||||||
|
Where where = queryBuilder.where();
|
||||||
|
where.between("date", from, to);
|
||||||
|
PreparedQuery<TemporaryBasal> preparedQuery = queryBuilder.prepare();
|
||||||
|
tempbasals = getDaoTemporaryBasal().query(preparedQuery);
|
||||||
|
return tempbasals;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Unhandled exception", e);
|
||||||
|
}
|
||||||
|
return new ArrayList<TemporaryBasal>();
|
||||||
|
}
|
||||||
|
|
||||||
private static void scheduleTemporaryBasalChange() {
|
private static void scheduleTemporaryBasalChange() {
|
||||||
class PostRunnable implements Runnable {
|
class PostRunnable implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -500,6 +500,23 @@ public class TreatmentService extends OrmLiteBaseService<DatabaseHelper> {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Treatment> getTreatmentDataFromTime(long from, long to, boolean ascending) {
|
||||||
|
try {
|
||||||
|
Dao<Treatment, Long> daoTreatments = getDao();
|
||||||
|
List<Treatment> treatments;
|
||||||
|
QueryBuilder<Treatment, Long> queryBuilder = daoTreatments.queryBuilder();
|
||||||
|
queryBuilder.orderBy("date", ascending);
|
||||||
|
Where where = queryBuilder.where();
|
||||||
|
where.between("date", from, to);
|
||||||
|
PreparedQuery<Treatment> preparedQuery = queryBuilder.prepare();
|
||||||
|
treatments = daoTreatments.query(preparedQuery);
|
||||||
|
return treatments;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("Unhandled exception", e);
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
|
|
|
@ -10,12 +10,15 @@ import info.nightscout.androidaps.events.EventChargingState;
|
||||||
|
|
||||||
public class ChargingStateReceiver extends BroadcastReceiver {
|
public class ChargingStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private static EventChargingState lastEvent;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
EventChargingState event = grabChargingState(context);
|
EventChargingState event = grabChargingState(context);
|
||||||
|
|
||||||
if (event != null)
|
if (event != null)
|
||||||
MainApp.bus().post(event);
|
MainApp.bus().post(event);
|
||||||
|
lastEvent = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventChargingState grabChargingState(Context context) {
|
public EventChargingState grabChargingState(Context context) {
|
||||||
|
@ -32,4 +35,7 @@ public class ChargingStateReceiver extends BroadcastReceiver {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public boolean isCharging() {
|
||||||
|
return lastEvent != null && lastEvent.isCharging;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -43,6 +43,12 @@ public class T {
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T months(long month) {
|
||||||
|
T t = new T();
|
||||||
|
t.time = month * 31 * 24 * 60 * 60 * 1000L;
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
public long msecs() {
|
public long msecs() {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1329,6 +1329,21 @@
|
||||||
<string name="key_tidepool_username" translatable="false">tidepool_username</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_password" translatable="false">tidepool_password</string>
|
||||||
<string name="key_tidepool_dev_servers" translatable="false">tidepool_dev_servers</string>
|
<string name="key_tidepool_dev_servers" translatable="false">tidepool_dev_servers</string>
|
||||||
|
<string name="key_tidepool_window_latency" translatable="false">tidepool_window_latency</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="key_cloud_storage_tidepool_enable" translatable="false">cloud_storage_tidepool_enable</string>
|
||||||
|
<string name="summary_tidepool_upload_screen">Upload data to the Tidepool service</string>
|
||||||
|
<string name="title_sync_to_tidepool">Sync to Tidepool</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="title_tidepool_window_latency">Data Age Mins</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="key_smbmaxminutes" translatable="false">smbmaxminutes</string>
|
<string name="key_smbmaxminutes" translatable="false">smbmaxminutes</string>
|
||||||
<string name="dst_plugin_name" translatable="false">Dayligh Saving time</string>
|
<string name="dst_plugin_name" translatable="false">Dayligh Saving time</string>
|
||||||
|
@ -1348,6 +1363,9 @@
|
||||||
<string name="old_version">old version</string>
|
<string name="old_version">old version</string>
|
||||||
<string name="very_old_version">very old version</string>
|
<string name="very_old_version">very old version</string>
|
||||||
<string name="new_version_warning">New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days</string>
|
<string name="new_version_warning">New version for at least %1$d days available! Fallback to LGS after 60 days, loop will be disabled after 90 days</string>
|
||||||
|
<string name="tidepool">Tidepool</string>
|
||||||
|
<string name="tidepool_shortname">TDP</string>
|
||||||
|
<string name="description_tidepool">Uploads data to Tidepool</string>
|
||||||
|
|
||||||
<plurals name="objective_days">
|
<plurals name="objective_days">
|
||||||
<item quantity="one">%1$d day</item>
|
<item quantity="one">%1$d day</item>
|
||||||
|
|
49
app/src/main/res/xml/pref_tidepool.xml
Normal file
49
app/src/main/res/xml/pref_tidepool.xml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:key="tidepool_upload_screen"
|
||||||
|
android:summary="@string/summary_tidepool_upload_screen"
|
||||||
|
android:title="@string/tidepool">
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/key_cloud_storage_tidepool_enable"
|
||||||
|
android:title="@string/title_sync_to_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" />
|
||||||
|
<SeekBarPreference
|
||||||
|
android:defaultValue="0"
|
||||||
|
android:dependency="cloud_storage_tidepool_enable"
|
||||||
|
android:key="@string/key_tidepool_window_latency"
|
||||||
|
android:max="300"
|
||||||
|
android:min="0"
|
||||||
|
android:summary=""
|
||||||
|
android:title="@string/title_tidepool_window_latency" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:defaultValue="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" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
|
|
Loading…
Reference in a new issue