diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d44c35b0b3..d3adf0b799 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,7 @@ + @@ -78,33 +79,33 @@ + android:name=".receivers.DataReceiver" + android:enabled="true" + android:exported="true"> - + - + - + - + - + - + + android:name=".receivers.SmsReceiver" + android:enabled="true" + android:exported="true" + android:permission="android.permission.BROADCAST_SMS"> - + @@ -289,6 +290,7 @@ android:name=".plugins.pump.medtronic.service.RileyLinkMedtronicService" android:enabled="true" android:exported="true" /> + @@ -301,7 +303,16 @@ android:theme="@style/Theme.AppCompat.NoTitle" /> - + + + + + diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 734c7dd0ed..d2759e04ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -1,14 +1,10 @@ package info.nightscout.androidaps; import android.app.Application; -import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.os.SystemClock; -import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.PluralsRes; @@ -68,8 +64,6 @@ import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin; import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin; import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; -import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; -import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; @@ -77,6 +71,7 @@ import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin; import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; @@ -187,7 +182,10 @@ public class MainApp extends Application { if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin()); pluginsList.add(CareportalPlugin.getPlugin()); if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin()); - if (Config.PUMPDRIVERS && engineeringMode) pluginsList.add(MedtronicPumpPlugin.getPlugin()); + if (Config.PUMPDRIVERS && engineeringMode) + pluginsList.add(MedtronicPumpPlugin.getPlugin()); + if (Config.PUMPDRIVERS && engineeringMode) + pluginsList.add(OmnipodPumpPlugin.getPlugin()); if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin()); pluginsList.add(VirtualPumpPlugin.getPlugin()); if (Config.APS) pluginsList.add(LoopPlugin.getPlugin()); @@ -461,7 +459,7 @@ public class MainApp extends Application { unregisterReceiver(btReceiver); } - if (timeDateOrTZChangeReceiver!=null) { + if (timeDateOrTZChangeReceiver != null) { unregisterReceiver(timeDateOrTZChangeReceiver); } diff --git a/app/src/main/java/info/nightscout/androidaps/data/Profile.java b/app/src/main/java/info/nightscout/androidaps/data/Profile.java index 8524246d02..e1d462c821 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -23,6 +23,7 @@ import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.MidnightTime; +import info.nightscout.androidaps.utils.Round; public class Profile { private static Logger log = LoggerFactory.getLogger(Profile.class); @@ -475,6 +476,17 @@ public class Profile { public int timeAsSeconds; public double value; + + public boolean equals(Object otherObject) { + if (!(otherObject instanceof ProfileValue)) { + return false; + } + + ProfileValue otherProfileValue = (ProfileValue) otherObject; + + return (timeAsSeconds == otherProfileValue.timeAsSeconds) && Round.isSame(value, otherProfileValue.value); + + } } public synchronized ProfileValue[] getBasalValues() { @@ -665,4 +677,27 @@ public class Profile { public int getTimeshift() { return timeshift; } + + + public boolean isProfileTheSame(Profile otherProfile) { + + if (!Round.isSame(this.baseBasalSum(), otherProfile.baseBasalSum())) + return false; + + ProfileValue[] basalValues = this.getBasalValues(); + ProfileValue[] otherBasalValues = otherProfile.getBasalValues(); + + if (basalValues.length != otherBasalValues.length) + return false; + + for (int i = 0; i < basalValues.length; i++) { + if (!basalValues[i].equals(otherBasalValues[i])) { + return false; + } + } + + return true; + } + + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.java index ff6b59c45c..33de29a745 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/PumpPluginAbstract.java @@ -1,18 +1,18 @@ package info.nightscout.androidaps.plugins.pump.common; -import java.util.Date; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import com.squareup.otto.Subscribe; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -22,12 +22,14 @@ import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.events.EventCustomActionsChanged; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus; import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState; @@ -48,9 +50,9 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); protected static final PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult().success(false) - .enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump_driver)); + .enacted(false).comment(MainApp.gs(R.string.pump_operation_not_supported_by_pump_driver)); protected static final PumpEnactResult OPERATION_NOT_YET_SUPPORTED = new PumpEnactResult().success(false) - .enacted(false).comment(MainApp.gs(R.string.pump_operation_not_yet_supported_by_pump)); + .enacted(false).comment(MainApp.gs(R.string.pump_operation_not_yet_supported_by_pump)); protected PumpDescription pumpDescription = new PumpDescription(); protected PumpStatus pumpStatus; @@ -231,7 +233,7 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter @Override public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, - boolean enforceNew) { + boolean enforceNew) { if (isLoggingEnabled()) LOG.warn("setTempBasalAbsolute [PumpPluginAbstract] - Not implemented."); return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver); @@ -240,7 +242,7 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter @Override public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, - boolean enforceNew) { + boolean enforceNew) { if (isLoggingEnabled()) LOG.warn("setTempBasalPercent [PumpPluginAbstract] - Not implemented."); return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver); @@ -294,8 +296,8 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter // Short info for SMS, Wear etc public boolean isFakingTempsByExtendedBoluses() { - if (isLoggingEnabled()) - LOG.warn("isFakingTempsByExtendedBoluses [PumpPluginAbstract] - Not implemented."); + //if (isLoggingEnabled()) + // LOG.warn("isFakingTempsByExtendedBoluses [PumpPluginAbstract] - Not implemented."); return false; } @@ -332,7 +334,7 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (tb != null) { extended.put("TempBasalAbsoluteRate", - tb.tempBasalConvertedToAbsolute(System.currentTimeMillis(), profile)); + tb.tempBasalConvertedToAbsolute(System.currentTimeMillis(), profile)); extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); } @@ -364,20 +366,20 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter String ret = ""; if (pumpStatus.lastConnection != 0) { Long agoMsec = System.currentTimeMillis() - pumpStatus.lastConnection; - int agoMin = (int)(agoMsec / 60d / 1000d); + int agoMin = (int) (agoMsec / 60d / 1000d); ret += "LastConn: " + agoMin + " min ago\n"; } if (pumpStatus.lastBolusTime != null && pumpStatus.lastBolusTime.getTime() != 0) { ret += "LastBolus: " + DecimalFormatter.to2Decimal(pumpStatus.lastBolusAmount) + "U @" + // - android.text.format.DateFormat.format("HH:mm", pumpStatus.lastBolusTime) + "\n"; + android.text.format.DateFormat.format("HH:mm", pumpStatus.lastBolusTime) + "\n"; } TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin() - .getRealTempBasalFromHistory(System.currentTimeMillis()); + .getRealTempBasalFromHistory(System.currentTimeMillis()); if (activeTemp != null) { ret += "Temp: " + activeTemp.toStringFull() + "\n"; } ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory( - System.currentTimeMillis()); + System.currentTimeMillis()); if (activeExtendedBolus != null) { ret += "Extended: " + activeExtendedBolus.toString() + "\n"; } @@ -401,7 +403,7 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter if (isLoggingEnabled()) LOG.error("deliverTreatment: Invalid input"); return new PumpEnactResult().success(false).enacted(false).bolusDelivered(0d).carbsDelivered(0d) - .comment(MainApp.gs(R.string.danar_invalidinput)); + .comment(MainApp.gs(R.string.danar_invalidinput)); } else if (detailedBolusInfo.insulin > 0) { // bolus needed, ask pump to deliver it return deliverBolus(detailedBolusInfo); @@ -419,7 +421,7 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter LOG.debug("deliverTreatment: Carb only treatment."); return new PumpEnactResult().success(true).enacted(true).bolusDelivered(0d) - .carbsDelivered(detailedBolusInfo.carbs).comment(MainApp.gs(R.string.virtualpump_resultok)); + .carbsDelivered(detailedBolusInfo.carbs).comment(MainApp.gs(R.string.virtualpump_resultok)); } } finally { triggerUIChange(); @@ -443,4 +445,32 @@ public abstract class PumpPluginAbstract extends PluginBase implements PumpInter return new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(resourceId)); } -} + + @Override + public boolean canHandleDST() { + return false; + } + + + @Override + public ManufacturerType manufacturer() { + return pumpStatus.pumpType.getManufacturer(); + } + + @Override + public PumpType model() { + return pumpStatus.pumpType; + } + + + @Override + public void timeDateOrTimeZoneChanged() { + } + + + public void refreshCustomActionsList() { + MainApp.bus().post(new EventCustomActionsChanged()); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/data/TempBasalPair.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/data/TempBasalPair.java new file mode 100644 index 0000000000..1e53734b5f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/data/TempBasalPair.java @@ -0,0 +1,79 @@ +package info.nightscout.androidaps.plugins.pump.common.data; + +import com.google.gson.annotations.Expose; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; + +public class TempBasalPair { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + @Expose + protected double insulinRate = 0.0d; + @Expose + protected int durationMinutes = 0; + @Expose + protected boolean isPercent = false; + + private Long start; + private Long end; + + public TempBasalPair() { + } + + + public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) { + this.insulinRate = insulinRate; + this.isPercent = isPercent; + this.durationMinutes = durationMinutes; + } + + + public double getInsulinRate() { + return insulinRate; + } + + + public void setInsulinRate(double insulinRate) { + this.insulinRate = insulinRate; + } + + + public int getDurationMinutes() { + return durationMinutes; + } + + + public void setDurationMinutes(int durationMinutes) { + this.durationMinutes = durationMinutes; + } + + + public boolean isPercent() { + return isPercent; + } + + + public void setIsPercent(boolean yesIsPercent) { + this.isPercent = yesIsPercent; + } + + public void setStartTime(Long startTime) { + this.start = startTime; + } + + + public void setEndTime(Long endTime) { + this.end = endTime; + } + + + @Override + public String toString() { + return "TempBasalPair [" + "Rate=" + insulinRate + ", DurationMinutes=" + durationMinutes + ", IsPercent=" + + isPercent + "]"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java index 69837afd7c..a0df5790b9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java @@ -3,8 +3,6 @@ package info.nightscout.androidaps.plugins.pump.common.hw.rileylink; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import android.content.Context; - import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus; import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; @@ -38,7 +36,6 @@ public abstract class RileyLinkCommunicationManager { private static final int ALLOWED_PUMP_UNREACHABLE = 10 * 60 * 1000; // 10 minutes protected final RFSpy rfspy; - protected final Context context; protected int receiverDeviceAwakeForMinutes = 1; // override this in constructor of specific implementation protected String receiverDeviceID; // String representation of receiver device (ex. Pump (xxxxxx) or Pod (yyyyyy)) protected long lastGoodReceiverCommunicationTime = 0; @@ -51,8 +48,7 @@ public abstract class RileyLinkCommunicationManager { private int timeoutCount = 0; - public RileyLinkCommunicationManager(Context context, RFSpy rfspy) { - this.context = context; + public RileyLinkCommunicationManager(RFSpy rfspy) { this.rfspy = rfspy; this.rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData(); RileyLinkUtil.setRileyLinkCommunicationManager(this); @@ -125,7 +121,6 @@ public abstract class RileyLinkCommunicationManager { } - // FIXME change wakeup // TODO we might need to fix this. Maybe make pump awake for shorter time (battery factor for pump) - Andy public void wakeUp(int duration_minutes, boolean force) { @@ -143,8 +138,8 @@ public abstract class RileyLinkCommunicationManager { LOG.info("Waking pump..."); byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple - RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)200, - (byte)0, (byte)0, 25000, (byte)0); + RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, + (byte) 0, (byte) 0, 25000, (byte) 0); if (isLogEnabled()) LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw())); @@ -225,8 +220,8 @@ public abstract class RileyLinkCommunicationManager { for (int j = 0; j < tries; j++) { byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); - RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte)0, (byte)0, - (byte)0, (byte)0, 1250, (byte)0); + RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 0, + (byte) 0, (byte) 0, 1250, (byte) 0); if (resp.wasTimeout()) { LOG.error("scanForPump: Failed to find pump at frequency {}", frequencies[i]); } else if (resp.looksLikeRadioPacket()) { @@ -258,7 +253,7 @@ public abstract class RileyLinkCommunicationManager { trial.tries++; } sumRSSI += -99.0 * (trial.tries - trial.successes); - trial.averageRSSI2 = (double)(sumRSSI) / (double)(trial.tries); + trial.averageRSSI2 = (double) (sumRSSI) / (double) (trial.tries); trial.calculateAverage(); @@ -273,7 +268,7 @@ public abstract class RileyLinkCommunicationManager { FrequencyTrial one = results.trials.get(k); stringBuilder.append(String.format("Scan Result[%s]: Freq=%s, avg RSSI = %s\n", "" + k, "" - + one.frequencyMHz, "" + one.averageRSSI + ", RSSIs =" + one.rssiList)); + + one.frequencyMHz, "" + one.averageRSSI + ", RSSIs =" + one.rssiList)); } LOG.info(stringBuilder.toString()); @@ -315,7 +310,7 @@ public abstract class RileyLinkCommunicationManager { // RLMessage msg = makeRLMessage(RLMessageType.ReadSimpleData); byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); RadioPacket pkt = new RadioPacket(pumpMsgContent); - RFSpyResponse resp = rfspy.transmitThenReceive(pkt, (byte)0, (byte)0, (byte)0, (byte)0, SCAN_TIMEOUT, (byte)0); + RFSpyResponse resp = rfspy.transmitThenReceive(pkt, (byte) 0, (byte) 0, (byte) 0, (byte) 0, SCAN_TIMEOUT, (byte) 0); if (resp.wasTimeout()) { LOG.warn("tune_tryFrequency: no pump response at frequency {}", freqMHz); } else if (resp.looksLikeRadioPacket()) { @@ -327,8 +322,8 @@ public abstract class RileyLinkCommunicationManager { LOG.warn("tune_tryFrequency: saw response level {} at frequency {}", radioResponse.rssi, freqMHz); return calculateRssi(radioResponse.rssi); } else { - LOG.warn("tune_tryFrequency: invalid radio response:" - + ByteUtil.shortHexString(radioResponse.getPayload())); + LOG.warn("tune_tryFrequency: invalid radio response:" + + ByteUtil.shortHexString(radioResponse.getPayload())); } } catch (RileyLinkCommunicationException e) { @@ -350,7 +345,7 @@ public abstract class RileyLinkCommunicationManager { // Try again at larger step size stepsize += 0.05; } else { - if ((int)(evenBetterFrequency * 100) == (int)(betterFrequency * 100)) { + if ((int) (evenBetterFrequency * 100) == (int) (betterFrequency * 100)) { // value did not change, so we're done. break; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java index a887fede5c..80cad6fa9a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java @@ -8,6 +8,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLin import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; /** * Created by andy on 5/19/18. @@ -23,10 +24,11 @@ public class RLHistoryItem { private RileyLinkTargetDevice targetDevice; private PumpDeviceState pumpDeviceState; + private OmnipodCommandType omnipodCommandType; public RLHistoryItem(RileyLinkServiceState serviceState, RileyLinkError errorCode, - RileyLinkTargetDevice targetDevice) { + RileyLinkTargetDevice targetDevice) { this.targetDevice = targetDevice; this.dateTime = new LocalDateTime(); this.serviceState = serviceState; @@ -50,6 +52,13 @@ public class RLHistoryItem { } + public RLHistoryItem(OmnipodCommandType omnipodCommandType) { + this.dateTime = new LocalDateTime(); + this.omnipodCommandType = omnipodCommandType; + source = RLHistoryItemSource.OmnipodCommand; + } + + public LocalDateTime getDateTime() { return dateTime; } @@ -71,7 +80,7 @@ public class RLHistoryItem { switch (this.source) { case RileyLink: return "State: " + MainApp.gs(serviceState.getResourceId(targetDevice)) - + (this.errorCode == null ? "" : ", Error Code: " + errorCode); + + (this.errorCode == null ? "" : ", Error Code: " + errorCode); case MedtronicPump: return MainApp.gs(pumpDeviceState.getResourceId()); @@ -79,6 +88,9 @@ public class RLHistoryItem { case MedtronicCommand: return medtronicCommandType.name(); + case OmnipodCommand: + return omnipodCommandType.name(); + default: return "Unknown Description"; } @@ -97,7 +109,8 @@ public class RLHistoryItem { public enum RLHistoryItemSource { RileyLink("RileyLink"), // MedtronicPump("Medtronic"), // - MedtronicCommand("Medtronic"); + MedtronicCommand("Medtronic"), // + OmnipodCommand("Omnipod"); private String desc; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java index f67082cd93..621ed52f81 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java @@ -33,7 +33,6 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.events.EventCustomActionsChanged; import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; @@ -1560,11 +1559,6 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter this.hasTimeDateOrTimeZoneChanged = true; } - private void refreshCustomActionsList() { - MainApp.bus().post(new EventCustomActionsChanged()); - } - - public void setEnableCustomAction(MedtronicCustomActionType customAction, boolean isEnabled) { if (customAction == MedtronicCustomActionType.ClearBolusBlock) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java index d53d59197e..fc6672b9a5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java @@ -1,6 +1,5 @@ package info.nightscout.androidaps.plugins.pump.medtronic.comm; -import android.content.Context; import android.os.SystemClock; import org.joda.time.LocalDateTime; @@ -71,8 +70,8 @@ public class MedtronicCommunicationManager extends RileyLinkCommunicationManager private boolean doWakeUpBeforeCommand = true; - public MedtronicCommunicationManager(Context context, RFSpy rfspy) { - super(context, rfspy); + public MedtronicCommunicationManager(RFSpy rfspy) { + super(rfspy); medtronicCommunicationManager = this; this.medtronicConverter = new MedtronicConverter(); this.pumpHistoryDecoder = new MedtronicPumpHistoryDecoder(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java index 1c45cdbcef..2ad5e6897a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java @@ -1,7 +1,5 @@ package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; -import com.google.gson.annotations.Expose; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,18 +16,10 @@ import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; *

