Display Pod time in Omnipod tab and add button to manually set time

This commit is contained in:
Bart Sopers 2020-09-10 20:13:16 +02:00
parent b474e72a53
commit df1731e6b7
9 changed files with 170 additions and 45 deletions

View file

@ -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<CustomAction> customActions = new ArrayList<>();
private final List<OmnipodStatusRequestType> statusRequestList = new ArrayList<>();
private final Set<OmnipodStatusRequestType> 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<OmnipodStatusRequestType> iterator = statusRequestList.iterator();
} else if (!statusRequests.isEmpty()) {
synchronized (statusRequests) {
Iterator<OmnipodStatusRequestType> 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());
}
}

View file

@ -4,5 +4,6 @@ public enum OmnipodStatusRequestType {
ACKNOWLEDGE_ALERTS,
GET_POD_STATE,
GET_PULSE_LOG,
SUSPEND_DELIVERY
SUSPEND_DELIVERY,
SET_TIME
}

View file

@ -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);
}

View file

@ -533,6 +533,8 @@ public class OmnipodManager {
podStateManager.setTimeZone(oldTimeZone);
throw ex;
}
podStateManager.updateActivatedAt();
} finally {
logCommandExecutionFinished("setTime");
}

View file

@ -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 +
'}';

View file

@ -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()

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#72a8ff"
android:pathData="M22,5.72l-4.6,-3.86 -1.29,1.53 4.6,3.86L22,5.72zM7.88,3.39L6.6,1.86 2,5.71l1.29,1.53 4.59,-3.85zM12.5,8L11,8v6l4.75,2.85 0.75,-1.23 -4,-2.37L12.5,8zM12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View file

@ -243,6 +243,42 @@
android:textSize="14sp" />
</LinearLayout>
<!-- Pod Time -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.5"
android:gravity="end"
android:paddingRight="5dp"
android:text="@string/omnipod_pod_time_on_pod"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/omnipod_time_on_pod"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
<!-- Pod Expires -->
<LinearLayout
@ -782,6 +818,18 @@
android:text="@string/omnipod_suspend_delivery_short"
android:visibility="gone" />
<Button
android:id="@+id/omnipod_button_set_time"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_access_alarm"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/omnipod_set_time"
android:visibility="gone" />
<Button
android:id="@+id/omnipod_button_resume_delivery"
style="@style/ButtonSmallFontStyle"

View file

@ -157,6 +157,7 @@
<string name="omnipod_pod_tid">TID</string>
<string name="omnipod_pod_firmware_version">Firmware version</string>
<string name="omnipod_pod_firmware_version_value" translatable="false">PM %1$s / PI %2$s</string>
<string name="omnipod_pod_time_with_timezone" translatable="false">%1$s (%2$s)</string>
<string name="omnipod_errors">Errors</string>
<string name="omnipod_cmd_basal_profile_not_set_is_same">Basal profile is the same, so it will not be set again.</string>
<string name="omnipod_custom_action_reset_rileylink">Reset RileyLink config</string>
@ -193,6 +194,8 @@
<string name="omnipod_history_type">Type:</string>
<string name="omnipod_error_failed_to_set_profile_empty_profile">Failed to set basal profile: received an empty profile. Make sure to activate your basal profile.</string>
<string name="omnipod_error_set_initial_basal_schedule_no_profile">No basal profile is active. Make sure to activate your basal profile.</string>
<string name="omnipod_pod_time_on_pod">Time on Pod</string>
<string name="omnipod_set_time">Set time</string>
<plurals name="omnipod_minutes">
<item quantity="one">%1$d minute</item>