diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4f9639212a..210d732c7c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,7 +50,7 @@ android:value="41" /> + android:value="com.eveningoutpost.dexdrip.Models.ActiveBgAlert,com.eveningoutpost.dexdrip.Models.ActiveBluetoothDevice,com.eveningoutpost.dexdrip.Models.AlertType,com.eveningoutpost.dexdrip.Models.BgReading,com.eveningoutpost.dexdrip.Models.BgReading,com.eveningoutpost.dexdrip.Models.Calibration,com.eveningoutpost.dexdrip.Models.Calibration,com.eveningoutpost.dexdrip.Models.CalibrationRequest,com.eveningoutpost.dexdrip.Models.Sensor,com.eveningoutpost.dexdrip.Models.TransmitterData,com.eveningoutpost.dexdrip.Models.Treatments,com.eveningoutpost.dexdrip.Models.UserError,com.eveningoutpost.dexdrip.Models.UserNotification,com.eveningoutpost.dexdrip.ShareModels.Models,com.eveningoutpost.dexdrip.UtilityModels.BgSendQueue,com.eveningoutpost.dexdrip.UtilityModels.CalibrationSendQueue,com.eveningoutpost.dexdrip.UtilityModels.SensorSendQueue,com.eveningoutpost.dexdrip.Models.HeartRate,com.eveningoutpost.dexdrip.Models.PebbleMovement"/> BgGraphBuilder.NOISE_TRIGGER) && (BgGraphBuilder.best_bg_estimate > 0) @@ -2119,7 +2163,7 @@ private void displayCurrentInfoFromReading(BgReading lastBgReading, boolean pred // TODO this should be made more efficient probably if (Home.getPreferencesBooleanDefaultFalse("display_glucose_from_plugin") && (PluggableCalibration.getCalibrationPluginFromPreferences() != null)) { - currentBgValueText.setText("\u24C5" + currentBgValueText.getText()); // adds warning P in circle icon + currentBgValueText.setText(getString(R.string.p_in_circle) + currentBgValueText.getText()); // adds warning P in circle icon } } @@ -2556,8 +2600,10 @@ public static String getPreferencesStringWithDefault(final String pref, final St } if (prefs != null) { return prefs.getString(pref, def); + } else { + UserError.Log.wtf(TAG, "Could not initialize preferences in getPreferencesStringWithDefault: "+pref); + return ""; } - return ""; } public static long getPreferencesLong(final String pref, final long def) { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/Models/HeartRate.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/HeartRate.java new file mode 100644 index 0000000000..15f066e991 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/HeartRate.java @@ -0,0 +1,81 @@ +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.activeandroid.query.Select; +import com.activeandroid.util.SQLiteUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +/** + * Created by jamorham on 01/11/2016. + */ + + +@Table(name = "HeartRate", id = BaseColumns._ID) +public class HeartRate extends Model { + + private static boolean patched = false; + private final static String TAG = "HeartRate"; + + @Expose + @Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) + public long timestamp; + + @Expose + @Column(name = "bpm") + public int bpm; + + + // patches and saves + public Long saveit() { + fixUpTable(); + return save(); + } + + public String toS() { + final Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return gson.toJson(this); + } + + public static HeartRate last() { + try { + return new Select() + .from(HeartRate.class) + .orderBy("timestamp desc") + .executeSingle(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + return null; + } + } + + // create the table ourselves without worrying about model versioning and downgrading + private static void fixUpTable() { + if (patched) return; + String[] patchup = { + "CREATE TABLE HeartRate (_id INTEGER PRIMARY KEY AUTOINCREMENT);", + "ALTER TABLE HeartRate ADD COLUMN timestamp INTEGER;", + "ALTER TABLE HeartRate ADD COLUMN bpm INTEGER;", + "CREATE UNIQUE INDEX index_HeartRate_timestamp on HeartRate(timestamp);"}; + + 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/PebbleMovement.java b/app/src/main/java/com/eveningoutpost/dexdrip/Models/PebbleMovement.java new file mode 100644 index 0000000000..5247c3efe2 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/Models/PebbleMovement.java @@ -0,0 +1,128 @@ +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.activeandroid.query.Select; +import com.activeandroid.util.SQLiteUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +import java.util.List; + +/** + * Created by jamorham on 01/11/2016. + */ + + +@Table(name = "PebbleMovement", id = BaseColumns._ID) +public class PebbleMovement extends Model { + + private static boolean patched = false; + private final static String TAG = "PebbleMovement"; + + @Expose + @Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE) + public long timestamp; + + @Expose + @Column(name = "metric") + public int metric; + + + // patches and saves + public Long saveit() { + fixUpTable(); + return save(); + } + + public String toS() { + final Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .create(); + return gson.toJson(this); + } + + + // static methods + + public static PebbleMovement last() { + try { + return new Select() + .from(PebbleMovement.class) + .orderBy("timestamp desc") + .executeSingle(); + } catch (android.database.sqlite.SQLiteException e) { + fixUpTable(); + 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) { + return new Select() + .from(PebbleMovement.class) + .where("timestamp >= " + Math.max(startTime, 0)) + .where("timestamp <= " + endTime) + .orderBy("timestamp asc") // warn asc! + .limit(number) + .execute(); + } + + // expects pre-sorted in asc order? + public static List deltaListFromMovementList(List mList) { + int last_metric = -1; + int temp_metric = -1; + for (PebbleMovement pm : mList) { + // first item in list + if (last_metric == -1) { + last_metric = pm.metric; + pm.metric = 0; + } else { + // normal incrementing calculate delta + if (pm.metric >= last_metric) { + temp_metric = pm.metric - last_metric; + last_metric = pm.metric; + pm.metric = temp_metric; + } else { + last_metric = pm.metric; + } + } + } + return mList; + } + + + // create the table ourselves without worrying about model versioning and downgrading + private static void fixUpTable() { + if (patched) return; + String[] patchup = { + "CREATE TABLE PebbleMovement (_id INTEGER PRIMARY KEY AUTOINCREMENT);", + "ALTER TABLE PebbleMovement ADD COLUMN timestamp INTEGER;", + "ALTER TABLE PebbleMovement ADD COLUMN metric INTEGER;", + "CREATE UNIQUE INDEX index_PebbleMovement_timestamp on PebbleMovement(timestamp);"}; + + 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/UtilityModels/BgGraphBuilder.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/BgGraphBuilder.java index 001a6179d1..3b1bb5c197 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/BgGraphBuilder.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/BgGraphBuilder.java @@ -22,6 +22,7 @@ import com.eveningoutpost.dexdrip.Models.Forecast.TrendLine; import com.eveningoutpost.dexdrip.Models.Iob; import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.Models.PebbleMovement; import com.eveningoutpost.dexdrip.Models.Profile; import com.eveningoutpost.dexdrip.Models.Treatments; import com.eveningoutpost.dexdrip.Models.UserError; @@ -100,7 +101,7 @@ public class BgGraphBuilder { private static double low_occurs_at_processed_till_timestamp = -1; private final static String TAG = "jamorham graph"; //private final static int pluginColor = Color.parseColor("#AA00FFFF"); // temporary - private final static int pluginColor = Color.parseColor("#77ff7700"); // temporary + private final static int pluginSize = 2; final int pointSize; final int axisTextSize; @@ -276,6 +277,68 @@ private void extend_line(List points, float x, float y) { Log.d(TAG,"Extend line size: "+points.size()); } + // line illustrating result from step counter + private List stepsLines() { + final List stepsLines = new ArrayList<>(); + if (prefs.getBoolean("show_pebble_movement_line", true)) { + final List pmlist = PebbleMovement.deltaListFromMovementList(PebbleMovement.latestForGraph(2000, loaded_start, loaded_end)); + PointValue last_point = null; + final boolean d = false; + if (d) Log.d(TAG, "Delta: pmlist size: " + pmlist.size()); + final float yscale = doMgdl ? (float) Constants.MMOLL_TO_MGDL : 1f; + final float ypos = 6 * yscale; // TODO Configurable + //final long last_timestamp = pmlist.get(pmlist.size() - 1).timestamp; + final float MAX_SIZE = 40; + int flipper = 0; + int accumulator = 0; + + for (PebbleMovement pm : pmlist) { + if (last_point == null) { + last_point = new PointValue((float) pm.timestamp / FUZZER, ypos); + } else { + final PointValue this_point = new PointValue((float) pm.timestamp / FUZZER, ypos); + final float time_delta = this_point.getX() - last_point.getX(); + if (time_delta > 1) { + + final List new_points = new ArrayList<>(); + new_points.add(last_point); + new_points.add(this_point); + + last_point = this_point; // update pointer + final Line this_line = new Line(new_points); + flipper ^= 1; + this_line.setColor((flipper == 0) ? getCol(X.color_step_counter1) : getCol(X.color_step_counter2)); + + float stroke_size = Math.min(MAX_SIZE, (float) Math.log1p(((double) (pm.metric + accumulator)) / time_delta) * 5); + if (d) Log.d(TAG, "Delta stroke: " + stroke_size); + this_line.setStrokeWidth((int) stroke_size); + + if (d) + Log.d(TAG, "Delta-Line: " + JoH.dateTimeText(pm.timestamp) + " time delta: " + time_delta + " total: " + (pm.metric + accumulator) + " lsize: " + stroke_size + " / " + (int) stroke_size); + accumulator = 0; + + if (this_line.getStrokeWidth() > 0) { + stepsLines.add(this_line); + this_line.setHasPoints(false); + this_line.setHasLines(true); + } else { + if (d) Log.d(TAG, "Delta skip: " + JoH.dateTimeText(pm.timestamp)); + } + if (d) + Log.d(TAG, "Delta-List: " + JoH.dateTimeText(pm.timestamp) + " time delta: " + time_delta + " val: " + pm.metric); + } else { + accumulator += pm.metric; + if (d) + Log.d(TAG, "Delta: added: " + JoH.dateTimeText(pm.timestamp) + " metric: " + pm.metric + " to accumulator: " + accumulator); + } + } + } + if (d) + Log.d(TAG, "Delta returning stepsLines: " + stepsLines.size() + " final accumulator remaining: " + accumulator); + } + return stepsLines; + } + private List motionLine() { final ArrayList motion_datas = ActivityRecognizedService.getForGraph((long) start_time * FUZZER, (long) end_time * FUZZER); @@ -373,9 +436,11 @@ public LineChartData previewLineData(LineChartData hint) { unlabledLinesSize = 2; } for (Line lline : previewLineData.getLines()) { - if ((lline.getPointRadius() == pluginSize) && (lline.getPointColor() == pluginColor)) { - removeItems.add(lline); // remove plugin plot from preview graph + if (((lline.getPointRadius() == pluginSize) && (lline.getPointColor() == getCol(X.color_secondary_glucose_value))) + || ((lline.getColor() == getCol(X.color_step_counter1) || (lline.getColor() == getCol(X.color_step_counter2))))) { + removeItems.add(lline); // remove plugin or step counter plot from preview graph } + if ((lline.hasLabels() && (lline.getPointRadius() > 0))) { lline.setPointRadius(3); // preserve size for treatments @@ -405,6 +470,7 @@ public synchronized List defaultLines(boolean simple) { if (Home.getPreferencesBoolean("motion_tracking_enabled", false) && Home.getPreferencesBoolean("plot_motion", false)) { lines.addAll(motionLine()); } + lines.addAll(stepsLines()); } Line[] calib = calibrationValuesLine(); @@ -605,7 +671,7 @@ public List extraLines() line.setHasLines(false); line.setPointRadius(pluginSize); line.setHasPoints(true); - line.setColor(pluginColor); + line.setColor(getCol(X.color_secondary_glucose_value)); lines.add(line); return lines; } diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/ColorCache.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/ColorCache.java index ec0ae1dac2..fff164d2e1 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/ColorCache.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/ColorCache.java @@ -51,7 +51,10 @@ public enum X { color_treatment_dot_foreground("color_treatment_dot_foreground"), color_home_chart_background("color_home_chart_background"), color_notification_chart_background("color_notification_chart_background"), - color_widget_chart_background("color_widget_chart_background"); + color_widget_chart_background("color_widget_chart_background"), + color_secondary_glucose_value("color_secondary_glucose_value"), + color_step_counter1("color_step_counter1"), + color_step_counter2("color_step_counter2"); String internalName; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/pebble/PebbleWatchSync.java b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/pebble/PebbleWatchSync.java index 561cec5aab..ee99212502 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/pebble/PebbleWatchSync.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/UtilityModels/pebble/PebbleWatchSync.java @@ -6,7 +6,9 @@ import android.os.IBinder; import android.preference.PreferenceManager; +import com.eveningoutpost.dexdrip.Models.HeartRate; import com.eveningoutpost.dexdrip.Models.JoH; +import com.eveningoutpost.dexdrip.Models.PebbleMovement; import com.eveningoutpost.dexdrip.Models.UserError.Log; import com.eveningoutpost.dexdrip.UtilityModels.AlertPlayer; import com.eveningoutpost.dexdrip.UtilityModels.BgGraphBuilder; @@ -35,9 +37,17 @@ public class PebbleWatchSync extends Service { private final static String TAG = PebbleWatchSync.class.getSimpleName(); + private final static long sanity_timestamp = 1478197375; + private final static boolean d = false; + + // these must match in watchface + private final static int HEARTRATE_LOG = 101; + private final static int MOVEMENT_LOG = 103; public static int lastTransactionId; + private long last_heartrate_timestamp = 0; + private long last_movement_timestamp = 0; private static Context context; private static BgGraphBuilder bgGraphBuilder; @@ -146,6 +156,93 @@ public void receiveNack(Context context, int transactionId) { } }); + PebbleKit.registerDataLogReceiver(context, new PebbleKit.PebbleDataLogReceiver(currentWatchFaceUUID) { + @Override + public void receiveData(Context context, UUID logUuid, Long timestamp, + Long tag, int data) { + if (d) + Log.d(TAG, "receiveLogData: uuid:" + logUuid + " " + JoH.dateTimeText(timestamp * 1000) + " tag:" + tag + " data: " + data); + } + + @Override + public void receiveData(Context context, UUID logUuid, Long timestamp, + Long tag, Long data) { + Log.d(TAG, "receiveLogData: uuid:" + logUuid + " started: " + JoH.dateTimeText(timestamp * 1000) + " tag:" + tag + " data: " + data); + + if ((tag != null) && (data != null)) { + final int s = (int) (long) tag; + + + switch (s) { + case HEARTRATE_LOG: + if (data > sanity_timestamp) { + if (last_heartrate_timestamp > 0) { + Log.e(TAG, "Out of sequence heartrate timestamp received!"); + } + last_heartrate_timestamp = data; + } else { + if (data > 0) { + if (last_heartrate_timestamp > 0) { + final HeartRate hr = new HeartRate(); + hr.timestamp = last_heartrate_timestamp * 1000; + hr.bpm = (int) (long) data; + Log.d(TAG, "Saving HeartRate: " + hr.toS()); + hr.saveit(); + last_heartrate_timestamp = 0; // reset state + } else { + Log.e(TAG, "Out of sequence heartrate value received!"); + } + } + } + break; + + case MOVEMENT_LOG: + if (data > sanity_timestamp) { + if (last_movement_timestamp > 0) { + Log.e(TAG, "Out of sequence movement timestamp received!"); + } + last_movement_timestamp = data; + } else { + if (data > 0) { + if (last_movement_timestamp > 0) { + final PebbleMovement pm = new PebbleMovement(); + pm.timestamp = last_movement_timestamp * 1000; + pm.metric = (int) (long) data; + Log.d(TAG, "Saving Movement: " + pm.toS()); + pm.saveit(); + last_movement_timestamp = 0; // reset state + } else { + Log.e(TAG, "Out of sequence movement value received!"); + } + } + } + break; + + default: + Log.e(TAG, "Unknown pebble data log type received: " + s); + break; + + } + } else { + Log.e(TAG, "Got null Long in receive data"); + } + } + + + @Override + public void receiveData(Context context, UUID logUuid, Long timestamp, + Long tag, byte[] data) { + if (d) Log.d(TAG,"receiveLogData: uuid:"+logUuid+" "+JoH.dateTimeText(timestamp*1000)+" tag:"+tag+" hexdata: "+JoH.bytesToHex(data)); + } + + @Override + public void onFinishSession(Context context, UUID logUuid, Long timestamp, + Long tag) { + if (d) Log.i(TAG, "Session " + tag + " finished!"); + } + + }); + // control app PebbleKit.registerReceivedDataHandler(context, new PebbleKit.PebbleDataReceiver(PEBBLE_CONTROL_APP_UUID) { @Override diff --git a/app/src/main/res/drawable-xhdpi/ic_heart_pulse_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_heart_pulse_grey600_24dp.png new file mode 100644 index 0000000000..53d9bd1b66 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_heart_pulse_grey600_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_walk_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_walk_grey600_24dp.png new file mode 100644 index 0000000000..ceffae66b0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_walk_grey600_24dp.png differ diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 66baac2c0e..2174136099 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -363,6 +363,44 @@ android:paddingRight="4dp" android:paddingBottom="2dp" /> +