* Just need a class to keep the pair together, for parcel transport. */ -public class TempBasalPair { +public class TempBasalPair extends info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair { private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); - @Expose - private double insulinRate = 0.0d; - @Expose - private int durationMinutes = 0; - @Expose - private boolean isPercent = false; - - public TempBasalPair() { } @@ -42,6 +32,8 @@ public class TempBasalPair { * @param isPercent */ public TempBasalPair(byte rateByte, int startTimeByte, boolean isPercent) { + super(); + int rateInt = ByteUtil.asUINT8(rateByte); if (isPercent) @@ -53,13 +45,6 @@ public class TempBasalPair { } - public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) { - this.insulinRate = insulinRate; - this.isPercent = isPercent; - this.durationMinutes = durationMinutes; - } - - public TempBasalPair(byte[] response) { if (L.isEnabled(L.PUMPCOMM)) @@ -80,33 +65,8 @@ public class TempBasalPair { } - public double getInsulinRate() { - return insulinRate; - } - - - public void setInsulinRate(double insulinRate) { - this.insulinRate = insulinRate; - } - - - public int getDurationMinutes() { - return durationMinutes; - } - - - public void setDurationMinutes(int durationMinutes) { - this.durationMinutes = durationMinutes; - } - - - public boolean isPercent() { - return isPercent; - } - - - public void setIsPercent(boolean yesIsPercent) { - this.isPercent = yesIsPercent; + public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) { + super(insulinRate, isPercent, durationMinutes); } @@ -160,10 +120,4 @@ public class TempBasalPair { } } - - @Override - public String toString() { - return "TempBasalPair [" + "Rate=" + insulinRate + ", DurationMinutes=" + durationMinutes + ", IsPercent=" - + isPercent + "]"; - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java index cba33353f1..49d49ee06c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java @@ -109,7 +109,7 @@ public class RileyLinkMedtronicService extends RileyLinkService { RileyLinkUtil.setRileyLinkBLE(rileyLinkBLE); // init rileyLinkCommunicationManager - medtronicCommunicationManager = new MedtronicCommunicationManager(context, rfspy); + medtronicCommunicationManager = new MedtronicCommunicationManager(rfspy); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.java new file mode 100644 index 0000000000..343849729b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.java @@ -0,0 +1,514 @@ +package info.nightscout.androidaps.plugins.pump.omnipod; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.text.Spanned; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.crashlytics.android.Crashlytics; +import com.joanzapata.iconify.widget.IconTextView; +import com.squareup.otto.Subscribe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.events.EventExtendedBolusChange; +import info.nightscout.androidaps.events.EventPumpStatusChanged; +import info.nightscout.androidaps.events.EventTempBasalChange; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodDeviceStatusChange; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.service.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.queue.events.EventQueueChanged; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.SetWarnColor; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodFragment extends SubscriberFragment { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + @BindView(R.id.omnipod_lastconnection) + TextView lastConnectionView; + @BindView(R.id.omnipod_lastbolus) + TextView lastBolusView; + @BindView(R.id.omnipod_basabasalrate) + TextView basaBasalRateView; + + @BindView(R.id.omnipod_tempbasal) + TextView tempBasalView; + @BindView(R.id.omnipod_pumpstate_battery) + TextView batteryView; + @BindView(R.id.omnipod_rl_status) + IconTextView rileyLinkStatus; + @BindView(R.id.omnipod_reservoir) + TextView reservoirView; + @BindView(R.id.omnipod_errors) + TextView errorsView; + @BindView(R.id.omnipod_queue) + TextView queueView; + @BindView(R.id.overview_pumpstatuslayout_omnipod) + LinearLayout pumpStatusLayout; + @BindView(R.id.overview_pump_omnipod) + TextView overviewPumpOmnipodView; + @BindView(R.id.omnipod_pod_status) + IconTextView pumpStatusIconView; + @BindView(R.id.omnipod_refresh) + Button refreshButton; + private Handler loopHandler = new Handler(); + private static Activity localActivity; + + static Button refreshButtonStatic; + + private Runnable refreshLoop = new Runnable() { + + @Override + public void run() { + updateGUI(); + loopHandler.postDelayed(refreshLoop, 60 * 1000L); + } + }; + + + public OmnipodFragment() { + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + loopHandler.postDelayed(refreshLoop, 60 * 1000L); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + loopHandler.removeCallbacks(refreshLoop); + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + try { + View view = inflater.inflate(R.layout.omnipod_fragment, container, false); + unbinder = ButterKnife.bind(this, view); + + overviewPumpOmnipodView.setBackgroundColor(MainApp.sResources.getColor(R.color.colorInitializingBorder)); + + rileyLinkStatus.setText(getTranslation(RileyLinkServiceState.NotStarted.getResourceId(getTargetDevice()))); + rileyLinkStatus.setTextSize(14); + + pumpStatusIconView.setTextColor(Color.WHITE); + pumpStatusIconView.setTextSize(14); + pumpStatusIconView.setText("{fa-bed}"); + + refreshButtonStatic = refreshButton; + + return view; + } catch (Exception e) { + Crashlytics.logException(e); + } + + return null; + } + + + @OnClick(R.id.omnipod_pod_mgmt) + void onHistoryClick() { + // TODO +// if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { +// startActivity(new Intent(getContext(), MedtronicHistoryActivity.class)); +// } else { +// MedtronicUtil.displayNotConfiguredDialog(getContext()); +// } + } + + + @OnClick(R.id.omnipod_refresh) + void onRefreshClick() { + // TODO +// if (!MedtronicUtil.getPumpStatus().verifyConfiguration()) { +// MedtronicUtil.displayNotConfiguredDialog(getContext()); +// return; +// } +// +// if (refreshButtonStatic != null) +// refreshButtonStatic.setEnabled(false); +// +// MedtronicPumpPlugin.getPlugin().resetStatusState(); +// +// ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("Clicked refresh", new Callback() { +// +// @Override +// public void run() { +// Activity activity = getActivity(); +// +// if (activity != null) { +// activity.runOnUiThread(() -> { +// if (refreshButtonStatic != null) +// refreshButtonStatic.setEnabled(true); +// }); +// } +// } +// }); + } + + + @OnClick(R.id.omnipod_stats) + void onStatsClick() { + if (OmnipodUtil.getPumpStatus().verifyConfiguration()) { + startActivity(new Intent(getContext(), RileyLinkStatusActivity.class)); + } else { + OmnipodUtil.displayNotConfiguredDialog(getContext()); + } + } + + + @Subscribe + public void onStatusEvent(final EventPumpStatusChanged c) { + updateGUI(); + } + + + public static void refreshButtonEnabled(boolean enable) { + if (localActivity != null) { + localActivity.runOnUiThread(() -> { + if (refreshButtonStatic != null) { + refreshButtonStatic.setEnabled(enable); + } + }); + } + } + + + public static Activity getCustomActivity() { + return localActivity; + } + + + @Subscribe + public void onStatusEvent(final EventOmnipodDeviceStatusChange eventStatusChange) { + if (isLogEnabled()) + LOG.info("onStatusEvent(EventOmnipodDeviceStatusChange): {}", eventStatusChange); + Activity activity = getActivity(); + + if (activity != null) { + localActivity = activity; + activity.runOnUiThread(() -> { + OmnipodPumpStatus pumpStatus = OmnipodUtil.getPumpStatus(); + setDeviceStatus(pumpStatus); + }); + } + } + + + private synchronized void setDeviceStatus(OmnipodPumpStatus pumpStatus) { + + pumpStatus.rileyLinkServiceState = (RileyLinkServiceState) checkStatusSet(pumpStatus.rileyLinkServiceState, + RileyLinkUtil.getServiceState()); + + if (pumpStatus.rileyLinkServiceState != null && rileyLinkStatus != null) { + + int resourceId = pumpStatus.rileyLinkServiceState.getResourceId(getTargetDevice()); + rileyLinkStatus.setTextColor(Color.WHITE); + rileyLinkStatus.setTextSize(14); + + if (pumpStatus.rileyLinkServiceState == RileyLinkServiceState.NotStarted) { + rileyLinkStatus.setText(getTranslation(resourceId)); + rileyLinkStatus.setTextSize(14); + } else if (pumpStatus.rileyLinkServiceState.isConnecting()) { + rileyLinkStatus.setText("{fa-bluetooth-b spin} " + getTranslation(resourceId)); + } else if (pumpStatus.rileyLinkServiceState.isError()) { + + RileyLinkError rileyLinkError = RileyLinkUtil.getError(); + + if (rileyLinkError == null) + rileyLinkStatus.setText("{fa-bluetooth-b} " + getTranslation(resourceId)); + else + rileyLinkStatus.setText("{fa-bluetooth-b} " + + getTranslation(rileyLinkError.getResourceId(RileyLinkTargetDevice.MedtronicPump))); + + rileyLinkStatus.setTextColor(Color.RED); + } else { + rileyLinkStatus.setText("{fa-bluetooth-b} " + getTranslation(resourceId)); + } + } + + pumpStatus.rileyLinkError = (RileyLinkError) checkStatusSet(pumpStatus.rileyLinkError, RileyLinkUtil.getError()); + + if (errorsView != null) { + if (pumpStatus.rileyLinkError != null) { + int resourceId = pumpStatus.rileyLinkError.getResourceId(getTargetDevice()); + errorsView.setText(getTranslation(resourceId)); + } else + errorsView.setText("-"); + } + +// pumpStatus.pumpDeviceState = (PumpDeviceState) checkStatusSet(pumpStatus.pumpDeviceState, +// MedtronicUtil.getPumpDeviceState()); +// + if (pumpStatusIconView != null) { +// +// if (pumpStatus.pumpDeviceState != null) { +// +// switch (pumpStatus.pumpDeviceState) { +// case Sleeping: +// pumpStatusIconView.setText("{fa-bed} "); // + pumpStatus.pumpDeviceState.name()); +// break; +// +// case NeverContacted: +// case WakingUp: +// case PumpUnreachable: +// case ErrorWhenCommunicating: +// case TimeoutWhenCommunicating: +// case InvalidConfiguration: +// pumpStatusIconView.setText(" " + getTranslation(pumpStatus.pumpDeviceState.getResourceId())); +// break; +// +// case Active: { +// MedtronicCommandType cmd = MedtronicUtil.getCurrentCommand(); +// +// LOG.debug("Command: " + cmd); +// +// if (cmd == null) +// pumpStatusIconView.setText(" " + MainApp.gs(pumpStatus.pumpDeviceState.getResourceId())); +// else { +// Integer resourceId = cmd.getResourceId(); +// +// if (cmd == MedtronicCommandType.GetHistoryData) { +// +// if (MedtronicUtil.frameNumber == null) { +// pumpStatusIconView.setText(MainApp.gs( +// R.string.medtronic_cmd_desc_get_history_request, MedtronicUtil.pageNumber)); +// } else { +// pumpStatusIconView.setText(MainApp.gs(resourceId, MedtronicUtil.pageNumber, +// MedtronicUtil.frameNumber)); +// } +// +// } else { +// if (resourceId == null) { +// pumpStatusIconView.setText(" " + cmd.getCommandDescription()); +// } else { +// pumpStatusIconView.setText(" " + getTranslation(resourceId)); +// } +// } +// +// } +// +// } +// break; +// +// default: +// LOG.warn("Unknown pump state: " + pumpStatus.pumpDeviceState); +// } +// } else { +// pumpStatusIconView.setText("{fa-bed} "); +// } + + // TODO + pumpStatusIconView.setText("{fa-bed} "); + } + + if (queueView != null) { + Spanned status = ConfigBuilderPlugin.getPlugin().getCommandQueue().spannedStatus(); + if (status.toString().equals("")) { + queueView.setVisibility(View.GONE); + } else { + queueView.setVisibility(View.VISIBLE); + queueView.setText(status); + } + } + + } + + + public Object checkStatusSet(Object object1, Object object2) { + if (object1 == null) { + return object2; + } else { + if (!object1.equals(object2)) { + return object2; + } else + return object1; + } + } + + + public RileyLinkTargetDevice getTargetDevice() { + return RileyLinkTargetDevice.Omnipod; + } + + + public String getTranslation(int resourceId) { + return MainApp.gs(resourceId); + } + + + @Subscribe + public void onStatusEvent(final EventOmnipodPumpValuesChanged s) { + if (isLogEnabled()) + LOG.debug("EventOmnipodPumpValuesChanged triggered"); + updateGUI(); + } + + + @Subscribe + public void onStatusEvent(final EventTempBasalChange s) { + updateGUI(); + } + + + @Subscribe + public void onStatusEvent(final EventExtendedBolusChange s) { + updateGUI(); + } + + + @Subscribe + public void onStatusEvent(final EventQueueChanged s) { + updateGUI(); + } + + + // GUI functions + @Override + protected void updateGUI() { + Activity activity = getActivity(); + if (activity != null && basaBasalRateView != null) + activity.runOnUiThread(() -> { + + if (lastConnectionView == null) // ui not yet initialized + return; + + localActivity = activity; + OmnipodPumpPlugin plugin = OmnipodPumpPlugin.getPlugin(); + OmnipodPumpStatus pumpStatus = OmnipodUtil.getPumpStatus(); + + setDeviceStatus(pumpStatus); + + // last connection + String minAgo = DateUtil.minAgo(pumpStatus.lastConnection); + long min = (System.currentTimeMillis() - pumpStatus.lastConnection) / 1000 / 60; + if (pumpStatus.lastConnection + 60 * 1000 > System.currentTimeMillis()) { + lastConnectionView.setText(R.string.combo_pump_connected_now); + lastConnectionView.setTextColor(Color.WHITE); + } else if (pumpStatus.lastConnection + 30 * 60 * 1000 < System.currentTimeMillis()) { + + if (min < 60) { + lastConnectionView.setText(MainApp.gs(R.string.minago, min)); + } else if (min < 1440) { + int h = (int) (min / 60); + + lastConnectionView.setText(MainApp.gq(R.plurals.objective_hours, h, h) + " " + + MainApp.gs(R.string.ago)); + } else { + + int h = (int) (min / 60); + int d = h / 24; + // h = h - (d * 24); + + lastConnectionView.setText(MainApp.gq(R.plurals.objective_days, d, d) + " " + + MainApp.gs(R.string.ago)); + } + lastConnectionView.setTextColor(Color.RED); + } else { + lastConnectionView.setText(minAgo); + lastConnectionView.setTextColor(Color.WHITE); + } + + // last bolus + Double bolus = pumpStatus.lastBolusAmount; + Date bolusTime = pumpStatus.lastBolusTime; + if (bolus != null && bolusTime != null) { + long agoMsc = System.currentTimeMillis() - pumpStatus.lastBolusTime.getTime(); + double bolusMinAgo = agoMsc / 60d / 1000d; + String unit = MainApp.gs(R.string.insulin_unit_shortname); + String ago; + if ((agoMsc < 60 * 1000)) { + ago = MainApp.gs(R.string.combo_pump_connected_now); + } else if (bolusMinAgo < 60) { + ago = DateUtil.minAgo(pumpStatus.lastBolusTime.getTime()); + } else { + ago = DateUtil.hourAgo(pumpStatus.lastBolusTime.getTime()); + } + lastBolusView.setText(MainApp.gs(R.string.combo_last_bolus, bolus, unit, ago)); + } else { + lastBolusView.setText(""); + } + + // base basal rate + basaBasalRateView.setText("(" + (pumpStatus.activeProfileName) + ") " + + MainApp.gs(R.string.pump_basebasalrate, plugin.getBaseBasalRate())); + + if (ConfigBuilderPlugin.getPlugin().getActivePump().isFakingTempsByExtendedBoluses()) { + if (TreatmentsPlugin.getPlugin().isInHistoryRealTempBasalInProgress()) { + tempBasalView.setText(TreatmentsPlugin.getPlugin() + .getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + } else { + tempBasalView.setText(""); + } + } else { + // v2 plugin + if (TreatmentsPlugin.getPlugin().isTempBasalInProgress()) { + tempBasalView.setText(TreatmentsPlugin.getPlugin() + .getTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); + } else { + tempBasalView.setText(""); + } + } + + // battery + // TODO this might need to be removed, I am not sure if pods have battery status + batteryView.setText("{fa-battery-" + (pumpStatus.batteryRemaining / 25) + "} "); +// if (MedtronicUtil.getBatteryType() == BatteryType.None || pumpStatus.batteryVoltage == null) { +// batteryView.setText("{fa-battery-" + (pumpStatus.batteryRemaining / 25) + "} "); +// } else { +// batteryView.setText("{fa-battery-" + (pumpStatus.batteryRemaining / 25) + "} " + pumpStatus.batteryRemaining + "%" + String.format(" (%.2f V)", pumpStatus.batteryVoltage)); +// } + SetWarnColor.setColorInverse(batteryView, pumpStatus.batteryRemaining, 25d, 10d); + + // reservoir + reservoirView.setText(DecimalFormatter.to0Decimal(pumpStatus.reservoirRemainingUnits) + " / " + + pumpStatus.reservoirFullUnits + " " + MainApp.gs(R.string.insulin_unit_shortname)); + SetWarnColor.setColorInverse(reservoirView, pumpStatus.reservoirRemainingUnits, 50d, 20d); + + errorsView.setText(pumpStatus.getErrorInfo()); + + }); + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java new file mode 100644 index 0000000000..aadbb1ad58 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java @@ -0,0 +1,848 @@ +package info.nightscout.androidaps.plugins.pump.omnipod; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.SystemClock; + +import androidx.annotation.NonNull; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ResetRileyLinkConfigurationTask; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.ui.OmnipodUIComm; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.ui.OmnipodUITask; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCustomActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.service.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.service.RileyLinkOmnipodService; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 23.04.18. + * + * @author Andy Rozman (andy.rozman@gmail.com) + */ +public class OmnipodPumpPlugin extends PumpPluginAbstract implements PumpInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + protected static OmnipodPumpPlugin plugin = null; + private RileyLinkOmnipodService omnipodService; + private OmnipodPumpStatus pumpStatusLocal = null; + private OmnipodUIComm omnipodUIComm = new OmnipodUIComm(); + + // variables for handling statuses and history + private boolean firstRun = true; + private boolean isRefresh = false; + private boolean isBasalProfileInvalid = false; + private boolean basalProfileChanged = false; + private boolean isInitialized = false; + private OmnipodCommunicationManager omnipodCommunicationManager; + + public static boolean isBusy = false; + private List busyTimestamps = new ArrayList<>(); + private boolean sentIdToFirebase = false; + private boolean hasTimeDateOrTimeZoneChanged = false; + + private Profile currentProfile; + + + private OmnipodPumpPlugin() { + + super(new PluginDescription() // + .mainType(PluginType.PUMP) // + .fragmentClass(OmnipodFragment.class.getName()) // + .pluginName(R.string.omnipod_name) // + .shortName(R.string.omnipod_name_short) // + .preferencesId(R.xml.pref_omnipod) // + .description(R.string.description_pump_omnipod), // + PumpType.Insulet_Omnipod + ); + + displayConnectionMessages = false; + + serviceConnection = new ServiceConnection() { + + public void onServiceDisconnected(ComponentName name) { + if (isLoggingEnabled()) + LOG.debug("RileyLinkOmnipodService is disconnected"); + omnipodService = null; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + if (isLoggingEnabled()) + LOG.debug("RileyLinkOmnipodService is connected"); + RileyLinkOmnipodService.LocalBinder mLocalBinder = (RileyLinkOmnipodService.LocalBinder) service; + omnipodService = mLocalBinder.getServiceInstance(); + + new Thread(() -> { + + for (int i = 0; i < 20; i++) { + SystemClock.sleep(5000); + + if (OmnipodUtil.getPumpStatus() != null) { + if (isLoggingEnabled()) + LOG.debug("Starting OmniPod-RileyLink service"); + if (OmnipodUtil.getPumpStatus().setNotInPreInit()) { + break; + } + } + } + }).start(); + } + }; + } + + + public static OmnipodPumpPlugin getPlugin() { + if (plugin == null) + plugin = new OmnipodPumpPlugin(); + return plugin; + } + + + private String getLogPrefix() { + return "OmnipodPlugin::"; + } + + + @Override + public void initPumpStatusData() { + + this.pumpStatusLocal = new OmnipodPumpStatus(pumpDescription); + OmnipodUtil.setPumpStatus(pumpStatusLocal); + + pumpStatusLocal.lastConnection = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + pumpStatusLocal.lastDataTime = new LocalDateTime(pumpStatusLocal.lastConnection); + pumpStatusLocal.previousConnection = pumpStatusLocal.lastConnection; + + pumpStatusLocal.refreshConfiguration(); + + if (isLoggingEnabled()) + LOG.debug("initPumpStatusData: {}", this.pumpStatusLocal); + + this.pumpStatus = pumpStatusLocal; + + // set first Omnipod Start + if (!SP.contains(OmnipodConst.Statistics.FirstPumpStart)) { + SP.putLong(OmnipodConst.Statistics.FirstPumpStart, System.currentTimeMillis()); + } + + } + + + public void onStartCustomActions() { + + // check status every minute (if any status needs refresh we send readStatus command) + new Thread(() -> { + + do { + SystemClock.sleep(60000); + + if (this.isInitialized) { + clearBusyQueue(); + } + + } while (serviceRunning); + + }).start(); + } + + + public Class getServiceClass() { + return RileyLinkOmnipodService.class; + } + + + @Override + public String deviceID() { + return "Omnipod"; + } + + + // Pump Plugin + + private boolean isServiceSet() { + return omnipodService != null; + } + + + @Override + public boolean isInitialized() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isInitialized"); + return isServiceSet() && isInitialized; + } + + + @Override + public boolean isBusy() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isBusy"); + + if (isServiceSet()) { + + if (isBusy) + return true; + + if (busyTimestamps.size() > 0) { + + clearBusyQueue(); + + if (busyTimestamps.size() > 0) { + return true; + } + } + } + + return false; + } + + + private synchronized void clearBusyQueue() { + + if (busyTimestamps.size() == 0) { + return; + } + + Set deleteFromQueue = new HashSet<>(); + + for (Long busyTimestamp : busyTimestamps) { + + if (System.currentTimeMillis() > busyTimestamp) { + deleteFromQueue.add(busyTimestamp); + } + } + + if (deleteFromQueue.size() == busyTimestamps.size()) { + busyTimestamps.clear(); + //setEnableCustomAction(MedtronicCustomActionType.ClearBolusBlock, false); + } + + if (deleteFromQueue.size() > 0) { + busyTimestamps.removeAll(deleteFromQueue); + } + + } + + + @Override + public boolean isConnected() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isConnected"); + return isServiceSet() && omnipodService.isInitialized(); + } + + + @Override + public boolean isConnecting() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isConnecting"); + return !isServiceSet() || !omnipodService.isInitialized(); + } + + + @Override + public void getPumpStatus() { + + if (firstRun) { + initializePump(!isRefresh); + } + +// getPodPumpStatus(); +// +// if (firstRun) { +// initializePump(!isRefresh); +// } else { +// refreshAnyStatusThatNeedsToBeRefreshed(); +// } +// +// MainApp.bus().post(new EventMedtronicPumpValuesChanged()); + } + + + void resetStatusState() { + firstRun = true; + isRefresh = true; + } + + + private void setRefreshButtonEnabled(boolean enabled) { + OmnipodFragment.refreshButtonEnabled(enabled); + } + + + private void initializePump(boolean realInit) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "initializePump - start"); + + if (omnipodCommunicationManager == null) { + omnipodCommunicationManager = OmnipodCommunicationManager.getInstance(); + } + +// setRefreshButtonEnabled(false); +// +// getPodPumpStatus(); +// +// if (isRefresh) { +// if (isPumpNotReachable()) { +// if (isLoggingEnabled()) +// LOG.error(getLogPrefix() + "initializePump::Pump unreachable."); +// MedtronicUtil.sendNotification(MedtronicNotificationType.PumpUnreachable); +// +// setRefreshButtonEnabled(true); +// +// return; +// } +// +// MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); +// } +// +// this.pumpState = PumpDriverState.Connected; +// +// pumpStatusLocal.setLastCommunicationToNow(); +// setRefreshButtonEnabled(true); + + // TODO need to read status and BasalProfile if pod inited and pod status and set correct commands enabled + + if (!isRefresh) { + pumpState = PumpDriverState.Initialized; + } + + if (!sentIdToFirebase) { + Bundle params = new Bundle(); + params.putString("version", BuildConfig.VERSION); + MainApp.getFirebaseAnalytics().logEvent("OmnipodPumpInit", params); + + sentIdToFirebase = true; + } + + isInitialized = true; + // this.pumpState = PumpDriverState.Initialized; + + this.firstRun = false; + } + + + @Override + public boolean isThisProfileSet(Profile profile) { + + // status was not yet read from pod + if (currentProfile == null) { + return true; + } + + return (currentProfile.isProfileTheSame(profile)); + } + + + @Override + public long lastDataTime() { + getPodPumpStatusObject(); + + if (pumpStatusLocal.lastConnection != 0) { + return pumpStatusLocal.lastConnection; + } + + return System.currentTimeMillis(); + } + + + @Override + public double getBaseBasalRate() { + + if (currentProfile != null) { + int hour = (new GregorianCalendar()).get(Calendar.HOUR_OF_DAY); + return currentProfile.getBasalTimeFromMidnight(getTimeInS(hour * 60)); + } else { + return 0.0d; + } + } + + + @Override + public double getReservoirLevel() { + // TODO + return getPodPumpStatusObject().reservoirRemainingUnits; + } + + + @Override + public int getBatteryLevel() { + // TODO + return getPodPumpStatusObject().batteryRemaining; + } + + + private OmnipodPumpStatus getPodPumpStatusObject() { + if (pumpStatusLocal == null) { + // FIXME I don't know why this happens + if (isLoggingEnabled()) + LOG.warn("!!!! Reset Pump Status Local"); + pumpStatusLocal = OmnipodUtil.getPumpStatus(); + } + + return pumpStatusLocal; + } + + + protected void triggerUIChange() { + MainApp.bus().post(new EventOmnipodPumpValuesChanged()); + } + + + @NonNull + protected PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { + + LOG.info(getLogPrefix() + "deliverBolus - {}", detailedBolusInfo); + + setRefreshButtonEnabled(false); + + try { + + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.SetBolus, + detailedBolusInfo.insulin); + + Boolean response = (Boolean) responseTask.returnData; + + setRefreshButtonEnabled(true); + + if (response) { + + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true); + + // we subtract insulin, exact amount will be visible with next remainingInsulin update. + getPodPumpStatusObject().reservoirRemainingUnits -= detailedBolusInfo.insulin; + + incrementStatistics(detailedBolusInfo.isSMB ? OmnipodConst.Statistics.SMBBoluses + : OmnipodConst.Statistics.StandardBoluses); + + // calculate time for bolus and set driver to busy for that time + // TODO fix this + int bolusTime = (int) (detailedBolusInfo.insulin * 42.0d); + long time = System.currentTimeMillis() + (bolusTime * 1000); + + this.busyTimestamps.add(time); + + return new PumpEnactResult().success(true) // + .enacted(true) // + .bolusDelivered(detailedBolusInfo.insulin) // + .carbsDelivered(detailedBolusInfo.carbs); + + } else { + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_bolus_could_not_be_delivered)); + } + + } finally { + finishAction("Bolus"); + } + } + + + private PumpEnactResult setNotReachable(boolean isBolus, boolean success) { + setRefreshButtonEnabled(true); + + if (success) { + return new PumpEnactResult() // + .success(true) // + .enacted(false); + } else { + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_pump_status_pump_unreachable)); + } + } + + + public void stopBolusDelivering() { + + LOG.info(getLogPrefix() + "stopBolusDelivering"); + + setRefreshButtonEnabled(false); + + try { + + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.CancelBolus); + + Boolean response = (Boolean) responseTask.returnData; + + setRefreshButtonEnabled(true); + + LOG.info(getLogPrefix() + "stopBolusDelivering - wasSuccess={}", response); + + if (response) { + // TODO fix bolus record with cancel + + //TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true); + } + + } finally { + finishAction("Bolus"); + } + } + + + private void incrementStatistics(String statsKey) { + long currentCount = SP.getLong(statsKey, 0L); + currentCount++; + SP.putLong(statsKey, currentCount); + } + + + // if enforceNew===true current temp basal is canceled and new TBR set (duration is prolonged), + // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed + @Override + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, + boolean enforceNew) { + + getPodPumpStatusObject(); + + setRefreshButtonEnabled(false); + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute: rate: {}, duration={}", absoluteRate, durationInMinutes); + + // read current TBR + TempBasalPair tbrCurrent = readTBR(); + + if (tbrCurrent != null) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute: Current Basal: duration: {} min, rate={}", + tbrCurrent.getDurationMinutes(), tbrCurrent.getInsulinRate()); + } + + if (tbrCurrent != null && !enforceNew) { + + if (OmnipodUtil.isSame(tbrCurrent.getInsulinRate(), absoluteRate)) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - No enforceNew and same rate. Exiting."); + finishAction("TBR"); + return new PumpEnactResult().success(true).enacted(false); + } + } + + // if TBR is running we will cancel it. + if (tbrCurrent != null) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - TBR running - so canceling it."); + + // CANCEL + OmnipodUITask responseTask2 = omnipodUIComm.executeCommand(OmnipodCommandType.CancelTemporaryBasal); + + Boolean response = (Boolean) responseTask2.returnData; + + if (response) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - Current TBR cancelled."); + } else { + if (isLoggingEnabled()) + LOG.error(getLogPrefix() + "setTempBasalAbsolute - Cancel TBR failed."); + + finishAction("TBR"); + + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.medtronic_cmd_cant_cancel_tbr_stop_op)); + } + } + + // now start new TBR + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.SetTemporaryBasal, + absoluteRate, durationInMinutes); + + Boolean response = (Boolean) responseTask.returnData; + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - setTBR. Response: " + response); + + if (response) { + pumpStatusLocal.tempBasalStart = System.currentTimeMillis(); + pumpStatusLocal.tempBasalAmount = absoluteRate; + pumpStatusLocal.tempBasalLength = durationInMinutes; + pumpStatusLocal.tempBasalEnd = getTimeInFutureFromMinutes(durationInMinutes); + + TemporaryBasal tempStart = new TemporaryBasal() // + .date(System.currentTimeMillis()) // + .duration(durationInMinutes) // + .absolute(absoluteRate) // + .source(Source.USER); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStart); + + incrementStatistics(OmnipodConst.Statistics.TBRsSet); + + finishAction("TBR"); + + return new PumpEnactResult().success(true).enacted(true) // + .absolute(absoluteRate).duration(durationInMinutes); + + } else { + finishAction("TBR"); + + return new PumpEnactResult().success(false).enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_tbr_could_not_be_delivered)); + } + + } + + private TempBasalPair readTBR() { + // TODO we can do it like this or read status from pod ?? + if (pumpStatusLocal.tempBasalEnd < System.currentTimeMillis()) { + // TBR done + pumpStatusLocal.clearTemporaryBasal(); + + return null; + } + + return pumpStatusLocal.getTemporaryBasal(); + } + + + private void finishAction(String overviewKey) { + if (overviewKey != null) + MainApp.bus().post(new EventRefreshOverview(overviewKey)); + + triggerUIChange(); + + setRefreshButtonEnabled(true); + } + + + private long getTimeInFutureFromMinutes(int minutes) { + return System.currentTimeMillis() + getTimeInMs(minutes); + } + + + private long getTimeInMs(int minutes) { + return getTimeInS(minutes) * 1000L; + } + + private int getTimeInS(int minutes) { + return minutes * 60; + } + + + @Override + public PumpEnactResult cancelTempBasal(boolean enforceNew) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - started"); + + setRefreshButtonEnabled(false); + + TempBasalPair tbrCurrent = readTBR(); + + if (tbrCurrent == null) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - TBR already canceled."); + finishAction("TBR"); + return new PumpEnactResult().success(true).enacted(false); + + } + + OmnipodUITask responseTask2 = omnipodUIComm.executeCommand(OmnipodCommandType.CancelTemporaryBasal); + + Boolean response = (Boolean) responseTask2.returnData; + + finishAction("TBR"); + + if (response) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - Cancel TBR successful."); + + TemporaryBasal tempBasal = new TemporaryBasal() // + .date(System.currentTimeMillis()) // + .duration(0) // + .source(Source.USER); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); + + return new PumpEnactResult().success(true).enacted(true) // + .isTempCancel(true); + } else { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - Cancel TBR failed."); + + return new PumpEnactResult().success(response).enacted(response) // + .comment(MainApp.gs(R.string.medtronic_cmd_cant_cancel_tbr)); + } + + } + + @Override + public String serialNumber() { + return getPodPumpStatusObject().podNumber; + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setNewBasalProfile"); + + // this shouldn't be needed, but let's do check if profile setting we are setting is same as current one + if (this.currentProfile != null && this.currentProfile.isProfileTheSame(profile)) { + return new PumpEnactResult() // + .success(true) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_basal_profile_not_set_is_same)); + } + + setRefreshButtonEnabled(false); + + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.SetBasalProfile, + profile); + + Boolean response = (Boolean) responseTask.returnData; + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "Basal Profile was set: " + response); + + if (response) { + this.currentProfile = profile; + return new PumpEnactResult().success(true).enacted(true); + } else { + return new PumpEnactResult().success(response).enacted(response) // + .comment(MainApp.gs(R.string.medtronic_cmd_basal_profile_could_not_be_set)); + } + + } + + + // OPERATIONS not supported by Pump or Plugin + + private List customActions = null; + + private CustomAction customActionResetRLConfig = new CustomAction( + R.string.medtronic_custom_action_reset_rileylink, OmnipodCustomActionType.ResetRileyLinkConfiguration, true); + + private CustomAction customActionInitPod = new CustomAction( + R.string.omnipod_cmd_init_pod, OmnipodCustomActionType.InitPod, true); + + private CustomAction customActionDeactivatePod = new CustomAction( + R.string.omnipod_cmd_deactivate_pod, OmnipodCustomActionType.DeactivatePod, false); + + private CustomAction customActionResetPod = new CustomAction( + R.string.omnipod_cmd_reset_pod, OmnipodCustomActionType.ResetPodStatus, true); + + + @Override + public List getCustomActions() { + + if (customActions == null) { + this.customActions = Arrays.asList( + customActionResetRLConfig); + } + + return this.customActions; + } + + // TODO we need to brainstorm how we want to do this -- Andy + @Override + public void executeCustomAction(CustomActionType customActionType) { + + OmnipodCustomActionType mcat = (OmnipodCustomActionType) customActionType; + + switch (mcat) { + + case ResetRileyLinkConfiguration: { + ServiceTaskExecutor.startTask(new ResetRileyLinkConfigurationTask()); + } + break; + + case InitPod: { + omnipodUIComm.executeCommand(OmnipodCommandType.InitPod); + } + break; + + case DeactivatePod: { + omnipodUIComm.executeCommand(OmnipodCommandType.DeactivatePod); + } + break; + + case ResetPodStatus: { + omnipodUIComm.executeCommand(OmnipodCommandType.ResetPodStatus); + } + break; + + default: + break; + } + + } + + @Override + public void timeDateOrTimeZoneChanged() { + +// if (isLoggingEnabled()) +// LOG.warn(getLogPrefix() + "Time, Date and/or TimeZone changed. "); +// +// this.hasTimeDateOrTimeZoneChanged = true; + } + + + public void setEnableCustomAction(OmnipodCustomActionType customAction, boolean isEnabled) { + + switch (customAction) { + + case InitPod: { + this.customActionInitPod.setEnabled(isEnabled); + } + break; + + case DeactivatePod: { + this.customActionDeactivatePod.setEnabled(isEnabled); + } + break; + + default: + break; + } + + refreshCustomActionsList(); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationManager.java new file mode 100644 index 0000000000..ec4d286313 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationManager.java @@ -0,0 +1,121 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm; + +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.data.PodCommResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodCommunicationManager extends RileyLinkCommunicationManager implements OmnipodCommunicationManagerInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static OmnipodCommunicationManager omnipodCommunicationManager; + String errorMessage; + + + public OmnipodCommunicationManager(Context context, RFSpy rfspy) { + super(rfspy); + omnipodCommunicationManager = this; + OmnipodUtil.getPumpStatus().previousConnection = SP.getLong( + RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + } + + + public static OmnipodCommunicationManager getInstance() { + return omnipodCommunicationManager; + } + + + @Override + protected void configurePumpSpecificSettings() { + pumpStatus = MedtronicUtil.getPumpStatus(); + } + + + @Override + public E createResponseMessage(byte[] payload, Class clazz) { + //PumpMessage pumpMessage = new PumpMessage(payload); + //eturn (E) pumpMessage; + return null; + } + + + @Override + public boolean tryToConnectToDevice() { + return false; //isDeviceReachable(true); + } + + + public String getErrorResponse() { + return this.errorMessage; + } + + + @Override + public byte[] createPumpMessageContent(RLMessageType type) { + return new byte[0]; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + + // This are just skeleton methods, we need to see what we can get returned and act accordingly + + public PodCommResponse initPod() { + return null; + } + + + public PodCommResponse getPodStatus() { + return null; + } + + + public PodCommResponse deactivatePod() { + return null; + } + + public PodCommResponse setBasalProfile(Profile profile) { + return null; + } + + public PodCommResponse resetPodStatus() { + return null; + } + + public PodCommResponse setBolus(Double parameter) { + return null; + } + + public PodCommResponse cancelBolus() { + return null; + } + + public PodCommResponse setTemporaryBasal(TempBasalPair tbr) { + return null; + } + + public PodCommResponse cancelTemporaryBasal() { + return null; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationManagerInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationManagerInterface.java new file mode 100644 index 0000000000..02cae9d180 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationManagerInterface.java @@ -0,0 +1,61 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm; + +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.data.PodCommResponse; + +public interface OmnipodCommunicationManagerInterface { + + // TODO add methods that can be used by OmniPod Eros and Omnipod Dash + + /** + * Initialize Pod + */ + PodCommResponse initPod(); + + /** + * Get Pod Status (is pod running, battery left ?, reservoir, etc) + */ + PodCommResponse getPodStatus(); + + /** + * Deactivate Pod + */ + PodCommResponse deactivatePod(); + + /** + * Set Basal Profile + */ + PodCommResponse setBasalProfile(Profile profile); + + /** + * Reset Pod status (if we forget to disconnect Pod and want to init new pod, and want to forget current pod) + */ + PodCommResponse resetPodStatus(); + + /** + * Set Bolus + * + * @param amount amount of bolus in U + */ + PodCommResponse setBolus(Double amount); + + /** + * Cancel Bolus (if bolus is already stopped, return acknowledgment) + */ + PodCommResponse cancelBolus(); + + /** + * Set Temporary Basal + * + * @param tbr TempBasalPair object containg amount and duration in minutes + */ + PodCommResponse setTemporaryBasal(TempBasalPair tbr); + + /** + * Cancel Temporary Basal (if TB is already stopped, return acknowledgment) + */ + PodCommResponse cancelTemporaryBasal(); + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/data/PodCommResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/data/PodCommResponse.java new file mode 100644 index 0000000000..ebc7ccf966 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/data/PodCommResponse.java @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.data; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodResponseType; + +public class PodCommResponse { + + PodResponseType podResponseType; + + Boolean acknowledged; + Object customData; + Object errorResponse; + + // some status data if it can be returned (battery, reservoir, etc) + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUIComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUIComm.java new file mode 100644 index 0000000000..09647186f7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUIComm.java @@ -0,0 +1,83 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodUIComm { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + OmnipodCommunicationManager ocmInstance = null; + OmnipodUIPostprocessor uiPostprocessor = new OmnipodUIPostprocessor(); + + + private OmnipodCommunicationManager getCommunicationManager() { + if (ocmInstance == null) { + ocmInstance = OmnipodCommunicationManager.getInstance(); + } + + return ocmInstance; + } + + + public synchronized OmnipodUITask executeCommand(OmnipodCommandType commandType, Object... parameters) { + + if (isLogEnabled()) + LOG.warn("Execute Command: " + commandType.name()); + + OmnipodUITask task = new OmnipodUITask(commandType, parameters); + + OmnipodUtil.setCurrentCommand(commandType); + + // new Thread(() -> { + // LOG.warn("@@@ Start Thread"); + // + // task.execute(getCommunicationManager()); + // + // LOG.warn("@@@ End Thread"); + // }); + + task.execute(getCommunicationManager()); + + // for (int i = 0; i < getMaxWaitTime(commandType); i++) { + // synchronized (task) { + // // try { + // // + // // //task.wait(1000); + // // } catch (InterruptedException e) { + // // LOG.error("executeCommand InterruptedException", e); + // // } + // + // + // SystemClock.sleep(1000); + // } + // + // if (task.isReceived()) { + // break; + // } + // } + + if (!task.isReceived() && isLogEnabled()) { + LOG.warn("Reply not received for " + commandType); + } + + task.postProcess(uiPostprocessor); + + return task; + + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUIPostprocessor.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUIPostprocessor.java new file mode 100644 index 0000000000..03d3a21504 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUIPostprocessor.java @@ -0,0 +1,63 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCustomActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.service.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by andy on 4.8.2019 + */ + +public class OmnipodUIPostprocessor { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + OmnipodPumpStatus pumpStatus; + OmnipodPumpPlugin omnipodPumpPlugin; + + + public OmnipodUIPostprocessor() { + pumpStatus = OmnipodUtil.getPumpStatus(); + omnipodPumpPlugin = OmnipodPumpPlugin.getPlugin(); + } + + + // this is mostly intended for command that return certain statuses (Remaining Insulin, ...), and + // where responses won't be directly used + public void postProcessData(OmnipodUITask uiTask) { + + switch (uiTask.commandType) { + + case InitPod: { + omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.InitPod, false); + omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.DeactivatePod, true); + } + break; + + case DeactivatePod: + case ResetPodStatus: { + omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.InitPod, true); + omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.DeactivatePod, false); + } + break; + + + default: + if (isLogEnabled()) + LOG.trace("Post-processing not implemented for {}.", uiTask.commandType.name()); + + } + + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUITask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUITask.java new file mode 100644 index 0000000000..350ea34f15 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/ui/OmnipodUITask.java @@ -0,0 +1,184 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodResponseType; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodDeviceStatusChange; + +/** + * Created by andy on 4.8.2019 + */ + +public class OmnipodUITask { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + public OmnipodCommandType commandType; + public Object returnData; + private String errorDescription; + private Object[] parameters; + private PodResponseType responseType; + + + public OmnipodUITask(OmnipodCommandType commandType) { + this.commandType = commandType; + } + + + public OmnipodUITask(OmnipodCommandType commandType, Object... parameters) { + this.commandType = commandType; + this.parameters = parameters; + } + + + public void execute(OmnipodCommunicationManager communicationManager) { + + if (isLogEnabled()) + LOG.debug("OmnipodUITask: @@@ In execute. {}", commandType); + + switch (commandType) { + // TODO add commands this is just sample +// case PumpModel: { +// returnData = communicationManager.getPumpModel(); +// } +// break; + + case InitPod: + returnData = communicationManager.initPod(); + break; + + case DeactivatePod: + returnData = communicationManager.deactivatePod(); + break; + + case ResetPodStatus: + returnData = communicationManager.resetPodStatus(); + break; + + case SetBasalProfile: + returnData = communicationManager.setBasalProfile((Profile) parameters[0]); + break; + + case SetBolus: { + Double amount = getDoubleFromParameters(0); + + if (amount != null) + returnData = communicationManager.setBolus(amount); + } + break; + + case CancelBolus: + returnData = communicationManager.cancelBolus(); + break; + + case SetTemporaryBasal: { + TempBasalPair tbr = getTBRSettings(); + if (tbr != null) { + returnData = communicationManager.setTemporaryBasal(tbr); + } + } + break; + + case CancelTemporaryBasal: + returnData = communicationManager.cancelTemporaryBasal(); + break; + + + default: { + LOG.warn("This commandType is not supported (yet) - {}.", commandType); + } + + } + + } + + + private TempBasalPair getTBRSettings() { + return new TempBasalPair(getDoubleFromParameters(0), // + false, // + getIntegerFromParameters(1)); + } + + + private Float getFloatFromParameters(int index) { + return (Float) parameters[index]; + } + + + public Double getDoubleFromParameters(int index) { + return (Double) parameters[index]; + } + + + public Integer getIntegerFromParameters(int index) { + return (Integer) parameters[index]; + } + + + public Object getResult() { + return returnData; + } + + + public boolean isReceived() { + return (returnData != null || errorDescription != null); + } + + + public void postProcess(OmnipodUIPostprocessor postprocessor) { + + EventOmnipodDeviceStatusChange statusChange; + if (isLogEnabled()) + LOG.debug("OmnipodUITask: @@@ In execute. {}", commandType); + + if (responseType == PodResponseType.Data || responseType == PodResponseType.Acknowledgment) { + postprocessor.postProcessData(this); + } + + if (responseType == PodResponseType.Invalid) { + statusChange = new EventOmnipodDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating, + "Unsupported command in OmnipodUITask"); + MainApp.bus().post(statusChange); + } else if (responseType == PodResponseType.Error) { + statusChange = new EventOmnipodDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating, + errorDescription); + MainApp.bus().post(statusChange); + } else { + MainApp.bus().post(new EventMedtronicPumpValuesChanged()); + MedtronicUtil.getPumpStatus().setLastCommunicationToNow(); + } + + MedtronicUtil.setCurrentCommand(null); + } + + + public boolean hasData() { + return (responseType == PodResponseType.Data || responseType == PodResponseType.Acknowledgment); + } + + + public Object getParameter(int index) { + return parameters[index]; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + + + public PodResponseType getResponseType() { + return this.responseType; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommandType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommandType.java new file mode 100644 index 0000000000..ea0c74feb9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommandType.java @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +/** + * Created by andy on 4.8.2019 + */ +public enum OmnipodCommandType { + + InitPod, // + DeactivatePod, // + SetBasalProfile, // + SetBolus, // + CancelBolus, // + SetTemporaryBasal, + CancelTemporaryBasal, + ResetPodStatus; + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCustomActionType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCustomActionType.java new file mode 100644 index 0000000000..bc75946c48 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCustomActionType.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; + +/** + * Created by andy on 4.8.2019 + */ + +public enum OmnipodCustomActionType implements CustomActionType { + + ResetRileyLinkConfiguration(), // + InitPod(), // + DeactivatePod(), // + ResetPodStatus(), // + ; + + @Override + public String getKey() { + return this.name(); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodUIResponseType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodUIResponseType.java new file mode 100644 index 0000000000..b135c3f590 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodUIResponseType.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +/** + * Created by andy on 10/18/18. + */ + +public enum OmnipodUIResponseType { + + Data, + Error, + Invalid; + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodDeviceState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodDeviceState.java new file mode 100644 index 0000000000..5e903b5ae4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodDeviceState.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 4.8.2019 + */ + +public enum PodDeviceState { + + // FIXME + NeverContacted(R.string.medtronic_pump_status_never_contacted), // + Sleeping(R.string.medtronic_pump_status_sleeping), // + WakingUp(R.string.medtronic_pump_status_waking_up), // + Active(R.string.medtronic_pump_status_active), // + ErrorWhenCommunicating(R.string.medtronic_pump_status_error_comm), // + TimeoutWhenCommunicating(R.string.medtronic_pump_status_timeout_comm), // + // ProblemContacting(R.string.medtronic_pump_status_problem_contacting), // + PumpUnreachable(R.string.medtronic_pump_status_pump_unreachable), // + InvalidConfiguration(R.string.medtronic_pump_status_invalid_config); + + Integer resourceId = null; + + + PodDeviceState() { + + } + + + PodDeviceState(int resourceId) { + this.resourceId = resourceId; + } + + + public Integer getResourceId() { + return resourceId; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodResponseType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodResponseType.java new file mode 100644 index 0000000000..7314ae9591 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodResponseType.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum PodResponseType { + + Acknowledgment, // set commands would just acknowledge if data was sent + Data, // query commands would return data + Error, // communication/response produced an error + Invalid // invalid response (not supported, should never be returned) +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodDeviceStatusChange.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodDeviceStatusChange.java new file mode 100644 index 0000000000..30be8cdb71 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodDeviceStatusChange.java @@ -0,0 +1,47 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.events; + +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; + +/** + * Created by andy on 4.8.2019 + */ +public class EventOmnipodDeviceStatusChange extends Event { + + public RileyLinkServiceState rileyLinkServiceState; + public RileyLinkError rileyLinkError; + + public PumpDeviceState pumpDeviceState; + public String errorDescription; + + + public EventOmnipodDeviceStatusChange(RileyLinkServiceState rileyLinkServiceState) { + this(rileyLinkServiceState, null); + } + + + public EventOmnipodDeviceStatusChange(RileyLinkServiceState rileyLinkServiceState, RileyLinkError rileyLinkError) { + this.rileyLinkServiceState = rileyLinkServiceState; + this.rileyLinkError = rileyLinkError; + } + + + public EventOmnipodDeviceStatusChange(PumpDeviceState pumpDeviceState) { + this.pumpDeviceState = pumpDeviceState; + } + + + public EventOmnipodDeviceStatusChange(PumpDeviceState pumpDeviceState, String errorDescription) { + this.pumpDeviceState = pumpDeviceState; + this.errorDescription = errorDescription; + } + + + @Override + public String toString() { + return "EventOmnipodDeviceStatusChange [" + "rileyLinkServiceState=" + rileyLinkServiceState + + ", rileyLinkError=" + rileyLinkError + ", pumpDeviceState=" + pumpDeviceState + ']'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodPumpValuesChanged.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodPumpValuesChanged.java new file mode 100644 index 0000000000..b12b32007f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodPumpValuesChanged.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.events; + +import info.nightscout.androidaps.events.Event; + +/** + * Created by andy on 04.06.2018. + */ +public class EventOmnipodPumpValuesChanged extends Event { +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/OmnipodPumpStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/OmnipodPumpStatus.java new file mode 100644 index 0000000000..af1b95c956 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/OmnipodPumpStatus.java @@ -0,0 +1,157 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.service; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodPumpStatus extends PumpStatus { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + public String errorDescription = null; + public String rileyLinkAddress = null; + public boolean inPreInit = true; + + // statuses + public RileyLinkServiceState rileyLinkServiceState = RileyLinkServiceState.NotStarted; + public RileyLinkError rileyLinkError; + public double currentBasal = 0; + public long tempBasalStart; + public long tempBasalEnd; + public Double tempBasalAmount = 0.0d; + public Integer tempBasalLength; + + private boolean rileyLinkAddressChanged = false; + private String regexMac = "([\\da-fA-F]{1,2}(?:\\:|$)){6}"; + + + public String podNumber; + + + public OmnipodPumpStatus(PumpDescription pumpDescription) { + super(pumpDescription); + } + + + @Override + public void initSettings() { + this.activeProfileName = ""; + this.reservoirRemainingUnits = 75d; + this.batteryRemaining = 75; + this.lastConnection = SP.getLong(OmnipodConst.Statistics.LastGoodPumpCommunicationTime, 0L); + this.lastDataTime = new LocalDateTime(this.lastConnection); + this.pumpType = PumpType.Insulet_Omnipod; + } + + + public boolean verifyConfiguration() { + try { + + this.errorDescription = "-"; + + String rileyLinkAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, null); + + if (rileyLinkAddress == null) { + if (isLogEnabled()) + LOG.debug("RileyLink address invalid: null"); + this.errorDescription = MainApp.gs(R.string.medtronic_error_rileylink_address_invalid); + return false; + } else { + if (!rileyLinkAddress.matches(regexMac)) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_rileylink_address_invalid); + if (isLogEnabled()) + LOG.debug("RileyLink address invalid: {}", rileyLinkAddress); + } else { + if (!rileyLinkAddress.equals(this.rileyLinkAddress)) { + this.rileyLinkAddress = rileyLinkAddress; + rileyLinkAddressChanged = true; + } + } + } + + reconfigureService(); + + return true; + + } catch (Exception ex) { + this.errorDescription = ex.getMessage(); + LOG.error("Error on Verification: " + ex.getMessage(), ex); + return false; + } + } + + + private boolean reconfigureService() { + + if (!inPreInit && OmnipodUtil.getOmnipodService() != null) { + + if (rileyLinkAddressChanged) { + OmnipodUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkNewAddressSet); + rileyLinkAddressChanged = false; + } + } + + return (!rileyLinkAddressChanged); + } + + + public String getErrorInfo() { + verifyConfiguration(); + + return (this.errorDescription == null) ? "-" : this.errorDescription; + } + + + @Override + public void refreshConfiguration() { + verifyConfiguration(); + } + + + public boolean setNotInPreInit() { + this.inPreInit = false; + + return reconfigureService(); + } + + + public void clearTemporaryBasal() { + this.tempBasalStart = 0L; + this.tempBasalEnd = 0L; + this.tempBasalAmount = 0.0d; + this.tempBasalLength = 0; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + + public TempBasalPair getTemporaryBasal() { + + TempBasalPair tbr = new TempBasalPair(); + tbr.setDurationMinutes(tempBasalLength); + tbr.setInsulinRate(tempBasalAmount); + tbr.setStartTime(tempBasalStart); + tbr.setEndTime(tempBasalEnd); + + return tbr; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/RileyLinkOmnipodService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/RileyLinkOmnipodService.java new file mode 100644 index 0000000000..67c280ce3c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/RileyLinkOmnipodService.java @@ -0,0 +1,155 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.service; + +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.os.Binder; +import android.os.IBinder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4.8.2019 + * RileyLinkOmnipodService is intended to stay running when the gui-app is closed. + */ +public class RileyLinkOmnipodService extends RileyLinkService { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static RileyLinkOmnipodService instance; + + OmnipodCommunicationManager omnipodCommunicationManager; + OmnipodPumpStatus pumpStatus = null; + private IBinder mBinder = new LocalBinder(); + + + public RileyLinkOmnipodService() { + super(MainApp.instance().getApplicationContext()); + instance = this; + if (isLogEnabled()) + LOG.debug("RileyLinkOmnipodService newly constructed"); + OmnipodUtil.setOmnipodService(this); + pumpStatus = (OmnipodPumpStatus) OmnipodPumpPlugin.getPlugin().getPumpStatusData(); + } + + + public static RileyLinkOmnipodService getInstance() { + return instance; + } + + +// public static MedtronicCommunicationManager getCommunicationManager() { +// return instance.medtronicCommunicationManager; +// } + + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (isLogEnabled()) + LOG.warn("onConfigurationChanged"); + super.onConfigurationChanged(newConfig); + } + + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + + @Override + public RileyLinkEncodingType getEncoding() { + return RileyLinkEncodingType.Manchester; + } + + + /** + * If you have customized RileyLinkServiceData you need to override this + */ + public void initRileyLinkServiceData() { + + rileyLinkServiceData = new RileyLinkServiceData(RileyLinkTargetDevice.Omnipod); + + RileyLinkUtil.setRileyLinkServiceData(rileyLinkServiceData); + RileyLinkUtil.setTargetDevice(RileyLinkTargetDevice.Omnipod); + + // get most recently used RileyLink address + rileyLinkServiceData.rileylinkAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, ""); + + rileyLinkBLE = new RileyLinkBLE(this.context); // or this + rfspy = new RFSpy(rileyLinkBLE); + rfspy.startReader(); + + RileyLinkUtil.setRileyLinkBLE(rileyLinkBLE); + + // init rileyLinkCommunicationManager + omnipodCommunicationManager = new OmnipodCommunicationManager(context, rfspy); + } + + + public void resetRileyLinkConfiguration() { + rfspy.resetRileyLinkConfiguration(); + } + + + @Override + public RileyLinkCommunicationManager getDeviceCommunicationManager() { + return this.omnipodCommunicationManager; + } + + + public class LocalBinder extends Binder { + + public RileyLinkOmnipodService getServiceInstance() { + return RileyLinkOmnipodService.this; + } + } + + + /* private functions */ + + // PumpInterface - REMOVE + + public boolean isInitialized() { + return RileyLinkServiceState.isReady(RileyLinkUtil.getRileyLinkServiceData().serviceState); + } + + + @Override + public String getDeviceSpecificBroadcastsIdentifierPrefix() { + return null; + } + + + public boolean handleDeviceSpecificBroadcasts(Intent intent) { + return false; + } + + + @Override + public void registerDeviceSpecificBroadcasts(IntentFilter intentFilter) { + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java new file mode 100644 index 0000000000..0364541af4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java @@ -0,0 +1,28 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.util; + +/** + * Created by andy on 4.8.2019 + */ + +public class OmnipodConst { + + static final String Prefix = "AAPS.Omnipod."; + + public class Prefs { + + //public static final int BatteryType = R.string.pref_key_medtronic_battery_type; + } + + public class Statistics { + + public static final String StatsPrefix = "omnipod_"; + public static final String FirstPumpStart = Prefix + "first_pump_use"; + public static final String LastGoodPumpCommunicationTime = Prefix + "lastGoodPumpCommunicationTime"; + public static final String LastGoodPumpFrequency = Prefix + "LastGoodPumpFrequency"; + public static final String TBRsSet = StatsPrefix + "tbrs_set"; + public static final String StandardBoluses = StatsPrefix + "std_boluses_delivered"; + public static final String SMBBoluses = StatsPrefix + "smb_boluses_delivered"; + public static final String LastPumpHistoryEntry = StatsPrefix + "pump_history_entry"; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodUtil.java new file mode 100644 index 0000000000..1e3f6e9214 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodUtil.java @@ -0,0 +1,170 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.util; + +import android.content.Context; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicNotificationType; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.service.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.service.RileyLinkOmnipodService; +import info.nightscout.androidaps.utils.OKDialog; + +/** + * Created by andy on 4/8/19. + */ +// FIXME +public class OmnipodUtil extends RileyLinkUtil { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static boolean lowLevelDebug = true; + private static RileyLinkOmnipodService omnipodService; + private static OmnipodPumpStatus omnipodPumpStatus; + private static OmnipodCommandType currentCommand; + public static Gson gsonInstance = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + public static Gson gsonInstancePretty = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() + .setPrettyPrinting().create(); + + + public static Gson getGsonInstance() { + return gsonInstance; + } + + public static Gson getGsonInstancePretty() { + return gsonInstancePretty; + } + + + public static int makeUnsignedShort(int b2, int b1) { + int k = (b2 & 0xff) << 8 | b1 & 0xff; + return k; + } + + public static byte[] getByteArrayFromUnsignedShort(int shortValue, boolean returnFixedSize) { + byte highByte = (byte) (shortValue >> 8 & 0xFF); + byte lowByte = (byte) (shortValue & 0xFF); + + if (highByte > 0) { + return createByteArray(highByte, lowByte); + } else { + return returnFixedSize ? createByteArray(highByte, lowByte) : createByteArray(lowByte); + } + + } + + + public static byte[] createByteArray(byte... data) { + return data; + } + + + public static byte[] createByteArray(List data) { + + byte[] array = new byte[data.size()]; + + for (int i = 0; i < data.size(); i++) { + array[i] = data.get(i); + } + + return array; + } + + + public static void sendNotification(MedtronicNotificationType notificationType) { + Notification notification = new Notification( // + notificationType.getNotificationType(), // + MainApp.gs(notificationType.getResourceId()), // + notificationType.getNotificationUrgency()); + MainApp.bus().post(new EventNewNotification(notification)); + } + + + public static void sendNotification(MedtronicNotificationType notificationType, Object... parameters) { + Notification notification = new Notification( // + notificationType.getNotificationType(), // + MainApp.gs(notificationType.getResourceId(), parameters), // + notificationType.getNotificationUrgency()); + MainApp.bus().post(new EventNewNotification(notification)); + } + + + public static void dismissNotification(MedtronicNotificationType notificationType) { + MainApp.bus().post(new EventDismissNotification(notificationType.getNotificationType())); + } + + + public static boolean isLowLevelDebug() { + return lowLevelDebug; + } + + + public static void setLowLevelDebug(boolean lowLevelDebug) { + OmnipodUtil.lowLevelDebug = lowLevelDebug; + } + + + public static OmnipodCommunicationManager getOmnipodCommunicationManager() { + return (OmnipodCommunicationManager) RileyLinkUtil.rileyLinkCommunicationManager; + } + + + public static RileyLinkOmnipodService getOmnipodService() { + return OmnipodUtil.omnipodService; + } + + + public static void setOmnipodService(RileyLinkOmnipodService medtronicService) { + OmnipodUtil.omnipodService = medtronicService; + } + + public static OmnipodCommandType getCurrentCommand() { + return OmnipodUtil.currentCommand; + } + + + // FIXME + public static void setCurrentCommand(OmnipodCommandType currentCommand) { + OmnipodUtil.currentCommand = currentCommand; + + if (currentCommand != null) + historyRileyLink.add(new RLHistoryItem(currentCommand)); + + } + + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + + + public static void displayNotConfiguredDialog(Context context) { + OKDialog.show(context, MainApp.gs(R.string.combo_warning), + MainApp.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null); + } + + public static OmnipodPumpStatus getPumpStatus() { + return omnipodPumpStatus; + } + + public static void setPumpStatus(OmnipodPumpStatus omnipodPumpStatus) { + OmnipodUtil.omnipodPumpStatus = omnipodPumpStatus; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/Round.java b/app/src/main/java/info/nightscout/androidaps/utils/Round.java index ba7f7e3f86..b8e8d65ce4 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/Round.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/Round.java @@ -10,16 +10,25 @@ public class Round { } return 0d; } + public static Double floorTo(Double x, Double step) { if (x != 0d) { return Math.floor(x / step) * step; } return 0d; } + public static Double ceilTo(Double x, Double step) { if (x != 0d) { return Math.ceil(x / step) * step; } return 0d; } + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + } diff --git a/app/src/main/res/layout/omnipod_fragment.xml b/app/src/main/res/layout/omnipod_fragment.xml new file mode 100644 index 0000000000..e017a35329 --- /dev/null +++ b/app/src/main/res/layout/omnipod_fragment.xml @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +