Initial project commit
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user