package info.nightscout.androidaps.plugins.DanaR.Services; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Binder; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Calendar; import java.util.Date; import java.util.Set; import java.util.UUID; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.plugins.DanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.DanaR.DanaRPump; import info.nightscout.androidaps.plugins.DanaR.SerialIOThread; import info.nightscout.androidaps.plugins.DanaR.comm.MessageBase; import info.nightscout.androidaps.plugins.DanaR.comm.MsgBolusProgress; import info.nightscout.androidaps.plugins.DanaR.comm.MsgBolusStart; import info.nightscout.androidaps.plugins.DanaR.comm.MsgBolusStop; import info.nightscout.androidaps.plugins.DanaR.comm.MsgCheckValue; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryAlarm; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryBasalHour; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryBolus; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryCarbo; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryDailyInsulin; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryDone; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryError; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryGlucose; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistoryRefill; import info.nightscout.androidaps.plugins.DanaR.comm.MsgHistorySuspend; import info.nightscout.androidaps.plugins.DanaR.comm.MsgPCCommStart; import info.nightscout.androidaps.plugins.DanaR.comm.MsgPCCommStop; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetActivateBasalProfile; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetBasalProfile; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetCarbsEntry; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetExtendedBolusStart; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetExtendedBolusStop; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetTempBasalStart; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetTempBasalStop; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSetTime; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingActiveProfile; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingBasal; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingMeal; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingGlucose; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingMaxValues; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingProfileRatios; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingProfileRatiosAll; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingPumpTime; import info.nightscout.androidaps.plugins.DanaR.comm.MsgSettingShippingInfo; import info.nightscout.androidaps.plugins.DanaR.comm.MsgStatus; import info.nightscout.androidaps.plugins.DanaR.comm.MsgStatusBasic; import info.nightscout.androidaps.plugins.DanaR.comm.MsgStatusBolusExtended; import info.nightscout.androidaps.plugins.DanaR.comm.MsgStatusTempBasal; import info.nightscout.androidaps.plugins.DanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.DanaR.events.EventDanaRBolusStart; import info.nightscout.androidaps.plugins.DanaR.events.EventDanaRNewStatus; import info.nightscout.androidaps.plugins.Overview.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile; import info.nightscout.utils.SP; import info.nightscout.utils.SafeParse; import info.nightscout.utils.ToastUtils; public class ExecutionService extends Service { private static Logger log = LoggerFactory.getLogger(ExecutionService.class); private String devName; private SerialIOThread mSerialIOThread; private BluetoothSocket mRfcommSocket; private BluetoothDevice mBTDevice; private PowerManager.WakeLock mWakeLock; private IBinder mBinder = new LocalBinder(); private DanaRPump danaRPump; private Treatment bolusingTreatment = null; private static Boolean connectionInProgress = false; private static final Object connectionLock = new Object(); private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); String action = intent.getAction(); if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { log.debug("Device has disconnected " + device.getName());//Device has disconnected if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) { if (mSerialIOThread != null) { mSerialIOThread.disconnect("BT disconnection broadcast"); } MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); } } } }; public ExecutionService() { registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); danaRPump = DanaRPlugin.getDanaRPump(); PowerManager powerManager = (PowerManager) MainApp.instance().getApplicationContext().getSystemService(Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExecutionService"); } public class LocalBinder extends Binder { public ExecutionService getServiceInstance() { return ExecutionService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } private void registerBus() { try { MainApp.bus().unregister(this); } catch (RuntimeException x) { // Ignore } MainApp.bus().register(this); } @Subscribe public void onStatusEvent(EventAppExit event) { if (Config.logFunctionCalls) log.debug("EventAppExit received"); if (mSerialIOThread != null) mSerialIOThread.disconnect("Application exit"); MainApp.instance().getApplicationContext().unregisterReceiver(receiver); stopSelf(); if (Config.logFunctionCalls) log.debug("EventAppExit finished"); } public boolean isConnected() { return mRfcommSocket != null && mRfcommSocket.isConnected(); } public boolean isConnecting() { return connectionInProgress; } public void disconnect(String from) { if (mSerialIOThread != null) mSerialIOThread.disconnect(from); } public void connect(String from) { if (danaRPump.password != -1 && danaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.wrongpumppassword), R.raw.error); return; } while (isConnected() || isConnecting()) { if (Config.logDanaBTComm) log.debug("already connected/connecting from: " + from); waitMsec(3000); } final long maxConnectionTime = 5 * 60 * 1000L; // 5 min synchronized (connectionLock) { //log.debug("entering connection while loop"); connectionInProgress = true; mWakeLock.acquire(); getBTSocketForSelectedPump(); if (mRfcommSocket == null || mBTDevice == null) return; // Device not found long startTime = new Date().getTime(); while (!isConnected() && startTime + maxConnectionTime >= new Date().getTime()) { long secondsElapsed = (new Date().getTime() - startTime) / 1000L; MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); if (Config.logDanaBTComm) log.debug("connect waiting " + secondsElapsed + "sec from: " + from); try { mRfcommSocket.connect(); } catch (IOException e) { //e.printStackTrace(); if (e.getMessage().contains("socket closed")) { e.printStackTrace(); break; } } waitMsec(1000); if (isConnected()) { if (mSerialIOThread != null) { mSerialIOThread.disconnect("Recreate SerialIOThread"); } mSerialIOThread = new SerialIOThread(mRfcommSocket); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED, 0)); if (!getPumpStatus()) { mSerialIOThread.disconnect("getPumpStatus failed"); waitMsec(3000); if (!MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginBase.PUMP)) return; getBTSocketForSelectedPump(); startTime = new Date().getTime(); } } } if (!isConnected()) { MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); log.error("Pump connection timed out"); } connectionInProgress = false; mWakeLock.release(); } } private void getBTSocketForSelectedPump() { devName = SP.getString(MainApp.sResources.getString(R.string.key_danar_bt_name), ""); BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter != null) { Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : bondedDevices) { if (devName.equals(device.getName())) { mBTDevice = device; try { mRfcommSocket = mBTDevice.createRfcommSocketToServiceRecord(SPP_UUID); } catch (IOException e) { log.error("Error creating socket: ", e); } break; } } } else { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.nobtadapter)); } if (mBTDevice == null) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.devicenotfound)); } } @Subscribe public void onStatusEvent(final EventPreferenceChange pch) { if (mSerialIOThread != null) mSerialIOThread.disconnect("EventPreferenceChange"); } private boolean getPumpStatus() { try { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpstatus))); MsgStatus statusMsg = new MsgStatus(); MsgStatusBasic statusBasicMsg = new MsgStatusBasic(); MsgStatusTempBasal tempStatusMsg = new MsgStatusTempBasal(); MsgStatusBolusExtended exStatusMsg = new MsgStatusBolusExtended(); MsgCheckValue checkValue = new MsgCheckValue(); if (danaRPump.isNewPump) { mSerialIOThread.sendMessage(checkValue); if (!checkValue.received) { return false; } } mSerialIOThread.sendMessage(tempStatusMsg); // do this before statusBasic because here is temp duration mSerialIOThread.sendMessage(exStatusMsg); mSerialIOThread.sendMessage(statusMsg); mSerialIOThread.sendMessage(statusBasicMsg); if (!statusMsg.received) { mSerialIOThread.sendMessage(statusMsg); } if (!statusBasicMsg.received) { mSerialIOThread.sendMessage(statusBasicMsg); } if (!tempStatusMsg.received) { // Load of status of current basal rate failed, give one more try mSerialIOThread.sendMessage(tempStatusMsg); } if (!exStatusMsg.received) { // Load of status of current extended bolus failed, give one more try mSerialIOThread.sendMessage(exStatusMsg); } // Check we have really current status of pump if (!statusMsg.received || !statusBasicMsg.received || !tempStatusMsg.received || !exStatusMsg.received) { waitMsec(10 * 1000); log.debug("getPumpStatus failed"); return false; } Date now = new Date(); if (danaRPump.lastSettingsRead.getTime() + 60 * 60 * 1000L < now.getTime() || !((DanaRPlugin)MainApp.getSpecificPlugin(DanaRPlugin.class)).isInitialized()) { mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingActiveProfile()); mSerialIOThread.sendMessage(new MsgSettingMeal()); mSerialIOThread.sendMessage(new MsgSettingBasal()); //0x3201 mSerialIOThread.sendMessage(new MsgSettingMaxValues()); mSerialIOThread.sendMessage(new MsgSettingGlucose()); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); mSerialIOThread.sendMessage(new MsgSettingActiveProfile()); mSerialIOThread.sendMessage(new MsgSettingProfileRatios()); mSerialIOThread.sendMessage(new MsgSettingProfileRatiosAll()); mSerialIOThread.sendMessage(new MsgSetTime(new Date())); danaRPump.lastSettingsRead = now; } danaRPump.lastConnection = now; MainApp.bus().post(new EventDanaRNewStatus()); MainApp.bus().post(new EventInitializationChanged()); MainApp.getConfigBuilder().uploadDeviceStatus(); if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning ) { log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.sResources.getString(R.string.approachingdailylimit), Notification.URGENT); MainApp.bus().post(new EventNewNotification(reportFail)); MainApp.getConfigBuilder().uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); } } catch (Exception e) { e.printStackTrace(); } return true; } public boolean tempBasal(int percent, int durationInHours) { connect("tempBasal"); if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); mSerialIOThread.sendMessage(new MsgStatusTempBasal()); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } public boolean tempBasalStop() { connect("tempBasalStop"); if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); mSerialIOThread.sendMessage(new MsgStatusTempBasal()); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } public boolean extendedBolus(double insulin, int durationInHalfHours) { connect("extendedBolus"); if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStart(insulin, (byte) (durationInHalfHours & 0xFF))); mSerialIOThread.sendMessage(new MsgStatusBolusExtended()); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } public boolean extendedBolusStop() { connect("extendedBolusStop"); if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStop()); mSerialIOThread.sendMessage(new MsgStatusBolusExtended()); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } public boolean bolus(Double amount, int carbs, Treatment t) { bolusingTreatment = t; MsgBolusStart start = new MsgBolusStart(amount); MsgBolusStop stop = new MsgBolusStop(amount, t); connect("bolus"); if (!isConnected()) return false; if (carbs > 0) { Calendar time = Calendar.getInstance(); mSerialIOThread.sendMessage(new MsgSetCarbsEntry(time, carbs)); } MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables MainApp.bus().post(new EventDanaRBolusStart()); if (!stop.stopped) { mSerialIOThread.sendMessage(start); } else { t.insulin = 0d; return false; } while (!stop.stopped && !start.failed) { waitMsec(100); if ((new Date().getTime() - progress.lastReceive) > 5 * 1000L) { // if i didn't receive status for more than 5 sec expecting broken comm stop.stopped = true; stop.forced = true; log.debug("Communication stopped"); } } waitMsec(300); bolusingTreatment = null; getPumpStatus(); return true; } public void bolusStop() { if (Config.logDanaBTComm) log.debug("bolusStop >>>>> @ " + (bolusingTreatment == null ? "" : bolusingTreatment.insulin)); MsgBolusStop stop = new MsgBolusStop(); stop.forced = true; if (isConnected()) { mSerialIOThread.sendMessage(stop); while (!stop.stopped) { mSerialIOThread.sendMessage(stop); waitMsec(200); } } else { stop.stopped = true; } } public boolean carbsEntry(int amount) { connect("carbsEntry"); if (!isConnected()) return false; Calendar time = Calendar.getInstance(); MsgSetCarbsEntry msg = new MsgSetCarbsEntry(time, amount); mSerialIOThread.sendMessage(msg); return true; } public boolean loadHistory(byte type) { connect("loadHistory"); if (!isConnected()) return false; MessageBase msg = null; switch (type) { case RecordTypes.RECORD_TYPE_ALARM: msg = new MsgHistoryAlarm(); break; case RecordTypes.RECORD_TYPE_BASALHOUR: msg = new MsgHistoryBasalHour(); break; case RecordTypes.RECORD_TYPE_BOLUS: msg = new MsgHistoryBolus(); break; case RecordTypes.RECORD_TYPE_CARBO: msg = new MsgHistoryCarbo(); break; case RecordTypes.RECORD_TYPE_DAILY: msg = new MsgHistoryDailyInsulin(); break; case RecordTypes.RECORD_TYPE_ERROR: msg = new MsgHistoryError(); break; case RecordTypes.RECORD_TYPE_GLUCOSE: msg = new MsgHistoryGlucose(); break; case RecordTypes.RECORD_TYPE_REFILL: msg = new MsgHistoryRefill(); break; case RecordTypes.RECORD_TYPE_SUSPEND: msg = new MsgHistorySuspend(); break; } MsgHistoryDone done = new MsgHistoryDone(); mSerialIOThread.sendMessage(new MsgPCCommStart()); waitMsec(400); mSerialIOThread.sendMessage(msg); while (!done.received && mRfcommSocket.isConnected()) { waitMsec(100); } waitMsec(200); mSerialIOThread.sendMessage(new MsgPCCommStop()); return true; } public boolean updateBasalsInPump(final NSProfile profile) { connect("updateBasalsInPump"); if (!isConnected()) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.updatingbasalrates))); double[] basal = buildDanaRProfileRecord(profile); MsgSetBasalProfile msgSet = new MsgSetBasalProfile((byte) 0, basal); mSerialIOThread.sendMessage(msgSet); MsgSetActivateBasalProfile msgActivate = new MsgSetActivateBasalProfile((byte) 0); mSerialIOThread.sendMessage(msgActivate); danaRPump.lastSettingsRead = new Date(0); // force read full settings getPumpStatus(); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } private double[] buildDanaRProfileRecord(NSProfile nsProfile) { double[] record = new double[24]; for (Integer hour = 0; hour < 24; hour++) { double value = nsProfile.getBasal(hour * 60 * 60); if (Config.logDanaMessageDetail) log.debug("NS basal value for " + hour + ":00 is " + value); record[hour] = value; } return record; } private void waitMsec(long msecs) { try { Thread.sleep(msecs); } catch (InterruptedException e) { e.printStackTrace(); } } }