package com.nightscout.core.drivers;
import com.nightscout.core.BusProvider;
import com.nightscout.core.dexcom.CRCFailError;
import com.nightscout.core.dexcom.InvalidRecordLengthException;
import com.nightscout.core.dexcom.records.CalRecord;
import com.nightscout.core.dexcom.records.EGVRecord;
import com.nightscout.core.dexcom.records.InsertionRecord;
import com.nightscout.core.dexcom.records.MeterRecord;
import com.nightscout.core.dexcom.records.SensorRecord;
import com.nightscout.core.events.EventSeverity;
import com.nightscout.core.events.EventType;
import com.nightscout.core.model.CalibrationEntry;
import com.nightscout.core.model.Download;
import com.nightscout.core.model.DownloadStatus;
import com.nightscout.core.model.InsertionEntry;
import com.nightscout.core.model.MeterEntry;
import com.nightscout.core.model.SensorEntry;
import com.nightscout.core.model.SensorGlucoseValueEntry;
import com.nightscout.core.preferences.NightscoutPreferences;
import com.squareup.otto.Bus;
import org.joda.time.DateTime;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import rx.functions.Action1;
public class DexcomG4 extends AbstractDevice {
public static final int VENDOR_ID = 8867;
public static final int PRODUCT_ID = 71;
public static final int DEVICE_CLASS = 2;
public static final int DEVICE_SUBCLASS = 0;
public static final int PROTOCOL = 0;
protected NightscoutPreferences preferences;
protected int numOfPages;
protected AbstractUploaderDevice uploaderDevice;
protected DeviceTransport transport;
protected String receiverId = "";
protected String transmitterId = "";
protected List<SensorRecord> lastSensorRecords;
protected List<CalRecord> lastCalRecords;
protected Action1<G4ConnectionState> connectionStateListener = new Action1<G4ConnectionState>() {
@Override
public void call(G4ConnectionState connected) {
switch (connected) {
case CONNECTING:
onConnecting();
break;
case CONNECTED:
onConnect();
break;
case CLOSED:
onDisconnect();
break;
case CLOSING:
onDisconnecting();
break;
case READING:
onReading();
break;
case WRITING:
onWriting();
break;
default:
break;
}
}
};
public DexcomG4(DeviceTransport transport, NightscoutPreferences preferences,
AbstractUploaderDevice uploaderDevice) {
this.transport = transport;
this.preferences = preferences;
this.uploaderDevice = uploaderDevice;
this.deviceName = "DexcomG4";
this.deviceType = preferences.getDeviceType();
log.debug("New device being created: {}", this.deviceType);
if (transport != null) {
this.transport.registerConnectionListener(connectionStateListener);
}
}
public String getReceiverId() {
return receiverId;
}
@Override
public void onConnect() {
super.onConnect();
log.debug("onConnect Called DexcomG4 connection");
}
@Override
public boolean isConnected() {
return transport != null && transport.isConnected();
}
@Override
protected Download doDownload() {
Download.Builder downloadBuilder = new Download.Builder();
DateTime dateTime = new DateTime();
if (!isConnected()) {
reporter.report(EventType.DEVICE, EventSeverity.WARN, messages.getString("event_g4_not_connected"));
return downloadBuilder.download_timestamp(dateTime.toString())
.download_status(DownloadStatus.DEVICE_NOT_FOUND).build();
}
DownloadStatus status = DownloadStatus.SUCCESS;
ReadData readData = new ReadData(transport);
List<EGVRecord> recentRecords;
List<MeterRecord> meterRecords = new ArrayList<>();
List<SensorRecord> sensorRecords = new ArrayList<>();
List<CalRecord> calRecords = new ArrayList<>();
List<InsertionRecord> insertionRecords = new ArrayList<>();
List<SensorGlucoseValueEntry> cookieMonsterG4SGVs;
List<SensorEntry> cookieMonsterG4Sensors;
Bus bus = BusProvider.getInstance();
int batLevel = 100;
long systemTime = 0;
try {
if (receiverId.equals("")) {
receiverId = readData.readSerialNumber();
log.debug("ReceiverId: {}", receiverId);
} else {
log.warn("Using receiverId from session: {}", receiverId);
}
if (transmitterId.equals("")) {
transmitterId = readData.readTrasmitterId();
log.debug("TransmitterId: {}", transmitterId);
} else {
log.warn("Using TransmitterId from session: {}", transmitterId);
}
systemTime = readData.readSystemTime();
// FIXME: readData.readBatteryLevel() seems to flake out on battery level reads via serial.
// Removing for now.
if (preferences.getDeviceType() == SupportedDevices.DEXCOM_G4) {
batLevel = 100;
} else if (preferences.getDeviceType() == SupportedDevices.DEXCOM_G4_SHARE2) {
batLevel = readData.readBatteryLevel();
}
dateTime = new DateTime();
recentRecords = readData.getRecentEGVsPages(numOfPages, systemTime, dateTime.getMillis());
if (recentRecords.size() > 0) {
EGVRecord lastEgvRecord = recentRecords.get(recentRecords.size() - 1);
cookieMonsterG4SGVs = EGVRecord.toProtobufList(recentRecords);
boolean hasSensorData = (lastEgvRecord.getRawSystemTimeSeconds() > preferences.getLastEgvSysTime());
preferences.setLastEgvSysTime(lastEgvRecord.getRawSystemTimeSeconds());
UIDownload uiDownload = new UIDownload();
uiDownload.download = downloadBuilder.sgv(cookieMonsterG4SGVs)
.download_timestamp(dateTime.toString())
.receiver_system_time_sec(systemTime)
.build();
bus.post(uiDownload);
if ((preferences.isRawEnabled() && hasSensorData) || (preferences.isRawEnabled() && lastSensorRecords == null)) {
sensorRecords = readData.getRecentSensorRecords(numOfPages * 2, systemTime, dateTime.getMillis());
lastSensorRecords = sensorRecords;
} else {
sensorRecords = lastSensorRecords;
log.warn("Seems to be no new egv data. Assuming no sensor data either");
}
cookieMonsterG4Sensors = SensorRecord.toProtobufList(sensorRecords);
Download download = downloadBuilder.sgv(cookieMonsterG4SGVs)
.sensor(cookieMonsterG4Sensors)
.receiver_system_time_sec(systemTime)
.download_timestamp(dateTime.toString())
.download_status(status)
.receiver_id(receiverId)
.receiver_battery(batLevel)
.transmitter_id(transmitterId)
.build();
// FIXME - hack put in place to get data to the UI as soon as possible.
// Problem was it would take 1+ minutes for BLE to respond with all datasets
// enabled. This gets the data to the user as quickly as possible but
// spreads the bus posts across multiple classes. This should be managed by
// the collector service and not the download implementation.
bus.post(download);
}
boolean hasCalData = false;
if (preferences.isMeterUploadEnabled()) {
meterRecords = readData.getRecentMeterRecords(systemTime, dateTime.getMillis());
if (meterRecords.size() > 0) {
MeterRecord lastMeterRecord = meterRecords.get(meterRecords.size() - 1);
hasCalData = (lastMeterRecord.getRawSystemTimeSeconds() > preferences.getLastMeterSysTime());
preferences.setLastMeterSysTime(lastMeterRecord.getRawSystemTimeSeconds());
}
} else {
hasCalData = true;
}
if ((preferences.isRawEnabled() && hasCalData) || (preferences.isRawEnabled() && lastCalRecords == null)) {
calRecords = readData.getRecentCalRecords(systemTime, dateTime.getMillis());
lastCalRecords = calRecords;
} else {
calRecords = lastCalRecords;
log.warn("Seems to be no new new meter data or meter data is disabled. Assuming no cal data");
}
if (preferences.isInsertionUploadEnabled()) {
log.debug("Reading insertions");
insertionRecords = readData.getRecentInsertion(systemTime, dateTime.getMillis());
log.debug("Number of insertion records: {}", insertionRecords.size());
}
if (recentRecords.size() == 0) {
status = DownloadStatus.NO_DATA;
}
// TODO pull in other exceptions once we have the analytics/acra reporters
} catch (IOException e) {
//TODO record this in the event log later
reporter.report(EventType.DEVICE, EventSeverity.ERROR, "IO error to device");
log.error("IO error to device " + e);
status = DownloadStatus.IO_ERROR;
} catch (InvalidRecordLengthException e) {
reporter.report(EventType.DEVICE, EventSeverity.ERROR, "Application error " + e.getMessage());
log.error("Application error " + e);
status = DownloadStatus.APPLICATION_ERROR;
} catch (CRCFailError e) {
// FIXME: may consider localizing this catch at a lower level (like ReadData) so that
// if the CRC check fails on one type of record we can capture the values if it
// doesn't fail on other types of records. This means we'd need to broadcast back
// partial results to the UI. Adding it to a lower level could make the ReadData class
// more difficult to maintain - needs discussion.
log.error("CRC failed", e);
reporter.report(EventType.DEVICE, EventSeverity.ERROR, "CRC failed " + e);
}
List<CalibrationEntry> cookieMonsterG4Cals = CalRecord.toProtobufList(calRecords);
List<MeterEntry> cookieMonsterG4Meters = MeterRecord.toProtobufList(meterRecords);
List<InsertionEntry> cookieMonsterG4Inserts =
InsertionRecord.toProtobufList(insertionRecords);
log.debug("Number of insertion records (protobuf): {}", cookieMonsterG4Inserts.size());
// downloadBuilder = new G4Download.Builder();
downloadBuilder.cal(cookieMonsterG4Cals)
.meter(cookieMonsterG4Meters)
.insert(cookieMonsterG4Inserts)
.receiver_system_time_sec(systemTime)
.download_timestamp(dateTime.toString())
.download_status(status)
.uploader_battery(uploaderDevice.getBatteryLevel())
.receiver_battery(batLevel)
.receiver_id(receiverId)
.transmitter_id(transmitterId);
return downloadBuilder.build();
}
public void setNumOfPages(int numOfPages) {
this.numOfPages = numOfPages;
}
public class UIDownload {
public Download download;
}
}