package com.eveningoutpost.dexdrip.ImportedLibraries.dexcom; import android.app.IntentService; import android.bluetooth.BluetoothClass; import android.content.Intent; import android.content.Context; import android.content.SharedPreferences; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbManager; import android.os.PowerManager; import android.preference.PreferenceManager; import android.util.Log; import android.widget.Toast; import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.CdcAcmSerialDriver; import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.ProbeTable; import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.UsbSerialDriver; import com.eveningoutpost.dexdrip.ImportedLibraries.usbserial.driver.UsbSerialProber; import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.CalRecord; import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.EGVRecord; import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.GlucoseDataSet; import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.MeterRecord; import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.records.SensorRecord; import com.eveningoutpost.dexdrip.Models.Calibration; import org.json.JSONArray; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; // This code and this particular library are from the NightScout android uploader // Check them out here: https://github.com/nightscout/android-uploader // Some of this code may have been modified for use in this project /** * An {@link IntentService} subclass for handling asynchronous CGM Receiver downloads and cloud uploads * requests in a service on a separate handler thread. */ public class SyncingService extends IntentService { // Action for intent private static final String ACTION_SYNC = "com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.action.SYNC"; private static final String ACTION_CALIBRATION_CHECKIN = "com.eveningoutpost.dexdrip.CalibrationCheckInActivity"; // Parameters for intent private static final String SYNC_PERIOD = "com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.extra.SYNC_PERIOD"; // Response to broadcast to activity public static final String RESPONSE_SGV = "mySGV"; public static final String RESPONSE_TREND = "myTrend"; public static final String RESPONSE_TIMESTAMP = "myTimestamp"; public static final String RESPONSE_NEXT_UPLOAD_TIME = "myUploadTime"; public static final String RESPONSE_UPLOAD_STATUS = "myUploadStatus"; public static final String RESPONSE_DISPLAY_TIME = "myDisplayTime"; public static final String RESPONSE_JSON = "myJSON"; public static final String RESPONSE_BAT = "myBatLvl"; private final String TAG = SyncingService.class.getSimpleName(); private Context mContext; private UsbManager mUsbManager; private UsbSerialDriver mSerialDevice; private UsbDevice dexcom; private UsbDeviceConnection mConnection; // Constants private final int TIME_SYNC_OFFSET = 10000; public static final int MIN_SYNC_PAGES = 2; public static final int GAP_SYNC_PAGES = 20; /** * Starts this service to perform action Single Sync with the given parameters. If * the service is already performing a task this action will be queued. * * @see IntentService */ public static void startActionSingleSync(Context context, int numOfPages) { Intent intent = new Intent(context, SyncingService.class); intent.setAction(ACTION_SYNC); intent.putExtra(SYNC_PERIOD, numOfPages); context.startService(intent); } public static void startActionCalibrationCheckin(Context context) { Intent intent = new Intent(context, SyncingService.class); intent.setAction(ACTION_CALIBRATION_CHECKIN); context.startService(intent); } public SyncingService() { super("SyncingService"); } @Override protected void onHandleIntent(Intent intent) { mContext = getApplicationContext(); if (intent != null) { final String action = intent.getAction(); if (ACTION_SYNC.equals(action)) { final int param1 = intent.getIntExtra(SYNC_PERIOD, 1); handleActionSync(param1); } else if (ACTION_CALIBRATION_CHECKIN.equals(action)) { Log.w("CALIBRATION-CHECK-IN: ", "Beginning check in process"); performCalibrationCheckin(); } } } /** * Handle action Sync in the provided background thread with the provided * parameters. */ private void performCalibrationCheckin(){ PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NSDownload"); wl.acquire(); Log.w("CALIBRATION-CHECK-IN: ", "Wake Lock Acquired"); if (acquireSerialDevice()) { try { ReadData readData = new ReadData(mSerialDevice, mConnection, dexcom); // ReadData readData = new ReadData(mSerialDevice); CalRecord[] calRecords = readData.getRecentCalRecords(); Log.w("CALIBRATION-CHECK-IN: ", "Found "+ calRecords.length + " Records!"); save_most_recent_cal_record(calRecords); } catch (Exception e) { Log.wtf("Unhandled exception caught", e); } finally { // Close serial try { mSerialDevice.getPorts().get(0).close(); } catch (IOException e) { Log.e(TAG, "Unable to close", e); } } } else { Log.w("CALIBRATION-CHECK-IN: ", "Failed to acquire serial device"); } } private void handleActionSync(int numOfPages) { boolean broadcastSent = false; PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NSDownload"); wl.acquire(); if (acquireSerialDevice()) { try { ReadData readData = new ReadData(mSerialDevice); // TODO: need to check if numOfPages if valid on ReadData side EGVRecord[] recentRecords = readData.getRecentEGVsPages(numOfPages); MeterRecord[] meterRecords = readData.getRecentMeterRecords(); // TODO: need to check if numOfPages if valid on ReadData side SensorRecord[] sensorRecords = readData.getRecentSensorRecords(numOfPages); GlucoseDataSet[] glucoseDataSets = Utils.mergeGlucoseDataRecords(recentRecords, sensorRecords); // FIXME: This is a workaround for the new Dexcom AP which seems to have a new format SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext); CalRecord[] calRecords = new CalRecord[1]; if (prefs.getBoolean("cloud_cal_data", false)) { calRecords = readData.getRecentCalRecords(); } long timeSinceLastRecord = readData.getTimeSinceEGVRecord(recentRecords[recentRecords.length - 1]); // TODO: determine if the logic here is correct. I suspect it assumes the last record was less than 5 // minutes ago. If a reading is skipped and the device is plugged in then nextUploadTime will be // set to a negative number. This situation will eventually correct itself. long nextUploadTime = (1000 * 60 * 5) - (timeSinceLastRecord * (1000)); long displayTime = readData.readDisplayTime().getTime(); // FIXME: Device seems to flake out on battery level reads. Removing for now. // int batLevel = readData.readBatteryLevel(); int batLevel = 100; // convert into json for d3 plot JSONArray array = new JSONArray(); for (int i = 0; i < recentRecords.length; i++) array.put(recentRecords[i].toJSON()); EGVRecord recentEGV = recentRecords[recentRecords.length - 1]; // broadcastSGVToUI(recentEGV, uploadStatus, nextUploadTime + TIME_SYNC_OFFSET, // displayTime, array ,batLevel); broadcastSent=true; } catch (ArrayIndexOutOfBoundsException e) { Log.wtf("Unable to read from the dexcom, maybe it will work next time", e); } catch (NegativeArraySizeException e) { Log.wtf("Negative array exception from receiver", e); } catch (IndexOutOfBoundsException e) { Log.wtf("IndexOutOfBounds exception from receiver", e); } catch (CRCFailRuntimeException 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.wtf("CRC failed", e); } catch (Exception e) { Log.wtf("Unhandled exception caught", e); } finally { // Close serial try { mSerialDevice.getPorts().get(0).close(); } catch (IOException e) { Log.e(TAG, "Unable to close", e); } } } // if (!broadcastSent) broadcastSGVToUI(); wl.release(); } private void save_most_recent_cal_record(CalRecord[] calRecords) { int size = calRecords.length; Calibration.create(calRecords,getApplicationContext(), false, 0); } private boolean acquireSerialDevice() { UsbDevice found_device = findDexcom(); if(mUsbManager == null) { Log.w("CALIBRATION-CHECK-IN: ", "USB manager is null"); } if( mUsbManager.hasPermission(dexcom)) { // the system is allowing us to poke around this device ProbeTable customTable = new ProbeTable(); // From the USB library... customTable.addProduct(0x22A3, 0x0047, CdcAcmSerialDriver.class); // ...Specify the Vendor ID and Product ID UsbSerialProber prober = new UsbSerialProber(customTable); // Probe the device with the custom values List<UsbSerialDriver> drivers = prober.findAllDrivers(mUsbManager); // let's go through the list Iterator<UsbSerialDriver> foo = drivers.iterator(); // Invalid Return code while (foo.hasNext()) { // let's loop through UsbSerialDriver driver = foo.next(); // set fooDriver to the next available driver if (driver != null) { UsbDeviceConnection connection = mUsbManager.openDevice(driver.getDevice()); if (connection != null) { mSerialDevice = driver; mConnection = connection; Log.w("CALIBRATION-CHECK-IN: ", "CONNECTEDDDD!!"); return true; } } else { Log.w("CALIBRATION-CHECK-IN: ", "Driver was no good"); } } Log.w("CALIBRATION-CHECK-IN: ", "No usable drivers found"); } else { Log.w("CALIBRATION-CHECK-IN: ", "You dont have permissions for that dexcom!!"); } return false; } static public boolean isG4Connected(Context c){ UsbManager manager = (UsbManager) c.getSystemService(Context.USB_SERVICE); HashMap<String, UsbDevice> deviceList = manager.getDeviceList(); Log.w("USB DEVICES = ", deviceList.toString()); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); Log.w("USB DEVICES = ", String.valueOf(deviceList.size())); while(deviceIterator.hasNext()){ UsbDevice device = deviceIterator.next(); if (device.getVendorId() == 8867 && device.getProductId() == 71 && device.getDeviceClass() == 2 && device.getDeviceSubclass() ==0 && device.getDeviceProtocol() == 0){ Log.w("CALIBRATION-CHECK-IN: ", "Dexcom Found!"); return true; } } return false; } public UsbDevice findDexcom() { Log.w("CALIBRATION-CHECK-IN: ", "Searching for dexcom"); mUsbManager = (UsbManager) getApplicationContext().getSystemService(Context.USB_SERVICE); Log.w("USB MANAGER = ", mUsbManager.toString()); HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList(); Log.w("USB DEVICES = ", deviceList.toString()); Iterator<UsbDevice> deviceIterator = deviceList.values().iterator(); Log.w("USB DEVICES = ", String.valueOf(deviceList.size())); while(deviceIterator.hasNext()){ UsbDevice device = deviceIterator.next(); if (device.getVendorId() == 8867 && device.getProductId() == 71 && device.getDeviceClass() == 2 && device.getDeviceSubclass() ==0 && device.getDeviceProtocol() == 0){ dexcom = device; Log.w("CALIBRATION-CHECK-IN: ", "Dexcom Found!"); return device; } else { Log.w("CALIBRATION-CHECK-IN: ", "that was not a dexcom (I dont think)"); } } return null; } private void broadcastSGVToUI(EGVRecord egvRecord, boolean uploadStatus, long nextUploadTime, long displayTime, JSONArray json, int batLvl) { Log.d(TAG, "Current EGV: " + egvRecord.getBGValue()); Intent broadcastIntent = new Intent(); // broadcastIntent.setAction(MainActivity.CGMStatusReceiver.PROCESS_RESPONSE); broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT); broadcastIntent.putExtra(RESPONSE_SGV, egvRecord.getBGValue()); broadcastIntent.putExtra(RESPONSE_TREND, egvRecord.getTrend().getID()); broadcastIntent.putExtra(RESPONSE_TIMESTAMP, egvRecord.getDisplayTime().getTime()); broadcastIntent.putExtra(RESPONSE_NEXT_UPLOAD_TIME, nextUploadTime); broadcastIntent.putExtra(RESPONSE_UPLOAD_STATUS, uploadStatus); broadcastIntent.putExtra(RESPONSE_DISPLAY_TIME, displayTime); if (json!=null) broadcastIntent.putExtra(RESPONSE_JSON, json.toString()); broadcastIntent.putExtra(RESPONSE_BAT, batLvl); sendBroadcast(broadcastIntent); } private void broadcastSGVToUI() { EGVRecord record=new EGVRecord(-1, Constants.TREND_ARROW_VALUES.NONE,new Date(),new Date()); broadcastSGVToUI(record,false, (long) (1000 * 60 * 5) + TIME_SYNC_OFFSET, new Date().getTime(), null, 0); } }