package com.nightscout.core.dexcom.records; import com.nightscout.core.dexcom.InvalidRecordLengthException; import com.nightscout.core.dexcom.Utils; import com.nightscout.core.model.CalibrationEntry; import org.joda.time.DateTime; import org.joda.time.Seconds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; public class CalRecord extends GenericTimestampRecord { public final static int RECORD_SIZE = 147; public final static int RECORD_V2_SIZE = 248; private static final Logger LOG = LoggerFactory.getLogger(CalRecord.class); private double slope; private double intercept; private double scale; private int[] unk = new int[3]; private double decay; private int numRecords; private List<CalSubrecord> calSubrecords; private int SUB_LEN = 17; public CalRecord(byte[] packet, long rcvrTime, long refTime) { super(packet, rcvrTime, refTime); if (packet.length != RECORD_SIZE && packet.length != RECORD_V2_SIZE) { throw new InvalidRecordLengthException("Unexpected record size: " + packet.length + ". Expected size: " + RECORD_SIZE + ". Unparsed record: " + Utils.bytesToHex(packet)); } slope = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(8); intercept = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(16); scale = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(24); unk[0] = packet[32]; unk[1] = packet[33]; unk[2] = packet[34]; decay = ByteBuffer.wrap(packet).order(ByteOrder.LITTLE_ENDIAN).getDouble(35); numRecords = packet[43]; calSubrecords = new ArrayList<>(); long displayTimeOffset = Seconds.secondsBetween( new DateTime(getSystemTime()), new DateTime(getDisplayTime())).toStandardDuration().getMillis(); int start = 44; for (int i = 0; i < numRecords; i++) { byte[] temp = new byte[SUB_LEN]; System.arraycopy(packet, start, temp, 0, temp.length); calSubrecords.add(new CalSubrecord(temp, displayTimeOffset)); start += SUB_LEN; } setRecordType(); } public CalRecord(double intercept, double slope, double scale, double decay, DateTime displayTime, DateTime systemTime, List<CalSubrecord> subrecord, DateTime wallTime) { super(displayTime, systemTime, wallTime); this.intercept = intercept; this.slope = slope; this.scale = scale; this.decay = decay; this.numRecords = subrecord.size(); this.calSubrecords = subrecord; setRecordType(); } public CalRecord(double intercept, double slope, double scale, double decay, long displayTime, long systemTime, List<CalSubrecord> subrecord, long rcvrTime, long refTime) { super(displayTime, systemTime, rcvrTime, refTime); this.intercept = intercept; this.slope = slope; this.scale = scale; this.decay = decay; this.numRecords = subrecord.size(); this.calSubrecords = subrecord; setRecordType(); } public CalRecord(CalibrationEntry cal, long rcvrTime, long refTime) { super(cal.disp_timestamp_sec, cal.sys_timestamp_sec, rcvrTime, refTime); this.intercept = cal.intercept; this.slope = cal.slope; this.scale = cal.scale; this.decay = cal.decay; this.numRecords = 0; this.calSubrecords = new ArrayList<>(); setRecordType(); } public static List<CalibrationEntry> toProtobufList(List<CalRecord> list) { return toProtobufList(list, CalibrationEntry.class); } @Override public CalibrationEntry toProtobuf() { CalibrationEntry.Builder builder = new CalibrationEntry.Builder(); return builder.sys_timestamp_sec(rawSystemTimeSeconds) .disp_timestamp_sec(rawDisplayTimeSeconds) .intercept(intercept) .scale(scale) .slope(slope) .decay(decay) .build(); } public double getSlope() { return slope; } public double getIntercept() { return intercept; } public double getScale() { return scale; } public int[] getUnk() { return unk; } public double getDecay() { return decay; } public int getNumRecords() { return numRecords; } public List<CalSubrecord> getCalSubrecords() { return calSubrecords; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; CalRecord calRecord = (CalRecord) o; if (Double.compare(calRecord.decay, decay) != 0) return false; if (Double.compare(calRecord.intercept, intercept) != 0) return false; if (numRecords != calRecord.numRecords) return false; if (Double.compare(calRecord.scale, scale) != 0) return false; if (Double.compare(calRecord.slope, slope) != 0) return false; return !(calSubrecords != null ? !calSubrecords.equals(calRecord.calSubrecords) : calRecord.calSubrecords != null); } @Override protected void setRecordType() { this.recordType = "cal"; } public CalibrationEntry toProtoBuf() { return new CalibrationEntry.Builder() .sys_timestamp_sec(rawSystemTimeSeconds) .disp_timestamp_sec(rawDisplayTimeSeconds) .intercept(intercept) .slope(slope) .scale(scale) .build(); } @Override public int hashCode() { int result = super.hashCode(); long temp; temp = Double.doubleToLongBits(slope); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(intercept); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(scale); result = 31 * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(decay); result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + numRecords; result = 31 * result + (calSubrecords != null ? calSubrecords.hashCode() : 0); return result; } }