From 896724dd1aae97aede2486c23e33f10ba2a19b5e Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Sat, 14 May 2016 14:33:47 -0400 Subject: [PATCH 1/3] Initial commit of changes to support the Dexcom G5 Receiver --- .../core/dexcom/records/EGVRecord.java | 25 +++++++++++++++---- .../core/dexcom/records/MeterRecord.java | 24 ++++++++++++++---- .../com/nightscout/core/drivers/ReadData.java | 22 +++++++++++++--- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java b/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java index 6a54dbad..129fc467 100644 --- a/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java +++ b/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java @@ -18,20 +18,35 @@ import java.util.List; public class EGVRecord extends GenericTimestampRecord { - public final static int RECORD_SIZE = 12; + public final static int G4_RECORD_SIZE = 12; + public final static int G5_RECORD_SIZE = 22; private GlucoseReading reading; private TrendArrow trend; private G4Noise noiseMode; + private int recordVersion = 0; - public EGVRecord(byte[] packet) { + public EGVRecord(byte[] packet, int recordVersion) { super(packet); - if (packet.length != RECORD_SIZE) { + this.recordVersion = recordVersion; + if (recordVersion < 4 && packet.length != G4_RECORD_SIZE) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + - ". Expected size: " + RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); + ". Expected size: " + G4_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); + } + else if (recordVersion == 4 && packet.length != G5_RECORD_SIZE) { + throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + + ". Expected size: " + G5_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); } int bGValue = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8) & Constants.EGV_VALUE_MASK; reading = new GlucoseReading(bGValue, GlucoseUnit.MGDL); - byte trendAndNoise = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).get(10); + byte trendAndNoise; + if(recordVersion < 4) { + trendAndNoise = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).get(10); + } + else { + //A G5 has 'systemTimeSeconds' (4 bytes), 'transmitterTimeSeconds' (4 bytes), and 'filteredRateByte' (1 byte) before the trendArrowAndNoiseMode byte + trendAndNoise = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).get(19); + } + int trendValue = trendAndNoise & Constants.EGV_TREND_ARROW_MASK; byte noiseValue = (byte) ((trendAndNoise & Constants.EGV_NOISE_MASK) >> 4); trend = TrendArrow.values()[trendValue]; diff --git a/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java b/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java index bb2ba1bb..5dc4f007 100644 --- a/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java +++ b/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java @@ -12,19 +12,33 @@ import java.util.List; public class MeterRecord extends GenericTimestampRecord { - public final static int RECORD_SIZE = 15; + public final static int G4_RECORD_SIZE = 15; + public final static int G5_RECORD_SIZE = 20; private int meterTime; private GlucoseReading reading; + private int recordVersion = 0; - public MeterRecord(byte[] packet) { + public MeterRecord(byte[] packet, int recordVersion) { super(packet); - if (packet.length != RECORD_SIZE) { + this.recordVersion = recordVersion; + if (recordVersion < 3 && packet.length != G4_RECORD_SIZE) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + - ". Expected size: " + RECORD_SIZE + " record: " + Utils.bytesToHex(packet)); + ". Expected size: " + G4_RECORD_SIZE + " record: " + Utils.bytesToHex(packet)); + } + else if (recordVersion == 3 && packet.length != G5_RECORD_SIZE) { + throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + + ". Expected size: " + G5_RECORD_SIZE + " record: " + Utils.bytesToHex(packet)); } int meterBG = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8); reading = new GlucoseReading(meterBG, GlucoseUnit.MGDL); - meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(10); + if(recordVersion < 3) { + meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(10); + } + else if (recordVersion == 3) { + //The Dexcom G5 has added an entryType field after the meterValue byte field, which must be skipped for this + meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(11); + } + } public MeterRecord(int meterBgMgdl, int meterTime, Date displayTime, Date systemTime) { diff --git a/core/src/main/java/com/nightscout/core/drivers/ReadData.java b/core/src/main/java/com/nightscout/core/drivers/ReadData.java index 097def07..6b43b5b0 100644 --- a/core/src/main/java/com/nightscout/core/drivers/ReadData.java +++ b/core/src/main/java/com/nightscout/core/drivers/ReadData.java @@ -236,8 +236,16 @@ private List parsePage(byte[] data, Class< int startIdx; switch (pageHeader.getRecordType()) { case EGV_DATA: - startIdx = PageHeader.HEADER_SIZE + (EGVRecord.RECORD_SIZE + 1) * i; - records.add(clazz.cast(new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + EGVRecord.RECORD_SIZE)))); + + if(pageHeader.getRevision() == 0x04) { + //This is a record from a Dexcom G5 device + startIdx = PageHeader.HEADER_SIZE + (EGVRecord.G5_RECORD_SIZE + 1) * i; + records.add(clazz.cast(new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + EGVRecord.G5_RECORD_SIZE),pageHeader.getRevision()))); + } + else { + startIdx = PageHeader.HEADER_SIZE + (EGVRecord.G4_RECORD_SIZE + 1) * i; + records.add(clazz.cast(new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + EGVRecord.G4_RECORD_SIZE),pageHeader.getRevision()))); + } break; case CAL_SET: int recordLength = (pageHeader.getRevision() <= 2) ? CalRecord.RECORD_SIZE : CalRecord.RECORD_V2_SIZE; @@ -245,8 +253,14 @@ private List parsePage(byte[] data, Class< records.add(clazz.cast(new CalRecord(Arrays.copyOfRange(data, startIdx, startIdx + recordLength)))); break; case METER_DATA: - startIdx = PageHeader.HEADER_SIZE + (MeterRecord.RECORD_SIZE + 1) * i; - records.add(clazz.cast(new MeterRecord(Arrays.copyOfRange(data, startIdx, startIdx + MeterRecord.RECORD_SIZE)))); + if(pageHeader.getRevision() == 0x03) { + //This is a record from a Dexcom G5 device + startIdx = PageHeader.HEADER_SIZE + (MeterRecord.G5_RECORD_SIZE + 1) * i; + records.add(clazz.cast(new MeterRecord(Arrays.copyOfRange(data, startIdx, startIdx + MeterRecord.G5_RECORD_SIZE),pageHeader.getRevision()))); + }else { + startIdx = PageHeader.HEADER_SIZE + (MeterRecord.G4_RECORD_SIZE + 1) * i; + records.add(clazz.cast(new MeterRecord(Arrays.copyOfRange(data, startIdx, startIdx + MeterRecord.G4_RECORD_SIZE),pageHeader.getRevision()))); + } break; case SENSOR_DATA: startIdx = PageHeader.HEADER_SIZE + (SensorRecord.RECORD_SIZE + 1) * i; From 7d60cd73b0f969e9b69a4132de4ba2c4a1cb2c00 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Sat, 14 May 2016 15:00:11 -0400 Subject: [PATCH 2/3] Slight code tidy to handle record updates for EGVRecord and MeterRecord --- .../java/com/nightscout/core/dexcom/records/EGVRecord.java | 2 +- .../java/com/nightscout/core/dexcom/records/MeterRecord.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java b/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java index 129fc467..cae1b75f 100644 --- a/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java +++ b/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java @@ -32,7 +32,7 @@ public EGVRecord(byte[] packet, int recordVersion) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + G4_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); } - else if (recordVersion == 4 && packet.length != G5_RECORD_SIZE) { + else if (recordVersion >= 4 && packet.length != G5_RECORD_SIZE) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + G5_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); } diff --git a/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java b/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java index 5dc4f007..67cd6e62 100644 --- a/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java +++ b/core/src/main/java/com/nightscout/core/dexcom/records/MeterRecord.java @@ -25,7 +25,7 @@ public MeterRecord(byte[] packet, int recordVersion) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + G4_RECORD_SIZE + " record: " + Utils.bytesToHex(packet)); } - else if (recordVersion == 3 && packet.length != G5_RECORD_SIZE) { + else if (recordVersion >= 3 && packet.length != G5_RECORD_SIZE) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + G5_RECORD_SIZE + " record: " + Utils.bytesToHex(packet)); } @@ -34,7 +34,7 @@ else if (recordVersion == 3 && packet.length != G5_RECORD_SIZE) { if(recordVersion < 3) { meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(10); } - else if (recordVersion == 3) { + else { //The Dexcom G5 has added an entryType field after the meterValue byte field, which must be skipped for this meterTime = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getInt(11); } From 59da97a8e9809d381a3f30906001da6555636df6 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Tue, 27 Mar 2018 00:11:32 -0400 Subject: [PATCH 3/3] Added support for the Dexcom G5 Touch receiver --- .../java/com/nightscout/core/dexcom/records/EGVRecord.java | 7 ++++++- .../main/java/com/nightscout/core/drivers/ReadData.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java b/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java index cae1b75f..0bcadb3d 100644 --- a/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java +++ b/core/src/main/java/com/nightscout/core/dexcom/records/EGVRecord.java @@ -20,6 +20,7 @@ public class EGVRecord extends GenericTimestampRecord { public final static int G4_RECORD_SIZE = 12; public final static int G5_RECORD_SIZE = 22; + public final static int G5_TOUCH_RECORD_SIZE = 24; private GlucoseReading reading; private TrendArrow trend; private G4Noise noiseMode; @@ -32,10 +33,14 @@ public EGVRecord(byte[] packet, int recordVersion) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + G4_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); } - else if (recordVersion >= 4 && packet.length != G5_RECORD_SIZE) { + else if (recordVersion == 4 && packet.length != G5_RECORD_SIZE) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + G5_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); } + else if (recordVersion == 5 && packet.length != G5_TOUCH_RECORD_SIZE) { + throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + + ". Expected size: " + G5_TOUCH_RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); + } int bGValue = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getShort(8) & Constants.EGV_VALUE_MASK; reading = new GlucoseReading(bGValue, GlucoseUnit.MGDL); byte trendAndNoise; diff --git a/core/src/main/java/com/nightscout/core/drivers/ReadData.java b/core/src/main/java/com/nightscout/core/drivers/ReadData.java index 6b43b5b0..841eb108 100644 --- a/core/src/main/java/com/nightscout/core/drivers/ReadData.java +++ b/core/src/main/java/com/nightscout/core/drivers/ReadData.java @@ -237,7 +237,12 @@ private List parsePage(byte[] data, Class< switch (pageHeader.getRecordType()) { case EGV_DATA: - if(pageHeader.getRevision() == 0x04) { + if(pageHeader.getRevision() == 0x5) { + //This is a record from a Dexcom G5 Touch device + startIdx = PageHeader.HEADER_SIZE + (EGVRecord.G5_TOUCH_RECORD_SIZE + 1) * i; + records.add(clazz.cast(new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + EGVRecord.G5_TOUCH_RECORD_SIZE),pageHeader.getRevision()))); + } + else if(pageHeader.getRevision() == 0x4) { //This is a record from a Dexcom G5 device startIdx = PageHeader.HEADER_SIZE + (EGVRecord.G5_RECORD_SIZE + 1) * i; records.add(clazz.cast(new EGVRecord(Arrays.copyOfRange(data, startIdx, startIdx + EGVRecord.G5_RECORD_SIZE),pageHeader.getRevision())));