diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java index a3cf6414c5..c416ae4e28 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java @@ -19,11 +19,14 @@ import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.EnumSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; import javax.inject.Inject; @@ -125,7 +128,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private final PumpType pumpType = PumpType.Insulet_Omnipod; private final List customActions = new ArrayList<>(); - private final List statusRequestList = new ArrayList<>(); + private final Set statusRequests = Collections.synchronizedSet(EnumSet.noneOf(OmnipodStatusRequestType.class)); private final CompositeDisposable disposables = new CompositeDisposable(); // variables for handling statuses and history @@ -229,7 +232,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, } if (!getCommandQueue().statusInQueue()) { - if (!OmnipodPumpPlugin.this.statusRequestList.isEmpty()) { + if (!OmnipodPumpPlugin.this.statusRequests.isEmpty()) { getCommandQueue().readStatus("Status Refresh Requested", null); } else if (OmnipodPumpPlugin.this.hasTimeDateOrTimeZoneChanged) { getCommandQueue().readStatus("Date or Time Zone Changed", null); @@ -461,9 +464,9 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, public void getPumpStatus() { if (firstRun) { initializeAfterRileyLinkConnection(); - } else if (!statusRequestList.isEmpty()) { - synchronized (statusRequestList) { - Iterator iterator = statusRequestList.iterator(); + } else if (!statusRequests.isEmpty()) { + synchronized (statusRequests) { + Iterator iterator = statusRequests.iterator(); while (iterator.hasNext()) { OmnipodStatusRequestType statusRequest = iterator.next(); @@ -497,6 +500,9 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, case SUSPEND_DELIVERY: executeCommand(OmnipodCommandType.SUSPEND_DELIVERY, aapsOmnipodManager::suspendDelivery); break; + case SET_TIME: + executeCommand(OmnipodCommandType.SET_TIME, aapsOmnipodManager::setTime); + break; default: aapsLogger.error(LTag.PUMP, "Unknown status request: " + statusRequest.name()); } @@ -832,9 +838,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, } public void addPodStatusRequest(OmnipodStatusRequestType pumpStatusRequest) { - synchronized (statusRequestList) { - statusRequestList.add(pumpStatusRequest); - } + statusRequests.add(pumpStatusRequest); } @Override @@ -889,6 +893,10 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, return getOperationNotSupportedWithCustomText(info.nightscout.androidaps.core.R.string.pump_operation_not_supported_by_pump_driver); } + public OmnipodCommandType getCurrentCommand() { + return currentCommand; + } + private void initializeAfterRileyLinkConnection() { if (podStateManager.isPodInitialized() && podStateManager.getPodProgressStatus().isAtLeast(PodProgressStatus.PAIRING_COMPLETED)) { PumpEnactResult result = executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); @@ -933,14 +941,11 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, rileyLinkUtil.getRileyLinkHistory().add(new RLHistoryItemOmnipod(getInjector(), commandType)); - T pumpEnactResult = supplier.get(); - - rxBus.send(new EventRefreshOverview("Omnipod command: " + commandType.name(), false)); - rxBus.send(new EventOmnipodPumpValuesChanged()); - - return pumpEnactResult; + return supplier.get(); } finally { currentCommand = null; + rxBus.send(new EventRefreshOverview("Omnipod command: " + commandType.name(), false)); + rxBus.send(new EventOmnipodPumpValuesChanged()); } } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java index c657f6fc6e..50ccd62fb9 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java @@ -4,5 +4,6 @@ public enum OmnipodStatusRequestType { ACKNOWLEDGE_ALERTS, GET_POD_STATE, GET_PULSE_LOG, - SUSPEND_DELIVERY + SUSPEND_DELIVERY, + SET_TIME } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java index 990bed5de6..a75104d25a 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java @@ -56,7 +56,7 @@ public final class ExpirationReminderBuilder { } public ExpirationReminderBuilder lowReservoir(boolean active, int units) { - if (podStateManager.getReservoirLevel() == null || podStateManager.getReservoirLevel().intValue() > units) { + if (podStateManager.getReservoirLevel() == null || podStateManager.getReservoirLevel().intValue() >= units) { AlertConfiguration lowReservoirAlertConfiguration = AlertConfigurationUtil.createLowReservoirAlertConfiguration(active, (double) units); alerts.put(lowReservoirAlertConfiguration.getAlertSlot(), lowReservoirAlertConfiguration); } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java index 9083d6b42f..6af3de95a8 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java @@ -533,6 +533,8 @@ public class OmnipodManager { podStateManager.setTimeZone(oldTimeZone); throw ex; } + + podStateManager.updateActivatedAt(); } finally { logCommandExecutionFinished("setTime"); } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java index 21a873bf50..f7fa1ee694 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java @@ -84,6 +84,7 @@ public abstract class PodStateManager { /** * @return true if we have a Pod state and the Pod is running, meaning the activation process has completed and the Pod is not deactivated or in a fault state + * This does not mean the Pod is actually delivering insulin, combine with {@link #isSuspended() isSuspended()} for that */ public final boolean isPodRunning() { return isPodInitialized() && getPodProgressStatus().isRunning(); @@ -295,8 +296,21 @@ public abstract class PodStateManager { } public final DateTime getTime() { - DateTime now = DateTime.now(); - return now.withZone(getSafe(() -> podState.getTimeZone())); + DateTimeZone timeZone = getSafe(() -> podState.getTimeZone()); + if (timeZone == null) { + return DateTime.now(); + } + Duration timeActive = getSafe(() -> podState.getTimeActive()); + DateTime activatedAt = getSafe(() -> podState.getActivatedAt()); + DateTime lastUpdatedFromResponse = getSafe(() -> podState.getLastUpdatedFromResponse()); + if (timeActive == null || activatedAt == null) { + return DateTime.now().withZone(timeZone); + } + return activatedAt.plus(timeActive).plus(new Duration(lastUpdatedFromResponse, DateTime.now())); + } + + public final boolean timeDeviatesMoreThan(Duration duration) { + return new Duration(getTime(), DateTime.now().withZoneRetainFields(getSafe(() -> podState.getTimeZone()))).abs().isLongerThan(duration); } public final DateTime getActivatedAt() { @@ -304,22 +318,23 @@ public abstract class PodStateManager { return activatedAt == null ? null : activatedAt.withZone(getSafe(() -> podState.getTimeZone())); } + public final void updateActivatedAt() { + setAndStore(() -> podState.setActivatedAt(DateTime.now().withZone(getSafe(() -> podState.getTimeZone())).minus(getSafe(this::getTimeActive)))); + } + + public final Duration getTimeActive() { + return getSafe(() -> podState.getTimeActive()); + } + public final DateTime getExpiresAt() { - DateTime expiresAt = getSafe(() -> podState.getExpiresAt()); - return expiresAt == null ? null : expiresAt.withZone(getSafe(() -> podState.getTimeZone())); + DateTime activatedAt = getSafe(() -> podState.getActivatedAt()); + return activatedAt == null ? null : activatedAt.withZone(getSafe(() -> podState.getTimeZone())).plus(OmnipodConstants.NOMINAL_POD_LIFE); } public final PodProgressStatus getPodProgressStatus() { return getSafe(() -> podState.getPodProgressStatus()); } - public final void setPodProgressStatus(PodProgressStatus podProgressStatus) { - if (podProgressStatus == null) { - throw new IllegalArgumentException("Pod progress status can not be null"); - } - setAndStore(() -> podState.setPodProgressStatus(podProgressStatus)); - } - public final boolean isSuspended() { return getSafe(() -> podState.isSuspended()); } @@ -501,21 +516,16 @@ public abstract class PodStateManager { public final void updateFromResponse(StatusUpdatableResponse statusResponse) { setSafe(() -> { if (podState.getActivatedAt() == null) { - DateTime activatedAtCalculated = getTime().minus(statusResponse.getTimeActive()); + DateTime activatedAtCalculated = DateTime.now().withZone(podState.getTimeZone()).minus(statusResponse.getTimeActive()); podState.setActivatedAt(activatedAtCalculated); } - DateTime expiresAt = podState.getExpiresAt(); - DateTime expiresAtCalculated = podState.getActivatedAt().plus(OmnipodConstants.NOMINAL_POD_LIFE); - if (expiresAt == null || expiresAtCalculated.isBefore(expiresAt) || expiresAtCalculated.isAfter(expiresAt.plusMinutes(1))) { - podState.setExpiresAt(expiresAtCalculated); - } - podState.setSuspended(statusResponse.getDeliveryStatus() == DeliveryStatus.SUSPENDED); podState.setActiveAlerts(statusResponse.getUnacknowledgedAlerts()); podState.setLastDeliveryStatus(statusResponse.getDeliveryStatus()); podState.setReservoirLevel(statusResponse.getReservoirLevel()); podState.setTotalTicksDelivered(statusResponse.getTicksDelivered()); podState.setPodProgressStatus(statusResponse.getPodProgressStatus()); + podState.setTimeActive(statusResponse.getTimeActive()); if (statusResponse.getDeliveryStatus().isTbrRunning()) { if (!isTempBasalCertain() && isTempBasalRunning()) { podState.setTempBasalCertain(true); @@ -615,7 +625,7 @@ public abstract class PodStateManager { private DateTime lastUpdatedFromResponse; private DateTimeZone timeZone; private DateTime activatedAt; - private DateTime expiresAt; + private Duration timeActive; private PodInfoFaultEvent faultEvent; private Double reservoirLevel; private Integer totalTicksDelivered; @@ -733,12 +743,12 @@ public abstract class PodStateManager { this.activatedAt = activatedAt; } - DateTime getExpiresAt() { - return expiresAt; + public Duration getTimeActive() { + return timeActive; } - void setExpiresAt(DateTime expiresAt) { - this.expiresAt = expiresAt; + public void setTimeActive(Duration timeActive) { + this.timeActive = timeActive; } PodInfoFaultEvent getFaultEvent() { @@ -919,7 +929,7 @@ public abstract class PodStateManager { ", lastUpdatedFromResponse=" + lastUpdatedFromResponse + ", timeZone=" + timeZone + ", activatedAt=" + activatedAt + - ", expiresAt=" + expiresAt + + ", timeActive=" + timeActive + ", faultEvent=" + faultEvent + ", reservoirLevel=" + reservoirLevel + ", totalTicksDelivered=" + totalTicksDelivered + @@ -937,7 +947,7 @@ public abstract class PodStateManager { ", tempBasalStartTime=" + tempBasalStartTime + ", tempBasalDuration=" + tempBasalDuration + ", tempBasalCertain=" + tempBasalCertain + - ", expirationAlertHoursBeforeShutdown=" + expirationAlertTimeBeforeShutdown + + ", expirationAlertTimeBeforeShutdown=" + expirationAlertTimeBeforeShutdown + ", lowReservoirAlertUnits=" + lowReservoirAlertUnits + ", configuredAlerts=" + configuredAlerts + '}'; diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt index ca7382149e..ccff4b0c05 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt @@ -22,6 +22,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyL 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.R +import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodCommandType import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStatusRequestType import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.OmnipodConstants import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus @@ -47,7 +48,9 @@ import kotlinx.android.synthetic.main.omnipod_fragment.* import org.apache.commons.lang3.StringUtils import org.joda.time.DateTime import org.joda.time.Duration +import java.util.* import javax.inject.Inject +import kotlin.collections.ArrayList class OmnipodFragment : DaggerFragment() { companion object { @@ -133,6 +136,12 @@ class OmnipodFragment : DaggerFragment() { commandQueue.readStatus("Clicked Suspend Delivery", null) } + omnipod_button_set_time.setOnClickListener { + disablePodActionButtons() + omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.SET_TIME); + commandQueue.readStatus("Clicked Set Time", null) + } + omnipod_button_pulse_log.setOnClickListener { disablePodActionButtons() omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.GET_PULSE_LOG); @@ -168,7 +177,7 @@ class OmnipodFragment : DaggerFragment() { .toObservable(EventPreferenceChange::class.java) .observeOn(Schedulers.io()) .subscribe({ - updatePulseLogButton() + updatePodActionButtons() }, { fabricPrivacy.logException(it) }) updateUi() } @@ -227,23 +236,32 @@ class OmnipodFragment : DaggerFragment() { omnipod_pod_lot.text = PLACEHOLDER omnipod_pod_tid.text = PLACEHOLDER omnipod_pod_firmware_version.text = PLACEHOLDER + omnipod_time_on_pod.text = PLACEHOLDER omnipod_pod_expiry.text = PLACEHOLDER omnipod_pod_expiry.setTextColor(Color.WHITE) omnipod_base_basal_rate.text = PLACEHOLDER omnipod_total_delivered.text = PLACEHOLDER omnipod_reservoir.text = PLACEHOLDER + omnipod_reservoir.setTextColor(Color.WHITE) omnipod_pod_active_alerts.text = PLACEHOLDER } else { omnipod_pod_address.text = podStateManager.address.toString() omnipod_pod_lot.text = podStateManager.lot.toString() omnipod_pod_tid.text = podStateManager.tid.toString() omnipod_pod_firmware_version.text = resourceHelper.gs(R.string.omnipod_pod_firmware_version_value, podStateManager.pmVersion.toString(), podStateManager.piVersion.toString()) + + omnipod_time_on_pod.text = readableZonedTime(podStateManager.time) + omnipod_time_on_pod.setTextColor(if (podStateManager.timeDeviatesMoreThan(Duration.standardMinutes(5))) { + Color.RED + } else { + Color.WHITE + }) val expiresAt = podStateManager.expiresAt if (expiresAt == null) { omnipod_pod_expiry.text = PLACEHOLDER omnipod_pod_expiry.setTextColor(Color.WHITE) } else { - omnipod_pod_expiry.text = dateUtil.dateAndTimeString(expiresAt.toDate()) + omnipod_pod_expiry.text = readableZonedTime(expiresAt) omnipod_pod_expiry.setTextColor(if (DateTime.now().isAfter(expiresAt)) { Color.RED } else { @@ -419,12 +437,15 @@ class OmnipodFragment : DaggerFragment() { updateResumeDeliveryButton() updateAcknowledgeAlertsButton() updateSuspendDeliveryButton() + updateSetTimeButton() updatePulseLogButton() } private fun disablePodActionButtons() { omnipod_button_acknowledge_active_alerts.isEnabled = false omnipod_button_resume_delivery.isEnabled = false + omnipod_button_suspend_delivery.isEnabled = false + omnipod_button_set_time.isEnabled = false omnipod_button_refresh_status.isEnabled = false omnipod_button_pulse_log.isEnabled = false } @@ -445,8 +466,12 @@ class OmnipodFragment : DaggerFragment() { } private fun updateAcknowledgeAlertsButton() { - omnipod_button_acknowledge_active_alerts.isEnabled = podStateManager.isPodActivationCompleted && podStateManager.hasActiveAlerts() - && !podStateManager.isPodDead && rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() + if (podStateManager.isPodRunning && podStateManager.hasActiveAlerts()) { + omnipod_button_acknowledge_active_alerts.visibility = View.VISIBLE + omnipod_button_acknowledge_active_alerts.isEnabled = rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() + } else { + omnipod_button_acknowledge_active_alerts.visibility = View.GONE + } } private fun updateSuspendDeliveryButton() { @@ -459,6 +484,15 @@ class OmnipodFragment : DaggerFragment() { } } + private fun updateSetTimeButton() { + if (podStateManager.isPodRunning && (podStateManager.timeDeviatesMoreThan(Duration.standardMinutes(5)) || omnipodPumpPlugin.currentCommand == OmnipodCommandType.SET_TIME)) { + omnipod_button_set_time.visibility = View.VISIBLE + omnipod_button_set_time.isEnabled = !podStateManager.isSuspended && rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() + } else { + omnipod_button_set_time.visibility = View.GONE + } + } + private fun updatePulseLogButton() { if (omnipodManager.isPulseLogButtonEnabled) { omnipod_button_pulse_log.visibility = View.VISIBLE @@ -475,6 +509,19 @@ class OmnipodFragment : DaggerFragment() { } } + private fun readableZonedTime(time: DateTime): String { + val timeAsJavaData = time.toLocalDateTime().toDate() + val timeZone = podStateManager.timeZone.toTimeZone() + if (timeZone == TimeZone.getDefault()) { + return dateUtil.dateAndTimeString(timeAsJavaData) + } + + val isDaylightTime = timeZone.inDaylightTime(timeAsJavaData) + val locale = resources.configuration.locales.get(0) + val timeZoneDisplayName = timeZone.getDisplayName(isDaylightTime, TimeZone.SHORT, locale) + " " + timeZone.getDisplayName(isDaylightTime, TimeZone.LONG, locale) + return resourceHelper.gs(R.string.omnipod_pod_time_with_timezone, dateUtil.dateAndTimeString(timeAsJavaData), timeZoneDisplayName) + } + private fun readableDuration(dateTime: DateTime): String { val duration = Duration(dateTime, DateTime.now()) val hours = duration.standardHours.toInt() diff --git a/omnipod/src/main/res/drawable/ic_access_alarm.xml b/omnipod/src/main/res/drawable/ic_access_alarm.xml new file mode 100644 index 0000000000..f3520e7799 --- /dev/null +++ b/omnipod/src/main/res/drawable/ic_access_alarm.xml @@ -0,0 +1,9 @@ + + + diff --git a/omnipod/src/main/res/layout/omnipod_fragment.xml b/omnipod/src/main/res/layout/omnipod_fragment.xml index cad9139a1a..ee49ccba3e 100644 --- a/omnipod/src/main/res/layout/omnipod_fragment.xml +++ b/omnipod/src/main/res/layout/omnipod_fragment.xml @@ -243,6 +243,42 @@ android:textSize="14sp" /> + + + + + + + + + + +