From e5274e2d3a3a79bb4f24ad68a3ae3f338e7795b1 Mon Sep 17 00:00:00 2001 From: Bart Sopers Date: Sun, 9 Aug 2020 13:24:48 +0200 Subject: [PATCH] - Extract abstract class for PodStateManager on driver level - Remove PodStateChangedHandler - Add new fields to PodState - Add Lot, Tid and Firmware Version in Pod tab - Various small improvements - Add some FIXMEs --- .../dependencyInjection/OmnipodModule.kt | 18 +- .../plugins/pump/omnipod/OmnipodFragment.kt | 42 +- .../comm/OmnipodCommunicationManager.java | 8 + .../pump/omnipod/comm/OmnipodManager.java | 10 +- .../comm/action/CancelDeliveryAction.java | 6 +- .../comm/action/SetTempBasalAction.java | 6 +- .../plugins/pump/omnipod/defs/AlertSet.java | 6 +- .../OmnipodCommunicationManagerInterface.java | 4 - .../defs/state/PodStateChangedHandler.java | 6 - .../omnipod/defs/state/PodStateManager.java | 736 ++++++++++++++++-- .../driver/comm/AapsOmnipodManager.java | 125 --- .../driver/comm/AapsPodStateManager.java | 717 ++++------------- .../service/RileyLinkOmnipodService.java | 2 +- .../pump/omnipod/util/OmnipodConst.java | 1 + .../comm/OmnipodDashCommunicationManager.java | 5 - app/src/main/res/layout/omnipod_fragment.xml | 178 ++++- app/src/main/res/values/strings.xml | 3 + 17 files changed, 1039 insertions(+), 834 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OmnipodModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OmnipodModule.kt index e458957fb9..dc8f3ddc77 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OmnipodModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/OmnipodModule.kt @@ -11,7 +11,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod.In import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages.InitPodRefreshAction import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages.PodInfoFragment import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.removepod.RemoveActionFragment -import info.nightscout.androidaps.plugins.pump.omnipod.driver.comm.AapsOmnipodManager import info.nightscout.androidaps.plugins.pump.omnipod.driver.comm.AapsPodStateManager import info.nightscout.androidaps.plugins.pump.omnipod.driver.ui.OmnipodUITask @@ -20,24 +19,25 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.ui.OmnipodUITask abstract class OmnipodModule { // Activities - @ContributesAndroidInjector abstract fun contributesPodManagementActivity(): PodManagementActivity + @ContributesAndroidInjector + abstract fun contributesPodManagementActivity(): PodManagementActivity @ContributesAndroidInjector abstract fun contributesPodHistoryActivity(): PodHistoryActivity // Fragments - @ContributesAndroidInjector abstract fun initActionFragment() : InitActionFragment - @ContributesAndroidInjector abstract fun removeActionFragment() : RemoveActionFragment - @ContributesAndroidInjector abstract fun podInfoFragment() : PodInfoFragment + @ContributesAndroidInjector abstract fun initActionFragment(): InitActionFragment + @ContributesAndroidInjector abstract fun removeActionFragment(): RemoveActionFragment + @ContributesAndroidInjector abstract fun podInfoFragment(): PodInfoFragment // Service - @ContributesAndroidInjector abstract fun omnipodCommunicationManagerProvider(): OmnipodCommunicationManager - @ContributesAndroidInjector abstract fun aapsOmnipodManagerProvider(): AapsOmnipodManager + @ContributesAndroidInjector + abstract fun omnipodCommunicationManagerProvider(): OmnipodCommunicationManager // Data @ContributesAndroidInjector abstract fun omnipodUITaskProvider(): OmnipodUITask @ContributesAndroidInjector abstract fun initPodRefreshAction(): InitPodRefreshAction @ContributesAndroidInjector abstract fun podStateManager(): PodStateManager - @ContributesAndroidInjector abstract fun initPodTask() : InitPodTask + @ContributesAndroidInjector abstract fun initPodTask(): InitPodTask - @ContributesAndroidInjector abstract fun initAapsPodStateManager() : AapsPodStateManager + @ContributesAndroidInjector abstract fun initAapsPodStateManager(): AapsPodStateManager } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt index 27b7558d62..12e591cb23 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt @@ -207,16 +207,10 @@ class OmnipodFragment : DaggerFragment() { } @Synchronized - private fun setDeviceStatus(event : EventOmnipodDeviceStatusChange) { - - + private fun setDeviceStatus(event: EventOmnipodDeviceStatusChange) { } - - - - @Synchronized private fun setDeviceStatus() { //val omnipodPumpStatus: OmnipodPumpStatus = OmnipodUtil.getPumpStatus() @@ -250,20 +244,31 @@ class OmnipodFragment : DaggerFragment() { aapsLogger.info(LTag.PUMP, "getDriverState: [driverState={}]", driverState) + // FIXME for displaying pod info, we should look at PodStateManager instead of this driverState, + // because that way, we can also show the Pod info when the RL isn't initialized yet if (driverState == OmnipodDriverState.NotInitalized) { omnipod_pod_address.text = resourceHelper.gs(R.string.omnipod_pod_name_no_info) + omnipod_pod_lot.text = "-" + omnipod_pod_tid.text = "-" + omnipod_pod_fw_version.text = "-" omnipod_pod_expiry.text = "-" omnipod_pod_status.text = resourceHelper.gs(R.string.omnipod_pod_not_initalized) omnipodPumpStatus.podAvailable = false omnipodPumpStatus.podNumber == null } else if (driverState == OmnipodDriverState.Initalized_NoPod) { omnipod_pod_address.text = resourceHelper.gs(R.string.omnipod_pod_name_no_info) + omnipod_pod_lot.text = "-" + omnipod_pod_tid.text = "-" + omnipod_pod_fw_version.text = "-" omnipod_pod_expiry.text = "-" omnipod_pod_status.text = resourceHelper.gs(R.string.omnipod_pod_no_pod_connected) omnipodPumpStatus.podAvailable = false omnipodPumpStatus.podNumber == null } else if (driverState == OmnipodDriverState.Initalized_PodInitializing) { omnipod_pod_address.text = omnipodPumpStatus.podStateManager.address.toString() + omnipod_pod_lot.text = "-" + omnipod_pod_tid.text = "-" + omnipod_pod_fw_version.text = "-" omnipod_pod_expiry.text = "-" omnipod_pod_status.text = resourceHelper.gs(R.string.omnipod_pod_status_initalizing) + " (" + omnipodPumpStatus.podStateManager.getSetupProgress().name + ")" omnipodPumpStatus.podAvailable = false @@ -272,16 +277,24 @@ class OmnipodFragment : DaggerFragment() { omnipodPumpStatus.podLotNumber = "" + omnipodPumpStatus.podStateManager.lot omnipodPumpStatus.podAvailable = true omnipod_pod_address.text = omnipodPumpStatus.podStateManager.address.toString() + omnipod_pod_lot.text = if (omnipodPumpStatus.podStateManager.lot == null) "" else omnipodPumpStatus.podStateManager.lot.toString() + omnipod_pod_tid.text = if (omnipodPumpStatus.podStateManager.tid == null) "" else omnipodPumpStatus.podStateManager.tid.toString() + if (omnipodPumpStatus.podStateManager.pmVersion == null || omnipodPumpStatus.podStateManager.piVersion == null) { + omnipod_pod_fw_version.text = "" + } else { + omnipod_pod_fw_version.text = omnipodPumpStatus.podStateManager.pmVersion.toString() + " / " + omnipodPumpStatus.podStateManager.piVersion.toString() + } omnipod_pod_expiry.text = omnipodPumpStatus.podStateManager.expiryDateAsString omnipodPumpStatus.podNumber = omnipodPumpStatus.podStateManager.address.toString() var podDeviceState = omnipodPumpStatus.podDeviceState - var stateText : String? + var stateText: String? + // FIXME this PodDeviceState doesn't make much sense. We should use info from PodStateManager when (podDeviceState) { null, - PodDeviceState.Sleeping -> stateText = "{fa-bed} " // + pumpStatus.pumpDeviceState.name()); + PodDeviceState.Sleeping -> stateText = "{fa-bed} " // + pumpStatus.pumpDeviceState.name()); PodDeviceState.NeverContacted, PodDeviceState.WakingUp, PodDeviceState.PumpUnreachable, @@ -289,7 +302,7 @@ class OmnipodFragment : DaggerFragment() { PodDeviceState.TimeoutWhenCommunicating, PodDeviceState.InvalidConfiguration -> stateText = " " + resourceHelper.gs(podDeviceState.resourceId) - PodDeviceState.Active -> { + PodDeviceState.Active -> { stateText = resourceHelper.gs(R.string.omnipod_pod_status_active) // val cmd = OmnipodUtil.getCurrentCommand() // if (cmd == null) @@ -308,18 +321,19 @@ class OmnipodFragment : DaggerFragment() { // } // } } - else -> { + + else -> { aapsLogger.warn(LTag.PUMP, "Unknown pump state: " + omnipodPumpStatus.podDeviceState) stateText = resourceHelper.gs(R.string.omnipod_pod_status_unknown) } } - if (SetupProgress.COMPLETED.equals(omnipodPumpStatus.podStateManager.getSetupProgress())) { - if(omnipodPumpStatus.podStateManager.lastDeliveryStatus != null) { + if (omnipodPumpStatus.podStateManager.isSetupCompleted) { + if (omnipodPumpStatus.podStateManager.lastDeliveryStatus != null) { stateText += " (last delivery status: " + omnipodPumpStatus.podStateManager.lastDeliveryStatus.name + ")" } } else { - if(omnipodPumpStatus.podStateManager.setupProgress != null) { + if (omnipodPumpStatus.podStateManager.isPaired) { stateText += " (setup progress: " + omnipodPumpStatus.podStateManager.setupProgress.name + ")" } } 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 index 6968532adb..7c9a96c0b4 100644 --- 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 @@ -1,5 +1,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.comm; +import org.joda.time.DateTime; + import java.util.Collections; import java.util.List; @@ -144,6 +146,7 @@ public class OmnipodCommunicationManager extends RileyLinkCommunicationManager { } if (responseClass.isInstance(responseMessageBlock)) { + podStateManager.setLastSuccessfulCommunication(DateTime.now()); return (T) responseMessageBlock; } else { if (responseMessageBlock.getType() == MessageBlockType.ERROR_RESPONSE) { @@ -153,21 +156,26 @@ public class OmnipodCommunicationManager extends RileyLinkCommunicationManager { if (automaticallyResyncNonce) { message.resyncNonce(podStateManager.getCurrentNonce()); } else { + podStateManager.setLastFailedCommunication(DateTime.now()); throw new NonceOutOfSyncException(); } } else { + podStateManager.setLastFailedCommunication(DateTime.now()); throw new PodReturnedErrorResponseException(error); } } else if (responseMessageBlock.getType() == MessageBlockType.POD_INFO_RESPONSE && ((PodInfoResponse) responseMessageBlock).getSubType() == PodInfoType.FAULT_EVENT) { PodInfoFaultEvent faultEvent = ((PodInfoResponse) responseMessageBlock).getPodInfo(); podStateManager.setFaultEvent(faultEvent); + podStateManager.setLastFailedCommunication(DateTime.now()); throw new PodFaultException(faultEvent); } else { + podStateManager.setLastFailedCommunication(DateTime.now()); throw new IllegalResponseException(responseClass.getSimpleName(), responseMessageBlock.getType()); } } } + podStateManager.setLastFailedCommunication(DateTime.now()); throw new NonceResyncException(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java index 3f3974bb21..3de30a1845 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java @@ -208,9 +208,10 @@ public class OmnipodManager { } try { - return executeAndVerify(() -> communicationService.executeAction(new SetTempBasalAction( + StatusResponse statusResponse = executeAndVerify(() -> communicationService.executeAction(new SetTempBasalAction( podStateManager, rate, duration, acknowledgementBeep, completionBeep))); + return statusResponse; } catch (OmnipodException ex) { // Treat all exceptions as uncertain failures, because all delivery has been suspended here. // Setting this to an uncertain failure will enable for the user to get an appropriate warning @@ -266,6 +267,7 @@ public class OmnipodManager { } DateTime startDate = DateTime.now().minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); + podStateManager.setLastBolus(startDate, units); CompositeDisposable disposables = new CompositeDisposable(); Duration bolusDuration = calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE); @@ -352,8 +354,10 @@ public class OmnipodManager { private void discardActiveBolusData(double unitsNotDelivered) { synchronized (bolusDataMutex) { + double unitsDelivered = activeBolusData.getUnits() - unitsNotDelivered; + podStateManager.setLastBolus(activeBolusData.getStartDate(), unitsDelivered); activeBolusData.getDisposables().dispose(); - activeBolusData.getBolusCompletionSubject().onSuccess(new BolusDeliveryResult(activeBolusData.getUnits() - unitsNotDelivered)); + activeBolusData.getBolusCompletionSubject().onSuccess(new BolusDeliveryResult(unitsDelivered)); activeBolusData = null; } } @@ -446,7 +450,7 @@ public class OmnipodManager { } public boolean isReadyForDelivery() { - return podStateManager.isPaired() && podStateManager.getSetupProgress() == SetupProgress.COMPLETED; + return podStateManager.isSetupCompleted(); } public boolean hasActiveBolus() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java index 9df030fec5..c29135a24f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java @@ -50,7 +50,11 @@ public class CancelDeliveryAction implements OmnipodAction { acknowledgementBeep && deliveryTypes.size() == 1 ? BeepType.BEEP : BeepType.NO_BEEP, deliveryTypes)); } - return communicationService.exchangeMessages(StatusResponse.class, podStateManager, + StatusResponse statusResponse = communicationService.exchangeMessages(StatusResponse.class, podStateManager, new OmnipodMessage(podStateManager.getAddress(), messageBlocks, podStateManager.getMessageNumber())); + if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) { + podStateManager.setLastTempBasal(null, null, null); + } + return statusResponse; } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java index f316ece733..0610256d81 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; +import org.joda.time.DateTime; import org.joda.time.Duration; import java.util.Arrays; @@ -13,6 +14,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetI import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.TempBasalExtraCommand; import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateManager; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; public class SetTempBasalAction implements OmnipodAction { private final PodStateManager podStateManager; @@ -43,6 +45,8 @@ public class SetTempBasalAction implements OmnipodAction { new TempBasalExtraCommand(rate, duration, acknowledgementBeep, completionBeep, Duration.ZERO)); OmnipodMessage message = new OmnipodMessage(podStateManager.getAddress(), messageBlocks, podStateManager.getMessageNumber()); - return communicationService.exchangeMessages(StatusResponse.class, podStateManager, message); + StatusResponse statusResponse = communicationService.exchangeMessages(StatusResponse.class, podStateManager, message); + podStateManager.setLastTempBasal(new DateTime().minus(OmnipodConst.AVERAGE_TEMP_BASAL_COMMAND_COMMUNICATION_DURATION), rate, duration); + return statusResponse; } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java index 9d65d7d5aa..91590e2f12 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java @@ -15,8 +15,12 @@ public class AlertSet { } } + public AlertSet(AlertSet alertSet) { + this(alertSet == null ? new ArrayList<>() : alertSet.getAlertSlots()); + } + public AlertSet(List alertSlots) { - this.alertSlots = alertSlots; + this.alertSlots = alertSlots == null ? new ArrayList<>() : new ArrayList<>(alertSlots); } public List getAlertSlots() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java index 3999cd2923..d345a3c385 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java @@ -71,9 +71,5 @@ public interface OmnipodCommunicationManagerInterface { */ PumpEnactResult setTime(); - - void setPumpStatus(OmnipodPumpStatus pumpStatusLocal); - - PodInfoRecentPulseLog readPulseLog(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java deleted file mode 100644 index c1171b498e..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java +++ /dev/null @@ -1,6 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.defs.state; - -@FunctionalInterface -public interface PodStateChangedHandler { - void handle(PodStateManager podStateManager); -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateManager.java index 009503bad1..1956c696ac 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateManager.java @@ -1,9 +1,23 @@ package info.nightscout.androidaps.plugins.pump.omnipod.defs.state; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializer; + +import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Duration; +import org.joda.time.format.ISODateTimeFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; @@ -13,92 +27,726 @@ import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion; import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.utils.DateUtil; -public interface PodStateManager { +public abstract class PodStateManager { - boolean hasState(); + private final AAPSLogger aapsLogger; + private final Gson gsonInstance; + private PodState podState; - void removeState(); + public PodStateManager(AAPSLogger aapsLogger) { + this.aapsLogger = aapsLogger; + this.gsonInstance = createGson(); + } - void initState(int address); + public final boolean hasState() { + return podState != null; + } - boolean isPaired(); + public final void removeState() { + this.podState = null; + storePodState(); + notifyPodStateChanged(); + } - void setPairingParameters(int lot, int tid, FirmwareVersion piVersion, FirmwareVersion pmVersion, DateTimeZone timeZone); + public final void initState(int address) { + if (hasState()) { + throw new IllegalStateException("Can not init a new pod state: podState <> null"); + } + podState = new PodState(address); + storePodState(); + notifyPodStateChanged(); + } - int getAddress(); + public final boolean isPaired() { + return hasState() // + && podState.getLot() != null && podState.getTid() != null // + && podState.getPiVersion() != null && podState.getPmVersion() != null // + && podState.getTimeZone() != null // + && podState.getSetupProgress() != null; + } - int getMessageNumber(); + public final boolean isSetupCompleted() { + return isPaired() && SetupProgress.COMPLETED.equals(podState.getSetupProgress()); + } - void setMessageNumber(int messageNumber); + public final void setPairingParameters(int lot, int tid, FirmwareVersion piVersion, FirmwareVersion pmVersion, DateTimeZone timeZone) { + if (!hasState()) { + throw new IllegalStateException("Cannot set pairing parameters: podState is null"); + } + if (isPaired()) { + throw new IllegalStateException("Cannot set pairing parameters: pairing parameters have already been set"); + } + if (piVersion == null) { + throw new IllegalArgumentException("Cannot set pairing parameters: piVersion can not be null"); + } + if (pmVersion == null) { + throw new IllegalArgumentException("Cannot set pairing parameters: pmVersion can not be null"); + } + if (timeZone == null) { + throw new IllegalArgumentException("Cannot set pairing parameters: timeZone can not be null"); + } - int getPacketNumber(); + setAndStore(() -> { + podState.setLot(lot); + podState.setTid(tid); + podState.setPiVersion(piVersion); + podState.setPmVersion(pmVersion); + podState.setTimeZone(timeZone); + podState.setNonceState(new NonceState(lot, tid)); + podState.setSetupProgress(SetupProgress.ADDRESS_ASSIGNED); + podState.getConfiguredAlerts().put(AlertSlot.SLOT7, AlertType.FINISH_SETUP_REMINDER); + }); + } - void setPacketNumber(int packetNumber); + public final int getAddress() { + return getSafe(() -> podState.getAddress()); + } - void increaseMessageNumber(); + public final int getMessageNumber() { + return getSafe(() -> podState.getMessageNumber()); + } - void increasePacketNumber(); + public final void setMessageNumber(int messageNumber) { + setAndStore(() -> podState.setMessageNumber(messageNumber)); + } - void resyncNonce(int syncWord, int sentNonce, int sequenceNumber); + public final int getPacketNumber() { + return getSafe(() -> podState.getPacketNumber()); + } - int getCurrentNonce(); + public final void setPacketNumber(int packetNumber) { + setAndStore(() -> podState.setPacketNumber(packetNumber)); + } - void advanceToNextNonce(); + public final void increaseMessageNumber() { + setAndStore(() -> podState.setMessageNumber((podState.getMessageNumber() + 1) & 0b1111)); + } - boolean hasFaultEvent(); + public final void increasePacketNumber() { + setAndStore(() -> podState.setPacketNumber((podState.getPacketNumber() + 1) & 0b11111)); + } - PodInfoFaultEvent getFaultEvent(); + public final synchronized void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) { + if (!isPaired()) { + throw new IllegalStateException("Cannot resync nonce: Pod is not paired yet"); + } - void setFaultEvent(PodInfoFaultEvent faultEvent); + int sum = (sentNonce & 0xFFFF) + + OmniCRC.crc16lookup[sequenceNumber] + + (podState.getLot() & 0xFFFF) + + (podState.getTid() & 0xFFFF); + int seed = ((sum & 0xFFFF) ^ syncWord); + NonceState nonceState = new NonceState(podState.getLot(), podState.getTid(), (byte) (seed & 0xFF)); - AlertType getConfiguredAlertType(AlertSlot alertSlot); + setAndStore(() -> podState.setNonceState(nonceState)); + } - void putConfiguredAlert(AlertSlot alertSlot, AlertType alertType); + public final synchronized int getCurrentNonce() { + if (!isPaired()) { + throw new IllegalStateException("Cannot get current nonce: Pod is not paired yet"); + } + return podState.getNonceState().getCurrentNonce(); + } - void removeConfiguredAlert(AlertSlot alertSlot); + public final synchronized void advanceToNextNonce() { + if (!isPaired()) { + throw new IllegalStateException("Cannot advance to next nonce: Pod is not paired yet"); + } + setAndStore(() -> podState.getNonceState().advanceToNextNonce()); + } - boolean hasActiveAlerts(); + public final DateTime getLastSuccessfulCommunication() { + return getSafe(() -> podState.getLastSuccessfulCommunication()); + } - AlertSet getActiveAlerts(); + public final void setLastSuccessfulCommunication(DateTime dateTime) { + setAndStore(() -> podState.setLastSuccessfulCommunication(dateTime)); + } - Integer getLot(); + public final DateTime getLastFailedCommunication() { + return getSafe(() -> podState.getLastFailedCommunication()); + } - Integer getTid(); + public final void setLastFailedCommunication(DateTime dateTime) { + setAndStore(() -> podState.setLastFailedCommunication(dateTime)); + } - FirmwareVersion getPiVersion(); + public final boolean hasFaultEvent() { + return getSafe(() -> podState.getFaultEvent()) != null; + } - FirmwareVersion getPmVersion(); + public final PodInfoFaultEvent getFaultEvent() { + return getSafe(() -> podState.getFaultEvent()); + } - DateTimeZone getTimeZone(); + public final void setFaultEvent(PodInfoFaultEvent faultEvent) { + setAndStore(() -> podState.setFaultEvent(faultEvent)); + } - void setTimeZone(DateTimeZone timeZone); + public final AlertType getConfiguredAlertType(AlertSlot alertSlot) { + return getSafe(() -> podState.getConfiguredAlerts().get(alertSlot)); + } - DateTime getTime(); + public final void putConfiguredAlert(AlertSlot alertSlot, AlertType alertType) { + setAndStore(() -> podState.getConfiguredAlerts().put(alertSlot, alertType)); + } - DateTime getActivatedAt(); + public final void removeConfiguredAlert(AlertSlot alertSlot) { + setAndStore(() -> podState.getConfiguredAlerts().remove(alertSlot)); + } - DateTime getExpiresAt(); + public final boolean hasActiveAlerts() { + AlertSet activeAlerts = podState.getActiveAlerts(); + return activeAlerts != null && activeAlerts.size() > 0; + } - String getExpiryDateAsString(); + public final AlertSet getActiveAlerts() { + return new AlertSet(getSafe(() -> podState.getActiveAlerts())); + } - SetupProgress getSetupProgress(); + public final Integer getLot() { + return getSafe(() -> podState.getLot()); + } - void setSetupProgress(SetupProgress setupProgress); + public final Integer getTid() { + return getSafe(() -> podState.getTid()); + } - boolean isSuspended(); + public final FirmwareVersion getPiVersion() { + return getSafe(() -> podState.getPiVersion()); + } - Double getReservoirLevel(); + public final FirmwareVersion getPmVersion() { + return getSafe(() -> podState.getPmVersion()); + } - Duration getScheduleOffset(); + public final DateTimeZone getTimeZone() { + return getSafe(() -> podState.getTimeZone()); + } - BasalSchedule getBasalSchedule(); + public final void setTimeZone(DateTimeZone timeZone) { + if (timeZone == null) { + throw new IllegalArgumentException("Time zone can not be null"); + } + setAndStore(() -> podState.setTimeZone(timeZone)); + } - void setBasalSchedule(BasalSchedule basalSchedule); + public final DateTime getTime() { + DateTime now = DateTime.now(); + return now.withZone(getSafe(() -> podState.getTimeZone())); + } - DeliveryStatus getLastDeliveryStatus(); + public final DateTime getActivatedAt() { + DateTime activatedAt = getSafe(() -> podState.getActivatedAt()); + return activatedAt == null ? null : activatedAt.withZone(getSafe(() -> podState.getTimeZone())); + } - void updateFromStatusResponse(StatusResponse statusResponse); + public final DateTime getExpiresAt() { + DateTime expiresAt = getSafe(() -> podState.getExpiresAt()); + return expiresAt == null ? null : expiresAt.withZone(getSafe(() -> podState.getTimeZone())); + } - void setStateChangedHandler(PodStateChangedHandler handler); + // TODO doesn't belong here + public final String getExpiryDateAsString() { + DateTime expiresAt = getExpiresAt(); + return expiresAt == null ? "???" : DateUtil.dateAndTimeString(expiresAt.toDate()); + } + + public final SetupProgress getSetupProgress() { + return getSafe(() -> podState.getSetupProgress()); + } + + public final void setSetupProgress(SetupProgress setupProgress) { + if (setupProgress == null) { + throw new IllegalArgumentException("Setup progress can not be null"); + } + setAndStore(() -> podState.setSetupProgress(setupProgress)); + } + + public final boolean isSuspended() { + return getSafe(() -> podState.isSuspended()); + } + + public final Double getReservoirLevel() { + return getSafe(() -> podState.getReservoirLevel()); + } + + public final Duration getScheduleOffset() { + DateTime now = getTime(); + DateTime startOfDay = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), + 0, 0, 0, getSafe(() -> podState.getTimeZone())); + return new Duration(startOfDay, now); + } + + public final BasalSchedule getBasalSchedule() { + return getSafe(() -> podState.getBasalSchedule()); + } + + public final void setBasalSchedule(BasalSchedule basalSchedule) { + setAndStore(() -> podState.setBasalSchedule(basalSchedule)); + } + + public final DateTime getLastBolusStartTime() { + return getSafe(() -> podState.getLastBolusStartTime()); + } + + public final Double getLastBolusAmount() { + return getSafe(() -> podState.getLastBolusAmount()); + } + + public final void setLastBolus(DateTime startTime, double amount) { + setAndStore(() -> { + podState.setLastBolusStartTime(startTime); + podState.setLastBolusAmount(amount); + }); + } + + public final DateTime getLastTempBasalStartTime() { + return getSafe(() -> podState.getLastTempBasalStartTime()); + } + + public final Double getLastTempBasalAmount() { + return getSafe(() -> podState.getLastTempBasalAmount()); + } + + public final Duration getLastTempBasalDuration() { + return getSafe(() -> podState.getLastTempBasalDuration()); + } + + public final void setLastTempBasal(DateTime startTime, Double amount, Duration duration) { + setAndStore(() -> { + podState.setLastTempBasalStartTime(startTime); + podState.setLastTempBasalAmount(amount); + podState.setLastTempBasalDuration(duration); + }); + } + + public final DeliveryStatus getLastDeliveryStatus() { + return getSafe(() -> podState.getLastDeliveryStatus()); + } + + public final void updateFromStatusResponse(StatusResponse statusResponse) { + if (!hasState()) { + throw new IllegalStateException("Cannot update from status response: podState is null"); + } + setAndStore(() -> { + if (podState.getActivatedAt() == null) { + DateTime activatedAtCalculated = getTime().minus(statusResponse.getTimeActive()); + podState.setActivatedAt(activatedAtCalculated); + } + DateTime expiresAt = podState.getExpiresAt(); + DateTime expiresAtCalculated = podState.getActivatedAt().plus(OmnipodConst.NOMINAL_POD_LIFE); + if (expiresAt == null || expiresAtCalculated.isBefore(expiresAt) || expiresAtCalculated.isAfter(expiresAt.plusMinutes(1))) { + podState.setExpiresAt(expiresAtCalculated); + } + + boolean newSuspendedState = statusResponse.getDeliveryStatus() == DeliveryStatus.SUSPENDED; + if (podState.isSuspended() != newSuspendedState) { + aapsLogger.info(LTag.PUMPCOMM, "Updating pod suspended state in updateFromStatusResponse. newSuspendedState={}, statusResponse={}", newSuspendedState, statusResponse.toString()); + podState.setSuspended(newSuspendedState); + } + podState.setActiveAlerts(statusResponse.getAlerts()); + podState.setLastDeliveryStatus(statusResponse.getDeliveryStatus()); + podState.setReservoirLevel(statusResponse.getReservoirLevel()); + }); + } + + private void setAndStore(Runnable runnable) { + if (!hasState()) { + throw new IllegalStateException("Cannot mutate PodState: podState is null"); + } + runnable.run(); + storePodState(); + notifyPodStateChanged(); + } + + private void storePodState() { + String podState = gsonInstance.toJson(this.podState); + aapsLogger.info(LTag.PUMP, "storePodState: storing podState: " + podState); + storePodState(podState); + } + + protected abstract void storePodState(String podState); + + protected abstract String readPodState(); + + // Should be called after initializing the object + public final void loadPodState() { + podState = null; + + String storedPodState = readPodState(); + + if (StringUtils.isEmpty(storedPodState)) { + aapsLogger.info(LTag.PUMP, "loadPodState: no Pod state was provided"); + } else { + aapsLogger.info(LTag.PUMP, "loadPodState: serialized Pod state was provided: " + storedPodState); + try { + podState = gsonInstance.fromJson(storedPodState, PodState.class); + } catch (Exception ex) { + aapsLogger.error(LTag.PUMP, "loadPodState: could not deserialize PodState: " + storedPodState, ex); + } + } + + notifyPodStateChanged(); + } + + protected abstract void notifyPodStateChanged(); + + // Not actually "safe" as it throws an Exception, but it prevents NPEs + private T getSafe(Supplier supplier) { + if (!hasState()) { + throw new IllegalStateException("Cannot read from PodState: podState is null"); + } + return supplier.get(); + } + + private static Gson createGson() { + GsonBuilder gsonBuilder = new GsonBuilder() + .registerTypeAdapter(DateTime.class, (JsonSerializer) (dateTime, typeOfSrc, context) -> + new JsonPrimitive(ISODateTimeFormat.dateTime().print(dateTime))) + .registerTypeAdapter(DateTime.class, (JsonDeserializer) (json, typeOfT, context) -> + ISODateTimeFormat.dateTime().parseDateTime(json.getAsString())) + .registerTypeAdapter(DateTimeZone.class, (JsonSerializer) (timeZone, typeOfSrc, context) -> + new JsonPrimitive(timeZone.getID())) + .registerTypeAdapter(DateTimeZone.class, (JsonDeserializer) (json, typeOfT, context) -> + DateTimeZone.forID(json.getAsString())); + + return gsonBuilder.create(); + } + + @Override public String toString() { + return "AapsPodStateManager{" + + "podState=" + podState + + '}'; + } + + private static final class PodState { + private final int address; + private Integer lot; + private Integer tid; + private FirmwareVersion piVersion; + private FirmwareVersion pmVersion; + private int packetNumber; + private int messageNumber; + private DateTime lastSuccessfulCommunication; + private DateTime lastFailedCommunication; + private DateTimeZone timeZone; + private DateTime activatedAt; + private DateTime expiresAt; + private PodInfoFaultEvent faultEvent; + private Double reservoirLevel; + private boolean suspended; + private NonceState nonceState; + private SetupProgress setupProgress; + private DeliveryStatus lastDeliveryStatus; + private AlertSet activeAlerts; + private BasalSchedule basalSchedule; + private DateTime lastBolusStartTime; + private Double lastBolusAmount; + private Double lastTempBasalAmount; + private DateTime lastTempBasalStartTime; + private Duration lastTempBasalDuration; + private final Map configuredAlerts = new HashMap<>(); + + private PodState(int address) { + this.address = address; + } + + int getAddress() { + return address; + } + + Integer getLot() { + return lot; + } + + void setLot(int lot) { + this.lot = lot; + } + + Integer getTid() { + return tid; + } + + void setTid(int tid) { + this.tid = tid; + } + + FirmwareVersion getPiVersion() { + return piVersion; + } + + void setPiVersion(FirmwareVersion piVersion) { + if (this.piVersion != null) { + throw new IllegalStateException("piVersion has already been set"); + } + if (piVersion == null) { + throw new IllegalArgumentException("piVersion can not be null"); + } + this.piVersion = piVersion; + } + + FirmwareVersion getPmVersion() { + return pmVersion; + } + + void setPmVersion(FirmwareVersion pmVersion) { + this.pmVersion = pmVersion; + } + + int getPacketNumber() { + return packetNumber; + } + + void setPacketNumber(int packetNumber) { + this.packetNumber = packetNumber; + } + + int getMessageNumber() { + return messageNumber; + } + + void setMessageNumber(int messageNumber) { + this.messageNumber = messageNumber; + } + + DateTime getLastSuccessfulCommunication() { + return lastSuccessfulCommunication; + } + + void setLastSuccessfulCommunication(DateTime lastSuccessfulCommunication) { + this.lastSuccessfulCommunication = lastSuccessfulCommunication; + } + + DateTime getLastFailedCommunication() { + return lastFailedCommunication; + } + + void setLastFailedCommunication(DateTime lastFailedCommunication) { + this.lastFailedCommunication = lastFailedCommunication; + } + + DateTimeZone getTimeZone() { + return timeZone; + } + + void setTimeZone(DateTimeZone timeZone) { + this.timeZone = timeZone; + } + + DateTime getActivatedAt() { + return activatedAt; + } + + void setActivatedAt(DateTime activatedAt) { + this.activatedAt = activatedAt; + } + + DateTime getExpiresAt() { + return expiresAt; + } + + void setExpiresAt(DateTime expiresAt) { + this.expiresAt = expiresAt; + } + + PodInfoFaultEvent getFaultEvent() { + return faultEvent; + } + + void setFaultEvent(PodInfoFaultEvent faultEvent) { + this.faultEvent = faultEvent; + } + + Double getReservoirLevel() { + return reservoirLevel; + } + + void setReservoirLevel(Double reservoirLevel) { + this.reservoirLevel = reservoirLevel; + } + + public boolean isSuspended() { + return suspended; + } + + void setSuspended(boolean suspended) { + this.suspended = suspended; + } + + NonceState getNonceState() { + return nonceState; + } + + void setNonceState(NonceState nonceState) { + this.nonceState = nonceState; + } + + public SetupProgress getSetupProgress() { + return setupProgress; + } + + void setSetupProgress(SetupProgress setupProgress) { + this.setupProgress = setupProgress; + } + + DeliveryStatus getLastDeliveryStatus() { + return lastDeliveryStatus; + } + + void setLastDeliveryStatus(DeliveryStatus lastDeliveryStatus) { + this.lastDeliveryStatus = lastDeliveryStatus; + } + + AlertSet getActiveAlerts() { + return activeAlerts; + } + + void setActiveAlerts(AlertSet activeAlerts) { + this.activeAlerts = activeAlerts; + } + + BasalSchedule getBasalSchedule() { + return basalSchedule; + } + + void setBasalSchedule(BasalSchedule basalSchedule) { + this.basalSchedule = basalSchedule; + } + + public DateTime getLastBolusStartTime() { + return lastBolusStartTime; + } + + void setLastBolusStartTime(DateTime lastBolusStartTime) { + this.lastBolusStartTime = lastBolusStartTime; + } + + Double getLastBolusAmount() { + return lastBolusAmount; + } + + void setLastBolusAmount(Double lastBolusAmount) { + this.lastBolusAmount = lastBolusAmount; + } + + Double getLastTempBasalAmount() { + return lastTempBasalAmount; + } + + void setLastTempBasalAmount(Double lastTempBasalAmount) { + this.lastTempBasalAmount = lastTempBasalAmount; + } + + DateTime getLastTempBasalStartTime() { + return lastTempBasalStartTime; + } + + void setLastTempBasalStartTime(DateTime lastTempBasalStartTime) { + this.lastTempBasalStartTime = lastTempBasalStartTime; + } + + Duration getLastTempBasalDuration() { + return lastTempBasalDuration; + } + + void setLastTempBasalDuration(Duration lastTempBasalDuration) { + this.lastTempBasalDuration = lastTempBasalDuration; + } + + Map getConfiguredAlerts() { + return configuredAlerts; + } + + @Override public String toString() { + return "PodState{" + + "address=" + address + + ", lot=" + lot + + ", tid=" + tid + + ", piVersion=" + piVersion + + ", pmVersion=" + pmVersion + + ", packetNumber=" + packetNumber + + ", messageNumber=" + messageNumber + + ", lastSuccessfulCommunication=" + lastSuccessfulCommunication + + ", lastFailedCommunication=" + lastFailedCommunication + + ", timeZone=" + timeZone + + ", activatedAt=" + activatedAt + + ", expiresAt=" + expiresAt + + ", faultEvent=" + faultEvent + + ", reservoirLevel=" + reservoirLevel + + ", suspended=" + suspended + + ", nonceState=" + nonceState + + ", setupProgress=" + setupProgress + + ", lastDeliveryStatus=" + lastDeliveryStatus + + ", activeAlerts=" + activeAlerts + + ", basalSchedule=" + basalSchedule + + ", lastBolusStartTime=" + lastBolusStartTime + + ", lastBolusAmount=" + lastBolusAmount + + ", lastTempBasalAmount=" + lastTempBasalAmount + + ", lastTempBasalStartTime=" + lastTempBasalStartTime + + ", lastTempBasalDuration=" + lastTempBasalDuration + + ", configuredAlerts=" + configuredAlerts + + '}'; + } + } + + private static class NonceState { + private final long[] table = new long[21]; + private int index; + + private NonceState(int lot, int tid) { + initializeTable(lot, tid, (byte) 0x00); + } + + private NonceState(int lot, int tid, byte seed) { + initializeTable(lot, tid, seed); + } + + private void initializeTable(int lot, int tid, byte seed) { + table[0] = (long) (lot & 0xFFFF) + 0x55543DC3L + (((long) (lot) & 0xFFFFFFFFL) >> 16); + table[0] = table[0] & 0xFFFFFFFFL; + table[1] = (tid & 0xFFFF) + 0xAAAAE44EL + (((long) (tid) & 0xFFFFFFFFL) >> 16); + table[1] = table[1] & 0xFFFFFFFFL; + index = 0; + table[0] += seed; + for (int i = 0; i < 16; i++) { + table[2 + i] = generateEntry(); + } + index = (int) ((table[0] + table[1]) & 0X0F); + } + + private int generateEntry() { + table[0] = (((table[0] >> 16) + (table[0] & 0xFFFF) * 0x5D7FL) & 0xFFFFFFFFL); + table[1] = (((table[1] >> 16) + (table[1] & 0xFFFF) * 0x8CA0L) & 0xFFFFFFFFL); + return (int) ((table[1] + (table[0] << 16)) & 0xFFFFFFFFL); + } + + public int getCurrentNonce() { + return (int) table[(2 + index)]; + } + + void advanceToNextNonce() { + int nonce = getCurrentNonce(); + table[(2 + index)] = generateEntry(); + index = (nonce & 0x0F); + } + + @Override + public String toString() { + return "NonceState{" + + "table=" + Arrays.toString(table) + + ", index=" + index + + '}'; + } + } + + // TODO replace with java.util.function.Supplier when min API level >= 24 + @FunctionalInterface + private interface Supplier { + T get(); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java index d5ac74c01d..f814a1082f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java @@ -1,16 +1,13 @@ package info.nightscout.androidaps.plugins.pump.omnipod.driver.comm; import android.content.Intent; -import android.text.TextUtils; -import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.Duration; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Objects; import dagger.android.HasAndroidInjector; import info.nightscout.androidaps.MainApp; @@ -22,7 +19,6 @@ import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.interfaces.ActivePluginProvider; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.logging.AAPSLogger; @@ -32,7 +28,6 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotifi import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; -import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; @@ -60,9 +55,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.PodReturne import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertType; import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventCode; import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; @@ -74,8 +66,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateManage import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistory; import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistoryEntryType; -import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodAcknowledgeAlertsChanged; -import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException; import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; import info.nightscout.androidaps.utils.resources.ResourceHelper; @@ -97,9 +87,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface private static AapsOmnipodManager instance; - private Date lastBolusTime; - private Double lastBolusUnits; - public static AapsOmnipodManager getInstance() { return instance; } @@ -126,12 +113,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface this.activePlugin = activePlugin; this.pumpStatus = _pumpStatus; - podStateManager.setStateChangedHandler(manager -> { - // Handle pod state changes - updatePumpStatus(manager); - omnipodUtil.notifyDeviceStatusChanged(); - }); - delegate = new OmnipodManager(aapsLogger, sp, communicationService, podStateManager); instance = this; } @@ -140,74 +121,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface return podStateManager; } - private void updatePumpStatus(PodStateManager podStateManager) { - if (pumpStatus != null) { - if (!podStateManager.hasState()) { - pumpStatus.ackAlertsText = null; - pumpStatus.ackAlertsAvailable = false; - pumpStatus.lastBolusTime = null; - pumpStatus.lastBolusAmount = null; - pumpStatus.reservoirRemainingUnits = 0.0; - pumpStatus.pumpStatusType = PumpStatusType.Suspended; - sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); - sendEvent(new EventOmnipodPumpValuesChanged()); - sendEvent(new EventRefreshOverview("Omnipod Pump", false)); - } else { - // Update active alerts - if (podStateManager.hasActiveAlerts()) { - List alerts = translateActiveAlerts(podStateManager); - String alertsText = TextUtils.join("\n", alerts); - - if (!pumpStatus.ackAlertsAvailable || !alertsText.equals(pumpStatus.ackAlertsText)) { - pumpStatus.ackAlertsAvailable = true; - pumpStatus.ackAlertsText = TextUtils.join("\n", alerts); - - sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); - } - } else { - if (pumpStatus.ackAlertsAvailable || StringUtils.isNotEmpty(pumpStatus.ackAlertsText)) { - pumpStatus.ackAlertsText = null; - pumpStatus.ackAlertsAvailable = false; - sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); - } - } - - // Update other info: last bolus, units remaining, suspended - if (!Objects.equals(lastBolusTime, pumpStatus.lastBolusTime) // - || !Objects.equals(lastBolusUnits, pumpStatus.lastBolusAmount) // - || !isReservoirStatusUpToDate(pumpStatus, podStateManager.getReservoirLevel()) - || podStateManager.isSuspended() != PumpStatusType.Suspended.equals(pumpStatus.pumpStatusType)) { - pumpStatus.lastBolusTime = lastBolusTime; - pumpStatus.lastBolusAmount = lastBolusUnits; - pumpStatus.reservoirRemainingUnits = podStateManager.getReservoirLevel() == null ? 75.0 : podStateManager.getReservoirLevel(); - pumpStatus.pumpStatusType = podStateManager.isSuspended() ? PumpStatusType.Suspended : PumpStatusType.Running; - sendEvent(new EventOmnipodPumpValuesChanged()); - - if (podStateManager.isSuspended() != PumpStatusType.Suspended.equals(pumpStatus.pumpStatusType)) { - sendEvent(new EventRefreshOverview("Omnipod Pump", false)); - } - } - } - } - } - - private List translateActiveAlerts(PodStateManager podStateManager) { - List translatedAlerts = new ArrayList<>(); - AlertSet activeAlerts = podStateManager.getActiveAlerts(); - if (activeAlerts == null) { - return translatedAlerts; - } - for (AlertSlot alertSlot : activeAlerts.getAlertSlots()) { - translatedAlerts.add(translateAlertType(podStateManager.getConfiguredAlertType(alertSlot))); - } - return translatedAlerts; - } - - private static boolean isReservoirStatusUpToDate(OmnipodPumpStatus pumpStatus, Double unitsRemaining) { - double expectedUnitsRemaining = unitsRemaining == null ? 75.0 : unitsRemaining; - return Math.abs(expectedUnitsRemaining - pumpStatus.reservoirRemainingUnits) < 0.000001; - } - @Override public PumpEnactResult initPod(PodInitActionType podInitActionType, PodInitReceiver podInitReceiver, Profile profile) { long time = System.currentTimeMillis(); @@ -306,7 +219,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } - return new PumpEnactResult(injector).success(true).enacted(true); } @@ -356,11 +268,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface double unitsDelivered = bolusDeliveryResult.getUnitsDelivered(); - if (pumpStatus != null && !detailedBolusInfo.isSMB) { - lastBolusTime = pumpStatus.lastBolusTime = bolusStarted; - lastBolusUnits = pumpStatus.lastBolusAmount = unitsDelivered; - } - long pumpId = addSuccessToHistory(bolusStarted.getTime(), PodHistoryEntryType.SetBolus, unitsDelivered + ";" + detailedBolusInfo.carbs); detailedBolusInfo.date = bolusStarted.getTime(); @@ -467,12 +374,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface return new PumpEnactResult(injector).success(true).enacted(true); } - @Override - public void setPumpStatus(OmnipodPumpStatus pumpStatus) { - this.pumpStatus = pumpStatus; - updatePumpStatus(podStateManager); - } - // TODO should we add this to the OmnipodCommunicationManager interface? public PumpEnactResult getPodInfo(PodInfoType podInfoType) { long time = System.currentTimeMillis(); @@ -554,10 +455,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface return delegate.isReadyForDelivery(); } - public String getPodStateAsString() { - return podStateManager.toString(); - } - private void reportImplicitlyCanceledTbr() { //TreatmentsPlugin plugin = TreatmentsPlugin.getPlugin(); TreatmentsInterface plugin = activePlugin.getActiveTreatments(); @@ -718,28 +615,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface sendEvent(new EventNewNotification(notification)); } - private String translateAlertType(AlertType alertType) { - if (alertType == null) { - return getStringResource(R.string.omnipod_alert_unknown_alert); - } - switch (alertType) { - case FINISH_PAIRING_REMINDER: - return getStringResource(R.string.omnipod_alert_finish_pairing_reminder); - case FINISH_SETUP_REMINDER: - return getStringResource(R.string.omnipod_alert_finish_setup_reminder_reminder); - case EXPIRATION_ALERT: - return getStringResource(R.string.omnipod_alert_expiration); - case EXPIRATION_ADVISORY_ALERT: - return getStringResource(R.string.omnipod_alert_expiration_advisory); - case SHUTDOWN_IMMINENT_ALARM: - return getStringResource(R.string.omnipod_alert_shutdown_imminent); - case LOW_RESERVOIR_ALERT: - return getStringResource(R.string.omnipod_alert_low_reservoir); - default: - return alertType.name(); - } - } - private boolean isBolusBeepsEnabled() { return this.pumpStatus.beepBolusEnabled; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsPodStateManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsPodStateManager.java index 8d5feea0a5..1d365c6dba 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsPodStateManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsPodStateManager.java @@ -1,631 +1,182 @@ package info.nightscout.androidaps.plugins.pump.omnipod.driver.comm; -import com.google.gson.Gson; +import android.text.TextUtils; import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.Duration; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; -import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; +import info.nightscout.androidaps.plugins.bus.RxBusWrapper; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType; import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertType; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; -import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler; import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateManager; -import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodAcknowledgeAlertsChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; -import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; -public class AapsPodStateManager implements PodStateManager { +public class AapsPodStateManager extends PodStateManager { - @Inject protected AAPSLogger aapsLogger; - @Inject protected SP sp; - @Inject protected OmnipodUtil omnipodUtil; + private final AAPSLogger aapsLogger; + private final SP sp; + private final OmnipodUtil omnipodUtil; + private final OmnipodPumpStatus omnipodPumpStatus; + private final RxBusWrapper rxBus; + private final ResourceHelper resourceHelper; - private PodState podState; - private PodStateChangedHandler stateChangedHandler; + public AapsPodStateManager(AAPSLogger aapsLogger, SP sp, OmnipodUtil omnipodUtil, + OmnipodPumpStatus omnipodPumpStatus, RxBusWrapper rxBus, + ResourceHelper resourceHelper) { + super(aapsLogger); - public AapsPodStateManager(HasAndroidInjector injector) { - injector.androidInjector().inject(this); + if (aapsLogger == null) { + throw new IllegalArgumentException("aapsLogger can not be null"); + } + if (sp == null) { + throw new IllegalArgumentException("sp can not be null"); + } + if (omnipodUtil == null) { + throw new IllegalArgumentException("omnipodUtil can not be null"); + } + if (omnipodPumpStatus == null) { + throw new IllegalArgumentException("omnipodPumpStatus can not be null"); + } + if (rxBus == null) { + throw new IllegalArgumentException("rxBus can not be null"); + } + if (resourceHelper == null) { + throw new IllegalArgumentException("resourceHelper can not be null"); + } - // TODO is there something like @PostConstruct in Dagger? if so, we should probably move loading the pod state there - loadPodState(); - } - - @Override public boolean hasState() { - return podState != null; - } - - @Override public void removeState() { - this.podState = null; - persistPodState(); - notifyPodStateChanged(); + this.aapsLogger = aapsLogger; + this.sp = sp; + this.omnipodUtil = omnipodUtil; + this.omnipodPumpStatus = omnipodPumpStatus; + this.rxBus = rxBus; + this.resourceHelper = resourceHelper; } @Override - public void initState(int address) { - if (hasState()) { - throw new IllegalStateException("Can not init a new pod state: podState <> null"); - } - podState = new PodState(address); - persistPodState(); - notifyPodStateChanged(); - } - - @Override public boolean isPaired() { - return hasState() // - && podState.getLot() != null && podState.getTid() != null // - && podState.getPiVersion() != null && podState.getPmVersion() != null // - && podState.getTimeZone() != null // - && podState.getSetupProgress() != null; + protected String readPodState() { + return sp.getString(OmnipodConst.Prefs.PodState, ""); } @Override - public void setPairingParameters(int lot, int tid, FirmwareVersion piVersion, FirmwareVersion pmVersion, DateTimeZone timeZone) { - if (!hasState()) { - throw new IllegalStateException("Cannot set pairing parameters: podState is null"); - } - if (isPaired()) { - throw new IllegalStateException("Cannot set pairing parameters: pairing parameters have already been set"); - } - if (piVersion == null) { - throw new IllegalArgumentException("Cannot set pairing parameters: piVersion can not be null"); - } - if (pmVersion == null) { - throw new IllegalArgumentException("Cannot set pairing parameters: pmVersion can not be null"); - } - if (timeZone == null) { - throw new IllegalArgumentException("Cannot set pairing parameters: timeZone can not be null"); - } - - setAndStore(() -> { - podState.setLot(lot); - podState.setTid(tid); - podState.setPiVersion(piVersion); - podState.setPmVersion(pmVersion); - podState.setTimeZone(timeZone); - podState.setNonceState(new NonceState(lot, tid)); - podState.setSetupProgress(SetupProgress.ADDRESS_ASSIGNED); - podState.getConfiguredAlerts().put(AlertSlot.SLOT7, AlertType.FINISH_SETUP_REMINDER); - }); - } - - @Override public int getAddress() { - return getSafe(() -> podState.getAddress()); - } - - @Override public int getMessageNumber() { - return getSafe(() -> podState.getMessageNumber()); - } - - @Override public void setMessageNumber(int messageNumber) { - setAndStore(() -> podState.setMessageNumber(messageNumber)); - } - - @Override public int getPacketNumber() { - return getSafe(() -> podState.getPacketNumber()); - } - - @Override public void setPacketNumber(int packetNumber) { - setAndStore(() -> podState.setPacketNumber(packetNumber)); - } - - @Override public void increaseMessageNumber() { - setAndStore(() -> podState.setMessageNumber((podState.getMessageNumber() + 1) & 0b1111)); - } - - @Override public void increasePacketNumber() { - setAndStore(() -> podState.setPacketNumber((podState.getPacketNumber() + 1) & 0b11111)); - } - - @Override public synchronized void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) { - if (!isPaired()) { - throw new IllegalStateException("Cannot resync nonce: Pod is not paired yet"); - } - - int sum = (sentNonce & 0xFFFF) - + OmniCRC.crc16lookup[sequenceNumber] - + (podState.getLot() & 0xFFFF) - + (podState.getTid() & 0xFFFF); - int seed = ((sum & 0xFFFF) ^ syncWord); - NonceState nonceState = new NonceState(podState.getLot(), podState.getTid(), (byte) (seed & 0xFF)); - - setAndStore(() -> podState.setNonceState(nonceState)); - } - - @Override public synchronized int getCurrentNonce() { - if (!isPaired()) { - throw new IllegalStateException("Cannot get current nonce: Pod is not paired yet"); - } - return podState.getNonceState().getCurrentNonce(); - } - - @Override public synchronized void advanceToNextNonce() { - if (!isPaired()) { - throw new IllegalStateException("Cannot advance to next nonce: Pod is not paired yet"); - } - setAndStore(() -> podState.getNonceState().advanceToNextNonce()); - } - - @Override public boolean hasFaultEvent() { - return getSafe(() -> podState.getFaultEvent()) != null; - } - - @Override public PodInfoFaultEvent getFaultEvent() { - return getSafe(() -> podState.getFaultEvent()); - } - - @Override public void setFaultEvent(PodInfoFaultEvent faultEvent) { - setAndStore(() -> podState.setFaultEvent(faultEvent)); - } - - @Override public AlertType getConfiguredAlertType(AlertSlot alertSlot) { - return getSafe(() -> podState.getConfiguredAlerts().get(alertSlot)); - } - - @Override public void putConfiguredAlert(AlertSlot alertSlot, AlertType alertType) { - setAndStore(() -> podState.getConfiguredAlerts().put(alertSlot, alertType)); - } - - @Override public void removeConfiguredAlert(AlertSlot alertSlot) { - setAndStore(() -> podState.getConfiguredAlerts().remove(alertSlot)); - } - - @Override public boolean hasActiveAlerts() { - AlertSet activeAlerts = podState.getActiveAlerts(); - return activeAlerts != null && activeAlerts.size() > 0; - } - - @Override public AlertSet getActiveAlerts() { - return getSafe(() -> podState.getActiveAlerts()); - } - - @Override public Integer getLot() { - return getSafe(() -> podState.getLot()); - } - - @Override public Integer getTid() { - return getSafe(() -> podState.getTid()); - } - - @Override public FirmwareVersion getPiVersion() { - return getSafe(() -> podState.getPiVersion()); - } - - @Override public FirmwareVersion getPmVersion() { - return getSafe(() -> podState.getPmVersion()); - } - - @Override public DateTimeZone getTimeZone() { - return getSafe(() -> podState.getTimeZone()); - } - - @Override public void setTimeZone(DateTimeZone timeZone) { - if (timeZone == null) { - throw new IllegalArgumentException("Time zone can not be null"); - } - setAndStore(() -> podState.setTimeZone(timeZone)); - } - - @Override public DateTime getTime() { - DateTime now = DateTime.now(); - return now.withZone(getSafe(() -> podState.getTimeZone())); - } - - public DateTime getActivatedAt() { - DateTime activatedAt = getSafe(() -> podState.getActivatedAt()); - return activatedAt == null ? null : activatedAt.withZone(getSafe(() -> podState.getTimeZone())); - } - - public DateTime getExpiresAt() { - DateTime expiresAt = getSafe(() -> podState.getExpiresAt()); - return expiresAt == null ? null : expiresAt.withZone(getSafe(() -> podState.getTimeZone())); - } - - // TODO doesn't belong here - public String getExpiryDateAsString() { - DateTime expiresAt = getExpiresAt(); - return expiresAt == null ? "???" : DateUtil.dateAndTimeString(expiresAt.toDate()); - } - - public SetupProgress getSetupProgress() { - return getSafe(() -> podState.getSetupProgress()); - } - - public void setSetupProgress(SetupProgress setupProgress) { - if (setupProgress == null) { - throw new IllegalArgumentException("Setup progress can not be null"); - } - setAndStore(() -> podState.setSetupProgress(setupProgress)); - } - - @Override public boolean isSuspended() { - return getSafe(() -> podState.isSuspended()); - } - - @Override public Double getReservoirLevel() { - return getSafe(() -> podState.getReservoirLevel()); - } - - @Override public Duration getScheduleOffset() { - DateTime now = getTime(); - DateTime startOfDay = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), - 0, 0, 0, getSafe(() -> podState.getTimeZone())); - return new Duration(startOfDay, now); - } - - @Override public BasalSchedule getBasalSchedule() { - return getSafe(() -> podState.getBasalSchedule()); - } - - @Override public void setBasalSchedule(BasalSchedule basalSchedule) { - setAndStore(() -> podState.setBasalSchedule(basalSchedule)); - } - - @Override public DeliveryStatus getLastDeliveryStatus() { - return getSafe(() -> podState.getLastDeliveryStatus()); - } - - @Override public void updateFromStatusResponse(StatusResponse statusResponse) { - if (!hasState()) { - throw new IllegalStateException("Cannot update from status response: podState is null"); - } - setAndStore(() -> { - if (podState.getActivatedAt() == null) { - DateTime activatedAtCalculated = getTime().minus(statusResponse.getTimeActive()); - podState.setActivatedAt(activatedAtCalculated); - } - DateTime expiresAt = podState.getExpiresAt(); - DateTime expiresAtCalculated = podState.getActivatedAt().plus(OmnipodConst.NOMINAL_POD_LIFE); - if (expiresAt == null || expiresAtCalculated.isBefore(expiresAt) || expiresAtCalculated.isAfter(expiresAt.plusMinutes(1))) { - podState.setExpiresAt(expiresAtCalculated); - } - - boolean newSuspendedState = statusResponse.getDeliveryStatus() == DeliveryStatus.SUSPENDED; - if (podState.isSuspended() != newSuspendedState) { - aapsLogger.info(LTag.PUMPCOMM, "Updating pod suspended state in updateFromStatusResponse. newSuspendedState={}, statusResponse={}", newSuspendedState, statusResponse.toString()); - podState.setSuspended(newSuspendedState); - } - podState.setActiveAlerts(statusResponse.getAlerts()); - podState.setLastDeliveryStatus(statusResponse.getDeliveryStatus()); - podState.setReservoirLevel(statusResponse.getReservoirLevel()); - }); + protected void storePodState(String podState) { + sp.putString(OmnipodConst.Prefs.PodState, podState); } @Override - public void setStateChangedHandler(PodStateChangedHandler handler) { - // FIXME this is an ugly workaround for not being able to serialize the PodStateChangedHandler - if (stateChangedHandler != null) { - throw new IllegalStateException("A PodStateChangedHandler has already been already registered"); - } - stateChangedHandler = handler; - } + protected void notifyPodStateChanged() { + if (omnipodPumpStatus != null) { + if (!hasState()) { + omnipodPumpStatus.ackAlertsText = null; + omnipodPumpStatus.ackAlertsAvailable = false; + omnipodPumpStatus.lastBolusTime = null; + omnipodPumpStatus.lastBolusAmount = null; + omnipodPumpStatus.reservoirRemainingUnits = 0.0; + omnipodPumpStatus.pumpStatusType = PumpStatusType.Suspended; + sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); + sendEvent(new EventOmnipodPumpValuesChanged()); + sendEvent(new EventRefreshOverview("Omnipod Pump", false)); + } else { + // Update active alerts + if (hasActiveAlerts()) { + List alerts = getTranslatedActiveAlerts(); + String alertsText = TextUtils.join("\n", alerts); - private void setAndStore(Runnable runnable) { - if (!hasState()) { - throw new IllegalStateException("Cannot mutate PodState: podState is null"); - } - runnable.run(); - persistPodState(); - notifyPodStateChanged(); - } + if (!omnipodPumpStatus.ackAlertsAvailable || !alertsText.equals(omnipodPumpStatus.ackAlertsText)) { + omnipodPumpStatus.ackAlertsAvailable = true; + omnipodPumpStatus.ackAlertsText = TextUtils.join("\n", alerts); - private void persistPodState() { - Gson gson = omnipodUtil.getGsonInstance(); - String gsonValue = gson.toJson(podState); - aapsLogger.info(LTag.PUMPCOMM, "PodState-SP: Saved PodState to SharedPreferences: " + gsonValue); - sp.putString(OmnipodConst.Prefs.PodState, gsonValue); - } + sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); + } + } else { + if (omnipodPumpStatus.ackAlertsAvailable || StringUtils.isNotEmpty(omnipodPumpStatus.ackAlertsText)) { + omnipodPumpStatus.ackAlertsText = null; + omnipodPumpStatus.ackAlertsAvailable = false; + sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); + } + } - private void notifyPodStateChanged() { - if (stateChangedHandler != null) { - stateChangedHandler.handle(this); - } - } + Date lastBolusStartTime = getLastBolusStartTime() == null ? null : getLastBolusStartTime().toDate(); + Double lastBolusAmount = getLastBolusAmount(); - // Not actually "safe" as it throws an Exception, but it prevents NPEs - private T getSafe(Supplier supplier) { - if (!hasState()) { - throw new IllegalStateException("Cannot read from PodState: podState is null"); - } - return supplier.get(); - } + // Update other info: last bolus, units remaining, suspended + if (Objects.equals(lastBolusStartTime, omnipodPumpStatus.lastBolusTime) // + || !Objects.equals(lastBolusAmount, omnipodPumpStatus.lastBolusAmount) // + || !isReservoirStatusUpToDate(omnipodPumpStatus, getReservoirLevel()) + || isSuspended() != PumpStatusType.Suspended.equals(omnipodPumpStatus.pumpStatusType)) { + omnipodPumpStatus.lastBolusTime = lastBolusStartTime; + omnipodPumpStatus.lastBolusAmount = lastBolusAmount; + omnipodPumpStatus.reservoirRemainingUnits = getReservoirLevel() == null ? 75.0 : getReservoirLevel(); + omnipodPumpStatus.pumpStatusType = isSuspended() ? PumpStatusType.Suspended : PumpStatusType.Running; + sendEvent(new EventOmnipodPumpValuesChanged()); - private void loadPodState() { - podState = null; - - String storedPodState = sp.getString(OmnipodConst.Prefs.PodState, ""); - - if (StringUtils.isEmpty(storedPodState)) { - aapsLogger.info(LTag.PUMP, "PodState-SP: no PodState present in SharedPreferences"); - } else { - aapsLogger.info(LTag.PUMP, "PodState-SP: loaded PodState from SharedPreferences: " + storedPodState); - try { - podState = omnipodUtil.getGsonInstance().fromJson(storedPodState, PodState.class); - } catch (Exception ex) { - aapsLogger.error(LTag.PUMPCOMM, "PodState-SP: could not deserialize PodState", ex); + if (isSuspended() != PumpStatusType.Suspended.equals(omnipodPumpStatus.pumpStatusType)) { + sendEvent(new EventRefreshOverview("Omnipod Pump", false)); + } + } } } - - notifyPodStateChanged(); + omnipodUtil.notifyDeviceStatusChanged(); } - @Override public String toString() { - return "AapsPodStateManager{" + - "podState=" + podState + - '}'; + private List getTranslatedActiveAlerts() { + List translatedAlerts = new ArrayList<>(); + AlertSet activeAlerts = getActiveAlerts(); + + for (AlertSlot alertSlot : activeAlerts.getAlertSlots()) { + translatedAlerts.add(translateAlertType(getConfiguredAlertType(alertSlot))); + } + return translatedAlerts; } - private static class PodState { - private final int address; - private Integer lot; - private Integer tid; - private FirmwareVersion piVersion; - private FirmwareVersion pmVersion; - private int packetNumber; - private int messageNumber; - private DateTimeZone timeZone; - private DateTime activatedAt; - private DateTime expiresAt; - private PodInfoFaultEvent faultEvent; - private Double reservoirLevel; - private boolean suspended; - private NonceState nonceState; - private SetupProgress setupProgress; - private DeliveryStatus lastDeliveryStatus; - private AlertSet activeAlerts; - private BasalSchedule basalSchedule; - private final Map configuredAlerts = new HashMap<>(); - private PodState(int address) { - this.address = address; + private String translateAlertType(AlertType alertType) { + if (alertType == null) { + return getStringResource(R.string.omnipod_alert_unknown_alert); } - - public int getAddress() { - return address; - } - - public Integer getLot() { - return lot; - } - - public void setLot(int lot) { - this.lot = lot; - } - - public Integer getTid() { - return tid; - } - - public void setTid(int tid) { - this.tid = tid; - } - - public FirmwareVersion getPiVersion() { - return piVersion; - } - - public void setPiVersion(FirmwareVersion piVersion) { - if (this.piVersion != null) { - throw new IllegalStateException("piVersion has already been set"); - } - if (piVersion == null) { - throw new IllegalArgumentException("piVersion can not be null"); - } - this.piVersion = piVersion; - } - - public FirmwareVersion getPmVersion() { - return pmVersion; - } - - public void setPmVersion(FirmwareVersion pmVersion) { - this.pmVersion = pmVersion; - } - - public int getPacketNumber() { - return packetNumber; - } - - public void setPacketNumber(int packetNumber) { - this.packetNumber = packetNumber; - } - - public int getMessageNumber() { - return messageNumber; - } - - public void setMessageNumber(int messageNumber) { - this.messageNumber = messageNumber; - } - - public DateTimeZone getTimeZone() { - return timeZone; - } - - public void setTimeZone(DateTimeZone timeZone) { - this.timeZone = timeZone; - } - - public DateTime getActivatedAt() { - return activatedAt; - } - - public void setActivatedAt(DateTime activatedAt) { - this.activatedAt = activatedAt; - } - - public DateTime getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(DateTime expiresAt) { - this.expiresAt = expiresAt; - } - - public PodInfoFaultEvent getFaultEvent() { - return faultEvent; - } - - public void setFaultEvent(PodInfoFaultEvent faultEvent) { - this.faultEvent = faultEvent; - } - - public Double getReservoirLevel() { - return reservoirLevel; - } - - public void setReservoirLevel(Double reservoirLevel) { - this.reservoirLevel = reservoirLevel; - } - - public boolean isSuspended() { - return suspended; - } - - public void setSuspended(boolean suspended) { - this.suspended = suspended; - } - - public NonceState getNonceState() { - return nonceState; - } - - public void setNonceState(NonceState nonceState) { - this.nonceState = nonceState; - } - - public SetupProgress getSetupProgress() { - return setupProgress; - } - - public void setSetupProgress(SetupProgress setupProgress) { - this.setupProgress = setupProgress; - } - - public DeliveryStatus getLastDeliveryStatus() { - return lastDeliveryStatus; - } - - public void setLastDeliveryStatus(DeliveryStatus lastDeliveryStatus) { - this.lastDeliveryStatus = lastDeliveryStatus; - } - - public AlertSet getActiveAlerts() { - return activeAlerts; - } - - public void setActiveAlerts(AlertSet activeAlerts) { - this.activeAlerts = activeAlerts; - } - - public BasalSchedule getBasalSchedule() { - return basalSchedule; - } - - public void setBasalSchedule(BasalSchedule basalSchedule) { - this.basalSchedule = basalSchedule; - } - - public Map getConfiguredAlerts() { - return configuredAlerts; - } - - @Override public String toString() { - return "PodState{" + - "address=" + address + - ", lot=" + lot + - ", tid=" + tid + - ", piVersion=" + piVersion + - ", pmVersion=" + pmVersion + - ", packetNumber=" + packetNumber + - ", messageNumber=" + messageNumber + - ", timeZone=" + timeZone + - ", activatedAt=" + activatedAt + - ", expiresAt=" + expiresAt + - ", faultEvent=" + faultEvent + - ", reservoirLevel=" + reservoirLevel + - ", suspended=" + suspended + - ", nonceState=" + nonceState + - ", setupProgress=" + setupProgress + - ", lastDeliveryStatus=" + lastDeliveryStatus + - ", activeAlerts=" + activeAlerts + - ", basalSchedule=" + basalSchedule + - ", configuredAlerts=" + configuredAlerts + - '}'; + switch (alertType) { + case FINISH_PAIRING_REMINDER: + return getStringResource(R.string.omnipod_alert_finish_pairing_reminder); + case FINISH_SETUP_REMINDER: + return getStringResource(R.string.omnipod_alert_finish_setup_reminder_reminder); + case EXPIRATION_ALERT: + return getStringResource(R.string.omnipod_alert_expiration); + case EXPIRATION_ADVISORY_ALERT: + return getStringResource(R.string.omnipod_alert_expiration_advisory); + case SHUTDOWN_IMMINENT_ALARM: + return getStringResource(R.string.omnipod_alert_shutdown_imminent); + case LOW_RESERVOIR_ALERT: + return getStringResource(R.string.omnipod_alert_low_reservoir); + default: + return alertType.name(); } } - private static class NonceState { - private final long[] table = new long[21]; - private int index; - - private NonceState(int lot, int tid) { - initializeTable(lot, tid, (byte) 0x00); - } - - private NonceState(int lot, int tid, byte seed) { - initializeTable(lot, tid, seed); - } - - private void initializeTable(int lot, int tid, byte seed) { - table[0] = (long) (lot & 0xFFFF) + 0x55543DC3L + (((long) (lot) & 0xFFFFFFFFL) >> 16); - table[0] = table[0] & 0xFFFFFFFFL; - table[1] = (tid & 0xFFFF) + 0xAAAAE44EL + (((long) (tid) & 0xFFFFFFFFL) >> 16); - table[1] = table[1] & 0xFFFFFFFFL; - index = 0; - table[0] += seed; - for (int i = 0; i < 16; i++) { - table[2 + i] = generateEntry(); - } - index = (int) ((table[0] + table[1]) & 0X0F); - } - - private int generateEntry() { - table[0] = (((table[0] >> 16) + (table[0] & 0xFFFF) * 0x5D7FL) & 0xFFFFFFFFL); - table[1] = (((table[1] >> 16) + (table[1] & 0xFFFF) * 0x8CA0L) & 0xFFFFFFFFL); - return (int) ((table[1] + (table[0] << 16)) & 0xFFFFFFFFL); - } - - public int getCurrentNonce() { - return (int) table[(2 + index)]; - } - - public void advanceToNextNonce() { - int nonce = getCurrentNonce(); - table[(2 + index)] = generateEntry(); - index = (nonce & 0x0F); - } - - @Override - public String toString() { - return "NonceState{" + - "table=" + Arrays.toString(table) + - ", index=" + index + - '}'; - } + private String getStringResource(int id, Object... args) { + return resourceHelper.gs(id, args); } - // TODO replace with java.util.function.Supplier when min API level >= 24 - @FunctionalInterface - private interface Supplier { - T get(); + private static boolean isReservoirStatusUpToDate(OmnipodPumpStatus pumpStatus, Double unitsRemaining) { + double expectedUnitsRemaining = unitsRemaining == null ? 75.0 : unitsRemaining; + return Math.abs(expectedUnitsRemaining - pumpStatus.reservoirRemainingUnits) < 0.000001; + } + + private void sendEvent(Event event) { + rxBus.send(event); } } 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 index 8100f2afff..bc8440adfc 100644 --- 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 @@ -109,7 +109,7 @@ public class RileyLinkOmnipodService extends RileyLinkService { private void initializeErosOmnipodManager() { AapsOmnipodManager instance = AapsOmnipodManager.getInstance(); if (instance == null) { - PodStateManager podStateManager = new AapsPodStateManager(injector); + PodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp, omnipodUtil, omnipodPumpStatus, rxBus, resourceHelper); omnipodUtil.setPodStateManager(podStateManager); OmnipodCommunicationManager omnipodCommunicationService = new OmnipodCommunicationManager(injector, rfspy); 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 index f7b78250c3..35889383c2 100644 --- 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 @@ -44,6 +44,7 @@ public class OmnipodConst { public static final int DEFAULT_ADDRESS = 0xffffffff; public static final Duration AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION = Duration.millis(1500); + public static final Duration AVERAGE_TEMP_BASAL_COMMAND_COMMUNICATION_DURATION = Duration.millis(1500); public static final Duration SERVICE_DURATION = Duration.standardHours(80); public static final Duration EXPIRATION_ADVISORY_WINDOW = Duration.standardHours(9); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java index 992a248994..db594e6af1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java @@ -114,11 +114,6 @@ public class OmnipodDashCommunicationManager implements OmnipodCommunicationMana return null; } - @Override - public void setPumpStatus(OmnipodPumpStatus pumpStatusLocal) { - - } - @Override public PodInfoRecentPulseLog readPulseLog() { return null; diff --git a/app/src/main/res/layout/omnipod_fragment.xml b/app/src/main/res/layout/omnipod_fragment.xml index 2068f90557..c1ac656f2d 100644 --- a/app/src/main/res/layout/omnipod_fragment.xml +++ b/app/src/main/res/layout/omnipod_fragment.xml @@ -26,8 +26,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingBottom="5dp" android:paddingTop="2dp" + android:paddingBottom="5dp" android:visibility="gone"> @@ -93,10 +93,10 @@ @@ -119,8 +119,8 @@ android:layout_height="wrap_content" android:layout_weight="0" android:gravity="center_horizontal" - android:paddingEnd="2dp" android:paddingStart="2dp" + android:paddingEnd="2dp" android:text=":" android:textSize="14sp" /> @@ -133,12 +133,118 @@ android:paddingLeft="5dp" android:textColor="@android:color/white" android:textSize="14sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -175,7 +281,7 @@ - + @@ -212,7 +318,6 @@ android:textColor="@android:color/white" android:textSize="14sp" /> - @@ -270,10 +375,10 @@ @@ -316,10 +421,10 @@ @@ -361,10 +466,10 @@ @@ -406,10 +511,10 @@ @@ -432,8 +537,8 @@ android:layout_height="wrap_content" android:layout_weight="0" android:gravity="center_horizontal" - android:paddingEnd="2dp" android:paddingStart="2dp" + android:paddingEnd="2dp" android:text=":" android:textSize="14sp" /> @@ -453,10 +558,10 @@ @@ -498,10 +603,10 @@ @@ -543,10 +648,10 @@ @@ -621,8 +726,7 @@ android:drawableTop="@drawable/icon_cp_bolus_correction" android:paddingLeft="0dp" android:paddingRight="0dp" - android:text="@string/omnipod_read_pulse_log_short" - /> + android:text="@string/omnipod_read_pulse_log_short" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9f0a23abb0..4c754140dc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1982,5 +1982,8 @@ Unable to verify whether the bolus succeeded. Please verify that your Pod is bolusing or cancel the bolus. RL Stats Pulse Log + LOT + TID + PM / PI version