Initial project commit

This commit is contained in:
2020-07-18 21:44:27 -04:00
parent 8a1141b373
commit fea891a268
127 changed files with 20838 additions and 0 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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
}
}

View File

@@ -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);
//}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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() {
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}