Initial project commit
This commit is contained in:
1
lib/bluetooth-scanner
Submodule
1
lib/bluetooth-scanner
Submodule
Submodule lib/bluetooth-scanner added at 915ceb61c8
@@ -0,0 +1,25 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class AuthChallengeRxMessage extends BaseMessage {
|
||||
public static final int opcode = 0x03;
|
||||
public byte[] tokenHash;
|
||||
public byte[] challenge;
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
public AuthChallengeRxMessage(byte[] data) {
|
||||
UserError.Log.d(TAG,"AuthChallengeRX: "+ JoH.bytesToHex(data));
|
||||
if (data.length >= 17) {
|
||||
if (data[0] == opcode) {
|
||||
tokenHash = Arrays.copyOfRange(data, 1, 9);
|
||||
challenge = Arrays.copyOfRange(data, 9, 17);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
import com.eveningoutpost.dexdrip.G5Model.TransmitterMessage;
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class AuthChallengeTxMessage extends TransmitterMessage {
|
||||
byte opcode = 0x04;
|
||||
byte[] challengeHash;
|
||||
|
||||
public AuthChallengeTxMessage(byte[] challenge) {
|
||||
challengeHash = challenge;
|
||||
|
||||
data = ByteBuffer.allocate(9);
|
||||
data.put(opcode);
|
||||
data.put(challengeHash);
|
||||
|
||||
byteSequence = data.array();
|
||||
UserError.Log.d(TAG,"AuthChallengeTX: "+ JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.utils.CipherUtils.getRandomKey;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public class AuthRequestTxMessage extends BaseMessage {
|
||||
public final byte opcode = 0x01;
|
||||
public byte[] singleUseToken;
|
||||
private final byte endByteStd = 0x2;
|
||||
private final byte endByteAlt = 0x1;
|
||||
|
||||
|
||||
public AuthRequestTxMessage(int token_size) {
|
||||
this(token_size, false);
|
||||
}
|
||||
|
||||
public AuthRequestTxMessage(int token_size, boolean alt) {
|
||||
byte[] uuidBytes = getRandomKey();
|
||||
final UUID uuid = UUID.nameUUIDFromBytes(uuidBytes);
|
||||
|
||||
try {
|
||||
uuidBytes = uuid.toString().getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
final ByteBuffer bb = ByteBuffer.allocate(token_size);
|
||||
bb.put(uuidBytes, 0, token_size);
|
||||
singleUseToken = bb.array();
|
||||
|
||||
data = ByteBuffer.allocate(token_size + 2);
|
||||
data.put(opcode);
|
||||
data.put(singleUseToken);
|
||||
data.put(alt ? endByteAlt : endByteStd);
|
||||
|
||||
byteSequence = data.array();
|
||||
UserError.Log.d(TAG, "New AuthRequestTxMessage: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class AuthStatusRxMessage extends BaseMessage {
|
||||
public static final int opcode = 0x5;
|
||||
public int authenticated;
|
||||
public int bonded;
|
||||
|
||||
public AuthStatusRxMessage(byte[] packet) {
|
||||
if (packet.length >= 3) {
|
||||
if (packet[0] == opcode) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
authenticated = data.get(1);
|
||||
bonded = data.get(2);
|
||||
UserError.Log.d(TAG,"AuthRequestRxMessage: authenticated:"+authenticated+" bonded:"+bonded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAuthenticated() {
|
||||
return authenticated == 1;
|
||||
}
|
||||
public boolean isBonded() {
|
||||
return bonded == 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class BackFillRxMessage extends BaseMessage {
|
||||
|
||||
public static final int opcode = 0x51;
|
||||
private static final int length = 20;
|
||||
|
||||
private boolean valid = false;
|
||||
|
||||
BackFillRxMessage(byte[] packet) {
|
||||
|
||||
if (packet.length == length) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if ((data.get() == opcode) && checkCRC(packet)) {
|
||||
valid = true;
|
||||
// meh
|
||||
// 51 00 01 01 A1A00200 DDAF0200 82000000 2361 1625
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean valid() {
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.G5Model.DexTimeKeeper.fromDexTimeCached;
|
||||
|
||||
public class BackFillStream extends BaseMessage {
|
||||
|
||||
private int last_sequence = 0;
|
||||
|
||||
BackFillStream() {
|
||||
data = ByteBuffer.allocate(1000);
|
||||
data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
public synchronized void push(byte[] packet) {
|
||||
|
||||
if (packet == null) return;
|
||||
final int this_sequence = (int) packet[0];
|
||||
if (this_sequence == last_sequence + 1) {
|
||||
last_sequence++;
|
||||
|
||||
for (int i = 2; i < packet.length; i++) {
|
||||
if (data.position() < data.limit()) {
|
||||
data.put(packet[i]);
|
||||
} else {
|
||||
UserError.Log.wtf(TAG, "Reached limit for backfill stream size");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
UserError.Log.wtf(TAG, "Received backfill packet out of sequence: " + this_sequence + " vs " + (last_sequence + 1));
|
||||
}
|
||||
}
|
||||
|
||||
public List<Backsie> decode() {
|
||||
final List<Backsie> backsies = new LinkedList<>();
|
||||
|
||||
int extent = data.position();
|
||||
data.rewind();
|
||||
final int length = data.getInt();
|
||||
// TODO check length
|
||||
while (data.position() < extent) {
|
||||
final int dexTime = data.getInt();
|
||||
final int glucose = data.getShort();
|
||||
final byte type = data.get();
|
||||
final byte trend = data.get();
|
||||
|
||||
final CalibrationState state = CalibrationState.parse(type);
|
||||
|
||||
switch (state) {
|
||||
case Ok:
|
||||
case NeedsCalibration:
|
||||
insertBackfillItem(backsies, dexTime, glucose, trend);
|
||||
break;
|
||||
|
||||
case WarmingUp:
|
||||
break;
|
||||
|
||||
case Errors:
|
||||
/* This preference option has never been available outside of unit testing
|
||||
and can now be removed.
|
||||
if (Pref.getBooleanDefaultFalse("ob1_g5_use_errored_data")) {
|
||||
insertBackfillItem(backsies, dexTime, glucose, trend);
|
||||
}
|
||||
*/
|
||||
break;
|
||||
|
||||
case InsufficientCalibration:
|
||||
if (Pref.getBoolean("ob1_g5_use_insufficiently_calibrated", true)) {
|
||||
insertBackfillItem(backsies, dexTime, glucose, trend);
|
||||
}
|
||||
break;
|
||||
|
||||
case NeedsFirstCalibration:
|
||||
case NeedsSecondCalibration:
|
||||
case Unknown:
|
||||
break;
|
||||
|
||||
default:
|
||||
UserError.Log.wtf(TAG, "Encountered backfill data we don't recognise: " + type + " " + glucose + " " + trend + " " + " " + JoH.dateTimeText(fromDexTimeCached(dexTime)));
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return backsies;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void insertBackfillItem(List<Backsie> backsies, int dexTime, int glucose, byte trend) {
|
||||
if (dexTime != 0) {
|
||||
backsies.add(new Backsie(glucose, trend, dexTime));
|
||||
}
|
||||
}
|
||||
|
||||
public void enumerate(int size) {
|
||||
|
||||
System.out.println("Size:" + size);
|
||||
byte[] output = data.array();
|
||||
int i = 4;
|
||||
while (i < data.position()) {
|
||||
if ((i - 4) % size == 0) {
|
||||
System.out.println("");
|
||||
}
|
||||
System.out.print(JoH.bytesToHex(new byte[]{output[i]}));
|
||||
i++;
|
||||
}
|
||||
System.out.println("\n");
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor()
|
||||
public class Backsie {
|
||||
private final int glucose;
|
||||
private final int trend;
|
||||
private final int dextime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.G5Model.DexTimeKeeper.getDexTime;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class BackFillTxMessage extends BaseMessage {
|
||||
|
||||
final byte opcode = 0x50;
|
||||
final int length = 20;
|
||||
|
||||
public BackFillTxMessage(int startDexTime, int endDexTime) {
|
||||
init(opcode, length);
|
||||
data.put((byte) 0x5);
|
||||
data.put((byte) 0x2);
|
||||
data.put((byte) 0x0);
|
||||
data.putInt(startDexTime);
|
||||
data.putInt(endDexTime);
|
||||
data.put(new byte[6]);
|
||||
appendCRC();
|
||||
UserError.Log.d(TAG, "BackfillTxMessage dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
|
||||
public static BackFillTxMessage get(String id, long startTime, long endTime) {
|
||||
|
||||
final int dexStart = getDexTime(id, startTime);
|
||||
final int dexEnd = getDexTime(id, endTime);
|
||||
if (dexStart < 1 || dexEnd < 1) {
|
||||
UserError.Log.e(TAG, "Unable to calculate start or end time for BackFillTxMessage");
|
||||
return null;
|
||||
}
|
||||
return new BackFillTxMessage(dexStart, dexEnd);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class BaseAuthChallengeTxMessage extends BaseMessage {
|
||||
static final byte opcode = 0x04;
|
||||
|
||||
public BaseAuthChallengeTxMessage(final byte[] challenge) {
|
||||
|
||||
init(opcode, 9);
|
||||
data.put(challenge);
|
||||
byteSequence = data.array();
|
||||
UserError.Log.d(TAG, "BaseAuthChallengeTX: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 02/07/2018.
|
||||
*
|
||||
*/
|
||||
|
||||
@NoArgsConstructor
|
||||
public abstract class BaseGlucoseRxMessage extends BaseMessage {
|
||||
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
|
||||
public TransmitterStatus status;
|
||||
public int status_raw;
|
||||
public int timestamp;
|
||||
public int unfiltered;
|
||||
public int filtered;
|
||||
public int sequence; // : UInt32
|
||||
public boolean glucoseIsDisplayOnly; // : Bool
|
||||
public int glucose; // : UInt16
|
||||
public int state; //: UInt8
|
||||
public int trend; // : Int8 127 = invalid
|
||||
|
||||
|
||||
CalibrationState calibrationState() {
|
||||
return CalibrationState.parse(state);
|
||||
}
|
||||
|
||||
boolean usable() {
|
||||
return calibrationState().usableGlucose();
|
||||
}
|
||||
|
||||
boolean insufficient() {
|
||||
return calibrationState().insufficientCalibration();
|
||||
}
|
||||
|
||||
boolean OkToCalibrate() {
|
||||
return calibrationState().readyForCalibration();
|
||||
}
|
||||
|
||||
public Double getTrend() {
|
||||
return trend != 127 ? ((double) trend) / 10d : Double.NaN;
|
||||
}
|
||||
|
||||
public Integer getPredictedGlucose() {
|
||||
return null; // stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.chrispr.bluetooth.BluetoothScanner;
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class BaseMessage {
|
||||
protected static final String TAG = G5CollectionService.TAG; // meh
|
||||
static final int INVALID_TIME = 0xFFFFFFFF;
|
||||
@Expose
|
||||
long postExecuteGuardTime = 50;
|
||||
@Expose
|
||||
public volatile byte[] byteSequence;
|
||||
public ByteBuffer data;
|
||||
|
||||
|
||||
void init(final byte opcode, final int length) {
|
||||
data = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN);
|
||||
data.put(opcode);
|
||||
if (length == 1) {
|
||||
getByteSequence();
|
||||
} else if (length == 3) {
|
||||
appendCRC();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] appendCRC() {
|
||||
data.put(FastCRC16.calculate(getByteSequence(), byteSequence.length - 2));
|
||||
return getByteSequence();
|
||||
}
|
||||
|
||||
boolean checkCRC(byte[] data) {
|
||||
if ((data == null) || (data.length < 3)) return false;
|
||||
final byte[] crc = FastCRC16.calculate(data, data.length - 2);
|
||||
return crc[0] == data[data.length - 2] && crc[1] == data[data.length - 1];
|
||||
}
|
||||
|
||||
byte[] getByteSequence() {
|
||||
return byteSequence = data.array();
|
||||
}
|
||||
|
||||
long guardTime() {
|
||||
return postExecuteGuardTime;
|
||||
}
|
||||
|
||||
static long getUnsignedInt(ByteBuffer data) {
|
||||
return ((data.get() & 0xff) + ((data.get() & 0xff) << 8) + ((data.get() & 0xff) << 16) + ((data.get() & 0xff) << 24));
|
||||
}
|
||||
|
||||
static int getUnsignedShort(ByteBuffer data) {
|
||||
return ((data.get() & 0xff) + ((data.get() & 0xff) << 8));
|
||||
}
|
||||
|
||||
static int getUnsignedByte(ByteBuffer data) {
|
||||
return ((data.get() & 0xff));
|
||||
}
|
||||
|
||||
static String dottedStringFromData(ByteBuffer data, int length) {
|
||||
|
||||
final byte[] bytes = new byte[length];
|
||||
data.get(bytes);
|
||||
final StringBuilder sb = new StringBuilder(100);
|
||||
for (byte x : bytes) {
|
||||
if (sb.length() > 0) sb.append(".");
|
||||
sb.append(String.format(Locale.US, "%d", (x & 0xff)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static int getUnixTime() {
|
||||
return (int) (JoH.tsl() / 1000);
|
||||
}
|
||||
|
||||
public static class G5CollectionService {
|
||||
public static String TAG = "Message";
|
||||
}
|
||||
|
||||
public static class UserError {
|
||||
public static class Log {
|
||||
protected static final Logger logger = LoggerFactory.getLogger("Message");
|
||||
public static Marker getMarker(String markerName) {
|
||||
Marker m = MarkerFactory.getMarker(markerName);
|
||||
return m;
|
||||
}
|
||||
public static void e(String a, String b){
|
||||
//android.util.Log.e(a, b);
|
||||
logger.error(getMarker(a), b);
|
||||
|
||||
//new com.eveningoutpost.dexdrip.Models.UserError(a, b);
|
||||
}
|
||||
|
||||
public static void e(String tag, String b, Exception e){
|
||||
//android.util.Log.e(tag, b, e);
|
||||
logger.error(getMarker(tag), b, e);
|
||||
|
||||
//new com.eveningoutpost.dexdrip.Models.UserError(tag, b + "\n" + e.toString());
|
||||
}
|
||||
|
||||
public static void w(String tag, String b){
|
||||
//android.util.Log.w(tag, b);
|
||||
logger.warn(getMarker(tag), b);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserErrorLow(tag, b);
|
||||
}
|
||||
public static void w(String tag, String b, Exception e){
|
||||
//android.util.Log.w(tag, b, e);
|
||||
logger.warn(getMarker(tag), b, e);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserErrorLow(tag, b + "\n" + e.toString());
|
||||
}
|
||||
public static void wtf(String tag, String b){
|
||||
//android.util.Log.wtf(tag, b);
|
||||
logger.error(getMarker(tag), b);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserErrorHigh(tag, b);
|
||||
}
|
||||
public static void wtf(String tag, String b, Exception e){
|
||||
//android.util.Log.wtf(tag, b, e);
|
||||
logger.error(getMarker(tag), b, e);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserErrorHigh(tag, b + "\n" + e.toString());
|
||||
}
|
||||
public static void wtf(String tag, Exception e){
|
||||
//android.util.Log.wtf(tag, e);
|
||||
logger.error(getMarker(tag), "Error", e);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserErrorHigh(tag, e.toString());
|
||||
}
|
||||
|
||||
public static void uel(String tag, String b) {
|
||||
//android.util.Log.i(tag, b);
|
||||
logger.info(getMarker(tag), b);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserEventLow(tag, b);
|
||||
}
|
||||
|
||||
public static void ueh(String tag, String b) {
|
||||
//android.util.Log.i(tag, b);
|
||||
logger.info(getMarker(tag), b);
|
||||
//com.eveningoutpost.dexdrip.Models.UserError.UserEventHigh(tag, b);
|
||||
}
|
||||
|
||||
public static void d(String tag, String b){
|
||||
//android.util.Log.d(tag, b);
|
||||
logger.debug(getMarker(tag), b);
|
||||
//if(com.eveningoutpost.dexdrip.Models.UserError.ExtraLogTags.shouldLogTag(tag, android.util.Log.DEBUG)) {
|
||||
// UserErrorLow(tag, b);
|
||||
//}
|
||||
}
|
||||
|
||||
public static void v(String tag, String b){
|
||||
//android.util.Log.v(tag, b);
|
||||
logger.debug(getMarker(tag), b);
|
||||
//if(com.eveningoutpost.dexdrip.Models.UserError.ExtraLogTags.shouldLogTag(tag, android.util.Log.VERBOSE)) {
|
||||
// UserErrorLow(tag, b);
|
||||
//}
|
||||
}
|
||||
|
||||
public static void i(String tag, String b){
|
||||
//android.util.Log.i(tag, b);
|
||||
logger.info(getMarker(tag), b);
|
||||
//if(com.eveningoutpost.dexdrip.Models.UserError.ExtraLogTags.shouldLogTag(tag, android.util.Log.INFO)) {
|
||||
// UserErrorLow(tag, b);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
|
||||
public class BatteryInfoRxMessage extends BaseMessage {
|
||||
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
|
||||
public static final byte opcode = 0x23;
|
||||
|
||||
public int status;
|
||||
public int voltagea;
|
||||
public int voltageb;
|
||||
public int resist;
|
||||
public int runtime;
|
||||
public int temperature;
|
||||
|
||||
public BatteryInfoRxMessage(byte[] packet) {
|
||||
if (packet.length >= 10) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (data.get() == opcode) {
|
||||
status = data.get();
|
||||
voltagea = getUnsignedShort(data);
|
||||
voltageb = getUnsignedShort(data);
|
||||
resist = getUnsignedShort(data);
|
||||
runtime = getUnsignedByte(data);
|
||||
if (packet.length == 10) {
|
||||
runtime = -1; // this byte isn't runtime on rev2
|
||||
}
|
||||
temperature = data.get(); // not sure if signed or not, but <0c or >127C seems unlikely!
|
||||
} else {
|
||||
UserError.Log.wtf(TAG, "Invalid opcode for BatteryInfoRxMessage");
|
||||
}
|
||||
} else {
|
||||
UserError.Log.wtf(TAG, "Invalid length for BatteryInfoMessage: " + packet.length);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "Status: %s / VoltageA: %d / VoltageB: %d / Resistance: %d / Run Time: %d / Temperature: %d",
|
||||
TransmitterStatus.getBatteryLevel(status).toString(), voltagea, voltageb, resist, runtime, temperature);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
public class BatteryInfoTxMessage extends BaseMessage {
|
||||
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
static final byte opcode = 0x22;
|
||||
|
||||
public BatteryInfoTxMessage() {
|
||||
init(opcode, 3);
|
||||
UserError.Log.e(TAG, "BatteryInfoTx dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class BluetoothServices {
|
||||
|
||||
//Transmitter Service UUIDs
|
||||
public static final UUID DeviceInfo = UUID.fromString("0000180A-0000-1000-8000-00805F9B34FB");
|
||||
//iOS uses FEBC?
|
||||
public static final UUID Advertisement = UUID.fromString("0000FEBC-0000-1000-8000-00805F9B34FB");
|
||||
public static final UUID CGMService = UUID.fromString("F8083532-849E-531C-C594-30F1F86A4EA5");
|
||||
public static final UUID ServiceB = UUID.fromString("F8084532-849E-531C-C594-30F1F86A4EA5");
|
||||
|
||||
//DeviceInfoCharacteristicUUID, Read, DexcomUN
|
||||
public static final UUID ManufacturerNameString = UUID.fromString("00002A29-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
//CGMServiceCharacteristicUUID
|
||||
public static final UUID Communication = UUID.fromString("F8083533-849E-531C-C594-30F1F86A4EA5");
|
||||
public static final UUID Control = UUID.fromString("F8083534-849E-531C-C594-30F1F86A4EA5");
|
||||
public static final UUID Authentication = UUID.fromString("F8083535-849E-531C-C594-30F1F86A4EA5");
|
||||
public static final UUID ProbablyBackfill = UUID.fromString("F8083536-849E-531C-C594-30F1F86A4EA5");
|
||||
|
||||
//ServiceBCharacteristicUUID
|
||||
public static final UUID CharacteristicE = UUID.fromString("F8084533-849E-531C-C594-30F1F86A4EA5");
|
||||
public static final UUID CharacteristicF = UUID.fromString("F8084534-849E-531C-C594-30F1F86A4EA5");
|
||||
|
||||
//CharacteristicDescriptorUUID
|
||||
public static final UUID CharacteristicUpdateNotification = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB");
|
||||
|
||||
private static final HashMap<UUID, String> mapToName = new HashMap<>();
|
||||
|
||||
|
||||
static {
|
||||
mapToName.put(DeviceInfo, "DeviceInfo");
|
||||
mapToName.put(Advertisement, "Advertisement");
|
||||
mapToName.put(CGMService, "CGMService");
|
||||
mapToName.put(ServiceB, "ServiceB");
|
||||
mapToName.put(ManufacturerNameString, "ManufacturerNameString");
|
||||
mapToName.put(Communication, "Communication");
|
||||
mapToName.put(Control, "Control");
|
||||
mapToName.put(Authentication, "Authentication");
|
||||
mapToName.put(ProbablyBackfill, "ProbablyBackfill");
|
||||
mapToName.put(CharacteristicE, "CharacteristicE");
|
||||
mapToName.put(CharacteristicF, "CharacteristicF");
|
||||
mapToName.put(CharacteristicUpdateNotification, "CharacteristicUpdateNotification");
|
||||
}
|
||||
|
||||
|
||||
public static String getUUIDName(UUID uuid) {
|
||||
if (uuid == null) return "null";
|
||||
if (mapToName.containsKey(uuid)) {
|
||||
return mapToName.get(uuid);
|
||||
} else {
|
||||
return "Unknown uuid: " + uuid.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class BondRequestTxMessage extends BaseMessage {
|
||||
static final byte opcode = 0x07;
|
||||
|
||||
public BondRequestTxMessage() {
|
||||
init(opcode, 1);
|
||||
}
|
||||
}
|
||||
|
||||
25
lib/nightscout/com/eveningoutpost/dexdrip/G5Model/CRC.java
Normal file
25
lib/nightscout/com/eveningoutpost/dexdrip/G5Model/CRC.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.CRC16;
|
||||
|
||||
/**
|
||||
* Created by jcostik1 on 3/24/16.
|
||||
*/
|
||||
public class CRC {
|
||||
|
||||
public static byte[] calculate(byte b) {
|
||||
int crcShort = 0;
|
||||
crcShort = ((crcShort >>> 8) | (crcShort << 8)) & 0xffff;
|
||||
crcShort ^= (b & 0xff);
|
||||
crcShort ^= ((crcShort & 0xff) >> 4);
|
||||
crcShort ^= (crcShort << 12) & 0xffff;
|
||||
crcShort ^= ((crcShort & 0xFF) << 5) & 0xffff;
|
||||
crcShort &= 0xffff;
|
||||
return new byte[] {(byte) (crcShort & 0xff), (byte) ((crcShort >> 8) & 0xff)};
|
||||
}
|
||||
|
||||
public static byte[] calculate(byte[] bytes, int start, int end) {
|
||||
return CRC16.calculate(bytes, 0, end);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class CalibrateRxMessage extends BaseMessage {
|
||||
|
||||
public static final int opcode = 0x35;
|
||||
private static final int length = 5;
|
||||
|
||||
private byte info = (byte) 0xff;
|
||||
private byte result = (byte) 0xff;
|
||||
|
||||
CalibrateRxMessage(byte[] packet) {
|
||||
|
||||
if (packet.length == length) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if ((data.get() == opcode) && checkCRC(packet)) {
|
||||
info = data.get();
|
||||
result = data.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean accepted() {
|
||||
return result == 0x00 || result == 0x06 || result == 0x0D;
|
||||
}
|
||||
|
||||
boolean wantsCalibration() {
|
||||
return result == 0x06;
|
||||
}
|
||||
|
||||
String message() {
|
||||
// TODO i18n
|
||||
switch (result) {
|
||||
|
||||
case (byte) 0x00:
|
||||
return "OK";
|
||||
case (byte) 0x01:
|
||||
return "Code 1";
|
||||
case (byte) 0x06:
|
||||
return "Second calibration needed";
|
||||
case (byte) 0x08:
|
||||
return "Rejected";
|
||||
case (byte) 0x0B:
|
||||
return "Sensor stopped";
|
||||
case (byte) 0x0D:
|
||||
return "Duplicate";
|
||||
case (byte) 0x0E:
|
||||
return "Not ready to calibrate";
|
||||
case (byte) 0xFF:
|
||||
return "Unable to decode";
|
||||
default:
|
||||
return "Unknown code:" + result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
|
||||
// created by jamorham
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
public class CalibrateTxMessage extends BaseMessage {
|
||||
|
||||
final byte opcode = 0x34;
|
||||
final int length = 9;
|
||||
|
||||
final int glucose;
|
||||
|
||||
public CalibrateTxMessage(int glucose, int dexTime) {
|
||||
init(opcode, length);
|
||||
this.glucose = glucose;
|
||||
data.putShort((short) glucose);
|
||||
data.putInt(dexTime);
|
||||
appendCRC();
|
||||
UserError.Log.d(TAG, "CalibrateGlucoseTxMessage dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.Services.G5CollectionService.TAG;
|
||||
|
||||
public enum CalibrationState {
|
||||
|
||||
// TODO i18n
|
||||
|
||||
Unknown(0x00, "Unknown"),
|
||||
Stopped(0x01, "Stopped"),
|
||||
WarmingUp(0x02, "Warming Up"),
|
||||
ExcessNoise(0x03, "Excess Noise"),
|
||||
NeedsFirstCalibration(0x04, "Needs Initial Calibration"),
|
||||
NeedsSecondCalibration(0x05, "Needs Second Calibration"),
|
||||
Ok(0x06, "OK"),
|
||||
NeedsCalibration(0x07, "Needs Calibration"),
|
||||
CalibrationConfused1(0x08, "Confused Calibration 1"),
|
||||
CalibrationConfused2(0x09, "Confused Calibration 2"),
|
||||
NeedsDifferentCalibration(0x0a, "Needs More Calibration"),
|
||||
SensorFailed(0x0b, "Sensor Failed"),
|
||||
SensorFailed2(0x0c, "Sensor Failed 2"),
|
||||
UnusualCalibration(0x0d, "Unusual Calibration"),
|
||||
InsufficientCalibration(0x0e, "Insufficient Calibration"),
|
||||
Ended(0x0f, "Ended"),
|
||||
SensorFailed3(0x10, "Sensor Failed 3"),
|
||||
TransmitterProblem(0x11, "Transmitter Problem"),
|
||||
Errors(0x12, "Sensor Errors"),
|
||||
SensorFailed4(0x13, "Sensor Failed 4"),
|
||||
SensorFailed5(0x14, "Sensor Failed 5"),
|
||||
SensorFailed6(0x15, "Sensor Failed 6"),
|
||||
SensorFailedStart(0x16, "Sensor Failed Start");
|
||||
|
||||
@Getter
|
||||
byte value;
|
||||
@Getter
|
||||
String text;
|
||||
|
||||
|
||||
private static final SparseArray<CalibrationState> lookup = new SparseArray<>();
|
||||
private static final ImmutableSet<CalibrationState> failed = ImmutableSet.of(SensorFailed, SensorFailed2, SensorFailed3, SensorFailed4, SensorFailed5, SensorFailed6, SensorFailedStart);
|
||||
private static final ImmutableSet<CalibrationState> stopped = ImmutableSet.of(Stopped, Ended, SensorFailed, SensorFailed2, SensorFailed3, SensorFailed4, SensorFailed5, SensorFailed6, SensorFailedStart);
|
||||
|
||||
|
||||
CalibrationState(int value, String text) {
|
||||
this.value = (byte) value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
static {
|
||||
for (CalibrationState state : values()) {
|
||||
lookup.put(state.value, state);
|
||||
}
|
||||
}
|
||||
|
||||
public static CalibrationState parse(byte state) {
|
||||
final CalibrationState result = lookup.get(state);
|
||||
if (result == null) UserError.Log.e(TAG, "Unknown calibration state: " + state);
|
||||
return result != null ? result : Unknown;
|
||||
}
|
||||
|
||||
public static CalibrationState parse(int state) {
|
||||
return parse((byte) state);
|
||||
}
|
||||
|
||||
public boolean usableGlucose() {
|
||||
return this == Ok
|
||||
|| this == NeedsCalibration;
|
||||
}
|
||||
|
||||
public boolean insufficientCalibration() {
|
||||
return this == InsufficientCalibration;
|
||||
}
|
||||
|
||||
public boolean readyForCalibration() {
|
||||
return this == Ok
|
||||
|| needsCalibration();
|
||||
}
|
||||
|
||||
public boolean needsCalibration() {
|
||||
return this == NeedsCalibration
|
||||
|| this == NeedsFirstCalibration
|
||||
|| this == NeedsSecondCalibration
|
||||
|| this == NeedsDifferentCalibration;
|
||||
}
|
||||
|
||||
public boolean sensorStarted() {
|
||||
return !stopped.contains(this);
|
||||
}
|
||||
|
||||
public boolean sensorFailed() {
|
||||
return failed.contains(this);
|
||||
}
|
||||
|
||||
public boolean ended() {
|
||||
return this == Ended;
|
||||
}
|
||||
|
||||
public boolean warmingUp() {
|
||||
return this == WarmingUp;
|
||||
}
|
||||
|
||||
public boolean ok() {
|
||||
return this == Ok;
|
||||
}
|
||||
|
||||
public boolean readyForBackfill() {
|
||||
return this != WarmingUp && this != Stopped && this != Unknown && this != NeedsFirstCalibration && this != NeedsSecondCalibration && this != Errors;
|
||||
}
|
||||
|
||||
public String getExtendedText() {
|
||||
switch (this) {
|
||||
case Ok:
|
||||
if (DexSessionKeeper.isStarted()) {
|
||||
return getText() + " " + DexSessionKeeper.prettyTime();
|
||||
} else {
|
||||
return getText() + " time?";
|
||||
}
|
||||
case WarmingUp:
|
||||
if (DexSessionKeeper.isStarted()) {
|
||||
if (DexSessionKeeper.warmUpTimeValid()) {
|
||||
return getText() + "\n" + DexSessionKeeper.prettyTime() + " left";
|
||||
} else {
|
||||
return getText();
|
||||
}
|
||||
} else {
|
||||
return getText();
|
||||
}
|
||||
|
||||
default:
|
||||
return getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.CompatibleApps;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.CompatibleApps.createActionIntent;
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.CompatibleApps.createChoiceIntent;
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.CompatibleApps.showNotification;
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.Constants.DEX_BASE_ID;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class DexResetHelper {
|
||||
|
||||
private static final String TAG = "DexResetHelper";
|
||||
|
||||
public static void offer(String reason) {
|
||||
|
||||
if (JoH.pratelimit("offer-hard-reset-dex", 1200)) {
|
||||
int id = DEX_BASE_ID;
|
||||
|
||||
final String title = "Hard Reset Transmitter?";
|
||||
|
||||
// piggybacking on the existing choice dialog system for compatible apps
|
||||
showNotification(title, reason,
|
||||
createActionIntent(id, id + 1, CompatibleApps.Feature.HARD_RESET_TRANSMITTER),
|
||||
createActionIntent(id, id + 2, CompatibleApps.Feature.CANCEL),
|
||||
createChoiceIntent(id, id + 3, CompatibleApps.Feature.HARD_RESET_TRANSMITTER, title, reason),
|
||||
id);
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Not offering reset as within rate limit");
|
||||
}
|
||||
}
|
||||
|
||||
public static void cancel() {
|
||||
JoH.cancelNotification(DEX_BASE_ID);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
|
||||
// jamorham
|
||||
|
||||
// track active session time
|
||||
|
||||
public class DexSessionKeeper {
|
||||
|
||||
private static final String PREF_SESSION_START = "OB1-SESSION-START";
|
||||
private static final long WARMUP_PERIOD = Constants.HOUR_IN_MS * 2;
|
||||
|
||||
public static void clearStart() {
|
||||
PersistentStore.setLong(PREF_SESSION_START, 0);
|
||||
}
|
||||
|
||||
public static void setStart(long when) {
|
||||
// TODO sanity check
|
||||
PersistentStore.setLong(PREF_SESSION_START, when);
|
||||
}
|
||||
|
||||
public static long getStart() {
|
||||
// value 0 == not started
|
||||
return PersistentStore.getLong(PREF_SESSION_START);
|
||||
}
|
||||
|
||||
public static boolean isStarted() {
|
||||
return getStart() != 0;
|
||||
}
|
||||
|
||||
public static String prettyTime() {
|
||||
if (isStarted()) {
|
||||
final long elapsed = JoH.msSince(getStart());
|
||||
if (elapsed < WARMUP_PERIOD) {
|
||||
return JoH.niceTimeScalar((double) WARMUP_PERIOD - elapsed, 1);
|
||||
} else {
|
||||
return JoH.niceTimeScalar((double) elapsed, 1);
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// check for mismatch between sensor state currency and transmitter time
|
||||
public static boolean warmUpTimeValid() {
|
||||
return (isStarted() && (JoH.msSince(getStart()) <= WARMUP_PERIOD));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.BgGraphBuilder.DEXCOM_PERIOD;
|
||||
|
||||
public class DexSyncKeeper {
|
||||
|
||||
private static final String TAG = DexTimeKeeper.class.getSimpleName();
|
||||
private static final String DEX_SYNC_STORE = "DEX_SYNC_STORE-";
|
||||
private static final long OLDEST_POSSIBLE = 1533839836123L;
|
||||
private static final long GRACE_TIME = 5000;
|
||||
private static final long VALIDITY_PERIOD = Constants.DAY_IN_MS;
|
||||
|
||||
|
||||
// store sync time as now
|
||||
public static void store(final String transmitterId) {
|
||||
store(transmitterId, JoH.tsl());
|
||||
}
|
||||
|
||||
// store sync time
|
||||
public static void store(final String transmitterId, final long when) {
|
||||
|
||||
if ((transmitterId == null) || (transmitterId.length() != 6)) {
|
||||
UserError.Log.e(TAG, "Invalid dex transmitter in store: " + transmitterId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (when < OLDEST_POSSIBLE) {
|
||||
UserError.Log.wtf(TAG, "Invalid timestamp to store: " + JoH.dateTimeText(when));
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentStore.setLong(DEX_SYNC_STORE + transmitterId, when);
|
||||
UserError.Log.d(TAG, "Sync time updated to: " + JoH.dateTimeText(when));
|
||||
}
|
||||
|
||||
// anticpiate next wake up from now
|
||||
public static long anticipate(final String transmitterId) {
|
||||
return anticipate(transmitterId, JoH.tsl());
|
||||
}
|
||||
|
||||
// anticipate next wake up from time
|
||||
// -1 means we don't know anything
|
||||
static long anticipate(final String transmitterId, final long now) {
|
||||
final long last = PersistentStore.getLong(DEX_SYNC_STORE + transmitterId);
|
||||
if (last < OLDEST_POSSIBLE) {
|
||||
return -1;
|
||||
}
|
||||
if (last > now) {
|
||||
UserError.Log.e(TAG, "Anticipation time in the future! cannot use: " + JoH.dateTimeText(last));
|
||||
return -1; // can't be in the future
|
||||
}
|
||||
|
||||
if (now - last > VALIDITY_PERIOD) {
|
||||
UserError.Log.e(TAG, "Anticipation time too old to use: " + JoH.dateTimeText(last));
|
||||
return -1;
|
||||
}
|
||||
|
||||
final long modulo = (now - last) % DEXCOM_PERIOD;
|
||||
if ((modulo < GRACE_TIME) && ((now - last) > GRACE_TIME)) return now;
|
||||
final long next = now + (DEXCOM_PERIOD - modulo);
|
||||
return next;
|
||||
}
|
||||
|
||||
public static boolean isReady(final String transmitterId) {
|
||||
return anticipate(transmitterId, JoH.tsl()) != -1;
|
||||
}
|
||||
|
||||
// are we outside connection window?
|
||||
// TODO also handle waking up before window PRE_GRACE_TIME = 20seconds?
|
||||
public static boolean outsideWindow(final String transmitterId) {
|
||||
final long now = JoH.tsl();
|
||||
final long next = anticipate(transmitterId, now);
|
||||
return next != now;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
public class DexTimeKeeper {
|
||||
private static final String TAG = DexTimeKeeper.class.getSimpleName();
|
||||
|
||||
private static final String DEX_XMIT_START = "DEX_XMIT_START-";
|
||||
private static final long OLDEST_ALLOWED = 1512245359123L;
|
||||
private static final long DEX_TRANSMITTER_LIFE_SECONDS = 86400 * 120;
|
||||
|
||||
private static String lastTransmitterId = null;
|
||||
|
||||
// update the activation time stored for a transmitter
|
||||
public static void updateAge(final String transmitterId, final int dexTimeStamp) {
|
||||
updateAge(transmitterId, dexTimeStamp, false);
|
||||
}
|
||||
|
||||
// update the activation time stored for a transmitter
|
||||
public static void updateAge(final String transmitterId, final int dexTimeStamp, final boolean absolute) {
|
||||
|
||||
if ((transmitterId == null) || (transmitterId.length() != 6)) {
|
||||
UserError.Log.e(TAG, "Invalid dex transmitter in updateAge: " + transmitterId);
|
||||
return;
|
||||
}
|
||||
if (dexTimeStamp < 1) {
|
||||
UserError.Log.e(TAG, "Invalid dex timestamp in updateAge: " + dexTimeStamp);
|
||||
if (dexTimeStamp == 0 && absolute) {
|
||||
DexResetHelper.offer("Your transmitter clock has stopped or never started. Do you want to hard reset it?");
|
||||
}
|
||||
return;
|
||||
}
|
||||
final long longDexTimeStamp = (long) dexTimeStamp;
|
||||
final long activation_time = JoH.tsl() - (longDexTimeStamp * 1000L);
|
||||
|
||||
if (activation_time > JoH.tsl()) {
|
||||
UserError.Log.wtf(TAG, "Transmitter activation time is in the future. Not possible to update: " + dexTimeStamp);
|
||||
return;
|
||||
}
|
||||
|
||||
UserError.Log.d(TAG, "Activation time updated to: " + JoH.dateTimeText(activation_time));
|
||||
PersistentStore.setLong(DEX_XMIT_START + transmitterId, activation_time);
|
||||
|
||||
}
|
||||
|
||||
public static int getDexTime(String transmitterId, long timestamp) {
|
||||
|
||||
if ((transmitterId == null) || (transmitterId.length() != 6)) {
|
||||
UserError.Log.e(TAG, "Invalid dex transmitter in getDexTime: " + transmitterId);
|
||||
return -3;
|
||||
}
|
||||
if (timestamp < OLDEST_ALLOWED) {
|
||||
UserError.Log.e(TAG, "Invalid timestamp in getDexTime: " + timestamp);
|
||||
return -2;
|
||||
}
|
||||
|
||||
final long transmitter_start_timestamp = PersistentStore.getLong(DEX_XMIT_START + transmitterId);
|
||||
|
||||
if (transmitter_start_timestamp < OLDEST_ALLOWED) {
|
||||
if (JoH.ratelimit("no-valid-dex-timestamp-log", 60)) {
|
||||
UserError.Log.e(TAG, "No valid timestamp stored for transmitter: " + transmitterId);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
final long ms_since = timestamp - transmitter_start_timestamp;
|
||||
if (ms_since < 0) {
|
||||
UserError.Log.e(TAG, "Invalid timestamp comparison for transmitter id: " + transmitterId + " since: " + ms_since + " requested ts: " + JoH.dateTimeText(timestamp) + " with tx start: " + JoH.dateTimeText(transmitter_start_timestamp));
|
||||
return -4;
|
||||
}
|
||||
lastTransmitterId = transmitterId;
|
||||
return (int) (ms_since / 1000L);
|
||||
}
|
||||
|
||||
public static long fromDexTimeCached(int dexTimeStamp) {
|
||||
return fromDexTime(lastTransmitterId, dexTimeStamp);
|
||||
}
|
||||
|
||||
|
||||
public static long fromDexTime(String transmitterId, int dexTimeStamp) {
|
||||
if ((transmitterId == null) || (transmitterId.length() != 6)) {
|
||||
UserError.Log.e(TAG, "Invalid dex transmitter in fromDexTime: " + transmitterId);
|
||||
return -3;
|
||||
}
|
||||
lastTransmitterId = transmitterId;
|
||||
final long transmitter_start_timestamp = PersistentStore.getLong(DEX_XMIT_START + transmitterId);
|
||||
if (transmitter_start_timestamp > 0) {
|
||||
return transmitter_start_timestamp + (((long) dexTimeStamp) * 1000L);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// should we try to use this transmitter
|
||||
public static boolean isInDate(String transmitterId) {
|
||||
final int valid_time = getDexTime(transmitterId, JoH.tsl());
|
||||
return (valid_time >= 0) && (valid_time < DEX_TRANSMITTER_LIFE_SECONDS);
|
||||
}
|
||||
|
||||
public static int getTransmitterAgeInDays(final String transmitterId) {
|
||||
final int valid_time = getDexTime(transmitterId, JoH.tsl());
|
||||
return (valid_time >= 0) ? valid_time / 86400 : -1;
|
||||
}
|
||||
|
||||
public static String extractForStream(String transmitterId) {
|
||||
if (transmitterId == null || transmitterId.length() == 0) return null;
|
||||
final long result = PersistentStore.getLong(DEX_XMIT_START + transmitterId);
|
||||
if (result == 0) return null;
|
||||
return transmitterId + "^" + result;
|
||||
}
|
||||
|
||||
public static void injectFromStream(String stream) {
|
||||
if (stream == null) return;
|
||||
final String[] components = stream.split("\\^");
|
||||
try {
|
||||
if (components.length == 2) {
|
||||
final long time_stamp = Long.parseLong(components[1]);
|
||||
if (time_stamp > OLDEST_ALLOWED) {
|
||||
PersistentStore.setLong(DEX_XMIT_START + components[0], time_stamp);
|
||||
UserError.Log.d(TAG, "Updating time keeper: " + components[0] + " " + JoH.dateTimeText(time_stamp));
|
||||
} else {
|
||||
UserError.Log.wtf(TAG, "Dex Timestamp doesn't meet criteria: " + time_stamp);
|
||||
}
|
||||
} else {
|
||||
UserError.Log.e(TAG, "Invalid injectFromStream length: " + stream);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
UserError.Log.e(TAG, "Invalid injectFromStream: " + stream + " " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class DisconnectTxMessage extends BaseMessage {
|
||||
byte opcode = 0x09;
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
public DisconnectTxMessage() {
|
||||
data = ByteBuffer.allocate(1);
|
||||
data.put(opcode);
|
||||
|
||||
byteSequence = data.array();
|
||||
UserError.Log.d(TAG,"DisconnectTX: "+ JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class EGlucoseRxMessage extends BaseGlucoseRxMessage {
|
||||
|
||||
private static final String TAG = EGlucoseRxMessage.class.getSimpleName();
|
||||
|
||||
private Integer predicted_glucose; // : UInt16
|
||||
public static final byte opcode = 0x4f;
|
||||
|
||||
public EGlucoseRxMessage(byte[] packet) {
|
||||
UserError.Log.d(TAG, "EGlucoseRX dbg: " + JoH.bytesToHex(packet));
|
||||
if (packet.length >= 14) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if ((data.get() == opcode) && checkCRC(packet)) {
|
||||
|
||||
|
||||
//status_raw = data.get();
|
||||
status = TransmitterStatus.getBatteryLevel(data.get()); // ??
|
||||
sequence = data.getInt();
|
||||
timestamp = data.getInt();
|
||||
|
||||
|
||||
int glucoseBytes = data.getShort(); // check signed vs unsigned!!
|
||||
glucoseIsDisplayOnly = (glucoseBytes & 0xf000) > 0;
|
||||
glucose = glucoseBytes & 0xfff;
|
||||
|
||||
state = data.get();
|
||||
trend = data.get();
|
||||
|
||||
if (glucose > 13) {
|
||||
unfiltered = glucose * 1000;
|
||||
filtered = glucose * 1000;
|
||||
} else {
|
||||
filtered = glucose;
|
||||
unfiltered = glucose;
|
||||
}
|
||||
|
||||
predicted_glucose = data.getShort() & 0x03ff; // needs mask??? // remaining bits??
|
||||
|
||||
UserError.Log.d(TAG, "EGlucoseRX: seq:" + sequence + " ts:" + timestamp + " sg:" + glucose + " psg: " + predicted_glucose + " do:" + glucoseIsDisplayOnly + " ss:" + status + " sr:" + status_raw + " st:" + CalibrationState.parse(state) + " tr:" + getTrend());
|
||||
|
||||
}
|
||||
} else {
|
||||
UserError.Log.d(TAG, "GlucoseRxMessage packet length received wrong: " + packet.length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getPredictedGlucose() {
|
||||
return predicted_glucose;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
|
||||
public class EGlucoseTxMessage extends BaseMessage {
|
||||
|
||||
final byte opcode = 0x4e;
|
||||
|
||||
public EGlucoseTxMessage() {
|
||||
data = ByteBuffer.allocate(3).order(ByteOrder.LITTLE_ENDIAN);
|
||||
data.put(opcode);
|
||||
appendCRC();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/19/16.
|
||||
*/
|
||||
public class Extensions {
|
||||
|
||||
public static String bytesToHex(byte[] in) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for(byte b : in) {
|
||||
builder.append(String.format("%02x", b));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static String lastTwoCharactersOfString(final String s) {
|
||||
if (s == null) return "NULL";
|
||||
return s.length() > 1 ? s.substring(s.length() - 2) : "ERR-" + s;
|
||||
}
|
||||
|
||||
public static void doSleep(long time) {
|
||||
try {
|
||||
Thread.sleep(time);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class F2DUnknownRxMessage extends BaseMessage {
|
||||
|
||||
public static final byte opcode = (byte) 0xD0;
|
||||
|
||||
public F2DUnknownRxMessage(final byte[] packet) {
|
||||
// stub
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class FastCRC16 {
|
||||
|
||||
private static final int table[] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, 0x8616};
|
||||
|
||||
public static byte[] calculate(final byte b) {
|
||||
final int crc = table[b & 0xff];
|
||||
return new byte[]{(byte) (crc & 0xff), (byte) ((crc >> 8) & 0xff)};
|
||||
}
|
||||
|
||||
public static byte[] calculate(final byte[] bytes, int end) {
|
||||
int crc = 0;
|
||||
for (int i = 0; i < end; i++) {
|
||||
crc = (crc << 8) ^ table[(crc >>> 8 ^ bytes[i]) & 0xff];
|
||||
}
|
||||
return new byte[]{(byte) (crc & 0xff), (byte) ((crc >> 8) & 0xff)};
|
||||
}
|
||||
|
||||
public static byte[] calculate(final byte[] bytes) {
|
||||
return calculate(bytes, bytes.length);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.G5Model.Ob1G5StateMachine.getRawFirmwareVersionString;
|
||||
|
||||
public class FirmwareCapability {
|
||||
|
||||
private static final ImmutableSet<String> KNOWN_G5_FIRMWARES = ImmutableSet.of("1.0.0.13", "1.0.0.17", "1.0.4.10", "1.0.4.12");
|
||||
private static final ImmutableSet<String> KNOWN_G6_FIRMWARES = ImmutableSet.of("1.6.5.23", "1.6.5.25", "1.6.5.27");
|
||||
private static final ImmutableSet<String> KNOWN_G6_REV2_FIRMWARES = ImmutableSet.of("2.18.2.67", "2.18.2.88", "2.18.2.98");
|
||||
private static final ImmutableSet<String> KNOWN_G6_REV2_RAW_FIRMWARES = ImmutableSet.of("2.18.2.67");
|
||||
private static final ImmutableSet<String> KNOWN_G6_PLUS_FIRMWARES = ImmutableSet.of("2.4.2.88");
|
||||
private static final ImmutableSet<String> KNOWN_TIME_TRAVEL_TESTED = ImmutableSet.of("1.6.5.25");
|
||||
|
||||
// new G6 firmware versions will need to be added here / above
|
||||
static boolean isG6Firmware(final String version) {
|
||||
return version != null && (KNOWN_G6_FIRMWARES.contains(version)
|
||||
|| KNOWN_G6_REV2_FIRMWARES.contains(version)
|
||||
|| KNOWN_G6_PLUS_FIRMWARES.contains(version)
|
||||
|| version.startsWith("1.6.5.")
|
||||
|| version.startsWith("2.18.")
|
||||
|| version.startsWith("2.4."));
|
||||
}
|
||||
|
||||
public static boolean isG6Rev2(final String version) {
|
||||
return version != null && (KNOWN_G6_REV2_FIRMWARES.contains(version) || version.startsWith("2.18."));
|
||||
}
|
||||
|
||||
public static boolean isG6Plus(final String version) {
|
||||
return version != null && (KNOWN_G6_PLUS_FIRMWARES.contains(version) || version.startsWith("2.4."));
|
||||
}
|
||||
|
||||
static boolean isG5Firmware(final String version) {
|
||||
return KNOWN_G5_FIRMWARES.contains(version);
|
||||
}
|
||||
|
||||
static boolean isFirmwareTimeTravelCapable(final String version) {
|
||||
return KNOWN_TIME_TRAVEL_TESTED.contains(version);
|
||||
}
|
||||
|
||||
public static boolean isFirmwareTemperatureCapable(final String version) {
|
||||
return !isG6Rev2(version) && !isG6Plus(version);
|
||||
}
|
||||
|
||||
private static boolean isFirmwarePredictiveCapable(final String version) {
|
||||
return isG6Firmware(version);
|
||||
}
|
||||
|
||||
static boolean isFirmwareRawCapable(final String version) {
|
||||
return version == null
|
||||
|| version.equals("")
|
||||
|| KNOWN_G5_FIRMWARES.contains(version)
|
||||
|| KNOWN_G6_FIRMWARES.contains(version)
|
||||
|| KNOWN_G6_REV2_RAW_FIRMWARES.contains(version);
|
||||
}
|
||||
|
||||
static boolean isFirmwarePreemptiveRestartCapable(final String version) {
|
||||
return isFirmwareRawCapable(version); // hang off this for now as they are currently the same
|
||||
}
|
||||
|
||||
public static boolean isTransmitterPredictiveCapable(final String tx_id) {
|
||||
return isG6Firmware(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
public static boolean isTransmitterG5(final String tx_id) {
|
||||
return isG5Firmware(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
public static boolean isTransmitterG6(final String tx_id) {
|
||||
return isG6Firmware(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
public static boolean isTransmitterG6Rev2(final String tx_id) {
|
||||
return isG6Rev2(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
public static boolean isTransmitterTimeTravelCapable(final String tx_id) {
|
||||
return isFirmwareTimeTravelCapable(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
public static boolean isTransmitterRawCapable(final String tx_id) {
|
||||
return isFirmwareRawCapable(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
public static boolean isTransmitterPreemptiveRestartCapable(final String tx_id) {
|
||||
return isFirmwarePreemptiveRestartCapable(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
|
||||
static long getWarmupPeriodForVersion(final String version) {
|
||||
return isG6Plus(version) ? Constants.HOUR_IN_MS : Constants.HOUR_IN_MS * 2;
|
||||
}
|
||||
|
||||
public static long getWarmupPeriod(final String tx_id) {
|
||||
return getWarmupPeriod(getRawFirmwareVersionString(tx_id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class G6CalibrationParameters {
|
||||
|
||||
public static final String PREF_CURRENT_CODE = "G6-Current-Sensor-Code";
|
||||
|
||||
@Getter
|
||||
private final String code;
|
||||
@Getter
|
||||
private final int paramA;
|
||||
@Getter
|
||||
private final int paramB;
|
||||
|
||||
|
||||
public G6CalibrationParameters(String code) {
|
||||
|
||||
this.code = code;
|
||||
|
||||
switch (code) {
|
||||
|
||||
// special null code
|
||||
case "0000":
|
||||
paramA = 1;
|
||||
paramB = 0;
|
||||
break;
|
||||
|
||||
case "5915":
|
||||
paramA = 3100;
|
||||
paramB = 3600;
|
||||
break;
|
||||
|
||||
case "5917":
|
||||
paramA = 3000;
|
||||
paramB = 3500;
|
||||
break;
|
||||
|
||||
case "5931":
|
||||
paramA = 2900;
|
||||
paramB = 3400;
|
||||
break;
|
||||
|
||||
case "5937":
|
||||
paramA = 2800;
|
||||
paramB = 3300;
|
||||
break;
|
||||
|
||||
case "5951":
|
||||
paramA = 3100;
|
||||
paramB = 3500;
|
||||
break;
|
||||
|
||||
case "5955":
|
||||
paramA = 3000;
|
||||
paramB = 3400;
|
||||
break;
|
||||
|
||||
case "7171":
|
||||
paramA = 2700;
|
||||
paramB = 3300;
|
||||
break;
|
||||
|
||||
case "9117":
|
||||
paramA = 2700;
|
||||
paramB = 3200;
|
||||
break;
|
||||
|
||||
case "9159":
|
||||
paramA = 2600;
|
||||
paramB = 3200;
|
||||
break;
|
||||
|
||||
case "9311":
|
||||
paramA = 2600;
|
||||
paramB = 3100;
|
||||
break;
|
||||
|
||||
case "9371":
|
||||
paramA = 2500;
|
||||
paramB = 3100;
|
||||
break;
|
||||
|
||||
case "9515":
|
||||
paramA = 2500;
|
||||
paramB = 3000;
|
||||
break;
|
||||
|
||||
case "9551":
|
||||
paramA = 2400;
|
||||
paramB = 3000;
|
||||
break;
|
||||
|
||||
case "9577":
|
||||
paramA = 2400;
|
||||
paramB = 2900;
|
||||
break;
|
||||
|
||||
case "9713":
|
||||
paramA = 2300;
|
||||
paramB = 2900;
|
||||
break;
|
||||
|
||||
default:
|
||||
paramA = -1;
|
||||
paramB = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return paramA > 0;
|
||||
}
|
||||
|
||||
public boolean isNullCode() {
|
||||
return isValid() && paramB == 0;
|
||||
}
|
||||
|
||||
public static boolean checkCode(String code) {
|
||||
return new G6CalibrationParameters(code).isValid();
|
||||
}
|
||||
|
||||
public static String getCurrentSensorCode() {
|
||||
final String code = PersistentStore.getString(PREF_CURRENT_CODE);
|
||||
return code.equals("") ? null : code;
|
||||
}
|
||||
|
||||
public static void setCurrentSensorCode(String code) {
|
||||
if (checkCode(code)) {
|
||||
PersistentStore.setString(PREF_CURRENT_CODE, code);
|
||||
} else {
|
||||
PersistentStore.setString(PREF_CURRENT_CODE, "");
|
||||
throw new RuntimeException("Invalid sensor code: " + code);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*
|
||||
* Alternate mechanism for reading data using the transmitter's internal algorithm.
|
||||
*
|
||||
* initial packet structure cribbed from Loopkit
|
||||
*/
|
||||
|
||||
@NoArgsConstructor
|
||||
public class GlucoseRxMessage extends BaseGlucoseRxMessage {
|
||||
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
|
||||
public static final byte opcode = 0x31;
|
||||
|
||||
|
||||
public GlucoseRxMessage(byte[] packet) {
|
||||
UserError.Log.d(TAG, "GlucoseRX dbg: " + JoH.bytesToHex(packet));
|
||||
if (packet.length >= 14) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if ((data.get() == opcode) && checkCRC(packet)) {
|
||||
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
status_raw = data.get(1);
|
||||
status = TransmitterStatus.getBatteryLevel(data.get(1));
|
||||
sequence = data.getInt(2);
|
||||
timestamp = data.getInt(6);
|
||||
|
||||
|
||||
int glucoseBytes = data.getShort(10); // check signed vs unsigned!!
|
||||
glucoseIsDisplayOnly = (glucoseBytes & 0xf000) > 0;
|
||||
glucose = glucoseBytes & 0xfff;
|
||||
|
||||
state = data.get(12);
|
||||
trend = data.get(13);
|
||||
if (glucose > 13) {
|
||||
unfiltered = glucose * 1000;
|
||||
filtered = glucose * 1000;
|
||||
} else {
|
||||
filtered = glucose;
|
||||
unfiltered = glucose;
|
||||
}
|
||||
|
||||
UserError.Log.d(TAG, "GlucoseRX: seq:" + sequence + " ts:" + timestamp + " sg:" + glucose + " do:" + glucoseIsDisplayOnly + " ss:" + status + " sr:" + status_raw + " st:" + CalibrationState.parse(state) + " tr:" + getTrend());
|
||||
|
||||
}
|
||||
} else {
|
||||
UserError.Log.d(TAG, "GlucoseRxMessage packet length received wrong: " + packet.length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
public class GlucoseTxMessage extends BaseMessage {
|
||||
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
static final byte opcode = 0x30;
|
||||
|
||||
public GlucoseTxMessage() {
|
||||
init(opcode, 3);
|
||||
UserError.Log.d(TAG, "GlucoseTx dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class InvalidRxMessage extends BaseMessage {
|
||||
|
||||
public static final byte opcode = (byte) 0xFF;
|
||||
private static final int length = 3;
|
||||
|
||||
InvalidRxMessage(byte[] packet) {
|
||||
|
||||
if ((packet.length == length) && packet[0] == opcode) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
import com.eveningoutpost.dexdrip.Services.G5CollectionService;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class KeepAliveTxMessage extends BaseMessage {
|
||||
public static final int opcode = 0x06;
|
||||
private int time;
|
||||
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
|
||||
public KeepAliveTxMessage(int time) {
|
||||
this.time = time;
|
||||
|
||||
data = ByteBuffer.allocate(2);
|
||||
data.put(new byte[]{(byte) opcode, (byte) this.time});
|
||||
byteSequence = data.order(ByteOrder.LITTLE_ENDIAN).array();
|
||||
|
||||
UserError.Log.d(TAG, "New KeepAliveRequestTxMessage: " + JoH.bytesToHex(byteSequence));
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 12/10/2017.
|
||||
*/
|
||||
|
||||
public class Ob1Work {
|
||||
|
||||
private static final ImmutableSet<Class> streamClasses = ImmutableSet.of(SessionStartTxMessage.class, SessionStopTxMessage.class, CalibrateTxMessage.class);
|
||||
|
||||
@Expose
|
||||
public final BaseMessage msg;
|
||||
@Expose
|
||||
public final String text;
|
||||
@Expose
|
||||
public final long timestamp;
|
||||
public volatile int retry = 0;
|
||||
|
||||
Ob1Work(BaseMessage msg, String text) {
|
||||
this.msg = msg;
|
||||
this.text = text;
|
||||
this.timestamp = JoH.tsl();
|
||||
}
|
||||
|
||||
public boolean streamable() {
|
||||
return streamClasses.contains(msg.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
import static com.eveningoutpost.dexdrip.G5Model.Ob1G5StateMachine.usingG6;
|
||||
|
||||
public class RawScaling {
|
||||
|
||||
public enum DType {
|
||||
G5, G6v1, G6v2
|
||||
}
|
||||
|
||||
public static double scale(final long raw, final DType version, final boolean filtered) {
|
||||
switch (version) {
|
||||
case G6v1:
|
||||
return raw * 34;
|
||||
case G6v2:
|
||||
return (raw - 1151500000) / 110;
|
||||
default:
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
public static double scale(final long raw, final String transmitter_id, final boolean filtered) {
|
||||
final boolean g6 = usingG6();
|
||||
|
||||
if (!g6) {
|
||||
return scale(raw, DType.G5, filtered);
|
||||
} else {
|
||||
final boolean g6r2 = FirmwareCapability.isTransmitterG6Rev2(transmitter_id);
|
||||
return scale(raw, g6r2 ? DType.G6v2 : DType.G6v1, filtered);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
// jamorham
|
||||
|
||||
class ResetTxMessage extends BaseMessage {
|
||||
static final byte opcode = 0x42;
|
||||
|
||||
ResetTxMessage() {
|
||||
init(opcode, 3);
|
||||
UserError.Log.d(TAG, "ResetTx dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
|
||||
import android.text.SpannableString;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.Sensor;
|
||||
import com.eveningoutpost.dexdrip.R;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.StatusItem.Highlight;
|
||||
import com.eveningoutpost.dexdrip.ui.helpers.Span;
|
||||
import com.eveningoutpost.dexdrip.utils.DexCollectionType;
|
||||
import com.eveningoutpost.dexdrip.xdrip;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.G5Model.Ob1G5StateMachine.getFirmwareXDetails;
|
||||
import static com.eveningoutpost.dexdrip.Models.JoH.msSince;
|
||||
import static com.eveningoutpost.dexdrip.Models.JoH.roundDouble;
|
||||
import static com.eveningoutpost.dexdrip.Models.JoH.tsl;
|
||||
import static com.eveningoutpost.dexdrip.Services.G5BaseService.usingG6;
|
||||
import static com.eveningoutpost.dexdrip.Services.Ob1G5CollectionService.getTransmitterID;
|
||||
import static com.eveningoutpost.dexdrip.Services.Ob1G5CollectionService.usingNativeMode;
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.Constants.DAY_IN_MS;
|
||||
import static com.eveningoutpost.dexdrip.utils.DexCollectionType.getDexCollectionType;
|
||||
import static com.eveningoutpost.dexdrip.utils.DexCollectionType.hasDexcomRaw;
|
||||
import static com.eveningoutpost.dexdrip.utils.DexCollectionType.hasLibre;
|
||||
|
||||
// jamorham
|
||||
|
||||
// helper class to deal with sensor expiry
|
||||
|
||||
public class SensorDays {
|
||||
|
||||
private static final String TAG = "SensorDays";
|
||||
|
||||
private static final long UNKNOWN = -1;
|
||||
private static final int USE_DEXCOM_STRATEGY = 5;
|
||||
private static final int USE_LIBRE_STRATEGY = 6;
|
||||
|
||||
private static final long CAL_THRESHOLD1 = DAY_IN_MS * 4;
|
||||
private static final long CAL_THRESHOLD2 = Constants.HOUR_IN_MS * 18;
|
||||
|
||||
private static final HashMap<String, SensorDays> cache = new HashMap<>();
|
||||
|
||||
@Getter
|
||||
private long period = UNKNOWN;
|
||||
private long created = 0;
|
||||
private int strategy = 0;
|
||||
|
||||
// load current config and compute
|
||||
public static SensorDays get() {
|
||||
val type = getDexCollectionType();
|
||||
val tx_id = getTransmitterID();
|
||||
return get(type, tx_id);
|
||||
}
|
||||
|
||||
// compute based on type
|
||||
public static SensorDays get(final DexCollectionType type, final String tx_id) {
|
||||
|
||||
// get cached result
|
||||
val result = cache.get(type.toString() + tx_id);
|
||||
if (result != null && result.cacheValid()) return result;
|
||||
|
||||
val ths = new SensorDays();
|
||||
|
||||
if (hasLibre(type)) {
|
||||
ths.period = Constants.DAY_IN_MS * 14; // TODO 10 day sensors?
|
||||
ths.strategy = USE_LIBRE_STRATEGY;
|
||||
|
||||
} else if (hasDexcomRaw(type)) {
|
||||
ths.strategy = USE_DEXCOM_STRATEGY;
|
||||
val vr2 = (VersionRequest2RxMessage)
|
||||
getFirmwareXDetails(tx_id, 2);
|
||||
if (vr2 != null) {
|
||||
ths.period = Constants.DAY_IN_MS * vr2.typicalSensorDays;
|
||||
} else {
|
||||
if (usingG6()) {
|
||||
ths.period = Constants.DAY_IN_MS * 10; // G6 default
|
||||
} else {
|
||||
ths.period = Constants.DAY_IN_MS * 7; // G5
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// unknown type
|
||||
}
|
||||
ths.created = tsl();
|
||||
cache.put(type.toString() + tx_id, ths);
|
||||
return ths;
|
||||
}
|
||||
|
||||
private long getDexcomStart() {
|
||||
if (usingNativeMode()) {
|
||||
return DexSessionKeeper.getStart();
|
||||
} else {
|
||||
try {
|
||||
// In non-native mode the expiration is a guide only
|
||||
return Sensor.currentSensor().started_at;
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getLibreStart() {
|
||||
try {
|
||||
val age_minutes = Pref.getInt("nfc_sensor_age", -50000);
|
||||
if (age_minutes > 0) {
|
||||
return tsl() - (age_minutes * Constants.MINUTE_IN_MS);
|
||||
} else {
|
||||
return Sensor.currentSensor().started_at;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private long getStart() {
|
||||
switch (strategy) {
|
||||
case USE_DEXCOM_STRATEGY:
|
||||
return getDexcomStart();
|
||||
case USE_LIBRE_STRATEGY:
|
||||
return getLibreStart();
|
||||
default:
|
||||
return 0; // very large error default will be caught by sanity check
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isStarted() {
|
||||
return getStart() > 0;
|
||||
}
|
||||
|
||||
// returns 0 if invalid
|
||||
long getRemainingSensorPeriodInMs() {
|
||||
//UserError.Log.d(TAG, "Get start debug returns: " + JoH.dateTimeText(getStart()));
|
||||
if (isValid()) {
|
||||
val elapsed = msSince(getStart());
|
||||
long remaining = period - elapsed;
|
||||
// sanity check
|
||||
if ((remaining < 0) || (remaining > period)) {
|
||||
remaining = 0;
|
||||
}
|
||||
return remaining;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
long getSensorEndTimestamp() {
|
||||
if (isValid()) {
|
||||
return getStart() + period;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Add resolution / update cache
|
||||
public SpannableString getSpannable() {
|
||||
|
||||
val expiryMs = getRemainingSensorPeriodInMs();
|
||||
|
||||
if (expiryMs > 0) {
|
||||
if (expiryMs > CAL_THRESHOLD1) {
|
||||
val fmt = xdrip.gs(R.string.expires_days);
|
||||
return new SpannableString(MessageFormat.format(fmt, roundDouble((double) expiryMs / DAY_IN_MS, 1)));
|
||||
} else {
|
||||
// expiring soon
|
||||
val niceTime = new SimpleDateFormat(expiryMs < CAL_THRESHOLD2 ? "h:mm a" : "EEE, h:mm a", Locale.getDefault()).format(getSensorEndTimestamp());
|
||||
return Span.colorSpan(MessageFormat.format(xdrip.gs(R.string.expires_at), niceTime), expiryMs < CAL_THRESHOLD2 ? Highlight.BAD.color() : Highlight.NOTICE.color());
|
||||
}
|
||||
}
|
||||
return new SpannableString("");
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return isKnown() && isSessionLive() && isStarted();
|
||||
}
|
||||
|
||||
private boolean isSessionLive() {
|
||||
return Sensor.isActive();
|
||||
}
|
||||
|
||||
boolean isKnown() {
|
||||
return period != UNKNOWN;
|
||||
}
|
||||
|
||||
boolean cacheValid() {
|
||||
return msSince(created) < Constants.MINUTE_IN_MS * 10;
|
||||
}
|
||||
|
||||
void invalidateCache() {
|
||||
created = -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Created by jcostik1 on 3/26/16.
|
||||
*/
|
||||
public class SensorRxMessage extends BaseMessage {
|
||||
public static final byte opcode = 0x2f;
|
||||
public TransmitterStatus status;
|
||||
public int timestamp;
|
||||
public int unfiltered;
|
||||
public int filtered;
|
||||
private final static String TAG = G5CollectionService.TAG; // meh
|
||||
|
||||
public SensorRxMessage(byte[] packet) {
|
||||
UserError.Log.d(TAG, "SensorRX dbg: " + JoH.bytesToHex(packet));
|
||||
if (packet.length >= 14) {
|
||||
if (packet[0] == opcode) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
status = TransmitterStatus.getBatteryLevel(data.get(1));
|
||||
timestamp = data.getInt(2);
|
||||
|
||||
unfiltered = data.getInt(6);
|
||||
filtered = data.getInt(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Created by jcostik1 on 3/26/16.
|
||||
*/
|
||||
public class SensorTxMessage extends BaseMessage {
|
||||
byte opcode = 0x2e;
|
||||
byte[] crc = CRC.calculate(opcode);
|
||||
|
||||
|
||||
public SensorTxMessage() {
|
||||
data = ByteBuffer.allocate(3);
|
||||
data.put(opcode);
|
||||
data.put(crc);
|
||||
byteSequence = data.array();
|
||||
UserError.Log.d(TAG, "SensorTx dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class SessionStartRxMessage extends BaseMessage {
|
||||
public static final byte opcode = 0x27;
|
||||
final byte length = 17;
|
||||
|
||||
private byte status = (byte) 0xFF;
|
||||
private byte info = (byte) 0xFF;
|
||||
final String transmitterId;
|
||||
int sessionStartTime = 0;
|
||||
int requestedStartTime = 0;
|
||||
int transitterTime = 0;
|
||||
|
||||
boolean valid = false;
|
||||
|
||||
public SessionStartRxMessage(byte[] packet, String transmitterId) {
|
||||
this.transmitterId = transmitterId;
|
||||
if (packet.length == length) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if ((data.get() == opcode) && checkCRC(packet)) {
|
||||
valid = true;
|
||||
status = data.get();
|
||||
info = data.get();
|
||||
requestedStartTime = data.getInt();
|
||||
sessionStartTime = data.getInt();
|
||||
transitterTime = data.getInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
boolean isOkay() {
|
||||
return isValid() && status == 0x00 && (info == 0x01 || info == 0x05) && sessionStartTime != INVALID_TIME;
|
||||
}
|
||||
|
||||
// beyond hope?
|
||||
boolean isFubar() {
|
||||
return info == 0x04;
|
||||
}
|
||||
|
||||
long getSessionStart() {
|
||||
if (isOkay() && sessionStartTime > 0) {
|
||||
return DexTimeKeeper.fromDexTime(transmitterId, sessionStartTime);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
long getRequestedStart() {
|
||||
if (isOkay() && requestedStartTime > 0) {
|
||||
return DexTimeKeeper.fromDexTime(transmitterId, requestedStartTime);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
long getTransmitterTime() {
|
||||
if (isOkay() && transitterTime > 0) {
|
||||
return DexTimeKeeper.fromDexTime(transmitterId, transitterTime);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
String message() {
|
||||
switch (info) {
|
||||
case 0x01:
|
||||
return "OK";
|
||||
case 0x02:
|
||||
return "Already started";
|
||||
case 0x03:
|
||||
return "Invalid";
|
||||
case 0x04:
|
||||
return "Clock not synchronized or other error"; // probably
|
||||
case 0x05:
|
||||
return "OK G6"; // probably
|
||||
default:
|
||||
return "Unknown code: " + info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class SessionStartTxMessage extends BaseMessage {
|
||||
|
||||
final byte opcode = 0x26;
|
||||
@Getter
|
||||
private final long startTime;
|
||||
@Getter
|
||||
private final int dexTime;
|
||||
|
||||
public SessionStartTxMessage(int dexTime) {
|
||||
this((int) (JoH.tsl() / 1000), dexTime);
|
||||
}
|
||||
|
||||
public SessionStartTxMessage(long startTime, int dexTime) {
|
||||
this(startTime, dexTime, null);
|
||||
}
|
||||
|
||||
public SessionStartTxMessage(long startTime, int dexTime, String code) {
|
||||
this.startTime = startTime;
|
||||
this.dexTime = dexTime;
|
||||
final boolean using_g6 = (code != null);
|
||||
data = ByteBuffer.allocate(code == null || new G6CalibrationParameters(code).isNullCode() ? (using_g6 ? 13 : 11) : 17);
|
||||
data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
data.put(opcode);
|
||||
data.putInt(dexTime);
|
||||
data.putInt((int) (startTime / 1000));
|
||||
|
||||
if (code != null) {
|
||||
final G6CalibrationParameters params = new G6CalibrationParameters(code);
|
||||
if (params.isValid() && !params.isNullCode()) {
|
||||
data.putShort((short) params.getParamA());
|
||||
data.putShort((short) params.getParamB());
|
||||
} else {
|
||||
if (!params.isValid()) {
|
||||
throw new IllegalArgumentException("Invalid G6 code in SessionStartTxMessage");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (using_g6) {
|
||||
data.putShort((short) 0x0000);
|
||||
}
|
||||
appendCRC();
|
||||
UserError.Log.d(TAG, "SessionStartTxMessage dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class SessionStopRxMessage extends BaseMessage {
|
||||
|
||||
public static final byte opcode = 0x29;
|
||||
final byte length = 17;
|
||||
@Getter
|
||||
private byte status = (byte)0xFF;
|
||||
private byte received = (byte)0xFF;
|
||||
final String transmitterId;
|
||||
int sessionStartTime=0;
|
||||
int sessionStopTime =0;
|
||||
int transitterTime=0;
|
||||
boolean valid = false;
|
||||
|
||||
public SessionStopRxMessage(byte[] packet,String transmitterId) {
|
||||
this.transmitterId = transmitterId;
|
||||
if (packet.length == length) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if ((data.get() == opcode) && checkCRC(packet)) {
|
||||
valid = true;
|
||||
status = data.get();
|
||||
received = data.get();
|
||||
sessionStopTime = data.getInt();
|
||||
sessionStartTime = data.getInt();
|
||||
transitterTime = data.getInt();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
boolean isOkay() {
|
||||
return isValid() && status == 0x00;
|
||||
}
|
||||
|
||||
long getSessionStart() {
|
||||
if (isOkay() && sessionStartTime > 0) {
|
||||
return DexTimeKeeper.fromDexTime(transmitterId, sessionStartTime);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
long getSessionStop() {
|
||||
if (isOkay() && sessionStopTime > 0) {
|
||||
return DexTimeKeeper.fromDexTime(transmitterId, sessionStopTime);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
// created by jamorham
|
||||
|
||||
public class SessionStopTxMessage extends BaseMessage {
|
||||
|
||||
final byte opcode = 0x28;
|
||||
final int length = 7;
|
||||
{
|
||||
postExecuteGuardTime = 1000;
|
||||
}
|
||||
|
||||
SessionStopTxMessage(int stopTime) {
|
||||
|
||||
init(opcode, length);
|
||||
data.putInt(stopTime);
|
||||
appendCRC();
|
||||
}
|
||||
|
||||
SessionStopTxMessage(String transmitterId) {
|
||||
final int stopTime = DexTimeKeeper.getDexTime(transmitterId, JoH.tsl());
|
||||
init(opcode, 7);
|
||||
data.putInt(stopTime);
|
||||
appendCRC();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
// jamorham
|
||||
|
||||
public class TimeTxMessage extends BaseMessage {
|
||||
public static final byte opcode = 0x24;
|
||||
|
||||
TimeTxMessage() {
|
||||
init(opcode, 3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/19/16.
|
||||
*/
|
||||
public class Transmitter {
|
||||
public String transmitterId = "";
|
||||
|
||||
public Transmitter(String id) {
|
||||
transmitterId = id;
|
||||
}
|
||||
|
||||
public void authenticate() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Services.G5CollectionService;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
|
||||
public class TransmitterMessage {
|
||||
protected static final String TAG = G5CollectionService.TAG; // meh
|
||||
static final int INVALID_TIME = 0xFFFFFFFF;
|
||||
@Expose
|
||||
long postExecuteGuardTime = 50;
|
||||
|
||||
@Expose
|
||||
public volatile byte[] byteSequence = null;
|
||||
public ByteBuffer data = null;
|
||||
|
||||
|
||||
public void setData() {
|
||||
byte[] newData;
|
||||
}
|
||||
|
||||
static int getUnsignedShort(ByteBuffer data) {
|
||||
return ((data.get() & 0xff) + ((data.get() & 0xff) << 8));
|
||||
}
|
||||
|
||||
static int getUnsignedByte(ByteBuffer data) {
|
||||
return ((data.get() & 0xff));
|
||||
}
|
||||
|
||||
static int getUnixTime() {
|
||||
return (int) (JoH.tsl() / 1000);
|
||||
}
|
||||
|
||||
void init(byte opcode, int length) {
|
||||
data = ByteBuffer.allocate(length);
|
||||
data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
data.put(opcode);
|
||||
}
|
||||
|
||||
|
||||
byte[] appendCRC() {
|
||||
data.put(CRC.calculate(getByteSequence(), 0, byteSequence.length - 2));
|
||||
return getByteSequence();
|
||||
}
|
||||
|
||||
boolean checkCRC(byte[] data) {
|
||||
if ((data == null) || (data.length < 3)) return false;
|
||||
final byte[] crc = CRC.calculate(data, 0, data.length - 2);
|
||||
return crc[0] == data[data.length - 2] && crc[1] == data[data.length - 1];
|
||||
}
|
||||
|
||||
byte[] getByteSequence() {
|
||||
byteSequence = data.array();
|
||||
return byteSequence;
|
||||
}
|
||||
|
||||
long guardTime() {
|
||||
return postExecuteGuardTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/28/16.
|
||||
*/
|
||||
public enum TransmitterStatus {
|
||||
UNKNOWN, BRICKED, LOW, OK;
|
||||
|
||||
public static TransmitterStatus getBatteryLevel(int b) {
|
||||
if (b > 0x81) {
|
||||
return BRICKED;
|
||||
}
|
||||
else {
|
||||
if (b == 0x81) {
|
||||
return LOW;
|
||||
}
|
||||
else if (b == 0x00) {
|
||||
return OK;
|
||||
}
|
||||
else {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/28/16.
|
||||
*/
|
||||
public class TransmitterTimeRxMessage extends BaseMessage {
|
||||
public static final byte opcode = 0x25;
|
||||
@Getter
|
||||
private TransmitterStatus status;
|
||||
@Getter
|
||||
private int currentTime;
|
||||
@Getter
|
||||
private int sessionStartTime;
|
||||
|
||||
public TransmitterTimeRxMessage(byte[] packet) {
|
||||
if (packet.length >= 10) {
|
||||
if (packet[0] == opcode && checkCRC(packet)) {
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
status = TransmitterStatus.getBatteryLevel(data.get(1));
|
||||
currentTime = data.getInt(2);
|
||||
sessionStartTime = data.getInt(6);
|
||||
// TODO more bytes after this?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean sessionInProgress() {
|
||||
return sessionStartTime != -1 && currentTime != sessionStartTime;
|
||||
}
|
||||
|
||||
public long getRealSessionStartTime(long now) {
|
||||
return now - ((currentTime - sessionStartTime) * 1000);
|
||||
}
|
||||
|
||||
public long getRealSessionStartTime() {
|
||||
|
||||
if (sessionInProgress()) {
|
||||
return getRealSessionStartTime(JoH.tsl());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public long getSessionDuration() {
|
||||
if (sessionInProgress()) {
|
||||
return JoH.msSince(getRealSessionStartTime());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/28/16.
|
||||
*/
|
||||
public class TransmitterTimeTxMessage extends TransmitterMessage {
|
||||
public static final byte opcode = 0x24;
|
||||
private final static byte[] crc = CRC.calculate(opcode);
|
||||
|
||||
public TransmitterTimeTxMessage() {
|
||||
data = ByteBuffer.allocate(3);
|
||||
data.put(opcode);
|
||||
data.put(crc);
|
||||
byteSequence = data.array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Created by joeginley on 3/16/16.
|
||||
*/
|
||||
public class UnbondRequestTxMessage extends TransmitterMessage {
|
||||
byte opcode = 0x6;
|
||||
|
||||
public UnbondRequestTxMessage() {
|
||||
data = ByteBuffer.allocate(1);
|
||||
data.put(opcode);
|
||||
|
||||
byteSequence = data.array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
public class VersionRequest1RxMessage extends BaseMessage {
|
||||
|
||||
public static final byte opcode = 0x4B;
|
||||
|
||||
public int status;
|
||||
public String firmware_version_string;
|
||||
public long build_version;
|
||||
public int version_code;
|
||||
public int inactive_days;
|
||||
public int max_inactive_days;
|
||||
public int max_runtime_days;
|
||||
|
||||
|
||||
public VersionRequest1RxMessage(byte[] packet) {
|
||||
if (packet.length >= 18) {
|
||||
// TODO check CRC??
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (data.get() == opcode) {
|
||||
status = data.get();
|
||||
firmware_version_string = dottedStringFromData(data, 4);
|
||||
build_version = getUnsignedInt(data);
|
||||
inactive_days = getUnsignedShort(data);
|
||||
version_code = getUnsignedByte(data);
|
||||
max_runtime_days = getUnsignedShort(data);
|
||||
max_inactive_days = getUnsignedShort(data);
|
||||
// crc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "Status: %s / FW version: %s / Version Code: %d / Build: %d / Inactive: %d / Max Inactive: %d / Max Runtime: %d",
|
||||
TransmitterStatus.getBatteryLevel(status).toString(), firmware_version_string, version_code, build_version, inactive_days, max_inactive_days, max_runtime_days);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
|
||||
public class VersionRequest2RxMessage extends BaseMessage {
|
||||
|
||||
public static final byte opcode = 0x53;
|
||||
|
||||
public int status;
|
||||
public int typicalSensorDays;
|
||||
public int featureBits;
|
||||
|
||||
|
||||
public VersionRequest2RxMessage(byte[] packet) {
|
||||
if (packet.length >= 18) {
|
||||
// TODO check CRC??
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (data.get() == opcode) {
|
||||
status = data.get();
|
||||
typicalSensorDays = getUnsignedByte(data);
|
||||
featureBits = getUnsignedShort(data);
|
||||
// 12 more bytes of unknown data
|
||||
// crc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "Status: %s / Typical Days: %d / : Feature Bits %d",
|
||||
TransmitterStatus.getBatteryLevel(status).toString(), typicalSensorDays, featureBits);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
|
||||
public class VersionRequestRxMessage extends BaseMessage {
|
||||
|
||||
public static final byte opcode = 0x21;
|
||||
|
||||
public int status;
|
||||
public String firmware_version_string;
|
||||
public String bluetooth_firmware_version_string;
|
||||
public int hardwarev;
|
||||
public String other_firmware_version;
|
||||
public int asic;
|
||||
|
||||
|
||||
public VersionRequestRxMessage(byte[] packet) {
|
||||
if (packet.length >= 18) {
|
||||
// TODO check CRC??
|
||||
data = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN);
|
||||
if (data.get() == opcode) {
|
||||
status = data.get();
|
||||
firmware_version_string = dottedStringFromData(data, 4);
|
||||
bluetooth_firmware_version_string = dottedStringFromData(data, 4);
|
||||
hardwarev = data.get();
|
||||
other_firmware_version = dottedStringFromData(data, 3);
|
||||
asic = getUnsignedShort(data); // check signed vs unsigned & byte order!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format(Locale.US, "Status: %s / Firmware: %s / BT-Firmware: %s / Other-FW: %s / hardwareV: %d / asic: %d",
|
||||
TransmitterStatus.getBatteryLevel(status).toString(), firmware_version_string, bluetooth_firmware_version_string, other_firmware_version, hardwarev, asic);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.eveningoutpost.dexdrip.G5Model;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 25/11/2016.
|
||||
*/
|
||||
|
||||
public class VersionRequestTxMessage extends BaseMessage {
|
||||
|
||||
static final byte opcode0 = 0x20;
|
||||
static final byte opcode1 = 0x4A;
|
||||
static final byte opcode2 = 0x52;
|
||||
|
||||
public VersionRequestTxMessage() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public VersionRequestTxMessage(final int version) {
|
||||
byte this_opcode = 0;
|
||||
switch (version) {
|
||||
case 0:
|
||||
this_opcode = opcode0;
|
||||
break;
|
||||
case 1:
|
||||
this_opcode = opcode1;
|
||||
break;
|
||||
case 2:
|
||||
this_opcode = opcode2;
|
||||
break;
|
||||
|
||||
}
|
||||
init(this_opcode, 3);
|
||||
UserError.Log.d(TAG, "VersionTx (" + version + ") dbg: " + JoH.bytesToHex(byteSequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class CRC16 {
|
||||
public static byte[] calculate(byte[] buff, int start, int end) {
|
||||
int crcShort = 0;
|
||||
for (int i = start; i < end; i++) {
|
||||
crcShort = ((crcShort >>> 8) | (crcShort << 8) )& 0xffff;
|
||||
crcShort ^= (buff[i] & 0xff);
|
||||
crcShort ^= ((crcShort & 0xff) >> 4);
|
||||
crcShort ^= (crcShort << 12) & 0xffff;
|
||||
crcShort ^= ((crcShort & 0xFF) << 5) & 0xffff;
|
||||
}
|
||||
crcShort &= 0xffff;
|
||||
return new byte[] {(byte) (crcShort & 0xff), (byte) ((crcShort >> 8) & 0xff)};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class CRCFailRuntimeException extends RuntimeException {
|
||||
public CRCFailRuntimeException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class Dex_Constants {
|
||||
|
||||
public final static int NULL = 0;
|
||||
public final static int ACK = 1;
|
||||
public final static int NAK = 2;
|
||||
public final static int INVALID_COMMAND = 3;
|
||||
public final static int INVALID_PARAM = 4;
|
||||
public final static int INCOMPLETE_PACKET_RECEIVED = 5;
|
||||
public final static int RECEIVER_ERROR = 6;
|
||||
public final static int INVALID_MODE = 7;
|
||||
public final static int PING = 10;
|
||||
public final static int READ_FIRMWARE_HEADER = 11;
|
||||
public final static int READ_DATABASE_PARTITION_INFO = 15;
|
||||
public final static int READ_DATABASE_PAGE_RANGE = 16;
|
||||
public final static int READ_DATABASE_PAGES = 17;
|
||||
public final static int READ_DATABASE_PAGE_HEADER = 18;
|
||||
public final static int READ_TRANSMITTER_ID = 25;
|
||||
public final static int WRITE_TRANSMITTER_ID = 26;
|
||||
public final static int READ_LANGUAGE = 27;
|
||||
public final static int WRITE_LANGUAGE = 28;
|
||||
public final static int READ_DISPLAY_TIME_OFFSET = 29;
|
||||
public final static int WRITE_DISPLAY_TIME_OFFSET = 30;
|
||||
public final static int READ_RTC = 31;
|
||||
public final static int RESET_RECEIVER = 32;
|
||||
public final static int READ_BATTERY_LEVEL = 33;
|
||||
public final static int READ_SYSTEM_TIME = 34;
|
||||
public final static int READ_SYSTEM_TIME_OFFSET = 35;
|
||||
public final static int WRITE_SYSTEM_TIME = 36;
|
||||
public final static int READ_GLUCOSE_UNIT = 37;
|
||||
public final static int WRITE_GLUCOSE_UNIT = 38;
|
||||
public final static int READ_BLINDED_MODE = 39;
|
||||
public final static int WRITE_BLINDED_MODE = 40;
|
||||
public final static int READ_CLOCK_MODE = 41;
|
||||
public final static int WRITE_CLOCK_MODE = 42;
|
||||
public final static int READ_DEVICE_MODE = 43;
|
||||
public final static int ERASE_DATABASE = 45;
|
||||
public final static int SHUTDOWN_RECEIVER = 46;
|
||||
public final static int WRITE_PC_PARAMETERS = 47;
|
||||
public final static int READ_BATTERY_STATE = 48;
|
||||
public final static int READ_HARDWARE_BOARD_ID = 49;
|
||||
public final static int READ_FIRMWARE_SETTINGS = 54;
|
||||
public final static int READ_ENABLE_SETUP_WIZARD_FLAG = 55;
|
||||
public final static int READ_SETUP_WIZARD_STATE = 57;
|
||||
public final static int MAX_COMMAND = 59;
|
||||
public final static int MAX_POSSIBLE_COMMAND = 255;
|
||||
public final static int EGV_VALUE_MASK = 1023;
|
||||
public final static int EGV_DISPLAY_ONLY_MASK = 32768;
|
||||
public final static int EGV_TREND_ARROW_MASK = 15;
|
||||
public final static int EGV_NOISE_MASK = 112;
|
||||
public final static float MG_DL_TO_MMOL_L = 0.05556f;
|
||||
public final static int CRC_LEN = 2;
|
||||
public static final int TRANSMITTER_BATTERY_LOW = 210;
|
||||
public static final int TRANSMITTER_BATTERY_EMPTY = 207;
|
||||
|
||||
public enum BATTERY_STATES {
|
||||
NONE,
|
||||
CHARGING,
|
||||
NOT_CHARGING,
|
||||
NTC_FAULT,
|
||||
BAD_BATTERY
|
||||
}
|
||||
|
||||
public enum RECORD_TYPES {
|
||||
MANUFACTURING_DATA,
|
||||
FIRMWARE_PARAMETER_DATA,
|
||||
PC_SOFTWARE_PARAMETER,
|
||||
SENSOR_DATA,
|
||||
EGV_DATA,
|
||||
CAL_SET,
|
||||
DEVIATION,
|
||||
INSERTION_TIME,
|
||||
RECEIVER_LOG_DATA,
|
||||
RECEIVER_ERROR_DATA,
|
||||
METER_DATA,
|
||||
USER_EVENT_DATA,
|
||||
USER_SETTING_DATA,
|
||||
MAX_VALUE
|
||||
}
|
||||
|
||||
public enum TREND_ARROW_VALUES {
|
||||
NONE(0),
|
||||
DOUBLE_UP(1,"\u21C8", "DoubleUp"),
|
||||
SINGLE_UP(2,"\u2191", "SingleUp"),
|
||||
UP_45(3,"\u2197", "FortyFiveUp"),
|
||||
FLAT(4,"\u2192", "Flat"),
|
||||
DOWN_45(5,"\u2198", "FortyFiveDown"),
|
||||
SINGLE_DOWN(6,"\u2193", "SingleDown"),
|
||||
DOUBLE_DOWN(7,"\u21CA", "DoubleDown"),
|
||||
NOT_COMPUTABLE(8, "", "NOT_COMPUTABLE"),
|
||||
OUT_OF_RANGE(9, "", "OUT_OF_RANGE");
|
||||
|
||||
private String arrowSymbol;
|
||||
private String trendName;
|
||||
private int myID;
|
||||
|
||||
TREND_ARROW_VALUES(int id, String a, String t) {
|
||||
myID=id;
|
||||
arrowSymbol = a;
|
||||
trendName = t;
|
||||
}
|
||||
|
||||
TREND_ARROW_VALUES(int id) {
|
||||
this(id,null, null);
|
||||
}
|
||||
|
||||
public String Symbol() {
|
||||
if (arrowSymbol == null) {
|
||||
return "\u2194";
|
||||
} else {
|
||||
return arrowSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
public String friendlyTrendName() {
|
||||
if (trendName == null) {
|
||||
return this.name().replace("_", " ");
|
||||
} else {
|
||||
return this.trendName;
|
||||
}
|
||||
}
|
||||
|
||||
public int getID(){
|
||||
return myID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum SPECIALBGVALUES_MGDL {
|
||||
NONE("??0", 0),
|
||||
SENSORNOTACTIVE("?SN", 1),
|
||||
MINIMALLYEGVAB("??2", 2),
|
||||
NOANTENNA("?NA", 3),
|
||||
SENSOROUTOFCAL("?NC", 5),
|
||||
COUNTSAB("?CD", 6),
|
||||
ABSOLUTEAB("?AD", 9),
|
||||
POWERAB("???", 10),
|
||||
RFBADSTATUS("?RF", 12);
|
||||
|
||||
|
||||
private String name;
|
||||
private int val;
|
||||
private SPECIALBGVALUES_MGDL(String s, int i){
|
||||
name=s;
|
||||
val=i;
|
||||
}
|
||||
|
||||
public int getValue(){
|
||||
return val;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
|
||||
public static SPECIALBGVALUES_MGDL getEGVSpecialValue(int val){
|
||||
for (SPECIALBGVALUES_MGDL e: values()){
|
||||
if (e.getValue()==val)
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isSpecialValue(int val){
|
||||
for (SPECIALBGVALUES_MGDL e: values()){
|
||||
if (e.getValue()==val)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum InsertionState {
|
||||
NONE,
|
||||
REMOVED,
|
||||
EXPIRED,
|
||||
RESIDUAL_DEVIATION,
|
||||
COUNTS_DEVIATION,
|
||||
SECOND_SESSION,
|
||||
OFF_TIME_LOSS,
|
||||
STARTED,
|
||||
BAD_TRANSMITTER,
|
||||
MANUFACTURING_MODE,
|
||||
MAX_VALUE
|
||||
}
|
||||
|
||||
public enum NOISE {
|
||||
NOISE_NONE(0),
|
||||
CLEAN(1),
|
||||
LIGHT(2),
|
||||
MEDIUM(3),
|
||||
HEAVY(4),
|
||||
NOT_COMPUTED(5),
|
||||
MAX(6);
|
||||
|
||||
private final int value;
|
||||
|
||||
private NOISE(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class PacketBuilder {
|
||||
public static final int MAX_PAYLOAD = 1584;
|
||||
public static final int MIN_LEN = 6;
|
||||
public static final int MAX_LEN = MAX_PAYLOAD + MIN_LEN;
|
||||
public static final byte SOF = 0x01;
|
||||
public static final int OFFSET_SOF = 0;
|
||||
public static final int OFFSET_LENGTH = 1;
|
||||
public static final int OFFSET_NULL = 2;
|
||||
public static final byte NULL = 0x00;
|
||||
public static final int OFFSET_CMD = 3;
|
||||
public static final int OFFSET_PAYLOAD = 4;
|
||||
public static final int CRC_LEN = 2;
|
||||
public static final int HEADER_LEN = 4;
|
||||
public ArrayList<Byte> packet;
|
||||
public int command;
|
||||
public ArrayList<Byte> payload;
|
||||
|
||||
public PacketBuilder(int command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public PacketBuilder(int command, ArrayList<Byte> payload) {
|
||||
this.command = command;
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public byte[] compose() {
|
||||
packet = new ArrayList<Byte>();
|
||||
packet.add(OFFSET_SOF, SOF);
|
||||
packet.add(OFFSET_LENGTH, getLength());
|
||||
packet.add(OFFSET_NULL, NULL);
|
||||
packet.add(OFFSET_CMD, (byte) command);
|
||||
if (this.payload != null) { this.packet.addAll(OFFSET_PAYLOAD, this.payload); }
|
||||
byte[] crc16 = CRC16.calculate(toBytes(), 0, this.packet.size());
|
||||
this.packet.add(crc16[0]);
|
||||
this.packet.add(crc16[1]);
|
||||
Log.d("ShareTest", "About to start adding to Byte, size: " + this.packet.size());
|
||||
return this.toBytes();
|
||||
}
|
||||
|
||||
public List<byte[]> composeList() {
|
||||
packet = new ArrayList<Byte>();
|
||||
packet.add(OFFSET_SOF, SOF);
|
||||
packet.add(OFFSET_LENGTH, getLength());
|
||||
packet.add(OFFSET_NULL, NULL);
|
||||
packet.add(OFFSET_CMD, (byte) command);
|
||||
if (this.payload != null) { this.packet.addAll(OFFSET_PAYLOAD, this.payload); }
|
||||
byte[] crc16 = CRC16.calculate(toBytes(), 0, this.packet.size());
|
||||
this.packet.add(crc16[0]);
|
||||
this.packet.add(crc16[1]);
|
||||
Log.d("ShareTest", "About to start adding to ByteList, size: " + this.packet.size());
|
||||
return this.toBytesList();
|
||||
}
|
||||
|
||||
private byte getLength() {
|
||||
int packetSize = payload == null ? MIN_LEN : payload.size() + CRC_LEN + HEADER_LEN;
|
||||
|
||||
if (packetSize > MAX_LEN) {
|
||||
throw new IndexOutOfBoundsException(packetSize + " bytes, but packet must between "
|
||||
+ MIN_LEN + " and " + MAX_LEN + " bytes.");
|
||||
}
|
||||
|
||||
return (byte) packetSize;
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
byte[] b = new byte[this.packet.size()];
|
||||
for (int i = 0; i < this.packet.size(); i++) {
|
||||
b[i] = this.packet.get(i).byteValue();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
public List<byte[]> toBytesList() {
|
||||
List<byte[]> byteMessages = new ArrayList<byte[]>();
|
||||
double totalPacketSize = packet.size();
|
||||
int messages =(int) Math.ceil(totalPacketSize/18);
|
||||
for(int m = 0; m < messages; m++) {
|
||||
int thisPacketSize;
|
||||
if (m == messages - 1) {
|
||||
thisPacketSize = ((this.packet.size()+2) % 18);
|
||||
} else {
|
||||
thisPacketSize = (20);
|
||||
}
|
||||
int offset = m * 18;
|
||||
Log.d("ShareTest", "This packet size: " + thisPacketSize);
|
||||
byte[] b = new byte[thisPacketSize];
|
||||
b[0] = (byte) (m + 1);
|
||||
b[1] = (byte) (messages);
|
||||
for (int i = 2; i < thisPacketSize; i++) {
|
||||
b[i] = packet.get(offset + i - 2).byteValue();
|
||||
}
|
||||
byteMessages.add(b);
|
||||
}
|
||||
return byteMessages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.CalRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.EGVRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.GenericXMLRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.MeterRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.PageHeader;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.SensorRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.UsbSerialDriver;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
public class ReadData {
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
private static final String TAG = ReadData.class.getSimpleName();
|
||||
private static final int IO_TIMEOUT = 3000;
|
||||
private static final int MIN_LEN = 256;
|
||||
private UsbSerialDriver mSerialDevice;
|
||||
protected final Object mReadBufferLock = new Object();
|
||||
private UsbDeviceConnection mConnection;
|
||||
private UsbDevice mDevice;
|
||||
|
||||
public ReadData(){}
|
||||
public ReadData(UsbSerialDriver device) {
|
||||
mSerialDevice = device;
|
||||
}
|
||||
public ReadData(UsbSerialDriver device, UsbDeviceConnection connection, UsbDevice usbDevice) {
|
||||
mSerialDevice = device;
|
||||
mConnection = connection;
|
||||
mDevice = usbDevice;
|
||||
try {
|
||||
mSerialDevice.getPorts().get(0).open(connection);
|
||||
} catch(IOException e) {
|
||||
Log.d("FAILED WHILE", "trying to open");
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
public EGVRecord[] getRecentEGVs() {
|
||||
int recordType = Dex_Constants.RECORD_TYPES.EGV_DATA.ordinal();
|
||||
int endPage = readDataBasePageRange(recordType);
|
||||
return readDataBasePage(recordType, endPage);
|
||||
}
|
||||
|
||||
public EGVRecord[] getRecentEGVsPages(int numOfRecentPages) {
|
||||
if (numOfRecentPages < 1) {
|
||||
throw new IllegalArgumentException("Number of pages must be greater than 1.");
|
||||
}
|
||||
Log.d(TAG, "Reading EGV page range...");
|
||||
int recordType = Dex_Constants.RECORD_TYPES.EGV_DATA.ordinal();
|
||||
int endPage = readDataBasePageRange(recordType);
|
||||
Log.d(TAG, "Reading " + numOfRecentPages + " EGV page(s)...");
|
||||
numOfRecentPages = numOfRecentPages - 1;
|
||||
EGVRecord[] allPages = new EGVRecord[0];
|
||||
for (int i = Math.min(numOfRecentPages,endPage); i >= 0; i--) {
|
||||
int nextPage = endPage - i;
|
||||
Log.d(TAG, "Reading #" + i + " EGV pages (page number " + nextPage + ")");
|
||||
EGVRecord[] ithEGVRecordPage = readDataBasePage(recordType, nextPage);
|
||||
EGVRecord[] result = Arrays.copyOf(allPages, allPages.length + ithEGVRecordPage.length);
|
||||
System.arraycopy(ithEGVRecordPage, 0, result, allPages.length, ithEGVRecordPage.length);
|
||||
allPages = result;
|
||||
}
|
||||
Log.d(TAG, "Read complete of EGV pages.");
|
||||
return allPages;
|
||||
}
|
||||
|
||||
public long getTimeSinceEGVRecord(EGVRecord egvRecord) {
|
||||
return readSystemTime() - egvRecord.getSystemTimeSeconds();
|
||||
}
|
||||
|
||||
public MeterRecord[] getRecentMeterRecords() {
|
||||
Log.d(TAG, "Reading Meter page...");
|
||||
int recordType = Dex_Constants.RECORD_TYPES.METER_DATA.ordinal();
|
||||
int endPage = readDataBasePageRange(recordType);
|
||||
return readDataBasePage(recordType, endPage);
|
||||
}
|
||||
|
||||
public SensorRecord[] getRecentSensorRecords(int numOfRecentPages) {
|
||||
if (numOfRecentPages < 1) {
|
||||
throw new IllegalArgumentException("Number of pages must be greater than 1.");
|
||||
}
|
||||
Log.d(TAG, "Reading Sensor page range...");
|
||||
int recordType = Dex_Constants.RECORD_TYPES.SENSOR_DATA.ordinal();
|
||||
int endPage = readDataBasePageRange(recordType);
|
||||
Log.d(TAG, "Reading " + numOfRecentPages + " Sensor page(s)...");
|
||||
numOfRecentPages = numOfRecentPages - 1;
|
||||
SensorRecord[] allPages = new SensorRecord[0];
|
||||
for (int i = Math.min(numOfRecentPages,endPage); i >= 0; i--) {
|
||||
int nextPage = endPage - i;
|
||||
Log.d(TAG, "Reading #" + i + " Sensor pages (page number " + nextPage + ")");
|
||||
SensorRecord[] ithSensorRecordPage = readDataBasePage(recordType, nextPage);
|
||||
SensorRecord[] result = Arrays.copyOf(allPages, allPages.length + ithSensorRecordPage.length);
|
||||
System.arraycopy(ithSensorRecordPage, 0, result, allPages.length, ithSensorRecordPage.length);
|
||||
allPages = result;
|
||||
}
|
||||
Log.d(TAG, "Read complete of Sensor pages.");
|
||||
return allPages;
|
||||
}
|
||||
|
||||
public CalRecord[] getRecentCalRecords() {
|
||||
Log.d(TAG, "Reading Cal Records page range...");
|
||||
int recordType = Dex_Constants.RECORD_TYPES.CAL_SET.ordinal();
|
||||
int endPage = readDataBasePageRange(recordType);
|
||||
Log.d(TAG, "Reading Cal Records page...");
|
||||
return readDataBasePage(recordType, endPage);
|
||||
}
|
||||
public byte[] getRecentCalRecordsTest() {
|
||||
Log.d(TAG, "Reading Cal Records page range...");
|
||||
int recordType = Dex_Constants.RECORD_TYPES.CAL_SET.ordinal();
|
||||
int endPage = readDataBasePageRange(recordType);
|
||||
Log.d(TAG, "Reading Cal Records page...");
|
||||
return readDataBasePageTest(recordType, endPage);
|
||||
}
|
||||
|
||||
public boolean ping() {
|
||||
writeCommand(Dex_Constants.PING);
|
||||
return read(MIN_LEN).getCommand() == Dex_Constants.ACK;
|
||||
}
|
||||
|
||||
public int readBatteryLevel() {
|
||||
Log.d(TAG, "Reading battery level...");
|
||||
writeCommand(Dex_Constants.READ_BATTERY_LEVEL);
|
||||
byte[] readData = read(MIN_LEN).getData();
|
||||
return ByteBuffer.wrap(readData).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||
}
|
||||
|
||||
public String readSerialNumber() {
|
||||
int PAGE_OFFSET = 0;
|
||||
byte[] readData = readDataBasePage(Dex_Constants.RECORD_TYPES.MANUFACTURING_DATA.ordinal(), PAGE_OFFSET);
|
||||
Element md = ParsePage(readData, Dex_Constants.RECORD_TYPES.MANUFACTURING_DATA.ordinal());
|
||||
return md.getAttribute("SerialNumber");
|
||||
}
|
||||
|
||||
public Date readDisplayTime() {
|
||||
return Utils.receiverTimeToDate(readSystemTime() + readDisplayTimeOffset());
|
||||
}
|
||||
|
||||
public long readSystemTime() {
|
||||
Log.d(TAG, "Reading system time...");
|
||||
writeCommand(Dex_Constants.READ_SYSTEM_TIME);
|
||||
byte[] readData = read(MIN_LEN).getData();
|
||||
return ByteBuffer.wrap(readData).order(ByteOrder.LITTLE_ENDIAN).getInt() & 0xffffffff;
|
||||
}
|
||||
|
||||
public int readDisplayTimeOffset() {
|
||||
Log.d(TAG, "Reading display time offset...");
|
||||
writeCommand(Dex_Constants.READ_DISPLAY_TIME_OFFSET);
|
||||
byte[] readData = read(MIN_LEN).getData();
|
||||
return ByteBuffer.wrap(readData).order(ByteOrder.LITTLE_ENDIAN).getInt() & 0xffffffff;
|
||||
}
|
||||
|
||||
private int readDataBasePageRange(int recordType) {
|
||||
ArrayList<Byte> payload = new ArrayList<Byte>();
|
||||
Log.d(TAG, "adding Payload");
|
||||
payload.add((byte) recordType);
|
||||
Log.d(TAG, "Sending write command");
|
||||
writeCommand(Dex_Constants.READ_DATABASE_PAGE_RANGE, payload);
|
||||
Log.d(TAG, "About to call getdata");
|
||||
byte[] readData = read(MIN_LEN).getData();
|
||||
Log.d(TAG, "Going to return");
|
||||
return ByteBuffer.wrap(readData).order(ByteOrder.LITTLE_ENDIAN).getInt(4);
|
||||
}
|
||||
|
||||
private <T> T readDataBasePage(int recordType, int page) {
|
||||
byte numOfPages = 1;
|
||||
if (page < 0){
|
||||
throw new IllegalArgumentException("Invalid page requested:" + page);
|
||||
}
|
||||
ArrayList<Byte> payload = new ArrayList<Byte>();
|
||||
payload.add((byte) recordType);
|
||||
byte[] pageInt = ByteBuffer.allocate(4).putInt(page).array();
|
||||
payload.add(pageInt[3]);
|
||||
payload.add(pageInt[2]);
|
||||
payload.add(pageInt[1]);
|
||||
payload.add(pageInt[0]);
|
||||
payload.add(numOfPages);
|
||||
writeCommand(Dex_Constants.READ_DATABASE_PAGES, payload);
|
||||
byte[] readData = read(2122).getData();
|
||||
return ParsePage(readData, recordType);
|
||||
}
|
||||
private byte[] readDataBasePageTest(int recordType, int page) {
|
||||
byte numOfPages = 1;
|
||||
if (page < 0){
|
||||
throw new IllegalArgumentException("Invalid page requested:" + page);
|
||||
}
|
||||
ArrayList<Byte> payload = new ArrayList<Byte>();
|
||||
payload.add((byte) recordType);
|
||||
byte[] pageInt = ByteBuffer.allocate(4).putInt(page).array();
|
||||
payload.add(pageInt[3]);
|
||||
payload.add(pageInt[2]);
|
||||
payload.add(pageInt[1]);
|
||||
payload.add(pageInt[0]);
|
||||
payload.add(numOfPages);
|
||||
return writeCommandTest(Dex_Constants.READ_DATABASE_PAGES, payload);
|
||||
}
|
||||
|
||||
private void writeCommand(int command, ArrayList<Byte> payload) {
|
||||
byte[] packet = new PacketBuilder(command, payload).compose();
|
||||
if (mSerialDevice != null) {
|
||||
try {
|
||||
// UsbInterface mDataInterface = mDevice.getInterface(1);
|
||||
// UsbEndpoint mWriteEndpoint = mDataInterface.getEndpoint(0);
|
||||
// mConnection.bulkTransfer(mWriteEndpoint, packet, packet.length, IO_TIMEOUT);
|
||||
mSerialDevice.getPorts().get(0).write(packet, IO_TIMEOUT);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to write to serial device.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
private byte[] writeCommandTest(int command, ArrayList<Byte> payload) {
|
||||
byte[] packet = new PacketBuilder(command, payload).compose();
|
||||
return packet;
|
||||
}
|
||||
private void writeCommand(int command) {
|
||||
byte[] packet = new PacketBuilder(command).compose();
|
||||
if (mSerialDevice != null) {
|
||||
try {
|
||||
// UsbInterface mDataInterface = mDevice.getInterface(1);
|
||||
// UsbEndpoint mWriteEndpoint = mDataInterface.getEndpoint(0);
|
||||
// mConnection.bulkTransfer(mWriteEndpoint, packet, packet.length, IO_TIMEOUT);
|
||||
mSerialDevice.getPorts().get(0).write(packet, IO_TIMEOUT);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to write to serial device.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ReadPacket read(int numOfBytes) {
|
||||
byte[] readData = new byte[numOfBytes];
|
||||
int len = 0;
|
||||
try {
|
||||
// UsbInterface mDataInterface = mDevice.getInterface(1);
|
||||
// UsbEndpoint mReadEndpoint = mDataInterface.getEndpoint(1);
|
||||
// byte[] mReadBuffer;
|
||||
// mReadBuffer = new byte[16 * 1024];
|
||||
//
|
||||
// int readAmt = Math.min(readData.length, mReadBuffer.length);
|
||||
// synchronized (mReadBufferLock) {
|
||||
//
|
||||
//
|
||||
// Log.d(TAG, "Read about to call bulk transfer.");
|
||||
// if (len < 0) {
|
||||
// // This sucks: we get -1 on timeout, not 0 as preferred.
|
||||
// // We *should* use UsbRequest, except it has a bug/api oversight
|
||||
// // where there is no way to determine the number of bytes read
|
||||
// // in response :\ -- http://b.android.com/28023
|
||||
// if (IO_TIMEOUT == Integer.MAX_VALUE) {
|
||||
// // Hack: Special case "~infinite timeout" as an error.
|
||||
// len = -1;
|
||||
// }
|
||||
// len = 0;
|
||||
// }
|
||||
//
|
||||
//// System.arraycopy(mReadBuffer, 0, readData, 0, readAmt);
|
||||
// }
|
||||
// len = mConnection.bulkTransfer(mReadEndpoint, readData, readAmt, IO_TIMEOUT);
|
||||
|
||||
len = mSerialDevice.getPorts().get(0).read(readData, IO_TIMEOUT);
|
||||
|
||||
Log.d(TAG, "Read " + len + " byte(s) complete.");
|
||||
|
||||
// Add a 100ms delay for when multiple write/reads are occurring in series
|
||||
Thread.sleep(100);
|
||||
|
||||
// TODO: this debug code to print data of the read, should be removed after
|
||||
// finding the source of the reading issue
|
||||
String bytes = "";
|
||||
int readAmount = len;
|
||||
for (int i = 0; i < readAmount; i++) bytes += String.format("%02x", readData[i]) + " ";
|
||||
Log.d(TAG, "Read data: " + bytes);
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to read from serial device.", e);
|
||||
}
|
||||
byte[] data = Arrays.copyOfRange(readData, 0, len);
|
||||
return new ReadPacket(data);
|
||||
}
|
||||
|
||||
private <T> T ParsePage(byte[] data, int recordType) {
|
||||
int HEADER_LEN = 28;
|
||||
PageHeader pageHeader=new PageHeader(data);
|
||||
int NUM_REC_OFFSET = 4;
|
||||
int numRec = data[NUM_REC_OFFSET];
|
||||
int rec_len;
|
||||
|
||||
switch (Dex_Constants.RECORD_TYPES.values()[recordType]) {
|
||||
case MANUFACTURING_DATA:
|
||||
GenericXMLRecord xmlRecord = new GenericXMLRecord(Arrays.copyOfRange(data, HEADER_LEN, data.length - 1));
|
||||
return (T) xmlRecord;
|
||||
case SENSOR_DATA:
|
||||
rec_len = 20;
|
||||
SensorRecord[] sensorRecords = new SensorRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
sensorRecords[i] = new SensorRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
return (T) sensorRecords;
|
||||
case EGV_DATA:
|
||||
rec_len = 13;
|
||||
EGVRecord[] egvRecords = new EGVRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
egvRecords[i] = new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
return (T) egvRecords;
|
||||
case METER_DATA:
|
||||
rec_len = 16;
|
||||
MeterRecord[] meterRecords = new MeterRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
meterRecords[i] = new MeterRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
return (T) meterRecords;
|
||||
case CAL_SET:
|
||||
rec_len = 249;
|
||||
if (pageHeader.getRevision()<=2) {
|
||||
rec_len = 148;
|
||||
}
|
||||
CalRecord[] calRecords = new CalRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
calRecords[i] = new CalRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
return (T) calRecords;
|
||||
default:
|
||||
// Throw error "Database record not supported"
|
||||
break;
|
||||
}
|
||||
|
||||
return (T) null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.CalRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.EGVRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.GenericXMLRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.MeterRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.PageHeader;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.SensorRecord;
|
||||
import com.eveningoutpost.dexdrip.Services.DexShareCollectionService;
|
||||
import com.eveningoutpost.dexdrip.ShareTest;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import rx.Observable;
|
||||
import rx.functions.Action1;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class ReadDataShare {
|
||||
byte[] accumulatedResponse;
|
||||
private ShareTest mShareTest;
|
||||
private DexShareCollectionService mCollectionService;
|
||||
|
||||
public ReadDataShare(ShareTest aShareTest){
|
||||
mShareTest = aShareTest;
|
||||
}
|
||||
public ReadDataShare(DexShareCollectionService collectionService){
|
||||
mCollectionService = collectionService;
|
||||
}
|
||||
|
||||
public void getRecentEGVs(final Action1<EGVRecord[]> recordListener) {
|
||||
final int recordType = Dex_Constants.RECORD_TYPES.EGV_DATA.ordinal();
|
||||
final Action1<byte[]> fullPageListener = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) { ParsePage(read(0,s).getData(), recordType, recordListener); }
|
||||
};
|
||||
Action1<Integer> databasePageRangeCaller = new Action1<Integer>() {
|
||||
@Override
|
||||
public void call(Integer s) { readDataBasePage(recordType, s, fullPageListener); }
|
||||
};
|
||||
readDataBasePageRange(recordType, databasePageRangeCaller);
|
||||
}
|
||||
|
||||
public void getRecentMeterRecords(final Action1<MeterRecord[]> recordListener) {
|
||||
final int recordType = Dex_Constants.RECORD_TYPES.METER_DATA.ordinal();
|
||||
final Action1<byte[]> fullPageListener = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) { ParsePage(read(0,s).getData(), recordType, recordListener); }
|
||||
};
|
||||
Action1<Integer> databasePageRangeCaller = new Action1<Integer>() {
|
||||
@Override
|
||||
public void call(Integer s) { readDataBasePage(recordType, s, fullPageListener); }
|
||||
};
|
||||
readDataBasePageRange(recordType, databasePageRangeCaller);
|
||||
}
|
||||
|
||||
public void getRecentCalRecords(final Action1<CalRecord[]> recordListener) {
|
||||
final int recordType = Dex_Constants.RECORD_TYPES.CAL_SET.ordinal();
|
||||
final Action1<byte[]> fullPageListener = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) { ParsePage(read(0,s).getData(), recordType, recordListener); }
|
||||
};
|
||||
Action1<Integer> databasePageRangeCaller = new Action1<Integer>() {
|
||||
@Override
|
||||
public void call(Integer s) { readDataBasePage(recordType, s, fullPageListener); }
|
||||
};
|
||||
readDataBasePageRange(recordType, databasePageRangeCaller);
|
||||
}
|
||||
|
||||
|
||||
public void getRecentSensorRecords(final Action1<SensorRecord[]> recordListener) {
|
||||
final int recordType = Dex_Constants.RECORD_TYPES.SENSOR_DATA.ordinal();
|
||||
final Action1<byte[]> fullPageListener = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) { ParsePage(read(0,s).getData(), recordType, recordListener); }
|
||||
};
|
||||
Action1<Integer> databasePageRangeCaller = new Action1<Integer>() {
|
||||
@Override
|
||||
public void call(Integer s) { readDataBasePage(recordType, s, fullPageListener); }
|
||||
};
|
||||
readDataBasePageRange(recordType, databasePageRangeCaller);
|
||||
}
|
||||
|
||||
public void getTimeSinceEGVRecord(final EGVRecord egvRecord, final Action1<Long> timeSinceEgvRecord) {
|
||||
Action1<Long> tempSystemTimeListener = new Action1<Long>() {
|
||||
@Override
|
||||
public void call(Long s) { Observable.just(s - egvRecord.getSystemTimeSeconds()).subscribe(timeSinceEgvRecord); }
|
||||
};
|
||||
readSystemTime(tempSystemTimeListener);
|
||||
}
|
||||
|
||||
public void ping(final Action1<Boolean> pingListener) {
|
||||
Action1<byte[]> pingReader = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) { Observable.just(read(0, s).getCommand() == Dex_Constants.ACK).subscribe(pingListener); }
|
||||
};
|
||||
writeCommand(Dex_Constants.PING, pingReader);
|
||||
}
|
||||
|
||||
public void readBatteryLevel(final Action1<Integer> batteryLevelListener) {
|
||||
Action1<byte[]> batteryLevelReader = new Action1<byte[]>() {
|
||||
@Override //TODO: find out if this should be wrapped in read(s).getData();
|
||||
public void call(byte[] s) { Observable.just(ByteBuffer.wrap(s).order(ByteOrder.LITTLE_ENDIAN).getInt()).subscribe(batteryLevelListener); }
|
||||
};
|
||||
writeCommand(Dex_Constants.READ_BATTERY_LEVEL, batteryLevelReader);
|
||||
}
|
||||
|
||||
public void readSerialNumber(final Action1<String> serialNumberListener) {
|
||||
final Action1<byte[]> manufacturingDataListener = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) {
|
||||
Element el = ParsePage(s, Dex_Constants.RECORD_TYPES.MANUFACTURING_DATA.ordinal());
|
||||
Observable.just(el.getAttribute("SerialNumber")).subscribe(serialNumberListener);
|
||||
}
|
||||
};
|
||||
readDataBasePage(Dex_Constants.RECORD_TYPES.MANUFACTURING_DATA.ordinal(), 0, manufacturingDataListener);
|
||||
}
|
||||
|
||||
public void readDisplayTime(final Action1<Date> displayTimeListener) {
|
||||
Action1<Long> tempSystemTimeListener = new Action1<Long>() {
|
||||
@Override
|
||||
public void call(Long s) {
|
||||
final long systemTime = s;
|
||||
Action1<Long> tempSystemTimeListener = new Action1<Long>() {
|
||||
@Override
|
||||
public void call(Long s) {
|
||||
Date dateDisplayTime = Utils.receiverTimeToDate(systemTime + s);
|
||||
Observable.just(dateDisplayTime).subscribe(displayTimeListener); }
|
||||
};
|
||||
readDisplayTimeOffset(tempSystemTimeListener);
|
||||
}
|
||||
};
|
||||
readSystemTime(tempSystemTimeListener);
|
||||
}
|
||||
|
||||
public void readSystemTime(final Action1<Long> systemTimeListener) {
|
||||
Action1<byte[]> systemTimeReader = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) {
|
||||
Observable.just(Utils.receiverTimeToDate(ByteBuffer.wrap(read(0,s).getData()).order(ByteOrder.LITTLE_ENDIAN).getInt()).getTime()).subscribe(systemTimeListener);
|
||||
}
|
||||
};
|
||||
writeCommand(Dex_Constants.READ_SYSTEM_TIME, systemTimeReader);
|
||||
}
|
||||
|
||||
public void readDisplayTimeOffset(final Action1<Long> displayTimeOffsetListener) {
|
||||
Action1<byte[]> displayTimeOffsetReader = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) { Observable.just((long) ByteBuffer.wrap(read(0,s).getData()).order(ByteOrder.LITTLE_ENDIAN).getInt()).subscribe(displayTimeOffsetListener); }
|
||||
};
|
||||
writeCommand(Dex_Constants.READ_DISPLAY_TIME_OFFSET, displayTimeOffsetReader);
|
||||
}
|
||||
|
||||
private void readDataBasePageRange(int recordType, final Action1<Integer> databasePageRangeCaller) {
|
||||
ArrayList<Byte> payload = new ArrayList<Byte>();
|
||||
payload.add((byte) recordType);
|
||||
final Action1<byte[]> databasePageRangeListener = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) {
|
||||
Observable.just(ByteBuffer.wrap(new ReadPacket(s).getData()).order(ByteOrder.LITTLE_ENDIAN).getInt(4)).subscribe(databasePageRangeCaller);
|
||||
}
|
||||
};
|
||||
writeCommand(Dex_Constants.READ_DATABASE_PAGE_RANGE, payload, databasePageRangeListener);
|
||||
}
|
||||
|
||||
private <T> T readDataBasePage(final int recordType, int page, final Action1<byte[]> fullPageListener) {
|
||||
byte numOfPages = 1;
|
||||
if (page < 0){ throw new IllegalArgumentException("Invalid page requested:" + page); }
|
||||
ArrayList<Byte> payload = new ArrayList<Byte>();
|
||||
payload.add((byte) recordType);
|
||||
byte[] pageInt = ByteBuffer.allocate(4).putInt(page).array();
|
||||
payload.add(pageInt[3]);
|
||||
payload.add(pageInt[2]);
|
||||
payload.add(pageInt[1]);
|
||||
payload.add(pageInt[0]);
|
||||
payload.add(numOfPages);
|
||||
accumulatedResponse = null;
|
||||
final Action1<byte[]> databasePageReader = new Action1<byte[]>() {
|
||||
@Override
|
||||
public void call(byte[] s) {
|
||||
Log.d("ShareTest", "Database Page Reader received SIZE: " + s.length);
|
||||
byte[] temp = s;
|
||||
if (accumulatedResponse == null) {
|
||||
accumulatedResponse = s;
|
||||
} else {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
outputStream.write(accumulatedResponse);
|
||||
outputStream.write(temp);
|
||||
accumulatedResponse = outputStream.toByteArray();
|
||||
Log.d("ShareTest", "Combined Response length: " + accumulatedResponse.length);
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
if (temp.length < 20) { Observable.just(accumulatedResponse).subscribe(fullPageListener).unsubscribe(); }
|
||||
}
|
||||
};
|
||||
writeCommand(Dex_Constants.READ_DATABASE_PAGES, payload, databasePageReader);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void writeCommand(int command, ArrayList<Byte> payload, Action1<byte[]> responseListener) {
|
||||
List<byte[]> packets = new PacketBuilder(command, payload).composeList();
|
||||
if(mShareTest != null) { mShareTest.writeCommand(packets, 0, responseListener); }
|
||||
else if (mCollectionService != null) { mCollectionService.writeCommand(packets, 0, responseListener); }
|
||||
}
|
||||
|
||||
private void writeCommand(int command, Action1<byte[]> responseListener) {
|
||||
List<byte[]> packets = new PacketBuilder(command).composeList();
|
||||
if(mShareTest != null) { mShareTest.writeCommand(packets, 0, responseListener); }
|
||||
else if (mCollectionService != null) { mCollectionService.writeCommand(packets, 0, responseListener); }
|
||||
}
|
||||
|
||||
private ReadPacket read(int numOfBytes, byte[] readPacket) {
|
||||
return new ReadPacket(Arrays.copyOfRange(readPacket, 0, readPacket.length));
|
||||
}
|
||||
|
||||
private <T> T ParsePage(byte[] data, int recordType) { return ParsePage(data, recordType, null); }
|
||||
private <T> T ParsePage(byte[] data, int recordType, Action1<T> parsedPageReceiver) {
|
||||
int HEADER_LEN = 28;
|
||||
PageHeader pageHeader=new PageHeader(data);
|
||||
int NUM_REC_OFFSET = 4;
|
||||
int numRec = data[NUM_REC_OFFSET];
|
||||
int rec_len;
|
||||
|
||||
switch (Dex_Constants.RECORD_TYPES.values()[recordType]) {
|
||||
case MANUFACTURING_DATA:
|
||||
GenericXMLRecord xmlRecord = new GenericXMLRecord(Arrays.copyOfRange(data, HEADER_LEN, data.length - 1));
|
||||
if(parsedPageReceiver != null) {
|
||||
Observable.just((T) xmlRecord).subscribe(parsedPageReceiver);
|
||||
} else {
|
||||
return (T) xmlRecord;
|
||||
}
|
||||
break;
|
||||
case SENSOR_DATA:
|
||||
rec_len = 20;
|
||||
SensorRecord[] sensorRecords = new SensorRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
sensorRecords[i] = new SensorRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
if(parsedPageReceiver != null) {
|
||||
Observable.just((T) sensorRecords).subscribe(parsedPageReceiver);
|
||||
} else {
|
||||
return (T) sensorRecords;
|
||||
}
|
||||
break;
|
||||
case EGV_DATA:
|
||||
rec_len = 13;
|
||||
EGVRecord[] egvRecords = new EGVRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
egvRecords[i] = new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
if(parsedPageReceiver != null) {
|
||||
Observable.just((T) egvRecords).subscribe(parsedPageReceiver);
|
||||
} else {
|
||||
return (T) egvRecords;
|
||||
}
|
||||
break;
|
||||
case METER_DATA:
|
||||
rec_len = 16;
|
||||
MeterRecord[] meterRecords = new MeterRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
meterRecords[i] = new MeterRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
if(parsedPageReceiver != null) {
|
||||
Observable.just((T) meterRecords).subscribe(parsedPageReceiver);
|
||||
} else {
|
||||
return (T) meterRecords;
|
||||
}
|
||||
break;
|
||||
case CAL_SET:
|
||||
rec_len = 249;
|
||||
if (pageHeader.getRevision()<=2) { rec_len = 148; }
|
||||
CalRecord[] calRecords = new CalRecord[numRec];
|
||||
for (int i = 0; i < numRec; i++) {
|
||||
int startIdx = HEADER_LEN + rec_len * i;
|
||||
calRecords[i] = new CalRecord(Arrays.copyOfRange(data, startIdx, startIdx + rec_len - 1));
|
||||
}
|
||||
if(parsedPageReceiver != null) {
|
||||
Observable.just((T) calRecords).subscribe(parsedPageReceiver);
|
||||
} else {
|
||||
return (T) calRecords;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Observable.just((T) null).subscribe(parsedPageReceiver);
|
||||
return (T) null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
|
||||
public class ReadPacket {
|
||||
private int command;
|
||||
private byte[] data;
|
||||
private byte[] crc_calc;
|
||||
private byte[] crc;
|
||||
private int OFFSET_CMD = 3;
|
||||
private int OFFSET_DATA = 4;
|
||||
private int CRC_LEN = 2;
|
||||
|
||||
public ReadPacket(byte[] readPacket) {
|
||||
this.command = readPacket[OFFSET_CMD];
|
||||
this.data = Arrays.copyOfRange(readPacket, OFFSET_DATA, readPacket.length - CRC_LEN);
|
||||
this.crc = Arrays.copyOfRange(readPacket, readPacket.length - CRC_LEN, readPacket.length);
|
||||
this.crc_calc=CRC16.calculate(readPacket, 0, readPacket.length - 2);
|
||||
if (!Arrays.equals(this.crc, this.crc_calc)) {
|
||||
throw new CRCFailRuntimeException("CRC check failed: " + Utils.bytesToHex(this.crc) + " vs " + Utils.bytesToHex(this.crc_calc));
|
||||
}
|
||||
}
|
||||
|
||||
public int getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.PowerManager;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.JoH;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.CalRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.EGVRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.GlucoseDataSet;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.MeterRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.SensorRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.CdcAcmSerialDriver;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.ProbeTable;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.UsbSerialDriver;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.UsbSerialProber;
|
||||
import com.eveningoutpost.dexdrip.Models.Calibration;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
|
||||
/**
|
||||
* An {@link IntentService} subclass for handling asynchronous CGM Receiver downloads and cloud uploads
|
||||
* requests in a service on a separate handler thread.
|
||||
*/
|
||||
public class SyncingService extends IntentService {
|
||||
|
||||
// Action for intent
|
||||
private static final String ACTION_SYNC = "com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.action.SYNC";
|
||||
private static final String ACTION_CALIBRATION_CHECKIN = "com.eveningoutpost.dexdrip.CalibrationCheckInActivity";
|
||||
|
||||
// Parameters for intent
|
||||
private static final String SYNC_PERIOD = "com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.extra.SYNC_PERIOD";
|
||||
|
||||
// Response to broadcast to activity
|
||||
public static final String RESPONSE_SGV = "mySGV";
|
||||
public static final String RESPONSE_TREND = "myTrend";
|
||||
public static final String RESPONSE_TIMESTAMP = "myTimestamp";
|
||||
public static final String RESPONSE_NEXT_UPLOAD_TIME = "myUploadTime";
|
||||
public static final String RESPONSE_UPLOAD_STATUS = "myUploadStatus";
|
||||
public static final String RESPONSE_DISPLAY_TIME = "myDisplayTime";
|
||||
public static final String RESPONSE_JSON = "myJSON";
|
||||
public static final String RESPONSE_BAT = "myBatLvl";
|
||||
|
||||
private final String TAG = SyncingService.class.getSimpleName();
|
||||
private Context mContext;
|
||||
private UsbManager mUsbManager;
|
||||
private UsbSerialDriver mSerialDevice;
|
||||
private UsbDevice dexcom;
|
||||
private UsbDeviceConnection mConnection;
|
||||
|
||||
// Constants
|
||||
private final int TIME_SYNC_OFFSET = 10000;
|
||||
public static final int MIN_SYNC_PAGES = 2;
|
||||
public static final int GAP_SYNC_PAGES = 20;
|
||||
|
||||
|
||||
/**
|
||||
* Starts this service to perform action Single Sync with the given parameters. If
|
||||
* the service is already performing a task this action will be queued.
|
||||
*
|
||||
* @see IntentService
|
||||
*/
|
||||
public static void startActionSingleSync(Context context, int numOfPages) {
|
||||
Intent intent = new Intent(context, SyncingService.class);
|
||||
intent.setAction(ACTION_SYNC);
|
||||
intent.putExtra(SYNC_PERIOD, numOfPages);
|
||||
context.startService(intent);
|
||||
}
|
||||
public static void startActionCalibrationCheckin(Context context) {
|
||||
Intent intent = new Intent(context, SyncingService.class);
|
||||
intent.setAction(ACTION_CALIBRATION_CHECKIN);
|
||||
context.startService(intent);
|
||||
}
|
||||
public SyncingService() {
|
||||
super("SyncingService");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
mContext = getApplicationContext();
|
||||
if (intent != null) {
|
||||
final String action = intent.getAction();
|
||||
if (ACTION_SYNC.equals(action)) {
|
||||
final int param1 = intent.getIntExtra(SYNC_PERIOD, 1);
|
||||
handleActionSync(param1);
|
||||
} else if (ACTION_CALIBRATION_CHECKIN.equals(action)) {
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "Beginning check in process");
|
||||
performCalibrationCheckin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle action Sync in the provided background thread with the provided
|
||||
* parameters.
|
||||
*/
|
||||
private void performCalibrationCheckin(){
|
||||
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
|
||||
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NSDownload");
|
||||
wl.acquire();
|
||||
try {
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "Wake Lock Acquired");
|
||||
if (acquireSerialDevice()) {
|
||||
try {
|
||||
ReadData readData = new ReadData(mSerialDevice, mConnection, dexcom);
|
||||
|
||||
// ReadData readData = new ReadData(mSerialDevice);
|
||||
CalRecord[] calRecords = readData.getRecentCalRecords();
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "Found " + calRecords.length + " Records!");
|
||||
save_most_recent_cal_record(calRecords);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.wtf("Unhandled exception caught", e);
|
||||
} finally {
|
||||
// Close serial
|
||||
try {
|
||||
mSerialDevice.getPorts().get(0).close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Unable to close", e);
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
Log.w("CALIBRATION-CHECK-IN: ", "Failed to acquire serial device");
|
||||
}
|
||||
} finally {
|
||||
JoH.releaseWakeLock(wl);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleActionSync(int numOfPages) {
|
||||
boolean broadcastSent = false;
|
||||
|
||||
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
|
||||
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NSDownload");
|
||||
wl.acquire();
|
||||
|
||||
try {
|
||||
sync(numOfPages);
|
||||
} finally {
|
||||
wl.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void sync(int numOfPages) {
|
||||
boolean broadcastSent;
|
||||
if (acquireSerialDevice()) {
|
||||
try {
|
||||
|
||||
ReadData readData = new ReadData(mSerialDevice);
|
||||
// TODO: need to check if numOfPages if valid on ReadData side
|
||||
EGVRecord[] recentRecords = readData.getRecentEGVsPages(numOfPages);
|
||||
MeterRecord[] meterRecords = readData.getRecentMeterRecords();
|
||||
// TODO: need to check if numOfPages if valid on ReadData side
|
||||
SensorRecord[] sensorRecords = readData.getRecentSensorRecords(numOfPages);
|
||||
GlucoseDataSet[] glucoseDataSets = Utils.mergeGlucoseDataRecords(recentRecords, sensorRecords);
|
||||
|
||||
// FIXME: This is a workaround for the new Dexcom AP which seems to have a new format
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
CalRecord[] calRecords = new CalRecord[1];
|
||||
if (prefs.getBoolean("cloud_cal_data", false)) {
|
||||
calRecords = readData.getRecentCalRecords();
|
||||
}
|
||||
|
||||
long timeSinceLastRecord = readData.getTimeSinceEGVRecord(recentRecords[recentRecords.length - 1]);
|
||||
// TODO: determine if the logic here is correct. I suspect it assumes the last record was less than 5
|
||||
// minutes ago. If a reading is skipped and the device is plugged in then nextUploadTime will be
|
||||
// set to a negative number. This situation will eventually correct itself.
|
||||
long nextUploadTime = (1000 * 60 * 5) - (timeSinceLastRecord * (1000));
|
||||
long displayTime = readData.readDisplayTime().getTime();
|
||||
// FIXME: Device seems to flake out on battery level reads. Removing for now.
|
||||
// int batLevel = readData.readBatteryLevel();
|
||||
int batLevel = 100;
|
||||
|
||||
// convert into json for d3 plot
|
||||
JSONArray array = new JSONArray();
|
||||
for (int i = 0; i < recentRecords.length; i++) array.put(recentRecords[i].toJSON());
|
||||
|
||||
EGVRecord recentEGV = recentRecords[recentRecords.length - 1];
|
||||
// broadcastSGVToUI(recentEGV, uploadStatus, nextUploadTime + TIME_SYNC_OFFSET,
|
||||
// displayTime, array ,batLevel);
|
||||
broadcastSent=true;
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
Log.wtf("Unable to read from the dexcom, maybe it will work next time", e);
|
||||
} catch (NegativeArraySizeException e) {
|
||||
Log.wtf("Negative array exception from receiver", e);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
Log.wtf("IndexOutOfBounds exception from receiver", e);
|
||||
} catch (CRCFailRuntimeException e){
|
||||
// FIXME: may consider localizing this catch at a lower level (like ReadData) so that
|
||||
// if the CRC check fails on one type of record we can capture the values if it
|
||||
// doesn't fail on other types of records. This means we'd need to broadcast back
|
||||
// partial results to the UI. Adding it to a lower level could make the ReadData class
|
||||
// more difficult to maintain - needs discussion.
|
||||
Log.wtf("CRC failed", e);
|
||||
} catch (Exception e) {
|
||||
Log.wtf("Unhandled exception caught", e);
|
||||
} finally {
|
||||
// Close serial
|
||||
try {
|
||||
mSerialDevice.getPorts().get(0).close();
|
||||
} catch (IOException e) {
|
||||
|
||||
Log.e(TAG, "Unable to close", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// if (!broadcastSent) broadcastSGVToUI();
|
||||
}
|
||||
|
||||
private void save_most_recent_cal_record(CalRecord[] calRecords) {
|
||||
int size = calRecords.length;
|
||||
Calibration.create(calRecords,getApplicationContext(), false, 0);
|
||||
}
|
||||
|
||||
private boolean acquireSerialDevice() {
|
||||
UsbDevice found_device = findDexcom();
|
||||
|
||||
if (mUsbManager == null) {
|
||||
Log.w("CALIBRATION-CHECK-IN: ", "USB manager is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dexcom == null) {
|
||||
Log.e(TAG, "dex device == null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if( mUsbManager.hasPermission(dexcom)) { // the system is allowing us to poke around this device
|
||||
|
||||
ProbeTable customTable = new ProbeTable(); // From the USB library...
|
||||
customTable.addProduct(0x22A3, 0x0047, CdcAcmSerialDriver.class); // ...Specify the Vendor ID and Product ID
|
||||
|
||||
UsbSerialProber prober = new UsbSerialProber(customTable); // Probe the device with the custom values
|
||||
List<UsbSerialDriver> drivers = prober.findAllDrivers(mUsbManager); // let's go through the list
|
||||
Iterator<UsbSerialDriver> foo = drivers.iterator(); // Invalid Return code
|
||||
while (foo.hasNext()) { // let's loop through
|
||||
UsbSerialDriver driver = foo.next(); // set fooDriver to the next available driver
|
||||
if (driver != null) {
|
||||
UsbDeviceConnection connection = mUsbManager.openDevice(driver.getDevice());
|
||||
if (connection != null) {
|
||||
mSerialDevice = driver;
|
||||
|
||||
mConnection = connection;
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "CONNECTEDDDD!!");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
Log.w("CALIBRATION-CHECK-IN: ", "Driver was no good");
|
||||
}
|
||||
}
|
||||
Log.w("CALIBRATION-CHECK-IN: ", "No usable drivers found");
|
||||
} else {
|
||||
Log.w("CALIBRATION-CHECK-IN: ", "You dont have permissions for that dexcom!!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static public boolean isG4Connected(Context c){
|
||||
UsbManager manager = (UsbManager) c.getSystemService(Context.USB_SERVICE);
|
||||
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
|
||||
Log.i("USB DEVICES = ", deviceList.toString());
|
||||
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
|
||||
Log.i("USB DEVICES = ", String.valueOf(deviceList.size()));
|
||||
|
||||
while(deviceIterator.hasNext()){
|
||||
UsbDevice device = deviceIterator.next();
|
||||
if (device.getVendorId() == 8867 && device.getProductId() == 71
|
||||
&& device.getDeviceClass() == 2 && device.getDeviceSubclass() ==0
|
||||
&& device.getDeviceProtocol() == 0){
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "Dexcom Found!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public UsbDevice findDexcom() {
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "Searching for dexcom");
|
||||
mUsbManager = (UsbManager) getApplicationContext().getSystemService(Context.USB_SERVICE);
|
||||
Log.i("USB MANAGER = ", mUsbManager.toString());
|
||||
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
|
||||
Log.i("USB DEVICES = ", deviceList.toString());
|
||||
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
|
||||
Log.i("USB DEVICES = ", String.valueOf(deviceList.size()));
|
||||
|
||||
while(deviceIterator.hasNext()){
|
||||
UsbDevice device = deviceIterator.next();
|
||||
if (device.getVendorId() == 8867 && device.getProductId() == 71
|
||||
&& device.getDeviceClass() == 2 && device.getDeviceSubclass() ==0
|
||||
&& device.getDeviceProtocol() == 0){
|
||||
dexcom = device;
|
||||
Log.i("CALIBRATION-CHECK-IN: ", "Dexcom Found!");
|
||||
return device;
|
||||
} else {
|
||||
Log.w("CALIBRATION-CHECK-IN: ", "that was not a dexcom (I dont think)");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void broadcastSGVToUI(EGVRecord egvRecord, boolean uploadStatus,
|
||||
long nextUploadTime, long displayTime,
|
||||
JSONArray json, int batLvl) {
|
||||
Log.d(TAG, "Current EGV: " + egvRecord.getBGValue());
|
||||
Intent broadcastIntent = new Intent();
|
||||
// broadcastIntent.setAction(MainActivity.CGMStatusReceiver.PROCESS_RESPONSE);
|
||||
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
broadcastIntent.putExtra(RESPONSE_SGV, egvRecord.getBGValue());
|
||||
broadcastIntent.putExtra(RESPONSE_TREND, egvRecord.getTrend().getID());
|
||||
broadcastIntent.putExtra(RESPONSE_TIMESTAMP, egvRecord.getDisplayTime().getTime());
|
||||
broadcastIntent.putExtra(RESPONSE_NEXT_UPLOAD_TIME, nextUploadTime);
|
||||
broadcastIntent.putExtra(RESPONSE_UPLOAD_STATUS, uploadStatus);
|
||||
broadcastIntent.putExtra(RESPONSE_DISPLAY_TIME, displayTime);
|
||||
if (json!=null)
|
||||
broadcastIntent.putExtra(RESPONSE_JSON, json.toString());
|
||||
broadcastIntent.putExtra(RESPONSE_BAT, batLvl);
|
||||
sendBroadcast(broadcastIntent);
|
||||
}
|
||||
|
||||
private void broadcastSGVToUI() {
|
||||
EGVRecord record=new EGVRecord(-1, Dex_Constants.TREND_ARROW_VALUES.NONE,new Date(),new Date());
|
||||
broadcastSGVToUI(record,false, (long) (1000 * 60 * 5) + TIME_SYNC_OFFSET, new Date().getTime(), null, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.EGVRecord;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.GlucoseDataSet;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.SensorRecord;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static Date receiverTimeToDate(long delta) {
|
||||
int currentTZOffset = TimeZone.getDefault().getRawOffset();
|
||||
long epochMS = 1230768000000L; // Jan 01, 2009 00:00 in UTC
|
||||
long milliseconds = epochMS - currentTZOffset;
|
||||
long timeAdd = milliseconds + (1000L * delta);
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
if (tz.inDaylightTime(new Date())) timeAdd = timeAdd - (1000 * 60 * 60);
|
||||
return new Date(timeAdd);
|
||||
}
|
||||
|
||||
public static String getTimeString(long timeDeltaMS) {
|
||||
long minutes = (timeDeltaMS / 1000) / 60;
|
||||
long hours = minutes / 60;
|
||||
long days = hours / 24;
|
||||
long weeks = days / 7;
|
||||
minutes= minutes - hours * 60;
|
||||
hours = hours - days * 24;
|
||||
days= days - weeks * 7;
|
||||
|
||||
String timeAgoString = "";
|
||||
if (weeks > 0) {
|
||||
timeAgoString += weeks + " weeks ";
|
||||
}
|
||||
if (days > 0) {
|
||||
timeAgoString += days + " days ";
|
||||
}
|
||||
if (hours > 0) {
|
||||
timeAgoString += hours + " hours ";
|
||||
}
|
||||
if (minutes >= 0) {
|
||||
timeAgoString += minutes + " min ";
|
||||
}
|
||||
|
||||
return (timeAgoString.equals("") ? "--" : timeAgoString + "ago");
|
||||
}
|
||||
|
||||
public static GlucoseDataSet[] mergeGlucoseDataRecords(EGVRecord[] egvRecords,
|
||||
SensorRecord[] sensorRecords) {
|
||||
int egvLength = egvRecords.length;
|
||||
int sensorLength = sensorRecords.length;
|
||||
int smallerLength = egvLength < sensorLength ? egvLength : sensorLength;
|
||||
GlucoseDataSet[] glucoseDataSets = new GlucoseDataSet[smallerLength];
|
||||
for (int i = 1; i <= smallerLength; i++) {
|
||||
glucoseDataSets[smallerLength - i] = new GlucoseDataSet(egvRecords[egvLength - i], sensorRecords[sensorLength - i]);
|
||||
}
|
||||
return glucoseDataSets;
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||
char[] hexChars = new char[bytes.length * 3];
|
||||
for ( int j = 0; j < bytes.length; j++ ) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 3] = hexArray[v >>> 4];
|
||||
hexChars[j * 3 + 1] = hexArray[v & 0x0F];
|
||||
hexChars[j * 3 + 2] = " ".toCharArray()[0];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class CalRecord extends GenericTimestampRecord {
|
||||
private static final String TAG = CalRecord.class.getSimpleName();
|
||||
private double slope;
|
||||
private double intercept;
|
||||
private double scale;
|
||||
private int[] unk = new int[3];
|
||||
private double decay;
|
||||
private int numRecords;
|
||||
private CalSubrecord[] calSubrecords = new CalSubrecord[12];
|
||||
private int SUB_LEN = 17;
|
||||
|
||||
public CalRecord(byte[] packet) {
|
||||
super(packet);
|
||||
slope = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(8);
|
||||
intercept = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(16);
|
||||
scale = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(24);
|
||||
unk[0] = packet[32];
|
||||
unk[1] = packet[33];
|
||||
unk[2] = packet[34];
|
||||
decay = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(35);
|
||||
numRecords = packet[43];
|
||||
long displayTimeOffset = (getDisplayTime().getTime() - getSystemTime().getTime()) / (1000);
|
||||
int start = 44;
|
||||
for (int i = 0; i < numRecords; i++) {
|
||||
Log.d("CalDebug","Loop #"+i);
|
||||
byte[] temp = new byte[SUB_LEN];
|
||||
System.arraycopy(packet, start, temp, 0, temp.length);
|
||||
calSubrecords[i] = new CalSubrecord(temp, displayTimeOffset);
|
||||
start += SUB_LEN;
|
||||
}
|
||||
|
||||
Log.d("ShareTest", "slope: " + slope + " intercept: " + intercept);
|
||||
}
|
||||
|
||||
public double getSlope() {
|
||||
return slope;
|
||||
}
|
||||
|
||||
public double getIntercept() {
|
||||
return intercept;
|
||||
}
|
||||
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public int[] getUnk() {
|
||||
return unk;
|
||||
}
|
||||
|
||||
public double getDecay() {
|
||||
return decay;
|
||||
}
|
||||
|
||||
public int getNumRecords() {
|
||||
return numRecords;
|
||||
}
|
||||
|
||||
public CalSubrecord[] getCalSubrecords() {
|
||||
return calSubrecords;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Utils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Date;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class CalSubrecord {
|
||||
private static final String TAG = CalSubrecord.class.getSimpleName();
|
||||
private Date dateEntered;
|
||||
private int calBGL;
|
||||
private int calRaw;
|
||||
private Date dateApplied;
|
||||
private byte unk;
|
||||
|
||||
public CalSubrecord(byte[] packet, long displayTimeOffset) {
|
||||
int delta = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||
dateEntered = Utils.receiverTimeToDate(delta + displayTimeOffset);
|
||||
calBGL = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(4);
|
||||
calRaw = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(8);
|
||||
delta = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(12);
|
||||
dateApplied = Utils.receiverTimeToDate(delta + displayTimeOffset);
|
||||
unk = packet[16];
|
||||
}
|
||||
|
||||
public Date getDateEntered() {
|
||||
return dateEntered;
|
||||
}
|
||||
|
||||
public int getCalBGL() {
|
||||
return calBGL;
|
||||
}
|
||||
|
||||
public int getCalRaw() {
|
||||
return calRaw;
|
||||
}
|
||||
|
||||
public Date getDateApplied() {
|
||||
return dateApplied;
|
||||
}
|
||||
|
||||
public byte getUnk() {
|
||||
return unk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Dex_Constants;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Date;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class EGVRecord extends GenericTimestampRecord {
|
||||
|
||||
private int bGValue;
|
||||
private int noise;
|
||||
private Dex_Constants.TREND_ARROW_VALUES trend;
|
||||
|
||||
public EGVRecord(byte[] packet) {
|
||||
// system_time (UInt), display_time (UInt), glucose (UShort), trend_arrow (Byte), crc (UShort))
|
||||
super(packet);
|
||||
bGValue = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8) & Dex_Constants.EGV_VALUE_MASK;
|
||||
byte trendAndNoise = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).get(10);
|
||||
int trendValue = trendAndNoise & Dex_Constants.EGV_TREND_ARROW_MASK;
|
||||
byte noiseValue = (byte) ((trendAndNoise & Dex_Constants.EGV_NOISE_MASK) >> 4);
|
||||
trend = Dex_Constants.TREND_ARROW_VALUES.values()[trendValue];
|
||||
noise = noiseValue;
|
||||
}
|
||||
|
||||
public EGVRecord(int bGValue, Dex_Constants.TREND_ARROW_VALUES trend, Date displayTime, Date systemTime){
|
||||
super(displayTime, systemTime);
|
||||
this.bGValue=bGValue;
|
||||
this.trend=trend;
|
||||
}
|
||||
|
||||
public String noiseValue() { return String.valueOf(noise); }
|
||||
public int getBGValue() {
|
||||
return bGValue;
|
||||
}
|
||||
|
||||
public Dex_Constants.TREND_ARROW_VALUES getTrend() {
|
||||
return trend;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("sgv", getBGValue());
|
||||
obj.put("date", getDisplayTimeSeconds());
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Utils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Date;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class GenericTimestampRecord {
|
||||
|
||||
protected final int OFFSET_SYS_TIME = 0;
|
||||
protected final int OFFSET_DISPLAY_TIME = 4;
|
||||
protected Date systemTime;
|
||||
protected int systemTimeSeconds;
|
||||
protected Date displayTime;
|
||||
|
||||
public GenericTimestampRecord(byte[] packet) {
|
||||
systemTimeSeconds = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_SYS_TIME);
|
||||
systemTime = Utils.receiverTimeToDate(systemTimeSeconds);
|
||||
int dt = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_DISPLAY_TIME);
|
||||
displayTime = Utils.receiverTimeToDate(dt);
|
||||
}
|
||||
|
||||
public GenericTimestampRecord(Date displayTime, Date systemTime){
|
||||
this.displayTime=displayTime;
|
||||
this.systemTime=systemTime;
|
||||
}
|
||||
|
||||
public Date getSystemTime() {
|
||||
return systemTime;
|
||||
}
|
||||
|
||||
public int getSystemTimeSeconds() {
|
||||
return systemTimeSeconds;
|
||||
}
|
||||
|
||||
public Date getDisplayTime() {
|
||||
return displayTime;
|
||||
}
|
||||
public long getDisplayTimeSeconds() {
|
||||
return displayTime.getTime();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.Serializable;
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class GenericXMLRecord extends GenericTimestampRecord {
|
||||
int XML_START = 8;
|
||||
int XML_END = 241;
|
||||
|
||||
private final String TAG = GenericXMLRecord.class.getSimpleName();
|
||||
|
||||
private Element xmlElement;
|
||||
|
||||
public GenericXMLRecord(byte[] packet) {
|
||||
super(packet);
|
||||
Document document;
|
||||
// TODO: it would be best if we could just remove /x00 characters and read till end
|
||||
String xml = new String(Arrays.copyOfRange(packet, XML_START, XML_END));
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder;
|
||||
try
|
||||
{
|
||||
builder = factory.newDocumentBuilder();
|
||||
document = builder.parse(new InputSource(new StringReader(xml)));
|
||||
xmlElement = document.getDocumentElement();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to build xml element", e);
|
||||
}
|
||||
}
|
||||
|
||||
// example: String sn = getXmlElement().getAttribute("SerialNumber");
|
||||
public Element getXmlElement() {
|
||||
return xmlElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Dex_Constants;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class GlucoseDataSet {
|
||||
|
||||
private Date systemTime;
|
||||
private Date displayTime;
|
||||
private int bGValue;
|
||||
private Dex_Constants.TREND_ARROW_VALUES trend;
|
||||
private long unfiltered;
|
||||
private long filtered;
|
||||
private int rssi;
|
||||
|
||||
public GlucoseDataSet(EGVRecord egvRecord, SensorRecord sensorRecord) {
|
||||
// TODO check times match between record
|
||||
systemTime = egvRecord.getSystemTime();
|
||||
displayTime = egvRecord.getDisplayTime();
|
||||
bGValue = egvRecord.getBGValue();
|
||||
trend = egvRecord.getTrend();
|
||||
unfiltered = sensorRecord.getUnfiltered();
|
||||
filtered = sensorRecord.getFiltered();
|
||||
rssi = sensorRecord.getRSSI();
|
||||
}
|
||||
|
||||
public Date getSystemTime() {
|
||||
return systemTime;
|
||||
}
|
||||
|
||||
public Date getDisplayTime() {
|
||||
return displayTime;
|
||||
}
|
||||
|
||||
public int getBGValue() {
|
||||
return bGValue;
|
||||
}
|
||||
|
||||
public Dex_Constants.TREND_ARROW_VALUES getTrend() {
|
||||
return trend;
|
||||
}
|
||||
|
||||
public String getTrendSymbol() {
|
||||
return trend.Symbol();
|
||||
}
|
||||
|
||||
public long getUnfiltered() {
|
||||
return unfiltered;
|
||||
}
|
||||
|
||||
public long getFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public int getRssi() {
|
||||
return rssi;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class MeterRecord extends GenericTimestampRecord {
|
||||
|
||||
private int meterBG;
|
||||
private int meterTime;
|
||||
|
||||
public MeterRecord(byte[] packet) {
|
||||
super(packet);
|
||||
meterBG = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8);
|
||||
meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(10);
|
||||
}
|
||||
|
||||
public int getMeterBG() {
|
||||
return meterBG;
|
||||
}
|
||||
|
||||
public int getMeterTime() {
|
||||
return meterTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.CRC16;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.CRCFailRuntimeException;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Dex_Constants;
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Utils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class PageHeader {
|
||||
protected final int HEADER_SIZE=28;
|
||||
protected final int FIRSTRECORDINDEX_OFFSET=0;
|
||||
protected final int NUMRECS_OFFSET=4;
|
||||
protected final int RECTYPE_OFFSET=8;
|
||||
protected final int REV_OFFSET=9;
|
||||
protected final int PAGENUMBER_OFFSET=10;
|
||||
protected final int RESERVED2_OFFSET=14;
|
||||
protected final int RESERVED3_OFFSET=18;
|
||||
protected final int RESERVED4_OFFSET=22;
|
||||
|
||||
protected int firstRecordIndex;
|
||||
protected int numOfRecords;
|
||||
protected Dex_Constants.RECORD_TYPES recordType;
|
||||
protected byte revision;
|
||||
protected int pageNumber;
|
||||
protected int reserved2;
|
||||
protected int reserved3;
|
||||
protected int reserved4;
|
||||
protected byte[] crc=new byte[2];
|
||||
|
||||
|
||||
public PageHeader(byte[] packet) {
|
||||
Log.d("ShareTest", "Header Packet Data Length: " + packet.length);
|
||||
|
||||
firstRecordIndex = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(FIRSTRECORDINDEX_OFFSET);
|
||||
numOfRecords = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(NUMRECS_OFFSET);
|
||||
recordType = Dex_Constants.RECORD_TYPES.values()[packet[RECTYPE_OFFSET]];
|
||||
revision = packet[REV_OFFSET];
|
||||
pageNumber = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(PAGENUMBER_OFFSET);
|
||||
reserved2 = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(RESERVED2_OFFSET);
|
||||
reserved3 = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(RESERVED3_OFFSET);
|
||||
reserved4 = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(RESERVED4_OFFSET);
|
||||
System.arraycopy(packet,HEADER_SIZE- Dex_Constants.CRC_LEN,crc,0, Dex_Constants.CRC_LEN);
|
||||
byte[] crc_calc = CRC16.calculate(packet,0,HEADER_SIZE - Dex_Constants.CRC_LEN);
|
||||
if (!Arrays.equals(this.crc, crc_calc)) {
|
||||
throw new CRCFailRuntimeException("CRC check failed: " + Utils.bytesToHex(this.crc) + " vs " + Utils.bytesToHex(crc_calc));
|
||||
}
|
||||
}
|
||||
|
||||
public byte getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public Dex_Constants.RECORD_TYPES getRecordType() {
|
||||
return recordType;
|
||||
}
|
||||
|
||||
public int getFirstRecordIndex() {
|
||||
return firstRecordIndex;
|
||||
}
|
||||
|
||||
public int getNumOfRecords() {
|
||||
return numOfRecords;
|
||||
}
|
||||
|
||||
public int getPageNumber() {
|
||||
return pageNumber;
|
||||
}
|
||||
|
||||
public int getReserved2() {
|
||||
return reserved2;
|
||||
}
|
||||
|
||||
public int getReserved3() {
|
||||
return reserved3;
|
||||
}
|
||||
|
||||
public int getReserved4() {
|
||||
return reserved4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records;
|
||||
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// This code and this particular library are from the NightScout android uploader
|
||||
// Check them out here: https://github.com/nightscout/android-uploader
|
||||
// Some of this code may have been modified for use in this project
|
||||
|
||||
public class SensorRecord extends GenericTimestampRecord {
|
||||
|
||||
private int unfiltered;
|
||||
private int filtered;
|
||||
private int rssi;
|
||||
private int OFFSET_UNFILTERED = 8;
|
||||
private int OFFSET_FILTERED = 12;
|
||||
private int OFFSET_RSSI = 16;
|
||||
|
||||
public SensorRecord(byte[] packet) {
|
||||
super(packet);
|
||||
unfiltered = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_UNFILTERED);
|
||||
filtered = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(OFFSET_FILTERED);
|
||||
byte [] usRSSI = new byte[]{packet[17],packet[16]};
|
||||
rssi = usRSSI[0] << 8 | usRSSI[1];
|
||||
//rssi = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(OFFSET_RSSI);
|
||||
Log.d("ShareTest", "filtered: " + filtered + " unfiltered: " + unfiltered);
|
||||
}
|
||||
|
||||
public long getUnfiltered() {
|
||||
return unfiltered;
|
||||
}
|
||||
|
||||
public long getFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
public int getRSSI() {
|
||||
return rssi;
|
||||
}
|
||||
}
|
||||
151
lib/nightscout/com/eveningoutpost/dexdrip/Models/APStatus.java
Normal file
151
lib/nightscout/com/eveningoutpost/dexdrip/Models/APStatus.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.wearintegration.ExternalStatusService;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 11/06/2018.
|
||||
*/
|
||||
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "APStatus", id = BaseColumns._ID)
|
||||
public class APStatus extends PlusModel {
|
||||
|
||||
private static boolean patched = false;
|
||||
private final static String TAG = APStatus.class.getSimpleName();
|
||||
private final static boolean d = false;
|
||||
|
||||
private static final String[] schema = {
|
||||
"CREATE TABLE APStatus (_id INTEGER PRIMARY KEY AUTOINCREMENT);",
|
||||
"ALTER TABLE APStatus ADD COLUMN timestamp INTEGER;",
|
||||
"ALTER TABLE APStatus ADD COLUMN basal_percent INTEGER;",
|
||||
"CREATE UNIQUE INDEX index_APStatus_timestamp on APStatus(timestamp);"};
|
||||
|
||||
|
||||
@Expose
|
||||
@Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE)
|
||||
public long timestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "basal_percent")
|
||||
public int basal_percent;
|
||||
|
||||
|
||||
public String toS() {
|
||||
return JoH.defaultGsonInstance().toJson(this);
|
||||
}
|
||||
|
||||
// static methods
|
||||
|
||||
public static APStatus createEfficientRecord(long timestamp_ms, int basal_percent) {
|
||||
final APStatus existing = last();
|
||||
if (existing == null || (existing.basal_percent != basal_percent)) {
|
||||
|
||||
if (existing != null && existing.timestamp > timestamp_ms) {
|
||||
UserError.Log.e(TAG, "Refusing to create record older than current: " + JoH.dateTimeText(timestamp_ms) + " vs " + JoH.dateTimeText(existing.timestamp));
|
||||
return null;
|
||||
}
|
||||
|
||||
final APStatus fresh = APStatus.builder()
|
||||
.timestamp(timestamp_ms)
|
||||
.basal_percent(basal_percent)
|
||||
.build();
|
||||
|
||||
UserError.Log.d(TAG, "New record created: " + fresh.toS());
|
||||
|
||||
fresh.save();
|
||||
return fresh;
|
||||
} else {
|
||||
return existing;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO use persistent store?
|
||||
public static APStatus last() {
|
||||
try {
|
||||
return new Select()
|
||||
.from(APStatus.class)
|
||||
.orderBy("timestamp desc")
|
||||
.executeSingle();
|
||||
} catch (android.database.sqlite.SQLiteException e) {
|
||||
updateDB();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<APStatus> latestForGraph(int number, double startTime) {
|
||||
return latestForGraph(number, (long) startTime, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static List<APStatus> latestForGraph(int number, long startTime) {
|
||||
return latestForGraph(number, startTime, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static List<APStatus> latestForGraph(int number, long startTime, long endTime) {
|
||||
try {
|
||||
final List<APStatus> results = new Select()
|
||||
.from(APStatus.class)
|
||||
.where("timestamp >= " + Math.max(startTime, 0))
|
||||
.where("timestamp <= " + endTime)
|
||||
.orderBy("timestamp asc") // warn asc!
|
||||
.limit(number)
|
||||
.execute();
|
||||
// extend line to now if we have current data but it is continuation of last record
|
||||
// so not generating a new efficient record.
|
||||
if (results != null && (results.size() > 0)) {
|
||||
final APStatus last = results.get(results.size() - 1);
|
||||
final long last_raw_record_timestamp = ExternalStatusService.getLastStatusLineTime();
|
||||
// check are not already using the latest.
|
||||
if (last_raw_record_timestamp > last.timestamp) {
|
||||
final Integer last_recorded_tbr = ExternalStatusService.getTBRInt();
|
||||
if (last_recorded_tbr != null) {
|
||||
if ((last.basal_percent == last_recorded_tbr)
|
||||
&& (JoH.msSince(last.timestamp) < Constants.HOUR_IN_MS * 3)
|
||||
&& (JoH.msSince(ExternalStatusService.getLastStatusLineTime()) < Constants.MINUTE_IN_MS * 20)) {
|
||||
results.add(new APStatus(JoH.tsl(), last_recorded_tbr));
|
||||
UserError.Log.d(TAG, "Adding extension record");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
} catch (android.database.sqlite.SQLiteException e) {
|
||||
updateDB();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<APStatus> cleanup(int retention_days) {
|
||||
return new Delete()
|
||||
.from(APStatus.class)
|
||||
.where("timestamp < ?", JoH.tsl() - (retention_days * 86400000L))
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
||||
// create the table ourselves without worrying about model versioning and downgrading
|
||||
public static void updateDB() {
|
||||
patched = fixUpTable(schema, patched);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
193
lib/nightscout/com/eveningoutpost/dexdrip/Models/Accuracy.java
Normal file
193
lib/nightscout/com/eveningoutpost/dexdrip/Models/Accuracy.java
Normal file
@@ -0,0 +1,193 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 01/02/2017.
|
||||
*/
|
||||
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.eveningoutpost.dexdrip.BestGlucose;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Table(name = "Accuracy", id = BaseColumns._ID)
|
||||
public class Accuracy extends PlusModel {
|
||||
private static final String TAG = "Accuracy";
|
||||
private static boolean patched = false;
|
||||
static final String[] schema = {
|
||||
"CREATE TABLE Accuracy (_id INTEGER PRIMARY KEY AUTOINCREMENT);",
|
||||
"ALTER TABLE Accuracy ADD COLUMN timestamp INTEGER;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN bg REAL;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN bgtimestamp INTEGER;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN bgsource TEXT;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN plugin TEXT;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN calculated REAL;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN lag INTEGER;",
|
||||
"ALTER TABLE Accuracy ADD COLUMN difference REAL;",
|
||||
"CREATE INDEX index_Accuracy_timestamp on Accuracy(timestamp);",
|
||||
"CREATE INDEX index_Accuracy_bgtimestamp on Accuracy(bgtimestamp);"
|
||||
};
|
||||
|
||||
@Expose
|
||||
@Column(name = "timestamp", index = true)
|
||||
public long timestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "bg")
|
||||
public double bg;
|
||||
|
||||
@Expose
|
||||
@Column(name = "bgtimestamp", index = true)
|
||||
public long bgtimestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "bgsource")
|
||||
public String bgsource;
|
||||
|
||||
@Expose
|
||||
@Column(name = "plugin")
|
||||
public String plugin;
|
||||
|
||||
@Expose
|
||||
@Column(name = "calculated")
|
||||
public double calculated;
|
||||
|
||||
@Expose
|
||||
@Column(name = "lag")
|
||||
public boolean lag;
|
||||
|
||||
@Expose
|
||||
@Column(name = "difference")
|
||||
public double difference;
|
||||
|
||||
private static final boolean d = false;
|
||||
|
||||
public static Accuracy create(BloodTest bloodTest, BestGlucose.DisplayGlucose dg) {
|
||||
if (dg == null) return null;
|
||||
final BgReading from_dg = new BgReading();
|
||||
from_dg.timestamp = dg.timestamp;
|
||||
from_dg.calculated_value = dg.mgdl;
|
||||
return create(bloodTest, from_dg, dg.plugin_name);
|
||||
}
|
||||
|
||||
|
||||
public static Accuracy create(BloodTest bloodTest, BgReading bgReading, String plugin) {
|
||||
if ((bloodTest == null) || (bgReading == null)) return null;
|
||||
patched = fixUpTable(schema, patched);
|
||||
if (getForPreciseTimestamp(bgReading.timestamp, Constants.MINUTE_IN_MS, plugin) != null) {
|
||||
UserError.Log.d(TAG, "Duplicate accuracy timestamp for: " + JoH.dateTimeText(bgReading.timestamp));
|
||||
return null;
|
||||
}
|
||||
final Accuracy ac = new Accuracy();
|
||||
ac.timestamp = bgReading.timestamp;
|
||||
ac.bg = bloodTest.mgdl;
|
||||
ac.bgtimestamp = bloodTest.timestamp;
|
||||
ac.bgsource = bloodTest.source;
|
||||
ac.plugin = plugin;
|
||||
ac.calculated = bgReading.calculated_value;
|
||||
//ac.lag = bgReading.timestamp-bloodTest.timestamp;
|
||||
ac.difference = bgReading.calculated_value - bloodTest.mgdl;
|
||||
ac.save();
|
||||
return ac;
|
||||
}
|
||||
|
||||
static Accuracy getForPreciseTimestamp(double timestamp, double precision, String plugin) {
|
||||
patched = fixUpTable(schema, patched);
|
||||
final Accuracy accuracy = new Select()
|
||||
.from(Accuracy.class)
|
||||
.where("timestamp <= ?", (timestamp + precision))
|
||||
.where("timestamp >= ?", (timestamp - precision))
|
||||
.where("plugin = ?", plugin)
|
||||
.orderBy("abs(timestamp - " + timestamp + ") asc")
|
||||
.executeSingle();
|
||||
if (accuracy != null && Math.abs(accuracy.timestamp - timestamp) < precision) {
|
||||
return accuracy;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<Accuracy> latestForGraph(int number, long startTime, long endTime) {
|
||||
try {
|
||||
return new Select()
|
||||
.from(Accuracy.class)
|
||||
.where("timestamp >= " + Math.max(startTime, 0))
|
||||
.where("timestamp <= " + endTime)
|
||||
.orderBy("timestamp desc, _id asc")
|
||||
.limit(number)
|
||||
.execute();
|
||||
} catch (android.database.sqlite.SQLiteException e) {
|
||||
patched = fixUpTable(schema, patched);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static String evaluateAccuracy(long period) {
|
||||
// TODO CACHE ?
|
||||
final boolean domgdl = Pref.getString("units", "mgdl").equals("mgdl");
|
||||
final Map<String, Double> totals = new HashMap<>();
|
||||
final Map<String, Double> signed_totals = new HashMap<>();
|
||||
final Map<String, Integer> count = new HashMap<>();
|
||||
final List<Accuracy> alist = latestForGraph(500, JoH.tsl() - period, JoH.tsl());
|
||||
|
||||
// total up differences
|
||||
for (Accuracy entry : alist) {
|
||||
if (totals.containsKey(entry.plugin)) {
|
||||
totals.put(entry.plugin, totals.get(entry.plugin) + Math.abs(entry.difference));
|
||||
signed_totals.put(entry.plugin, signed_totals.get(entry.plugin) + entry.difference);
|
||||
count.put(entry.plugin, count.get(entry.plugin) + 1);
|
||||
} else {
|
||||
totals.put(entry.plugin, Math.abs(entry.difference));
|
||||
signed_totals.put(entry.plugin, entry.difference);
|
||||
count.put(entry.plugin, 1);
|
||||
}
|
||||
}
|
||||
String result = "";
|
||||
int plugin_count = 0;
|
||||
for (Map.Entry<String, Double> total : totals.entrySet()) {
|
||||
plugin_count++;
|
||||
final String plugin = total.getKey();
|
||||
final int this_count = count.get(plugin);
|
||||
final double this_total = total.getValue();
|
||||
// calculate the abs mean, 0 = perfect
|
||||
final double this_mean = this_total / this_count;
|
||||
final double signed_total = signed_totals.get(plugin);
|
||||
final double signed_mean = signed_total / this_count;
|
||||
// calculate the bias ratio. 0% means totally unbiased, 100% means all data skewed towards signed mean
|
||||
final double signed_ratio = (Math.abs(signed_mean) / this_mean) * 100;
|
||||
|
||||
if (d) UserError.Log.d(TAG, plugin + ": total: " + JoH.qs(this_total) + " count: " + this_count + " avg: " + JoH.qs(this_mean) + " mmol: " + JoH.qs((this_mean) * Constants.MGDL_TO_MMOLL) + " bias: " + JoH.qs(signed_mean) + " " + JoH.qs(signed_ratio, 0) + "%");
|
||||
String plugin_result = plugin.substring(0, 1).toLowerCase() + ": " + asString(this_mean, signed_mean, signed_ratio, domgdl);
|
||||
UserError.Log.d(TAG, plugin_result);
|
||||
if (result.length() > 0) result += " ";
|
||||
result += plugin_result;
|
||||
}
|
||||
|
||||
return plugin_count == 1 ? result : result.replaceFirst(" mmol", "").replaceFirst(" mgdl", " ");
|
||||
}
|
||||
|
||||
private static String asString(double mean, double signed_mean, double signed_ratio, boolean domgdl) {
|
||||
|
||||
String symbol = "err";
|
||||
if (signed_ratio < 90) {
|
||||
symbol = "\u00B1"; // +- symbol
|
||||
} else {
|
||||
if (signed_mean < 0) {
|
||||
symbol = "\u207B"; // superscript minus
|
||||
} else {
|
||||
symbol = "\u207A"; // superscript plus
|
||||
}
|
||||
}
|
||||
return symbol + (!domgdl ? JoH.qs(mean * Constants.MGDL_TO_MMOLL, 2) + " mmol" : JoH.qs(mean, 1) + " mgdl");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.AlertPlayer;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Created by Emma Black on 1/14/15.
|
||||
*/
|
||||
@Table(name = "ActiveBgAlert", id = BaseColumns._ID)
|
||||
public class ActiveBgAlert extends Model {
|
||||
|
||||
private final static String TAG = AlertPlayer.class.getSimpleName();
|
||||
private static boolean patched = false;
|
||||
|
||||
@Column(name = "alert_uuid")
|
||||
public String alert_uuid;
|
||||
|
||||
@Column(name = "is_snoozed")
|
||||
public volatile boolean is_snoozed;
|
||||
|
||||
@Column(name = "last_alerted_at") // Do we need this
|
||||
public volatile Long last_alerted_at;
|
||||
|
||||
@Column(name = "next_alert_at")
|
||||
public volatile Long next_alert_at;
|
||||
|
||||
// This is needed in order to have ascending alerts
|
||||
// we set the real value of it when is_snoozed is being turned to false
|
||||
@Column(name = "alert_started_at")
|
||||
public volatile Long alert_started_at;
|
||||
|
||||
|
||||
public boolean ready_to_alarm() {
|
||||
if(new Date().getTime() > next_alert_at) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean currentlyAlerting() {
|
||||
final ActiveBgAlert activeBgAlert = getOnly();
|
||||
return activeBgAlert != null && !activeBgAlert.is_snoozed;
|
||||
}
|
||||
|
||||
public static boolean alertSnoozeOver() {
|
||||
ActiveBgAlert activeBgAlert = getOnly();
|
||||
if (activeBgAlert == null) {
|
||||
// no alert exists, so snoozing is over... (this should not happen)
|
||||
Log.wtf(TAG, "ActiveBgAlert getOnly returning null (we have just checked it)");
|
||||
return true;
|
||||
}
|
||||
return activeBgAlert.ready_to_alarm();
|
||||
}
|
||||
|
||||
public void snooze(int minutes) {
|
||||
next_alert_at = new Date().getTime() + minutes * 60000;
|
||||
is_snoozed = true;
|
||||
Log.ueh("Snoozed Alert","Snoozed until: "+JoH.dateTimeText(next_alert_at));
|
||||
save();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
try {
|
||||
String alert_uuid = "alert_uuid: " + this.alert_uuid;
|
||||
String is_snoozed = "is_snoozed: " + this.is_snoozed;
|
||||
String last_alerted_at = "last_alerted_at: " + DateFormat.getDateTimeInstance(
|
||||
DateFormat.LONG, DateFormat.LONG).format(new Date(this.last_alerted_at));
|
||||
String next_alert_at = "next_alert_at: " + DateFormat.getDateTimeInstance(
|
||||
DateFormat.LONG, DateFormat.LONG).format(new Date(this.next_alert_at));
|
||||
|
||||
String alert_started_at = "alert_started_at: " + DateFormat.getDateTimeInstance(
|
||||
DateFormat.LONG, DateFormat.LONG).format(new Date(this.alert_started_at));
|
||||
|
||||
return alert_uuid + " " + is_snoozed + " " + last_alerted_at + " " + next_alert_at + " " + alert_started_at;
|
||||
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(TAG, "Got Nullpointer exception in toString()! " + e);
|
||||
return "Nullpointer exception in toString!";
|
||||
}
|
||||
}
|
||||
|
||||
// We should only have at most one active alert at any given time.
|
||||
// This means that we will only have one of this objects at the database at any given time.
|
||||
// so we have the following static functions: getOnly, saveData, ClearData
|
||||
|
||||
public static ActiveBgAlert getOnly() {
|
||||
ActiveBgAlert aba = new Select()
|
||||
.from(ActiveBgAlert.class)
|
||||
.orderBy("_ID asc")
|
||||
.executeSingle();
|
||||
|
||||
if (aba != null) {
|
||||
Log.v(TAG, "ActiveBgAlert getOnly aba = " + aba.toString());
|
||||
} else {
|
||||
Log.v(TAG, "ActiveBgAlert getOnly returning null");
|
||||
}
|
||||
|
||||
return aba;
|
||||
}
|
||||
|
||||
public static AlertType alertTypegetOnly() {
|
||||
return alertTypegetOnly(getOnly());
|
||||
}
|
||||
|
||||
public static AlertType alertTypegetOnly(final ActiveBgAlert aba) {
|
||||
|
||||
if (aba == null) {
|
||||
Log.v(TAG, "ActiveBgAlert: alertTypegetOnly returning null");
|
||||
return null;
|
||||
}
|
||||
|
||||
AlertType alert = AlertType.get_alert(aba.alert_uuid);
|
||||
if(alert == null) {
|
||||
Log.d(TAG, "alertTypegetOnly did not find the active alert as part of existing alerts. returning null");
|
||||
// removing the alert to be in a better state.
|
||||
ClearData();
|
||||
return null;
|
||||
}
|
||||
if(!alert.uuid.equals(aba.alert_uuid)) {
|
||||
Log.wtf(TAG, "AlertType.get_alert did not return the correct alert");
|
||||
}
|
||||
return alert;
|
||||
}
|
||||
|
||||
public static void Create(String alert_uuid, boolean is_snoozed, Long next_alert_at) {
|
||||
Log.d(TAG, "ActiveBgAlert Create called");
|
||||
fixUpTable();
|
||||
ActiveBgAlert aba = getOnly();
|
||||
if (aba == null) {
|
||||
aba = new ActiveBgAlert();
|
||||
}
|
||||
aba.alert_uuid = alert_uuid;
|
||||
aba.is_snoozed = is_snoozed;
|
||||
aba.last_alerted_at = 0L;
|
||||
aba.next_alert_at = next_alert_at;
|
||||
aba.alert_started_at = new Date().getTime();
|
||||
aba.save();
|
||||
}
|
||||
|
||||
public static void ClearData() {
|
||||
Log.d(TAG, "ActiveBgAlert ClearData called");
|
||||
ActiveBgAlert aba = getOnly();
|
||||
if (aba != null) {
|
||||
aba.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearIfSnoozeFinished() {
|
||||
Log.d(TAG, "ActiveBgAlert ClearIfSnoozeFinished called");
|
||||
ActiveBgAlert aba = getOnly();
|
||||
if (aba != null) {
|
||||
if(new Date().getTime() > aba.next_alert_at) {
|
||||
Log.d(TAG, "ActiveBgAlert ClearIfSnoozeFinished deleting allert");
|
||||
aba.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called from ClockTick, when we play
|
||||
// If we were snoozed, we update the snooze to false, and update the start time.
|
||||
// return the time in minutes from the time playing the alert has started
|
||||
public int getAndUpdateAlertingMinutes() {
|
||||
if(is_snoozed) {
|
||||
is_snoozed = false;
|
||||
alert_started_at = new Date().getTime();
|
||||
save();
|
||||
}
|
||||
Long timeSeconds = (new Date().getTime() - alert_started_at) / 1000;
|
||||
return (int)Math.round(timeSeconds / 60.0);
|
||||
}
|
||||
|
||||
public void updateNextAlertAt(long nextAlertTime){
|
||||
next_alert_at = nextAlertTime;
|
||||
save();
|
||||
}
|
||||
|
||||
private static void fixUpTable() {
|
||||
if (patched) return;
|
||||
String[] patchup = {
|
||||
"ALTER TABLE ActiveBgAlert ADD COLUMN alert_started_at INTEGER;"
|
||||
};
|
||||
|
||||
for (String patch : patchup) {
|
||||
try {
|
||||
SQLiteUtils.execSql(patch);
|
||||
Log.e(TAG, "Processed patch should not have succeeded!!: " + patch);
|
||||
} catch (Exception e) {
|
||||
// Log.d(TAG, "Patch: " + patch + " generated exception as it should: " + e.toString());
|
||||
}
|
||||
}
|
||||
patched = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Created by Emma Black on 11/3/14.
|
||||
*/
|
||||
@Table(name = "ActiveBluetoothDevice", id = BaseColumns._ID)
|
||||
public class ActiveBluetoothDevice extends Model {
|
||||
@Column(name = "name")
|
||||
public String name;
|
||||
|
||||
@Column(name = "address")
|
||||
public String address;
|
||||
|
||||
@Column(name = "connected")
|
||||
public boolean connected;
|
||||
|
||||
|
||||
public static final Object table_lock = new Object();
|
||||
|
||||
public static synchronized ActiveBluetoothDevice first() {
|
||||
return new Select()
|
||||
.from(ActiveBluetoothDevice.class)
|
||||
.orderBy("_ID asc")
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public static synchronized void forget() {
|
||||
ActiveBluetoothDevice activeBluetoothDevice = ActiveBluetoothDevice.first();
|
||||
if (activeBluetoothDevice != null) {
|
||||
activeBluetoothDevice.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void connected() {
|
||||
ActiveBluetoothDevice activeBluetoothDevice = ActiveBluetoothDevice.first();
|
||||
if(activeBluetoothDevice != null) {
|
||||
activeBluetoothDevice.connected = true;
|
||||
activeBluetoothDevice.save();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void disconnected() {
|
||||
ActiveBluetoothDevice activeBluetoothDevice = ActiveBluetoothDevice.first();
|
||||
if(activeBluetoothDevice != null) {
|
||||
activeBluetoothDevice.connected = false;
|
||||
activeBluetoothDevice.save();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized boolean is_connected() {
|
||||
ActiveBluetoothDevice activeBluetoothDevice = ActiveBluetoothDevice.first();
|
||||
return (activeBluetoothDevice != null && activeBluetoothDevice.connected);
|
||||
}
|
||||
|
||||
}
|
||||
677
lib/nightscout/com/eveningoutpost/dexdrip/Models/AlertType.java
Normal file
677
lib/nightscout/com/eveningoutpost/dexdrip/Models/AlertType.java
Normal file
@@ -0,0 +1,677 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.eveningoutpost.dexdrip.Services.ActivityRecognizedService;
|
||||
import com.eveningoutpost.dexdrip.Services.MissedReadingService;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.AlertPlayer;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Notifications;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.internal.bind.DateTypeAdapter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/**
|
||||
* Created by Emma Black on 1/14/15.
|
||||
*/
|
||||
@Table(name = "AlertType", id = BaseColumns._ID)
|
||||
public class AlertType extends Model {
|
||||
|
||||
@Expose
|
||||
@Column(name = "name")
|
||||
public String name;
|
||||
|
||||
@Expose
|
||||
@Column(name = "active")
|
||||
public boolean active;
|
||||
|
||||
@Expose
|
||||
@Column(name = "volume")
|
||||
public int volume;
|
||||
|
||||
@Expose
|
||||
@Column(name = "vibrate")
|
||||
public boolean vibrate;
|
||||
|
||||
@Expose
|
||||
@Column(name = "light")
|
||||
public boolean light;
|
||||
|
||||
@Expose
|
||||
@Column(name = "override_silent_mode")
|
||||
public boolean override_silent_mode;
|
||||
|
||||
@Expose
|
||||
@Column(name = "force_speaker")
|
||||
public boolean force_speaker;
|
||||
|
||||
@Expose
|
||||
@Column(name = "predictive")
|
||||
public boolean predictive;
|
||||
|
||||
@Expose
|
||||
@Column(name = "time_until_threshold_crossed")
|
||||
public double time_until_threshold_crossed;
|
||||
|
||||
// If it is not above, then it must be below.
|
||||
@Expose
|
||||
@Column(name = "above")
|
||||
public boolean above;
|
||||
|
||||
@Expose
|
||||
@Column(name = "threshold")
|
||||
public double threshold;
|
||||
|
||||
@Expose
|
||||
@Column(name = "all_day")
|
||||
public boolean all_day;
|
||||
|
||||
@Expose
|
||||
@Column(name = "start_time_minutes")
|
||||
public int start_time_minutes; // This have probable be in minutes from start of day. this is not time...
|
||||
|
||||
@Expose
|
||||
@Column(name = "end_time_minutes")
|
||||
public int end_time_minutes;
|
||||
|
||||
@Expose
|
||||
@Column(name = "minutes_between") //??? what is the difference between minutes_between and default_snooze ???
|
||||
public int minutes_between; // The idea here was if ignored it will go off again each x minutes, snooze would be if it was aknowledged and dismissed it will go off again in y minutes
|
||||
// that said, Im okay with doing away with the minutes between and just doing it at a set 5 mins like dex
|
||||
|
||||
@Expose
|
||||
@Column(name = "default_snooze")
|
||||
public int default_snooze;
|
||||
|
||||
@Expose
|
||||
@Column(name = "text") // ??? what's that? is it different from name?
|
||||
public String text; // I figured if we wanted some special text, Its
|
||||
|
||||
@Expose
|
||||
@Column(name = "mp3_file")
|
||||
public String mp3_file;
|
||||
|
||||
@Expose
|
||||
@Column(name = "uuid", index = true)
|
||||
public String uuid;
|
||||
|
||||
public final static String LOW_ALERT_55 = "c5f1999c-4ec5-449e-adad-3980b172b920";
|
||||
private final static String TAG = Notifications.class.getSimpleName();
|
||||
private final static String TAG_ALERT = "AlertBg";
|
||||
private static boolean patched = false;
|
||||
|
||||
// This shouldn't be needed but it seems it is
|
||||
public static void fixUpTable() {
|
||||
if (patched) return;
|
||||
String[] patchup = {
|
||||
"ALTER TABLE AlertType ADD COLUMN volume INTEGER;",
|
||||
"ALTER TABLE AlertType ADD COLUMN light INTEGER;",
|
||||
"ALTER TABLE AlertType ADD COLUMN predictive INTEGER;",
|
||||
"ALTER TABLE AlertType ADD COLUMN text TEXT;",
|
||||
"ALTER TABLE AlertType ADD COLUMN force_speaker INTEGER;",
|
||||
"ALTER TABLE AlertType ADD COLUMN time_until_threshold_crossed REAL;"
|
||||
};
|
||||
|
||||
for (String patch : patchup) {
|
||||
try {
|
||||
SQLiteUtils.execSql(patch);
|
||||
Log.e(TAG, "Processed patch should not have succeeded!!: " + patch);
|
||||
} catch (Exception e) {
|
||||
// Log.d(TAG, "Patch: " + patch + " generated exception as it should: " + e.toString());
|
||||
}
|
||||
}
|
||||
patched = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static AlertType get_alert(String uuid) {
|
||||
|
||||
return new Select()
|
||||
.from(AlertType.class)
|
||||
.where("uuid = ? ", uuid)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
/*
|
||||
* This function has 3 needs. In the case of "unclear state" return null
|
||||
* In the case of "unclear state" for more than predefined time, return the "55" alert
|
||||
* In case that alerts are turned off, only return the 55.
|
||||
*/
|
||||
public static AlertType get_highest_active_alert(Context context, double bg) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if(prefs.getLong("alerts_disabled_until", 0) > new Date().getTime()){
|
||||
Log.d("NOTIFICATIONS", "Notifications are currently disabled!!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bg <= 14) { // Special dexcom codes should not set off low alarms
|
||||
return null;
|
||||
}
|
||||
|
||||
AlertType at;
|
||||
at = get_highest_active_alert_helper(bg, prefs);
|
||||
if (at != null) {
|
||||
Log.d(TAG_ALERT, "get_highest_active_alert_helper returned alert uuid = " + at.uuid + " alert name = " + at.name);
|
||||
} else {
|
||||
Log.d(TAG_ALERT, "get_highest_active_alert_helper returned NULL");
|
||||
}
|
||||
return at;
|
||||
}
|
||||
|
||||
private static AlertType filter_alert_on_stale(AlertType alert, SharedPreferences prefs)
|
||||
{
|
||||
// this should already be happening in notifications.java but it doesn't seem to work so adding here as well
|
||||
if (prefs.getBoolean("disable_alerts_stale_data", false)) {
|
||||
final int stale_minutes = Math.max(6, Integer.parseInt(prefs.getString("disable_alerts_stale_data_minutes", "15")) + 2);
|
||||
if (!BgReading.last_within_minutes(stale_minutes)) {
|
||||
Log.w(TAG, "Blocking alarm raise as data older than: " + stale_minutes);
|
||||
return null; // block
|
||||
}
|
||||
}
|
||||
return alert; // allow
|
||||
}
|
||||
|
||||
// bg_minute is the estimatin of the bg change rate
|
||||
private static AlertType get_highest_active_alert_helper(double bg, SharedPreferences prefs) {
|
||||
// Chcek the low alerts
|
||||
|
||||
final double offset = ActivityRecognizedService.raise_limit_due_to_vehicle_mode() ? ActivityRecognizedService.getVehicle_mode_adjust_mgdl() : 0;
|
||||
|
||||
if(prefs.getLong("low_alerts_disabled_until", 0) > new Date().getTime()){
|
||||
Log.i("NOTIFICATIONS", "get_highest_active_alert_helper: Low alerts are currently disabled!! Skipping low alerts");
|
||||
|
||||
} else {
|
||||
List<AlertType> lowAlerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.where("threshold >= ?", bg-offset)
|
||||
.where("above = ?", false)
|
||||
.orderBy("threshold asc")
|
||||
.execute();
|
||||
|
||||
for (AlertType lowAlert : lowAlerts) {
|
||||
if(lowAlert.should_alarm(bg-offset)) {
|
||||
return filter_alert_on_stale(lowAlert,prefs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no low alert found or low alerts disabled, check higher alert.
|
||||
if(prefs.getLong("high_alerts_disabled_until", 0) > new Date().getTime()){
|
||||
Log.i("NOTIFICATIONS", "get_highest_active_alert_helper: High alerts are currently disabled!! Skipping high alerts");
|
||||
;
|
||||
} else {
|
||||
List<AlertType> HighAlerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.where("threshold <= ?", bg)
|
||||
.where("above = ?", true)
|
||||
.orderBy("threshold desc")
|
||||
.execute();
|
||||
|
||||
for (AlertType HighAlert : HighAlerts) {
|
||||
//Log.e(TAG, "Testing high alert " + HighAlert.toString());
|
||||
if(HighAlert.should_alarm(bg)) {
|
||||
return filter_alert_on_stale(HighAlert,prefs);
|
||||
}
|
||||
}
|
||||
}
|
||||
// no alert found
|
||||
return null;
|
||||
}
|
||||
|
||||
// returns true, if one allert is up and the second is down
|
||||
public static boolean OpositeDirection(AlertType a1, AlertType a2) {
|
||||
if (a1.above != a2.above) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Checks if a1 is more important than a2. returns the higher one
|
||||
public static AlertType HigherAlert(AlertType a1, AlertType a2) {
|
||||
if (a1.above && !a2.above) {
|
||||
return a2;
|
||||
}
|
||||
if (!a1.above && a2.above) {
|
||||
return a1;
|
||||
}
|
||||
if (a1.above && a2.above) {
|
||||
// both are high, the higher the better
|
||||
if (a1.threshold > a2.threshold) {
|
||||
return a1;
|
||||
} else {
|
||||
return a2;
|
||||
}
|
||||
}
|
||||
if (a1.above || a2.above) {
|
||||
Log.wtf(TAG, "a1.above and a2.above must be false");
|
||||
}
|
||||
// both are low, the lower the better
|
||||
if (a1.threshold < a2.threshold) {
|
||||
return a1;
|
||||
} else {
|
||||
return a2;
|
||||
}
|
||||
}
|
||||
|
||||
public static void remove_all() {
|
||||
List<AlertType> Alerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.execute();
|
||||
|
||||
for (AlertType alert : Alerts) {
|
||||
alert.delete();
|
||||
}
|
||||
ActiveBgAlert.ClearData();
|
||||
}
|
||||
|
||||
public static void add_alert(
|
||||
String uuid,
|
||||
String name,
|
||||
boolean above,
|
||||
double threshold,
|
||||
boolean all_day,
|
||||
int minutes_between,
|
||||
String mp3_file,
|
||||
int start_time_minutes,
|
||||
int end_time_minutes,
|
||||
boolean override_silent_mode,
|
||||
boolean force_speaker,
|
||||
int snooze,
|
||||
boolean vibrate,
|
||||
boolean active) {
|
||||
AlertType at = new AlertType();
|
||||
at.name = name;
|
||||
at.above = above;
|
||||
at.threshold = threshold;
|
||||
at.all_day = all_day;
|
||||
at.minutes_between = minutes_between;
|
||||
at.uuid = uuid != null? uuid : UUID.randomUUID().toString();
|
||||
at.active = active;
|
||||
at.mp3_file = mp3_file;
|
||||
at.start_time_minutes = start_time_minutes;
|
||||
at.end_time_minutes = end_time_minutes;
|
||||
at.override_silent_mode = override_silent_mode;
|
||||
at.force_speaker = force_speaker;
|
||||
at.default_snooze = snooze;
|
||||
at.vibrate = vibrate;
|
||||
at.save();
|
||||
}
|
||||
|
||||
public static void update_alert(
|
||||
String uuid,
|
||||
String name,
|
||||
boolean above,
|
||||
double threshold,
|
||||
boolean all_day,
|
||||
int minutes_between,
|
||||
String mp3_file,
|
||||
int start_time_minutes,
|
||||
int end_time_minutes,
|
||||
boolean override_silent_mode,
|
||||
boolean force_speaker,
|
||||
int snooze,
|
||||
boolean vibrate,
|
||||
boolean active) {
|
||||
|
||||
fixUpTable();
|
||||
|
||||
final AlertType at = get_alert(uuid);
|
||||
if (at == null) {
|
||||
Log.e(TAG, "Alert Type null during update");
|
||||
return;
|
||||
}
|
||||
at.name = name;
|
||||
at.above = above;
|
||||
at.threshold = threshold;
|
||||
at.all_day = all_day;
|
||||
at.minutes_between = minutes_between;
|
||||
at.uuid = uuid;
|
||||
at.active = active;
|
||||
at.mp3_file = mp3_file;
|
||||
at.start_time_minutes = start_time_minutes;
|
||||
at.end_time_minutes = end_time_minutes;
|
||||
at.override_silent_mode = override_silent_mode;
|
||||
at.force_speaker = force_speaker;
|
||||
at.default_snooze = snooze;
|
||||
at.vibrate = vibrate;
|
||||
at.save();
|
||||
}
|
||||
public static void remove_alert(String uuid) {
|
||||
AlertType alert = get_alert(uuid);
|
||||
if(alert != null) {
|
||||
alert.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
String name = "name: " + this.name;
|
||||
String above = "above: " + this.above;
|
||||
String threshold = "threshold: " + this.threshold;
|
||||
String all_day = "all_day: " + this.all_day;
|
||||
String time = "Start time: " + this.start_time_minutes + " end time: "+ this.end_time_minutes;
|
||||
String minutes_between = "minutes_between: " + this.minutes_between;
|
||||
String uuid = "uuid: " + this.uuid;
|
||||
|
||||
return name + " " + above + " " + threshold + " "+ all_day + " " +time +" " + minutes_between + " uuid" + uuid;
|
||||
}
|
||||
|
||||
public String toS() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
.registerTypeAdapter(Date.class, new DateTypeAdapter())
|
||||
.serializeSpecialFloatingPointValues()
|
||||
.create();
|
||||
return gson.toJson(this);
|
||||
}
|
||||
|
||||
public static void print_all() {
|
||||
List<AlertType> Alerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.execute();
|
||||
|
||||
Log.d(TAG,"List of all alerts");
|
||||
for (AlertType alert : Alerts) {
|
||||
Log.d(TAG, alert.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// get the first item in the alert list which is active for either high or low alert, sorted by when the threshold will be hit, eg the highest low alert or the lowest high alert
|
||||
public static double getFirstActiveAlertThreshold(final boolean highAlert) {
|
||||
final List<AlertType> list = getAll(highAlert);
|
||||
if (list != null) {
|
||||
for (final AlertType alert : list) {
|
||||
if (alert.active) return alert.threshold;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static List<AlertType> getAllActive() {
|
||||
List<AlertType> alerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.where("active = ?", true)
|
||||
.execute();
|
||||
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public static List<AlertType> getAll(boolean above) {
|
||||
String order;
|
||||
if (above) {
|
||||
order = "threshold asc";
|
||||
} else {
|
||||
order = "threshold desc";
|
||||
}
|
||||
List<AlertType> alerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.where("above = ?", above)
|
||||
.orderBy(order)
|
||||
.execute();
|
||||
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public static boolean activeLowAlertExists() {
|
||||
List<AlertType> alerts = getAll(false);
|
||||
if(alerts == null) {
|
||||
return false;
|
||||
}
|
||||
for (AlertType alert : alerts) {
|
||||
if(alert.active) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// This function is used to make sure that we always have a static alert on 55 low.
|
||||
// This alert will not be editable/removable.
|
||||
public static void CreateStaticAlerts() {
|
||||
if(get_alert(LOW_ALERT_55) == null) {
|
||||
add_alert(LOW_ALERT_55, "low alert ", false, 55, true, 1, null, 0, 0, true, true, 20, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void testAll(Context context) {
|
||||
remove_all();
|
||||
add_alert(null, "high alert 1", true, 180, true, 10, null, 0, 0, true, true, 20, true, true);
|
||||
add_alert(null, "high alert 2", true, 200, true, 10, null, 0, 0, true, true,20, true, true);
|
||||
add_alert(null, "high alert 3", true, 220, true, 10, null, 0, 0, true, true,20, true, true);
|
||||
print_all();
|
||||
AlertType a1 = get_highest_active_alert(context, 190);
|
||||
Log.d(TAG, "a1 = " + a1.toString());
|
||||
AlertType a2 = get_highest_active_alert(context, 210);
|
||||
Log.d(TAG, "a2 = " + a2.toString());
|
||||
|
||||
|
||||
AlertType a3 = get_alert(a1.uuid);
|
||||
Log.d(TAG, "a1 == a3 ? need to see true " + (a1==a3) + a1 + " " + a3);
|
||||
|
||||
add_alert(null, "low alert 1", false, 80, true, 10, null, 0, 0, true, true,20, true, true);
|
||||
add_alert(null, "low alert 2", false, 60, true, 10, null, 0, 0, true, true,20, true, true);
|
||||
|
||||
AlertType al1 = get_highest_active_alert(context, 90);
|
||||
Log.d(TAG, "al1 should be null " + al1);
|
||||
al1 = get_highest_active_alert(context, 80);
|
||||
Log.d(TAG, "al1 = " + al1.toString());
|
||||
AlertType al2 = get_highest_active_alert(context, 50);
|
||||
Log.d(TAG, "al2 = " + al2.toString());
|
||||
|
||||
Log.d(TAG, "HigherAlert(a1, a2) = a1?" + (HigherAlert(a1,a2) == a2));
|
||||
Log.d(TAG, "HigherAlert(al1, al2) = al1?" + (HigherAlert(al1,al2) == al2));
|
||||
Log.d(TAG, "HigherAlert(a1, al1) = al1?" + (HigherAlert(a1,al1) == al1));
|
||||
Log.d(TAG, "HigherAlert(al1, a2) = al1?" + (HigherAlert(al1,a2) == al1));
|
||||
|
||||
// Make sure we do not influance on real data...
|
||||
remove_all();
|
||||
|
||||
}
|
||||
|
||||
|
||||
private boolean in_time_frame() {
|
||||
return s_in_time_frame(all_day, start_time_minutes, end_time_minutes);
|
||||
}
|
||||
|
||||
static public boolean s_in_time_frame(boolean s_all_day, int s_start_time_minutes, int s_end_time_minutes) {
|
||||
if (s_all_day) {
|
||||
//Log.e(TAG, "in_time_frame returning true " );
|
||||
return true;
|
||||
}
|
||||
// time_now is the number of minutes that have passed from the start of the day.
|
||||
Calendar rightNow = Calendar.getInstance();
|
||||
int time_now = toTime(rightNow.get(Calendar.HOUR_OF_DAY), rightNow.get(Calendar.MINUTE));
|
||||
Log.d(TAG, "time_now is " + time_now + " minutes" + " start_time " + s_start_time_minutes + " end_time " + s_end_time_minutes);
|
||||
if(s_start_time_minutes < s_end_time_minutes) {
|
||||
if (time_now >= s_start_time_minutes && time_now <= s_end_time_minutes) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (time_now >= s_start_time_minutes || time_now <= s_end_time_minutes) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean beyond_threshold(double bg) {
|
||||
if (above && bg >= threshold) {
|
||||
// Log.e(TAG, "beyond_threshold returning true " );
|
||||
return true;
|
||||
} else if (!above && bg <= threshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean trending_to_threshold(double bg) {
|
||||
if (!predictive) { return false; }
|
||||
if (above && bg >= threshold) {
|
||||
return true;
|
||||
} else if (!above && bg <= threshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getNextAlertTime(Context ctx) {
|
||||
int time = minutes_between;
|
||||
if (time < 1 || AlertPlayer.isAscendingMode(ctx)) {
|
||||
time = 1;
|
||||
}
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return calendar.getTimeInMillis() + (time * 60000);
|
||||
}
|
||||
|
||||
public boolean should_alarm(double bg) {
|
||||
// Log.e(TAG, "should_alarm called active = " + active );
|
||||
if(in_time_frame() && active && (beyond_threshold(bg) || trending_to_threshold(bg))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void testAlert(
|
||||
String name,
|
||||
boolean above,
|
||||
double threshold,
|
||||
boolean all_day,
|
||||
int minutes_between,
|
||||
String mp3_file,
|
||||
int start_time_minutes,
|
||||
int end_time_minutes,
|
||||
boolean override_silent_mode,
|
||||
boolean force_speaker,
|
||||
int snooze,
|
||||
boolean vibrate,
|
||||
Context context) {
|
||||
AlertType at = new AlertType();
|
||||
at.name = name;
|
||||
at.above = above;
|
||||
at.threshold = threshold;
|
||||
at.all_day = all_day;
|
||||
at.minutes_between = minutes_between;
|
||||
at.uuid = UUID.randomUUID().toString();
|
||||
at.active = true;
|
||||
at.mp3_file = mp3_file;
|
||||
at.start_time_minutes = start_time_minutes;
|
||||
at.end_time_minutes = end_time_minutes;
|
||||
at.override_silent_mode = override_silent_mode;
|
||||
at.force_speaker = force_speaker;
|
||||
at.default_snooze = snooze;
|
||||
at.vibrate = vibrate;
|
||||
AlertPlayer.getPlayer().startAlert(context, false, at, "TEST", false);
|
||||
}
|
||||
|
||||
// Time is calculated in minutes. that is 01:20 means 80 minutes.
|
||||
|
||||
// This functions are a bit tricky. We can only set time from 00:00 to 23:59 which leaves one minute out. this is because we ignore the
|
||||
// seconds. so if the user has set 23:59 we will consider this as 24:00
|
||||
// This will be done at the code that reads the time from the ui.
|
||||
|
||||
|
||||
|
||||
// return the minutes part of the time
|
||||
public static int time2Minutes(int minutes) {
|
||||
return (minutes - 60*time2Hours(minutes)) ;
|
||||
}
|
||||
|
||||
// return the hours part of the time
|
||||
public static int time2Hours(int minutes) {
|
||||
return minutes / 60;
|
||||
}
|
||||
|
||||
// create the time from hours and minutes.
|
||||
public static int toTime(int hours, int minutes) {
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
// Convert all settings to a string and save it in the references. This is needed to allow it's backup.
|
||||
public static boolean toSettings(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
List<AlertType> alerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.execute();
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
.registerTypeAdapter(Date.class, new DateTypeAdapter())
|
||||
.serializeSpecialFloatingPointValues()
|
||||
.create();
|
||||
String output = gson.toJson(alerts);
|
||||
Log.e(TAG, "Created the string " + output);
|
||||
prefs.edit().putString("saved_alerts", output).commit(); // always leave this as commit
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Read all alerts from preference key and write them to db.
|
||||
public static boolean fromSettings(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String savedAlerts = prefs.getString("saved_alerts", "");
|
||||
if (savedAlerts.isEmpty()) {
|
||||
Log.i(TAG, "read saved_alerts string and it is empty");
|
||||
return true;
|
||||
}
|
||||
Log.i(TAG, "read alerts string " + savedAlerts);
|
||||
|
||||
AlertType[] newAlerts = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().fromJson(savedAlerts, AlertType[].class);
|
||||
if (newAlerts == null) {
|
||||
Log.e(TAG, "newAlerts is null");
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.i(TAG, "read successfuly " + newAlerts.length);
|
||||
// Now delete all existing alerts if we managed to unpack the json
|
||||
try {
|
||||
List<AlertType> alerts = new Select()
|
||||
.from(AlertType.class)
|
||||
.execute();
|
||||
for (AlertType alert : alerts) {
|
||||
alert.delete();
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(TAG, "Got null pointer exception: " + e);
|
||||
}
|
||||
|
||||
try {
|
||||
for (AlertType alert : newAlerts) {
|
||||
Log.e(TAG, "Saving alert " + alert.name);
|
||||
alert.save();
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(TAG, "Got null pointer exception 2: " + e);
|
||||
}
|
||||
// Delete the string, so next time we will not load the data
|
||||
prefs.edit().putString("saved_alerts", "").apply();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
2334
lib/nightscout/com/eveningoutpost/dexdrip/Models/BgReading.java
Normal file
2334
lib/nightscout/com/eveningoutpost/dexdrip/Models/BgReading.java
Normal file
File diff suppressed because it is too large
Load Diff
577
lib/nightscout/com/eveningoutpost/dexdrip/Models/BloodTest.java
Normal file
577
lib/nightscout/com/eveningoutpost/dexdrip/Models/BloodTest.java
Normal file
@@ -0,0 +1,577 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import com.activeandroid.Model;
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
import com.eveningoutpost.dexdrip.AddCalibration;
|
||||
import com.eveningoutpost.dexdrip.GlucoseMeter.GlucoseReadingRx;
|
||||
import com.eveningoutpost.dexdrip.Home;
|
||||
import com.eveningoutpost.dexdrip.Services.SyncService;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.BgGraphBuilder;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.UploaderQueue;
|
||||
import com.eveningoutpost.dexdrip.calibrations.CalibrationAbstract;
|
||||
import com.eveningoutpost.dexdrip.calibrations.NativeCalibrationPipe;
|
||||
import com.eveningoutpost.dexdrip.calibrations.PluggableCalibration;
|
||||
import com.eveningoutpost.dexdrip.messages.BloodTestMessage;
|
||||
import com.eveningoutpost.dexdrip.messages.BloodTestMultiMessage;
|
||||
import com.eveningoutpost.dexdrip.xdrip;
|
||||
import com.google.common.math.DoubleMath;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.squareup.wire.Wire;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
173
lib/nightscout/com/eveningoutpost/dexdrip/Models/Bubble.java
Executable file
173
lib/nightscout/com/eveningoutpost/dexdrip/Models/Bubble.java
Executable file
@@ -0,0 +1,173 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.util.HexDump;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
import com.eveningoutpost.dexdrip.NFCReaderX;
|
||||
import com.eveningoutpost.dexdrip.R;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.BridgeResponse;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.LibreUtils;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.xdrip.gs;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
public class Bubble {
|
||||
private static final String TAG = "Bubble";//?????"Bubble";
|
||||
|
||||
private static volatile byte[] s_full_data = null;
|
||||
private static volatile int s_acumulatedSize = 0;
|
||||
|
||||
public static boolean isBubble() {
|
||||
final ActiveBluetoothDevice activeBluetoothDevice = ActiveBluetoothDevice.first();
|
||||
if (activeBluetoothDevice == null || activeBluetoothDevice.name == null) {
|
||||
return false;
|
||||
}
|
||||
return activeBluetoothDevice.name.contentEquals("Bubble");
|
||||
}
|
||||
|
||||
|
||||
public static BridgeResponse getBubbleResponse() {
|
||||
final BridgeResponse reply = new BridgeResponse();
|
||||
ByteBuffer ackMessage = ByteBuffer.allocate(6);
|
||||
ackMessage.put(0, (byte) 0x02);
|
||||
ackMessage.put(1, (byte) 0x01);
|
||||
ackMessage.put(2, (byte) 0x00);
|
||||
ackMessage.put(3, (byte) 0x00);
|
||||
ackMessage.put(4, (byte) 0x00);
|
||||
ackMessage.put(5, (byte) 0x2B);
|
||||
reply.add(ackMessage);
|
||||
return reply;
|
||||
}
|
||||
|
||||
public static int lens = 344;
|
||||
public static int BUBBLE_FOOTER = 8;
|
||||
|
||||
static int errorCount = 0;
|
||||
|
||||
|
||||
static byte[] patchUid = null;
|
||||
static byte[] patchInfo = null;
|
||||
|
||||
public static BridgeResponse decodeBubblePacket(byte[] buffer, int len) {
|
||||
final BridgeResponse reply = new BridgeResponse();
|
||||
int first = 0xff & buffer[0];
|
||||
if (first == 0x80) {
|
||||
PersistentStore.setString("Bubblebattery", Integer.toString(buffer[4]));
|
||||
Pref.setInt("bridge_battery", buffer[4]);
|
||||
String bubblefirmware = buffer[2] + "." + buffer[3];
|
||||
String bubbleHArdware = buffer[buffer.length-2] + "." + buffer[buffer.length-1];
|
||||
PersistentStore.setString("BubbleHArdware", bubbleHArdware);
|
||||
PersistentStore.setString("BubbleFirmware", bubblefirmware);
|
||||
ByteBuffer ackMessage = ByteBuffer.allocate(6);
|
||||
ackMessage.put(0, (byte) 0x02);
|
||||
ackMessage.put(1, (byte) 0x01);
|
||||
ackMessage.put(2, (byte) 0x00);
|
||||
ackMessage.put(3, (byte) 0x00);
|
||||
ackMessage.put(4, (byte) 0x00);
|
||||
ackMessage.put(5, (byte) 0x2B);
|
||||
reply.add(ackMessage);
|
||||
s_full_data = null;
|
||||
return getBubbleResponse();
|
||||
}
|
||||
if (first == 0xC0) {
|
||||
patchUid = Arrays.copyOfRange(buffer, 2, 10);
|
||||
String SensorSn = LibreUtils.decodeSerialNumberKey(patchUid);
|
||||
PersistentStore.setString("LibreSN", SensorSn);
|
||||
return reply;
|
||||
}
|
||||
if (first == 0xC1) {
|
||||
double fv = JoH.tolerantParseDouble(PersistentStore.getString("BubbleFirmware"));
|
||||
if (fv < 1.35) {
|
||||
patchInfo = Arrays.copyOfRange(buffer, 3, 9);
|
||||
} else {
|
||||
if (buffer.length >= 11) {
|
||||
patchInfo = Arrays.copyOfRange(buffer, 5, 11);
|
||||
}
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
if (first == 0x82) {
|
||||
int expectedSize = lens + BUBBLE_FOOTER;
|
||||
if (s_full_data == null) {
|
||||
InitBuffer(expectedSize);
|
||||
}
|
||||
addData(buffer);
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
if (first == 0xBF) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
Log.e(TAG, "No sensor has been found");
|
||||
reply.setError_message(gs(R.string.no_sensor_found));
|
||||
s_full_data = null;
|
||||
errorCount++;
|
||||
if (errorCount <= 2) {
|
||||
return getBubbleResponse();
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
static void addData(byte[] buffer) {
|
||||
System.arraycopy(buffer, 4, s_full_data, s_acumulatedSize, buffer.length-4);
|
||||
s_acumulatedSize = s_acumulatedSize + buffer.length - 4;
|
||||
AreWeDone();
|
||||
}
|
||||
|
||||
|
||||
static void AreWeDone() {
|
||||
if (s_acumulatedSize < lens) {
|
||||
return;
|
||||
}
|
||||
long now = JoH.tsl();
|
||||
String SensorSn = PersistentStore.getString("LibreSN");
|
||||
|
||||
|
||||
byte[] data = Arrays.copyOfRange(s_full_data, 0, 344);
|
||||
|
||||
boolean checksum_ok = NFCReaderX.HandleGoodReading(SensorSn, data, now, true, patchUid, patchInfo);
|
||||
int expectedSize = lens + BUBBLE_FOOTER;
|
||||
InitBuffer(expectedSize);
|
||||
errorCount = 0;
|
||||
Log.e(TAG, "We have all the data that we need " + s_acumulatedSize + " checksum_ok = " + checksum_ok + HexDump.dumpHexString(data));
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void InitBuffer(int expectedSize) {
|
||||
s_full_data = new byte[expectedSize];
|
||||
s_acumulatedSize = 0;
|
||||
}
|
||||
|
||||
public static ArrayList<ByteBuffer> initialize() {
|
||||
Log.e(TAG, "initialize!");
|
||||
Pref.setInt("bridge_battery", 0); //force battery to no-value before first reading
|
||||
return resetBubbleState();
|
||||
}
|
||||
|
||||
private static ArrayList<ByteBuffer> resetBubbleState() {
|
||||
ArrayList<ByteBuffer> ret = new ArrayList<>();
|
||||
|
||||
// Make Bubble send data every 5 minutes
|
||||
ByteBuffer ackMessage = ByteBuffer.allocate(3);
|
||||
ackMessage.put(0, (byte) 0x00);
|
||||
ackMessage.put(1, (byte) 0x01);
|
||||
ackMessage.put(2, (byte) 0x05);
|
||||
ret.add(ackMessage);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
1414
lib/nightscout/com/eveningoutpost/dexdrip/Models/Calibration.java
Normal file
1414
lib/nightscout/com/eveningoutpost/dexdrip/Models/Calibration.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
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.eveningoutpost.dexdrip.Home;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Emma Black on 12/9/14.
|
||||
*/
|
||||
|
||||
@Table(name = "CalibrationRequest", id = BaseColumns._ID)
|
||||
public class CalibrationRequest extends Model {
|
||||
private static final int max = 250;
|
||||
private static final int min = 70;
|
||||
private static final String TAG = CalibrationRequest.class.getSimpleName();
|
||||
|
||||
@Column(name = "requestIfAbove")
|
||||
public double requestIfAbove;
|
||||
|
||||
@Column(name = "requestIfBelow")
|
||||
public double requestIfBelow;
|
||||
|
||||
public static void createRange(double low, double high) {
|
||||
CalibrationRequest calibrationRequest = new CalibrationRequest();
|
||||
calibrationRequest.requestIfAbove = low;
|
||||
calibrationRequest.requestIfBelow = high;
|
||||
calibrationRequest.save();
|
||||
}
|
||||
static void createOffset(double center, double distance) {
|
||||
CalibrationRequest calibrationRequest = new CalibrationRequest();
|
||||
calibrationRequest.requestIfAbove = center + distance;
|
||||
calibrationRequest.requestIfBelow = max;
|
||||
calibrationRequest.save();
|
||||
|
||||
calibrationRequest = new CalibrationRequest();
|
||||
calibrationRequest.requestIfAbove = min;
|
||||
calibrationRequest.requestIfBelow = center - distance;
|
||||
calibrationRequest.save();
|
||||
}
|
||||
|
||||
static void clearAll(){
|
||||
List<CalibrationRequest> calibrationRequests = new Select()
|
||||
.from(CalibrationRequest.class)
|
||||
.execute();
|
||||
if (calibrationRequests.size() >=1) {
|
||||
for (CalibrationRequest calibrationRequest : calibrationRequests) {
|
||||
calibrationRequest.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean shouldRequestCalibration(BgReading bgReading) {
|
||||
CalibrationRequest calibrationRequest = new Select()
|
||||
.from(CalibrationRequest.class)
|
||||
.where("requestIfAbove < ?", bgReading.calculated_value)
|
||||
.where("requestIfBelow > ?", bgReading.calculated_value)
|
||||
.executeSingle();
|
||||
return (calibrationRequest != null && isSlopeFlatEnough(bgReading, 1));
|
||||
}
|
||||
|
||||
public static boolean isSlopeFlatEnough() {
|
||||
BgReading bgReading = BgReading.last(true);
|
||||
if (bgReading == null) return false;
|
||||
if (JoH.msSince(bgReading.timestamp) > Home.stale_data_millis()) {
|
||||
UserError.Log.d(TAG, "Slope cannot be flat enough as data is stale");
|
||||
return false;
|
||||
}
|
||||
// TODO check if stale, check previous slope also, check that reading parameters also
|
||||
return isSlopeFlatEnough(bgReading);
|
||||
}
|
||||
|
||||
public static boolean isSlopeFlatEnough(BgReading bgReading) {
|
||||
return isSlopeFlatEnough(bgReading, 1);
|
||||
}
|
||||
|
||||
public static boolean isSlopeFlatEnough(BgReading bgReading, double limit) {
|
||||
if (bgReading == null) return false;
|
||||
// TODO use BestGlucose
|
||||
return Math.abs(bgReading.calculated_value_slope * 60000) < limit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 04/01/16.
|
||||
*/
|
||||
public class CobCalc {
|
||||
double initialCarbs;
|
||||
double decayedBy;
|
||||
double isDecaying;
|
||||
double carbTime;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
// from package info.nightscout.client.utils;
|
||||
|
||||
/**
|
||||
* Created by mike on 30.12.2015.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Class DateUtil. A simple wrapper around SimpleDateFormat to ease the handling of iso date string <-> date obj
|
||||
* with TZ
|
||||
*/
|
||||
public class DateUtil {
|
||||
|
||||
private static final String FORMAT_DATE_ISO = "yyyy-MM-dd'T'HH:mm:ss'Z'"; // eg 2017-03-24T22:03:27Z
|
||||
private static final String FORMAT_DATE_ISO2 = "yyyy-MM-dd'T'HH:mm:ssZ"; // eg 2017-03-27T17:38:14+0300
|
||||
private static final String FORMAT_DATE_ISO3 = "yyyy-MM-dd'T'HH:mmZ"; // eg 2017-05-12T08:16-0400
|
||||
|
||||
/**
|
||||
* Takes in an ISO date string of the following format:
|
||||
* yyyy-mm-ddThh:mm:ss.ms+HoMo
|
||||
*
|
||||
* @param isoDateString the iso date string
|
||||
* @return the date
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
private static Date fromISODateString(String isoDateString)
|
||||
throws Exception {
|
||||
SimpleDateFormat f = new SimpleDateFormat(FORMAT_DATE_ISO);
|
||||
f.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return f.parse(isoDateString);
|
||||
}
|
||||
|
||||
private static Date fromISODateString3(String isoDateString)
|
||||
throws Exception {
|
||||
SimpleDateFormat f = new SimpleDateFormat(FORMAT_DATE_ISO3);
|
||||
f.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return f.parse(isoDateString);
|
||||
}
|
||||
|
||||
private static Date fromISODateString2(String isoDateString)
|
||||
throws Exception {
|
||||
try {
|
||||
SimpleDateFormat f = new SimpleDateFormat(FORMAT_DATE_ISO2);
|
||||
f.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
return f.parse(isoDateString);
|
||||
} catch (java.text.ParseException e) {
|
||||
return fromISODateString3(isoDateString);
|
||||
}
|
||||
}
|
||||
|
||||
public static Date tolerantFromISODateString(String isoDateString)
|
||||
throws Exception {
|
||||
try {
|
||||
return fromISODateString(isoDateString.replaceFirst("\\.[0-9][0-9][0-9]Z$", "Z"));
|
||||
} catch (java.text.ParseException e) {
|
||||
return fromISODateString2(isoDateString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render date
|
||||
*
|
||||
* @param date the date obj
|
||||
* @param format - if not specified, will use FORMAT_DATE_ISO
|
||||
* @param tz - tz to set to, if not specified uses local timezone
|
||||
* @return the iso-formatted date string
|
||||
*/
|
||||
public static String toISOString(Date date, String format, TimeZone tz) {
|
||||
if (format == null) format = FORMAT_DATE_ISO;
|
||||
if (tz == null) tz = TimeZone.getDefault();
|
||||
DateFormat f = new SimpleDateFormat(format);
|
||||
f.setTimeZone(tz);
|
||||
return f.format(date);
|
||||
}
|
||||
|
||||
public static String toISOString(Date date) {
|
||||
return toISOString(date, FORMAT_DATE_ISO, TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public static String toISOString(long date) {
|
||||
return toISOString(new Date(date), FORMAT_DATE_ISO, TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public static String toNightscoutFormat(long date) {
|
||||
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
|
||||
format.setTimeZone(TimeZone.getDefault());
|
||||
return format.format(date);
|
||||
}
|
||||
}
|
||||
574
lib/nightscout/com/eveningoutpost/dexdrip/Models/DesertSync.java
Normal file
574
lib/nightscout/com/eveningoutpost/dexdrip/Models/DesertSync.java
Normal file
@@ -0,0 +1,574 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.eveningoutpost.dexdrip.GcmActivity;
|
||||
import com.eveningoutpost.dexdrip.GcmListenerSvc;
|
||||
import com.eveningoutpost.dexdrip.Home;
|
||||
import com.eveningoutpost.dexdrip.JamListenerSvc;
|
||||
import com.eveningoutpost.dexdrip.R;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Inevitable;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.StatusItem;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.desertsync.DesertComms;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.desertsync.RouteTools;
|
||||
import com.eveningoutpost.dexdrip.utils.CipherUtils;
|
||||
import com.eveningoutpost.dexdrip.webservices.XdripWebService;
|
||||
import com.eveningoutpost.dexdrip.xdrip;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import static com.eveningoutpost.dexdrip.GoogleDriveInterface.getDriveIdentityString;
|
||||
import static com.eveningoutpost.dexdrip.Models.JoH.emptyString;
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.desertsync.RouteTools.getBestInterfaceAddress;
|
||||
import static com.eveningoutpost.dexdrip.UtilityModels.desertsync.RouteTools.ip;
|
||||
|
||||
// created by jamorham 18/08/2018
|
||||
|
||||
// not to be confused with dessert sync, yum!
|
||||
|
||||
|
||||
@NoArgsConstructor
|
||||
@Table(name = "DesertSync", id = BaseColumns._ID)
|
||||
public class DesertSync extends PlusModel {
|
||||
|
||||
private static boolean patched = false;
|
||||
private static final String TAG = DesertSync.class.getSimpleName();
|
||||
public static final String NO_DATA_MARKER = "NO DATA";
|
||||
private static final String PREF_SENDER_UUID = "DesertSync-sender-uuid";
|
||||
private static final int MAX_CATCHUP = 20;
|
||||
private static final ReentrantLock sequence_lock = new ReentrantLock();
|
||||
private static final boolean d = false;
|
||||
private static volatile int duplicateIndicator = 0;
|
||||
private static volatile int catchupCounter = 0;
|
||||
private static String static_sender = null;
|
||||
private static RollCall myRollCall = null;
|
||||
private static JamListenerSvc service;
|
||||
private static HashMap<InetAddress, Long> peers;
|
||||
private static int spinner = 0;
|
||||
private static volatile String lastUsedIP = null;
|
||||
|
||||
private static volatile long highestPullTimeStamp = -1;
|
||||
|
||||
private static final String[] schema = {
|
||||
"CREATE TABLE DesertSync (_id INTEGER PRIMARY KEY AUTOINCREMENT);",
|
||||
"ALTER TABLE DesertSync ADD COLUMN timestamp INTEGER;",
|
||||
"ALTER TABLE DesertSync ADD COLUMN topic TEXT;",
|
||||
"ALTER TABLE DesertSync ADD COLUMN sender TEXT;",
|
||||
"ALTER TABLE DesertSync ADD COLUMN payload TEXT;",
|
||||
"ALTER TABLE DesertSync ADD COLUMN processed TEXT;",
|
||||
"CREATE UNIQUE INDEX index_DesertSync_timestamp on DesertSync(timestamp);",
|
||||
"CREATE INDEX index_DesertSync_payload on DesertSync(payload);",
|
||||
"CREATE INDEX index_DesertSync_processed on DesertSync(processed);",
|
||||
"CREATE INDEX index_DesertSync_topic on DesertSync(topic);"};
|
||||
|
||||
private static final int MAX_ITEMS = 50;
|
||||
|
||||
public static final String PREF_WEBSERVICE_SECRET = "xdrip_webservice_secret";
|
||||
|
||||
@Expose
|
||||
@Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE)
|
||||
public long timestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "topic")
|
||||
public String topic;
|
||||
|
||||
@Expose
|
||||
@Column(name = "sender")
|
||||
public String sender;
|
||||
|
||||
@Expose
|
||||
@Column(name = "payload")
|
||||
public String payload;
|
||||
|
||||
@Column(name = "processed")
|
||||
private String processed;
|
||||
|
||||
|
||||
@Builder
|
||||
private DesertSync(final long timestamp, final String topic, final String sender, final String payload, final boolean processedFlag) {
|
||||
this.timestamp = timestamp;
|
||||
this.topic = topic;
|
||||
this.sender = sender;
|
||||
if (processedFlag) {
|
||||
this.processed = payload;
|
||||
} else {
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DesertSync> since(final long position, final String topic) {
|
||||
if (topic == null) {
|
||||
return new Select()
|
||||
.from(DesertSync.class)
|
||||
.where("timestamp > ?", position)
|
||||
.orderBy("timestamp asc")
|
||||
.limit(MAX_ITEMS)
|
||||
.execute();
|
||||
} else {
|
||||
return new Select()
|
||||
.from(DesertSync.class)
|
||||
.where("topic = ?", topic)
|
||||
.where("timestamp > ?", position)
|
||||
.orderBy("timestamp asc")
|
||||
.limit(MAX_ITEMS)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alreadyInDatabase(final boolean processedFlag) {
|
||||
return new Select()
|
||||
.from(DesertSync.class)
|
||||
.where("topic = ?", topic)
|
||||
.where("processed = ?", processedFlag ? processed : processData())
|
||||
.executeSingle() != null;
|
||||
}
|
||||
|
||||
private static DesertSync last() {
|
||||
return new Select()
|
||||
.from(DesertSync.class)
|
||||
.where("topic = ?", getTopic())
|
||||
.orderBy("timestamp desc")
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
|
||||
public String toS() {
|
||||
return JoH.defaultGsonInstance().toJson(this);
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return getPayload(0);
|
||||
}
|
||||
|
||||
public String getPayload() {
|
||||
return getPayload(1);
|
||||
}
|
||||
|
||||
private String processData() {
|
||||
if (processed == null) {
|
||||
processed = CipherUtils.decryptString(payload);
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
|
||||
private String transmissionPayload() {
|
||||
if (payload == null) {
|
||||
payload = CipherUtils.compressEncryptString(processed);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
private String getPayload(int section) {
|
||||
if (processed == null) return "<null>";
|
||||
processData();
|
||||
try {
|
||||
final String[] ps = processed.split("\\^");
|
||||
return ps[section];
|
||||
} catch (Exception e) {
|
||||
return "<invalid payload>";
|
||||
}
|
||||
}
|
||||
|
||||
private RemoteMessage getMessage() {
|
||||
final HashMap<String, String> map = new HashMap<>();
|
||||
map.put("message", "From DesertSync");
|
||||
map.put("xfrom", sender);
|
||||
map.put("yfrom", getYfrom());
|
||||
map.put("datum", getPayload());
|
||||
map.put("action", getAction());
|
||||
return new RemoteMessage.Builder("internal").setData(map).build();
|
||||
}
|
||||
|
||||
// utility methods
|
||||
|
||||
public static String toJson(List<DesertSync> list) {
|
||||
return JoH.defaultGsonInstance().toJson(list);
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return Pref.getBooleanDefaultFalse("desert_sync_enabled");
|
||||
}
|
||||
|
||||
// input / output
|
||||
|
||||
public static void pullAsEnabled() {
|
||||
if (Home.get_follower()) {
|
||||
if (isEnabled()) {
|
||||
// TODO check if no data received? or maybe we don't - should this instead be called from do nothing service??
|
||||
DesertComms.pullFromOasis(getTopic(), getHighestPullTimeStamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized static long getHighestPullTimeStamp() {
|
||||
if (highestPullTimeStamp == -1) {
|
||||
try {
|
||||
highestPullTimeStamp = last().timestamp;
|
||||
} catch (NullPointerException e) {
|
||||
highestPullTimeStamp = 1;
|
||||
}
|
||||
}
|
||||
return highestPullTimeStamp;
|
||||
}
|
||||
|
||||
private static DesertSync createFromBundle(final Bundle data) {
|
||||
final String payload = data.getString("payload", data.getString("datum", ""));
|
||||
if (payload.length() > 0) {
|
||||
return new DesertSync(JoH.tsl(), data.getString("identity", getTopic()), mySender(), data.getString("action") + "^" + payload, true);
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Invalid bundle");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean fromGCM(final Bundle data) {
|
||||
if (isEnabled()) {
|
||||
final DesertSync ds = createFromBundle(data);
|
||||
if (ds != null && !ds.alreadyInDatabase(true)) {
|
||||
DesertComms.pushToOasis(ds.topic, ds.sender, ds.transmissionPayload());
|
||||
ds.save();
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Not pushing entry without payload / duplicate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean fromPush(String topic, String sender, String payload) {
|
||||
if (isEnabled()) {
|
||||
|
||||
UserError.Log.d(TAG, String.format("sender: %s, topic: %s, payload: %s", sender, topic, payload));
|
||||
if (sender == null || sender.length() != 32 || sender.equals(mySender())) return false;
|
||||
if (topic == null || topic.length() != 32) return false;
|
||||
if (payload == null || payload.length() == 0) return false;
|
||||
// TODO VALIDATE PARAMS
|
||||
|
||||
final DesertSync item = new DesertSync(JoH.tsl(), topic, sender, payload, false);
|
||||
processItem(item);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonAtomicOperationOnVolatileField")
|
||||
private static void processItem(final DesertSync item) {
|
||||
if (item != null) {
|
||||
if (item.topic != null && item.topic.equals(getTopic())) {
|
||||
if (!item.alreadyInDatabase(false)) {
|
||||
UserError.Log.d(TAG, "New item: " + item.payload);
|
||||
item.save();
|
||||
new Thread(() -> onMessageReceived(item.getMessage())).start();
|
||||
} else {
|
||||
duplicateIndicator++;
|
||||
UserError.Log.d(TAG, "Duplicate item: " + duplicateIndicator);
|
||||
}
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Invalid topic");
|
||||
}
|
||||
} else {
|
||||
UserError.Log.d(TAG, "processItem NULL");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("NonAtomicOperationOnVolatileField")
|
||||
public static void fromPull(final String json) {
|
||||
if (!json.startsWith(NO_DATA_MARKER)) {
|
||||
try {
|
||||
final List<DesertSync> items = JoH.defaultGsonInstance().fromJson(json, new TypeToken<ArrayList<DesertSync>>() {
|
||||
}.getType());
|
||||
if (items != null) {
|
||||
duplicateIndicator = 0;
|
||||
for (final DesertSync item : items) {
|
||||
if (item.timestamp > highestPullTimeStamp) {
|
||||
highestPullTimeStamp = item.timestamp;
|
||||
Inevitable.task("desert-sync-timestamp", 500, () -> {
|
||||
UserError.Log.d(TAG, "Synced up till: " + JoH.dateTimeText(highestPullTimeStamp));
|
||||
});
|
||||
}
|
||||
processItem(item);
|
||||
}
|
||||
if (items.size() == MAX_ITEMS) {
|
||||
UserError.Log.d(TAG, "Attempting to catch up as all history is duplicates or max size: " + catchupCounter);
|
||||
if (catchupCounter < MAX_CATCHUP) {
|
||||
catchupCounter++;
|
||||
Inevitable.task("Desert catchup", 6000, DesertSync::pullAsEnabled);
|
||||
}
|
||||
} else {
|
||||
catchupCounter = 0;
|
||||
}
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
UserError.Log.e(TAG, "fromPull error: " + e + "\n" + json);
|
||||
}
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Web service reported no data matching our query - either we are synced or other mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
public static void pullFailed(final String host) {
|
||||
UserError.Log.d(TAG, "Pull failed: host: " + host);
|
||||
if (host == null) return;
|
||||
final String hint = RollCall.getBestMasterHintIP();
|
||||
UserError.Log.d(TAG, "Best hint: " + hint);
|
||||
if (hint == null) return;
|
||||
if (host.equals(hint)) {
|
||||
UserError.Log.d(TAG, "Looking for hint but master is still the same: " + hint);
|
||||
final String backupIP = DesertComms.getOasisBackupIP();
|
||||
if (!emptyString(backupIP) && !backupIP.equals(host)) {
|
||||
UserError.Log.d(TAG, "Trying backup: " + backupIP);
|
||||
takeMasterHint(backupIP);
|
||||
}
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Got master hint for: " + hint);
|
||||
takeMasterHint(hint);
|
||||
}
|
||||
}
|
||||
|
||||
private static void takeMasterHint(String hint) {
|
||||
if (RouteTools.reachable(hint)) {
|
||||
UserError.Log.d(TAG, "Master hint of: " + hint + " is reachable - setting up probe");
|
||||
DesertComms.probeOasis(getTopic(), hint);
|
||||
}
|
||||
}
|
||||
|
||||
// identity
|
||||
|
||||
private static final String PREF_LAST_DESERT_MY_IP = "last-desert-sync-my-ip";
|
||||
|
||||
public static void checkIpChange(final String result) {
|
||||
// failed to reach peer
|
||||
UserError.Log.d(TAG, "CheckIpChange enter: " + result);
|
||||
if (result == null || (JoH.ratelimit("desert-check-ip-change", 60))) {
|
||||
final String currentIP = getBestInterfaceAddress();
|
||||
UserError.Log.d(TAG, "check ip change: current: " + currentIP);
|
||||
|
||||
if (!emptyString(currentIP)) {
|
||||
if (lastUsedIP == null) {
|
||||
lastUsedIP = PersistentStore.getString(PREF_LAST_DESERT_MY_IP);
|
||||
}
|
||||
UserError.Log.d(TAG, "check ip change last: " + lastUsedIP);
|
||||
if (emptyString(lastUsedIP) || !currentIP.equals(lastUsedIP)) {
|
||||
if (!emptyString(lastUsedIP)) {
|
||||
UserError.Log.uel(TAG, "Our IP appears to have changed from: " + lastUsedIP + " to " + currentIP + " sending notification to peers");
|
||||
UserError.Log.d(TAG, "check ip change send ping");
|
||||
GcmActivity.desertPing();
|
||||
}
|
||||
lastUsedIP = currentIP;
|
||||
|
||||
PersistentStore.setString(PREF_LAST_DESERT_MY_IP, lastUsedIP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTopic() {
|
||||
return getDriveIdentityString();
|
||||
}
|
||||
|
||||
public static void masterIdReply(final String result, final String host) {
|
||||
if (result == null) return;
|
||||
if (Home.get_follower()) {
|
||||
final RollCall rc = RollCall.fromJson(result);
|
||||
if (rc == null) return;
|
||||
if (rc.role.equals("Master")) {
|
||||
DesertComms.setOasisIP(host);
|
||||
pullAsEnabled();
|
||||
}
|
||||
} else {
|
||||
UserError.Log.e(TAG, "Refusing to process id reply as we are not a follower");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static String mySender() {
|
||||
if (static_sender == null) {
|
||||
synchronized (DesertSync.class) {
|
||||
if (static_sender == null) {
|
||||
String sender = PersistentStore.getString(PREF_SENDER_UUID);
|
||||
//UserError.Log.d(TAG, "From store: " + sender);
|
||||
if (sender.length() != 32) {
|
||||
sender = CipherUtils.getRandomHexKey();
|
||||
UserError.Log.d(TAG, "From key: " + sender);
|
||||
PersistentStore.setString(PREF_SENDER_UUID, sender);
|
||||
}
|
||||
static_sender = sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
UserError.Log.d(TAG, "Returning sender: " + static_sender);
|
||||
return static_sender;
|
||||
}
|
||||
|
||||
public static String getMyRollCall(final String topic) {
|
||||
if (topic != null && topic.equals(getTopic())) {
|
||||
if (myRollCall == null || JoH.msSince(myRollCall.created) > Constants.MINUTE_IN_MS * 15) {
|
||||
myRollCall = new RollCall();
|
||||
}
|
||||
return myRollCall.populate().toS();
|
||||
} else {
|
||||
return "Invalid topic";
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private static JamListenerSvc getInstance() {
|
||||
if (service == null) {
|
||||
service = new GcmListenerSvc();
|
||||
service.setInjectable();
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
private static void onMessageReceived(final RemoteMessage message) {
|
||||
if (sequence_lock.getQueueLength() > 0) {
|
||||
UserError.Log.d(TAG, "Sequence lock has: " + sequence_lock.getQueueLength() + " waiting");
|
||||
}
|
||||
try {
|
||||
sequence_lock.tryLock(20, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
//
|
||||
} finally {
|
||||
getInstance().onMessageReceived(message);
|
||||
try {
|
||||
sequence_lock.unlock();
|
||||
} catch (IllegalMonitorStateException e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void learnPeer(final InetAddress address) {
|
||||
if (peers == null) {
|
||||
peers = new HashMap<>();
|
||||
}
|
||||
if (!peers.containsKey(address)) {
|
||||
if (RouteTools.isLocal(address)) {
|
||||
UserError.Log.d(TAG, "Learned new peer: " + ip(address));
|
||||
} else {
|
||||
UserError.Log.d(TAG, "Refusing to Learn new peer: " + ip(address));
|
||||
return;
|
||||
}
|
||||
}
|
||||
peers.put(address, JoH.tsl());
|
||||
|
||||
spinner++;
|
||||
if (spinner % 10 == 0) {
|
||||
prunePeers();
|
||||
}
|
||||
}
|
||||
|
||||
private static void prunePeers() {
|
||||
InetAddress toRemove = null;
|
||||
for (final Map.Entry<InetAddress, Long> entry : peers.entrySet()) {
|
||||
if (JoH.msSince(entry.getValue()) > Constants.DAY_IN_MS * 3) {
|
||||
toRemove = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toRemove != null) peers.remove(toRemove);
|
||||
}
|
||||
|
||||
public static List<String> getActivePeers() {
|
||||
final List<String> list = new ArrayList<>();
|
||||
if (peers != null) {
|
||||
for (final Map.Entry<InetAddress, Long> entry : peers.entrySet()) {
|
||||
if (JoH.msSince(entry.getValue()) < Constants.HOUR_IN_MS * 3) {
|
||||
list.add(ip(entry.getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static String getActivePeersString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final String str : getActivePeers()) {
|
||||
sb.append(str);
|
||||
sb.append(",");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String getYfrom() {
|
||||
return xdrip.gs(R.string.gcmtpc) + topic;
|
||||
}
|
||||
|
||||
public static void settingsChanged() {
|
||||
if (isEnabled()) {
|
||||
correctWebServiceSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private static void correctWebServiceSettings() {
|
||||
Pref.setBoolean("xdrip_webservice", true);
|
||||
Pref.setBoolean("xdrip_webservice_open", true);
|
||||
if (Pref.getString(PREF_WEBSERVICE_SECRET, "").length() == 0) {
|
||||
Pref.setString(PREF_WEBSERVICE_SECRET, CipherUtils.getRandomHexKey());
|
||||
}
|
||||
Inevitable.task("web service changed", 2000, XdripWebService::settingsChanged);
|
||||
}
|
||||
|
||||
// maintenance
|
||||
|
||||
// create the table ourselves without worrying about model versioning and downgrading
|
||||
public static void updateDB() {
|
||||
patched = fixUpTable(schema, patched);
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
try {
|
||||
new Delete()
|
||||
.from(DesertSync.class)
|
||||
.where("timestamp < ?", JoH.tsl() - 86400000L)
|
||||
.execute();
|
||||
} catch (Exception e) {
|
||||
UserError.Log.d(TAG, "Exception cleaning uploader queue: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteAll() {
|
||||
new Delete()
|
||||
.from(DesertSync.class)
|
||||
.execute();
|
||||
}
|
||||
|
||||
// megastatus
|
||||
|
||||
// data for MegaStatus
|
||||
public static List<StatusItem> megaStatus() {
|
||||
final List<StatusItem> l = new ArrayList<>();
|
||||
if (isEnabled()) {
|
||||
if (Home.get_follower()) {
|
||||
l.addAll(DesertComms.megaStatus());
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
}
|
||||
159
lib/nightscout/com/eveningoutpost/dexdrip/Models/Forecast.java
Normal file
159
lib/nightscout/com/eveningoutpost/dexdrip/Models/Forecast.java
Normal file
@@ -0,0 +1,159 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.commons.math3.linear.MatrixUtils;
|
||||
import org.apache.commons.math3.linear.RealMatrix;
|
||||
import org.apache.commons.math3.stat.regression.OLSMultipleLinearRegression;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 08/02/2016.
|
||||
*/
|
||||
public class Forecast {
|
||||
|
||||
private static final String TAG = "jamorham forecast";
|
||||
// from stackoverflow.com/questions/17592139/trend-lines-regression-curve-fitting-java-library
|
||||
|
||||
public interface TrendLine {
|
||||
void setValues(double[] y, double[] x); // y ~ f(x)
|
||||
|
||||
double predict(double x); // get a predicted y for a given x
|
||||
|
||||
double errorVarience();
|
||||
}
|
||||
|
||||
public abstract static class OLSTrendLine implements TrendLine {
|
||||
|
||||
RealMatrix coef = null; // will hold prediction coefs once we get values
|
||||
Double last_error_rate = null;
|
||||
|
||||
protected abstract double[] xVector(double x); // create vector of values from x
|
||||
|
||||
protected abstract boolean logY(); // set true to predict log of y (note: y must be positive)
|
||||
|
||||
|
||||
@Override
|
||||
public void setValues(double[] y, double[] x) {
|
||||
if (x.length != y.length) {
|
||||
throw new IllegalArgumentException(String.format("The numbers of y and x values must be equal (%d != %d)", y.length, x.length));
|
||||
}
|
||||
double[][] xData = new double[x.length][];
|
||||
for (int i = 0; i < x.length; i++) {
|
||||
// the implementation determines how to produce a vector of predictors from a single x
|
||||
xData[i] = xVector(x[i]);
|
||||
}
|
||||
if (logY()) { // in some models we are predicting ln y, so we replace each y with ln y
|
||||
y = Arrays.copyOf(y, y.length); // user might not be finished with the array we were given
|
||||
for (int i = 0; i < x.length; i++) {
|
||||
y[i] = Math.log(y[i]);
|
||||
}
|
||||
}
|
||||
final OLSMultipleLinearRegression ols = new OLSMultipleLinearRegression();
|
||||
ols.setNoIntercept(true); // let the implementation include a constant in xVector if desired
|
||||
ols.newSampleData(y, xData); // provide the data to the model
|
||||
coef = MatrixUtils.createColumnRealMatrix(ols.estimateRegressionParameters()); // get our coefs
|
||||
last_error_rate = ols.estimateErrorVariance();
|
||||
Log.d(TAG, getClass().getSimpleName() + " Forecast Error rate: errorvar:"
|
||||
+ JoH.qs(last_error_rate, 4)
|
||||
+ " regssionvar:" + JoH.qs(ols.estimateRegressandVariance(), 4)
|
||||
+ " stderror:" + JoH.qs(ols.estimateRegressionStandardError(), 4));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double predict(double x) {
|
||||
double yhat = coef.preMultiply(xVector(x))[0]; // apply coefs to xVector
|
||||
if (logY()) yhat = (Math.exp(yhat)); // if we predicted ln y, we still need to get y
|
||||
return yhat;
|
||||
}
|
||||
|
||||
public static double[] toPrimitive(Double[] array) {
|
||||
if (array == null) {
|
||||
return null;
|
||||
} else if (array.length == 0) {
|
||||
return new double[0];
|
||||
}
|
||||
final double[] result = new double[array.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
result[i] = array[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double[] toPrimitiveFromList(Collection<Double> array) {
|
||||
if (array == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toPrimitive(array.toArray(new Double[array.size()]));
|
||||
}
|
||||
|
||||
public double errorVarience() {
|
||||
return last_error_rate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PolyTrendLine extends OLSTrendLine {
|
||||
final int degree;
|
||||
|
||||
public PolyTrendLine(int degree) {
|
||||
if (degree < 0)
|
||||
throw new IllegalArgumentException("The degree of the polynomial must not be negative");
|
||||
this.degree = degree;
|
||||
}
|
||||
|
||||
protected double[] xVector(double x) { // {1, x, x*x, x*x*x, ...}
|
||||
double[] poly = new double[degree + 1];
|
||||
double xi = 1;
|
||||
for (int i = 0; i <= degree; i++) {
|
||||
poly[i] = xi;
|
||||
xi *= x;
|
||||
}
|
||||
return poly;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean logY() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExpTrendLine extends OLSTrendLine {
|
||||
@Override
|
||||
protected double[] xVector(double x) {
|
||||
return new double[]{1, x};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean logY() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PowerTrendLine extends OLSTrendLine {
|
||||
@Override
|
||||
protected double[] xVector(double x) {
|
||||
return new double[]{1, Math.log(x)};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean logY() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogTrendLine extends OLSTrendLine {
|
||||
@Override
|
||||
protected double[] xVector(double x) {
|
||||
return new double[]{1, Math.log(x)};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean logY() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
// class from LibreAlarm
|
||||
|
||||
public class GlucoseData implements Comparable<GlucoseData> {
|
||||
|
||||
public long realDate;
|
||||
public String sensorId;
|
||||
public long sensorTime;
|
||||
public int glucoseLevel = -1;
|
||||
public int glucoseLevelRaw = -1;
|
||||
public long phoneDatabaseId;
|
||||
public int glucoseLevelRawSmoothed;
|
||||
|
||||
public GlucoseData(){}
|
||||
|
||||
// jamorham added constructor
|
||||
public GlucoseData(int glucoseLevelRaw, long timestamp) {
|
||||
this.glucoseLevelRaw = glucoseLevelRaw;
|
||||
this.realDate = timestamp;
|
||||
}
|
||||
|
||||
public String glucose(boolean mmol) {
|
||||
return glucose(glucoseLevel, mmol);
|
||||
}
|
||||
|
||||
public static String glucose(int mgdl, boolean mmol) {
|
||||
return mmol ? new DecimalFormat("##.0").format(mgdl/18f) : String.valueOf(mgdl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(GlucoseData another) {
|
||||
return (int) (realDate - another.realDate);
|
||||
}
|
||||
}
|
||||
129
lib/nightscout/com/eveningoutpost/dexdrip/Models/HeartRate.java
Normal file
129
lib/nightscout/com/eveningoutpost/dexdrip/Models/HeartRate.java
Normal file
@@ -0,0 +1,129 @@
|
||||
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.Delete;
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 01/11/2016.
|
||||
*/
|
||||
|
||||
|
||||
@Table(name = "HeartRate", id = BaseColumns._ID)
|
||||
public class HeartRate extends Model {
|
||||
|
||||
private final static String TAG = "HeartRate";
|
||||
private static boolean patched = false;
|
||||
@Expose
|
||||
@Column(name = "timestamp", unique = true, onUniqueConflicts = Column.ConflictAction.IGNORE)
|
||||
public long timestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "bpm")
|
||||
public int bpm;
|
||||
|
||||
@Expose
|
||||
@Column(name = "accuracy")
|
||||
public int accuracy;
|
||||
|
||||
public static HeartRate last() {
|
||||
try {
|
||||
return new Select()
|
||||
.from(HeartRate.class)
|
||||
.orderBy("timestamp desc")
|
||||
.executeSingle();
|
||||
} catch (android.database.sqlite.SQLiteException e) {
|
||||
fixUpTable();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void create(long timestamp, int bpm, int accuracy) {
|
||||
final HeartRate hr = new HeartRate();
|
||||
hr.timestamp = timestamp;
|
||||
hr.bpm = bpm;
|
||||
hr.accuracy = accuracy;
|
||||
hr.saveit();
|
||||
}
|
||||
|
||||
public static List<HeartRate> latestForGraph(int number, double startTime) {
|
||||
return latestForGraph(number, (long) startTime, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static List<HeartRate> latestForGraph(int number, long startTime) {
|
||||
return latestForGraph(number, startTime, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
// TODO efficient record creation?
|
||||
|
||||
public static List<HeartRate> latestForGraph(int number, long startTime, long endTime) {
|
||||
try {
|
||||
return new Select()
|
||||
.from(HeartRate.class)
|
||||
.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<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<HeartRate> cleanup(int retention_days) {
|
||||
return new Delete()
|
||||
.from(HeartRate.class)
|
||||
.where("timestamp < ?", JoH.tsl() - (retention_days * 86400000L))
|
||||
.execute();
|
||||
}
|
||||
|
||||
// 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;",
|
||||
"ALTER TABLE HeartRate ADD COLUMN accuracy 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;
|
||||
}
|
||||
|
||||
// patches and saves
|
||||
public Long saveit() {
|
||||
fixUpTable();
|
||||
return save();
|
||||
}
|
||||
|
||||
// TODO cache gson statically
|
||||
public String toS() {
|
||||
final Gson gson = new GsonBuilder()
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
.create();
|
||||
return gson.toJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import com.eveningoutpost.dexdrip.insulin.Insulin;
|
||||
import com.eveningoutpost.dexdrip.insulin.InsulinManager;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
public class InsulinInjection {
|
||||
private Insulin profile;
|
||||
|
||||
@Expose
|
||||
@Getter
|
||||
private double units;
|
||||
|
||||
@Expose
|
||||
@Getter
|
||||
private String insulin;
|
||||
|
||||
public InsulinInjection(final Insulin p, final double u) {
|
||||
profile = p;
|
||||
units = u;
|
||||
insulin = p.getName();
|
||||
}
|
||||
|
||||
|
||||
public Insulin getProfile() {
|
||||
// populate on demand
|
||||
if (profile == null) {
|
||||
profile = InsulinManager.getProfile(insulin);
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
// This is just a rough way to decide if it is a basal insulin without user needing to set it
|
||||
// question as to whether this should be here or call to encapsulated method in Insulin
|
||||
public boolean isBasal() {
|
||||
return getProfile().getMaxEffect() > 1000;
|
||||
}
|
||||
|
||||
}
|
||||
16
lib/nightscout/com/eveningoutpost/dexdrip/Models/Iob.java
Normal file
16
lib/nightscout/com/eveningoutpost/dexdrip/Models/Iob.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 02/01/16.
|
||||
*/
|
||||
public class Iob {
|
||||
public long timestamp;
|
||||
public double iob = 0;
|
||||
public CobCalc cobCalc;
|
||||
public double cob = 0;
|
||||
public double rawCarbImpact = 0;
|
||||
public double jCarbImpact = 0;
|
||||
public double jActivity = 0;
|
||||
}
|
||||
|
||||
|
||||
1690
lib/nightscout/com/eveningoutpost/dexdrip/Models/JoH.java
Normal file
1690
lib/nightscout/com/eveningoutpost/dexdrip/Models/JoH.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "Libre2RawValue2", id = BaseColumns._ID)
|
||||
public class Libre2RawValue extends PlusModel {
|
||||
|
||||
static final String[] schema = {
|
||||
"DROP TABLE Libre2RawValue;",
|
||||
"CREATE TABLE Libre2RawValue2 (_id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER, serial STRING, glucose REAL);",
|
||||
"CREATE INDEX index_Libre2RawValue2_ts on Libre2RawValue2(ts);"
|
||||
};
|
||||
|
||||
@Column(name = "serial", index = true)
|
||||
public String serial;
|
||||
|
||||
@Column(name = "ts", index = true)
|
||||
public long timestamp;
|
||||
|
||||
@Column(name = "glucose", index = false)
|
||||
public double glucose;
|
||||
|
||||
public static List<Libre2RawValue> last20Minutes() {
|
||||
double timestamp = (new Date().getTime()) - (60000 * 20);
|
||||
return new Select()
|
||||
.from(Libre2RawValue.class)
|
||||
.where("ts >= " + timestamp)
|
||||
.orderBy("ts asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static List<Libre2RawValue> latestForGraph(int number, double startTime) {
|
||||
return latestForGraph(number, (long) startTime, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static List<Libre2RawValue> latestForGraph(int number, long startTime) {
|
||||
return latestForGraph(number, startTime, Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
public static List<Libre2RawValue> latestForGraph(int number, long startTime, long endTime) {
|
||||
return new Select()
|
||||
.from(Libre2RawValue.class)
|
||||
.where("ts >= " + Math.max(startTime, 0))
|
||||
.where("ts <= " + endTime)
|
||||
.where("glucose != 0")
|
||||
.orderBy("ts desc")
|
||||
.limit(number)
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static void updateDB() {
|
||||
fixUpTable(schema, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
@Table(name = "Libre2Sensors", id = BaseColumns._ID)
|
||||
public class Libre2Sensor extends PlusModel {
|
||||
static final String TAG = "Libre2Sensor";
|
||||
|
||||
static final String[] schema = {
|
||||
"CREATE VIEW Libre2Sensors AS SELECT MIN(_id) as _id, serial, MIN(ts) as ts_from, MAX(ts) AS ts_to, COUNT(*) AS readings FROM Libre2RawValue2 GROUP BY serial ORDER BY ts DESC;"
|
||||
};
|
||||
|
||||
@Column(name = "serial", index = true)
|
||||
public String serial;
|
||||
|
||||
@Column(name = "ts_from", index = false)
|
||||
public long ts_from;
|
||||
|
||||
@Column(name = "ts_to", index = false)
|
||||
public long ts_to;
|
||||
|
||||
@Column(name = "readings", index = false)
|
||||
public long readings;
|
||||
|
||||
private static volatile String cachedStringSensors = null;
|
||||
|
||||
public static String Libre2Sensors() {
|
||||
String Sum = "";
|
||||
|
||||
if ((cachedStringSensors == null) || (JoH.ratelimit("libre2sensor-report", 120))) {
|
||||
|
||||
List<Libre2Sensor> rs = new Select()
|
||||
.from(Libre2Sensor.class)
|
||||
.execute();
|
||||
|
||||
for (Libre2Sensor Sensorpart : rs) {
|
||||
Long Diff_ts = Sensorpart.ts_to - Sensorpart.ts_from;
|
||||
Sum = Sum + Sensorpart.serial +
|
||||
"\n" + DateFormat.format("dd.MM.yy", Sensorpart.ts_from) +
|
||||
" to: " + DateFormat.format("dd.MM.yy", Sensorpart.ts_to) +
|
||||
" (" + JoH.niceTimeScalarShortWithDecimalHours(Diff_ts) + ")" +
|
||||
" readings: " + ((Sensorpart.readings * 100) / (Diff_ts / 60000)) + "%\n" +
|
||||
"------------------\n";
|
||||
}
|
||||
cachedStringSensors=Sum;
|
||||
}
|
||||
|
||||
return cachedStringSensors;
|
||||
}
|
||||
|
||||
public static void updateDB() {
|
||||
fixUpTable(schema, false);
|
||||
}
|
||||
}
|
||||
192
lib/nightscout/com/eveningoutpost/dexdrip/Models/LibreBlock.java
Normal file
192
lib/nightscout/com/eveningoutpost/dexdrip/Models/LibreBlock.java
Normal file
@@ -0,0 +1,192 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.activeandroid.query.Select;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.UploaderQueue;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
/**
|
||||
* Created by jamorham on 19/10/2017.
|
||||
*/
|
||||
|
||||
@Table(name = "LibreBlock", id = BaseColumns._ID)
|
||||
public class LibreBlock extends PlusModel {
|
||||
|
||||
private static final String TAG = "LibreBlock";
|
||||
static final String[] schema = {
|
||||
"CREATE TABLE LibreBlock (_id INTEGER PRIMARY KEY AUTOINCREMENT);",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN timestamp INTEGER;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN reference TEXT;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN blockbytes BLOB;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN bytestart INTEGER;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN byteend INTEGER;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN calculatedbg REAL;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN uuid TEXT;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN patchUid BLOB;",
|
||||
"ALTER TABLE LibreBlock ADD COLUMN patchInfo BLOB;",
|
||||
"CREATE INDEX index_LibreBlock_timestamp on LibreBlock(timestamp);",
|
||||
"CREATE INDEX index_LibreBlock_bytestart on LibreBlock(bytestart);",
|
||||
"CREATE INDEX index_LibreBlock_byteend on LibreBlock(byteend);",
|
||||
"CREATE INDEX index_LibreBlock_uuid on LibreBlock(uuid);"
|
||||
};
|
||||
|
||||
|
||||
@Expose
|
||||
@Column(name = "timestamp", index = true)
|
||||
public long timestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "bytestart", index = true)
|
||||
public long byte_start;
|
||||
|
||||
@Expose
|
||||
@Column(name = "byteend", index = true)
|
||||
public long byte_end;
|
||||
|
||||
@Expose
|
||||
@Column(name = "reference", index = true)
|
||||
public String reference;
|
||||
|
||||
@Expose
|
||||
@Column(name = "blockbytes")
|
||||
public byte[] blockbytes;
|
||||
|
||||
@Expose
|
||||
@Column(name = "calculatedbg")
|
||||
public double calculated_bg;
|
||||
|
||||
@Expose
|
||||
@Column(name = "uuid", index = true)
|
||||
public String uuid;
|
||||
|
||||
@Expose
|
||||
@Column(name = "patchUid")
|
||||
public byte[] patchUid;
|
||||
|
||||
@Expose
|
||||
@Column(name = "patchInfo")
|
||||
public byte[] patchInfo;
|
||||
|
||||
public static LibreBlock createAndSave(String reference, long timestamp, byte[] blocks, int byte_start) {
|
||||
return createAndSave(reference, timestamp, blocks, byte_start, false, null, null);
|
||||
}
|
||||
|
||||
// if you are indexing by block then just * 8 to get byte start
|
||||
public static LibreBlock createAndSave(String reference, long timestamp, byte[] blocks, int byte_start, boolean allowUpload, byte[] patchUid, byte[] patchInfo) {
|
||||
final LibreBlock lb = create(reference, timestamp, blocks, byte_start, patchUid, patchInfo);
|
||||
if (lb != null) {
|
||||
lb.save();
|
||||
if(byte_start == 0 && blocks.length == 344 && allowUpload) {
|
||||
Log.d(TAG, "sending new item to queue");
|
||||
UploaderQueue.newTransmitterDataEntry("create" ,lb);
|
||||
}
|
||||
}
|
||||
return lb;
|
||||
}
|
||||
|
||||
private static LibreBlock create(String reference, long timestamp, byte[] blocks, int byte_start, byte[] patchUid, byte[] patchInfo) {
|
||||
UserError.Log.e(TAG,"Backtrack: "+JoH.backTrace());
|
||||
if (reference == null) {
|
||||
UserError.Log.e(TAG, "Cannot save block with null reference");
|
||||
return null;
|
||||
}
|
||||
if (blocks == null) {
|
||||
UserError.Log.e(TAG, "Cannot save block with null data");
|
||||
return null;
|
||||
}
|
||||
|
||||
final LibreBlock lb = new LibreBlock();
|
||||
lb.reference = reference;
|
||||
lb.blockbytes = blocks;
|
||||
lb.byte_start = byte_start;
|
||||
lb.byte_end = byte_start + blocks.length;
|
||||
lb.timestamp = timestamp;
|
||||
lb.patchUid = patchUid;
|
||||
lb.patchInfo = patchInfo;
|
||||
lb.uuid = UUID.randomUUID().toString();
|
||||
return lb;
|
||||
}
|
||||
|
||||
public static LibreBlock getLatestForTrend() {
|
||||
return getLatestForTrend(JoH.tsl() - Constants.DAY_IN_MS, JoH.tsl() );
|
||||
}
|
||||
|
||||
|
||||
public static LibreBlock getLatestForTrend(long start_time, long end_time) {
|
||||
|
||||
return new Select()
|
||||
.from(LibreBlock.class)
|
||||
.where("bytestart == 0")
|
||||
.where("byteend >= 344")
|
||||
.where("timestamp >= ?", start_time)
|
||||
.where("timestamp <= ?", end_time)
|
||||
.orderBy("timestamp desc")
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public static List<LibreBlock> getForTrend(long start_time, long end_time) {
|
||||
|
||||
return new Select()
|
||||
.from(LibreBlock.class)
|
||||
.where("bytestart == 0")
|
||||
.where("byteend >= 344")
|
||||
.where("timestamp >= ?", start_time)
|
||||
.where("timestamp <= ?", end_time)
|
||||
.orderBy("timestamp asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static LibreBlock getForTimestamp(long timestamp) {
|
||||
final long margin = (3 * 1000);
|
||||
return new Select()
|
||||
.from(LibreBlock.class)
|
||||
.where("timestamp >= ?", (timestamp - margin))
|
||||
.where("timestamp <= ?", (timestamp + margin))
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
public static void UpdateBgVal(long timestamp, double calculated_value) {
|
||||
LibreBlock libreBlock = getForTimestamp(timestamp);
|
||||
if (libreBlock == null) {
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "Updating bg for timestamp " + timestamp);
|
||||
libreBlock.calculated_bg = calculated_value;
|
||||
libreBlock.save();
|
||||
}
|
||||
|
||||
public static LibreBlock findByUuid(String uuid) {
|
||||
try {
|
||||
return new Select()
|
||||
.from(LibreBlock.class)
|
||||
.where("uuid = ?", uuid)
|
||||
.executeSingle();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG,"findByUuid() Got exception on Select : "+e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean d = false;
|
||||
|
||||
public static void updateDB() {
|
||||
fixUpTable(schema, false);
|
||||
}
|
||||
|
||||
public static LibreBlock byid(long id) {
|
||||
return new Select()
|
||||
.from(LibreBlock.class)
|
||||
.where("_ID = ?", id)
|
||||
.executeSingle();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.activeandroid.annotation.Column;
|
||||
import com.activeandroid.annotation.Table;
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 19/10/2017.
|
||||
*/
|
||||
|
||||
@Table(name = "LibreData", id = BaseColumns._ID)
|
||||
public class LibreData extends PlusModel {
|
||||
private static final String TAG = "LibreData";
|
||||
static final String[] schema = {
|
||||
"CREATE TABLE LibreData (_id INTEGER PRIMARY KEY AUTOINCREMENT);",
|
||||
"ALTER TABLE LibreData ADD COLUMN timestamp INTEGER;",
|
||||
"ALTER TABLE LibreData ADD COLUMN temperature REAL;",
|
||||
"ALTER TABLE LibreData ADD COLUMN temperatureraw INTEGER;",
|
||||
|
||||
"CREATE INDEX index_LibreData_timestamp on LibreData(timestamp);"
|
||||
};
|
||||
|
||||
|
||||
@Expose
|
||||
@Column(name = "timestamp", index = true)
|
||||
public long timestamp;
|
||||
|
||||
@Expose
|
||||
@Column(name = "temperature")
|
||||
public double temperature;
|
||||
|
||||
@Expose
|
||||
@Column(name = "temperatureraw")
|
||||
public long temperatureraw;
|
||||
|
||||
|
||||
public static LibreData create(byte[] temp_bytes) {
|
||||
final LibreData ld = new LibreData();
|
||||
ld.timestamp = JoH.tsl();
|
||||
// TODO
|
||||
//ld.temperatureraw = get byte order value from temp_bytes
|
||||
//ld.temperature = evaluate temperature from temperature raw
|
||||
return ld;
|
||||
}
|
||||
|
||||
private static final boolean d = false;
|
||||
|
||||
public static void updateDB() {
|
||||
fixUpTable(schema, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.util.HexDump;
|
||||
import com.eveningoutpost.dexdrip.LibreAlarmReceiver;
|
||||
import com.eveningoutpost.dexdrip.Models.UserError.Log;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.CompatibleApps;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Intents;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.PersistentStore;
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
|
||||
import com.eveningoutpost.dexdrip.xdrip;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LibreOOPAlgorithm {
|
||||
private static final String TAG = "LibreOOPAlgorithm";
|
||||
|
||||
static public void SendData(byte[] fullData, long timestamp) {
|
||||
SendData(fullData, timestamp, null, null);
|
||||
}
|
||||
|
||||
static public void SendData(byte[] fullData, long timestamp, byte []patchUid, byte []patchInfo) {
|
||||
if(fullData == null) {
|
||||
Log.e(TAG, "SendData called with null data");
|
||||
return;
|
||||
}
|
||||
|
||||
if(fullData.length < 344) {
|
||||
Log.e(TAG, "SendData called with data size too small. " + fullData.length);
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "Sending full data to OOP Algorithm data-len = " + fullData.length);
|
||||
|
||||
fullData = java.util.Arrays.copyOfRange(fullData, 0, 0x158);
|
||||
Log.i(TAG, "Data that will be sent is " + HexDump.dumpHexString(fullData));
|
||||
|
||||
Intent intent = new Intent(Intents.XDRIP_PLUS_LIBRE_DATA);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putByteArray(Intents.LIBRE_DATA_BUFFER, fullData);
|
||||
bundle.putLong(Intents.LIBRE_DATA_TIMESTAMP, timestamp);
|
||||
bundle.putString(Intents.LIBRE_SN, PersistentStore.getString("LibreSN"));
|
||||
bundle.putInt(Intents.LIBRE_RAW_ID, android.os.Process.myPid());
|
||||
|
||||
if(patchUid != null) {
|
||||
bundle.putByteArray(Intents.LIBRE_PATCH_UID_BUFFER, patchUid);
|
||||
}
|
||||
if(patchInfo != null) {
|
||||
bundle.putByteArray(Intents.LIBRE_PATCH_INFO_BUFFER, patchInfo);
|
||||
}
|
||||
|
||||
intent.putExtras(bundle);
|
||||
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
|
||||
|
||||
final String packages = PersistentStore.getString(CompatibleApps.EXTERNAL_ALG_PACKAGES);
|
||||
if (packages.length() > 0) {
|
||||
final String[] packagesE = packages.split(",");
|
||||
for (final String destination : packagesE) {
|
||||
if (destination.length() > 3) {
|
||||
intent.setPackage(destination);
|
||||
Log.d(TAG, "Sending to package: " + destination);
|
||||
xdrip.getAppContext().sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Sending to generic package");
|
||||
xdrip.getAppContext().sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static public void HandleData(String oopData) {
|
||||
Log.e(TAG, "HandleData called with " + oopData);
|
||||
OOPResults oOPResults = null;
|
||||
try {
|
||||
final Gson gson = new GsonBuilder().create();
|
||||
OOPResultsContainer oOPResultsContainer = gson.fromJson(oopData, OOPResultsContainer.class);
|
||||
|
||||
if(oOPResultsContainer.Message != null) {
|
||||
Log.e(TAG, "recieved a message from oop algorithm:" + oOPResultsContainer.Message);
|
||||
}
|
||||
|
||||
if(oOPResultsContainer.oOPResultsArray.length > 0) {
|
||||
oOPResults = oOPResultsContainer.oOPResultsArray[0];
|
||||
} else {
|
||||
Log.e(TAG, "oOPResultsArray exists, but size is zero");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) { //TODO: what exception should we catch here.
|
||||
Log.e(TAG, "HandleData cought exception ", e);
|
||||
return;
|
||||
}
|
||||
boolean use_raw = Pref.getBooleanDefaultFalse("calibrate_external_libre_algorithm");
|
||||
ReadingData.TransferObject libreAlarmObject = new ReadingData.TransferObject();
|
||||
libreAlarmObject.data = new ReadingData();
|
||||
libreAlarmObject.data.trend = new ArrayList<GlucoseData>();
|
||||
|
||||
double factor = 1;
|
||||
if(use_raw) {
|
||||
// When handeling raw, data is expected to be bigger in a factor of 1000 and
|
||||
// is then devided by Constants.LIBRE_MULTIPLIER
|
||||
factor = 1000 / Constants.LIBRE_MULTIPLIER;
|
||||
}
|
||||
|
||||
// Add the first object, that is the current time
|
||||
GlucoseData glucoseData = new GlucoseData();
|
||||
glucoseData.sensorTime = oOPResults.currentTime;
|
||||
glucoseData.realDate = oOPResults.timestamp;
|
||||
glucoseData.glucoseLevel = (int)(oOPResults.currentBg * factor);
|
||||
glucoseData.glucoseLevelRaw = (int)(oOPResults.currentBg * factor);
|
||||
|
||||
libreAlarmObject.data.trend.add(glucoseData);
|
||||
|
||||
// TODO: Add here data of last 10 minutes or whatever.
|
||||
|
||||
|
||||
// Add the historic data
|
||||
libreAlarmObject.data.history = new ArrayList<GlucoseData>();
|
||||
for(HistoricBg historicBg : oOPResults.historicBg) {
|
||||
if(historicBg.quality == 0) {
|
||||
glucoseData = new GlucoseData();
|
||||
glucoseData.realDate = oOPResults.timestamp + (historicBg.time - oOPResults.currentTime) * 60000;
|
||||
glucoseData.glucoseLevel = (int)(historicBg.bg * factor);
|
||||
glucoseData.glucoseLevelRaw = (int)(historicBg.bg * factor);
|
||||
libreAlarmObject.data.history.add(glucoseData);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current point again. This is needed in order to have the last gaps closed.
|
||||
// TODO: Base this on real BG values.
|
||||
glucoseData = new GlucoseData();
|
||||
glucoseData.realDate = oOPResults.timestamp;
|
||||
glucoseData.glucoseLevel = (int)(oOPResults.currentBg * factor);
|
||||
glucoseData.glucoseLevelRaw = (int)(oOPResults.currentBg * factor);
|
||||
libreAlarmObject.data.history.add(glucoseData);
|
||||
|
||||
Log.e(TAG, "HandleData Created the following object " + libreAlarmObject.toString());
|
||||
LibreAlarmReceiver.CalculateFromDataTransferObject(libreAlarmObject, use_raw);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.eveningoutpost.dexdrip.Models;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.eveningoutpost.dexdrip.UtilityModels.Intents;
|
||||
import com.eveningoutpost.dexdrip.xdrip;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by jamorham on 23/02/2016.
|
||||
*/
|
||||
public class NSClientChat {
|
||||
|
||||
private static final String TAG = "jamorham nsclient";
|
||||
|
||||
|
||||
public static void pushTreatmentAsync(final Treatments thistreatment) {
|
||||
Thread testAddTreatment = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try {
|
||||
Context context = xdrip.getAppContext();
|
||||
JSONObject data = new JSONObject();
|
||||
if (thistreatment.carbs > 0) {
|
||||
if (thistreatment.insulin > 0) {
|
||||
data.put("eventType", "Meal Bolus");
|
||||
} else {
|
||||
data.put("eventType", "Carb Correction");
|
||||
}
|
||||
} else {
|
||||
if (thistreatment.insulin > 0) {
|
||||
data.put("eventType", "Correction Bolus");
|
||||
} else {
|
||||
if ((thistreatment.notes != null) && (thistreatment.notes.length() > 1)) {
|
||||
data.put("eventType", "Note");
|
||||
} else {
|
||||
data.put("eventType", "<None>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.put("insulin", thistreatment.insulin);
|
||||
if (thistreatment.insulinJSON != null) {
|
||||
data.put("insulinInjections", thistreatment.insulinJSON);
|
||||
}
|
||||
data.put("carbs", thistreatment.carbs);
|
||||
if (thistreatment.notes != null) {
|
||||
data.put("notes", thistreatment.notes);
|
||||
}
|
||||
// data.put("_id", thistreatment.uuid.replace("-",""));
|
||||
//data.put("uuid",thistreatment.uuid);
|
||||
data.put("created_at", DateUtil.toISOString(thistreatment.timestamp));
|
||||
// data.put("NSCLIENTTESTRECORD", "NSCLIENTTESTRECORD");
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("action", "dbAdd");
|
||||
bundle.putString("collection", "treatments"); // "treatments" || "entries" || "devicestatus" || "profile" || "food"
|
||||
bundle.putString("data", data.toString());
|
||||
Intent intent = new Intent(Intents.ACTION_DATABASE);
|
||||
intent.putExtras(bundle);
|
||||
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
|
||||
context.sendBroadcast(intent);
|
||||
List<ResolveInfo> q = context.getPackageManager().queryBroadcastReceivers(intent, 0);
|
||||
if (q.size() < 1) {
|
||||
Log.e(TAG, "DBADD No receivers");
|
||||
} else Log.e(TAG, "DBADD dbAdd " + q.size() + " receivers");
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Got exception with parsing: " + e.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
testAddTreatment.start();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user