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" />
+
+
+
+
+
+
diff --git a/app/src/main/res/raw/xdrip_pebble_classic_trend.bin b/app/src/main/res/raw/xdrip_pebble_classic_trend.bin
index 63dc774bd2..2b68658beb 100644
Binary files a/app/src/main/res/raw/xdrip_pebble_classic_trend.bin and b/app/src/main/res/raw/xdrip_pebble_classic_trend.bin differ
diff --git a/app/src/main/res/xml/xdrip_plus_prefs.xml b/app/src/main/res/xml/xdrip_plus_prefs.xml
index 820d8c21df..df899b3844 100644
--- a/app/src/main/res/xml/xdrip_plus_prefs.xml
+++ b/app/src/main/res/xml/xdrip_plus_prefs.xml
@@ -188,6 +188,39 @@
app:colorpicker_selectNoneButtonText="@string/revert_to_default"
app:colorpicker_showHex="false" />
+
+
+
+
+
+
+
+
+
+