- Add alerts when pump is suspended & add button to resume delivery

- Improve status check loop in OmnipodPumpPlugin
This commit is contained in:
Bart Sopers 2020-08-25 10:14:03 +02:00
parent 6b6b4eb759
commit dc7ed96657
7 changed files with 137 additions and 47 deletions

View file

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
@ -97,7 +98,8 @@ import io.reactivex.schedulers.Schedulers;
*/
@Singleton
public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, RileyLinkPumpDevice {
private static final long RILEY_LINK_CONNECT_TIMEOUT = 3 * 60 * 1000L; // 3 minutes
private static final long RILEY_LINK_CONNECT_TIMEOUT_MILLIS = 3 * 60 * 1000L; // 3 minutes
private static final long STATUS_CHECK_INTERVAL_MILLIS = 60 * 1000L; // 1 minute
private final PodStateManager podStateManager;
private final RileyLinkServiceData rileyLinkServiceData;
@ -118,13 +120,12 @@ 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> omnipodStatusRequestList = new ArrayList<>();
private final List<OmnipodStatusRequestType> statusRequestList = new ArrayList<>();
private final CompositeDisposable disposables = new CompositeDisposable();
// variables for handling statuses and history
protected boolean firstRun = true;
protected boolean hasTimeDateOrTimeZoneChanged = false;
protected boolean serviceRunning = false;
protected boolean displayConnectionMessages = false;
private RileyLinkOmnipodService rileyLinkOmnipodService;
private boolean busy = false;
@ -132,6 +133,9 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
private long nextPodCheck;
private boolean sentIdToFirebase;
private long lastConnectionTimeMillis;
private Handler loopHandler = new Handler();
private final Runnable statusChecker;
@Inject
public OmnipodPumpPlugin(
@ -207,16 +211,28 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
rileyLinkOmnipodService = null;
}
};
}
public PodStateManager getPodStateManager() {
return podStateManager;
statusChecker = new Runnable() {
@Override public void run() {
if (!OmnipodPumpPlugin.this.statusRequestList.isEmpty() || OmnipodPumpPlugin.this.hasTimeDateOrTimeZoneChanged) {
if (!getCommandQueue().statusInQueue()) {
getCommandQueue().readStatus(statusRequestList.isEmpty() ? "Date or Time Zone Changed" : "Status Refresh Requested", null);
}
}
doPodCheck();
loopHandler.postDelayed(this, STATUS_CHECK_INTERVAL_MILLIS);
}
};
}
@Override
protected void onStart() {
super.onStart();
loopHandler.postDelayed(statusChecker, STATUS_CHECK_INTERVAL_MILLIS);
// We can't do this in PodStateManager itself, because JodaTimeAndroid.init() hasn't been called yet
// When PodStateManager is created, which causes an IllegalArgumentException for DateTimeZones not being recognized
podStateManager.loadPodState();
@ -226,7 +242,6 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
Intent intent = new Intent(context, RileyLinkOmnipodService.class);
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
serviceRunning = true;
disposables.add(rxBus
.toObservable(EventAppExit.class)
@ -267,23 +282,6 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
}
}, fabricPrivacy::logException)
);
// check status every minute (if any status needs refresh we send readStatus command)
new Thread(() -> {
do {
SystemClock.sleep(60000);
if (!this.omnipodStatusRequestList.isEmpty() || this.hasTimeDateOrTimeZoneChanged) {
if (!getCommandQueue().statusInQueue()) {
getCommandQueue().readStatus("Status Refresh Requested", null);
}
}
doPodCheck();
} while (serviceRunning);
}).start();
}
@Override
@ -291,9 +289,9 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
super.onStop();
aapsLogger.debug(LTag.PUMP, "OmnipodPumpPlugin.onStop()");
context.unbindService(serviceConnection);
loopHandler.removeCallbacks(statusChecker);
serviceRunning = false;
context.unbindService(serviceConnection);
disposables.clear();
}
@ -305,6 +303,13 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
rxBus.send(new EventNewNotification(notification));
} else {
rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED));
if (podStateManager.isSuspended()) {
Notification notification = new Notification(Notification.OMNIPOD_POD_SUSPENDED, resourceHelper.gs(R.string.omnipod_error_pod_suspended), Notification.NORMAL);
rxBus.send(new EventNewNotification(notification));
} else {
rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED));
}
}
this.nextPodCheck = DateTimeUtil.getTimeInFutureFromMinutes(15);
@ -400,8 +405,8 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
public void getPumpStatus() {
if (firstRun) {
initializeAfterRileyLinkConnection();
} else if (!omnipodStatusRequestList.isEmpty()) {
Iterator<OmnipodStatusRequestType> iterator = omnipodStatusRequestList.iterator();
} else if (!statusRequestList.isEmpty()) {
Iterator<OmnipodStatusRequestType> iterator = statusRequestList.iterator();
while (iterator.hasNext()) {
OmnipodStatusRequestType statusRequest = iterator.next();
@ -469,6 +474,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
aapsLogger.info(LTag.PUMP, "Basal Profile was set: " + result.success);
if (result.success) {
rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED));
Notification notification = new Notification(Notification.PROFILE_SET_OK,
resourceHelper.gs(R.string.profile_set_ok),
Notification.INFO, 60);
@ -758,12 +764,14 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
// Exceeding the threshold alone is not a reason to trigger an alert: it could very well be that we just didn't need to send any commands for a while
// Below return statement covers these cases in which we will trigger an alert:
// - Sending the last command to the Pod failed
// - The Pod is suspended
// - RileyLink is in an error state
// - RileyLink has been connecting for over RILEY_LINK_CONNECT_TIMEOUT
return (podStateManager.getLastFailedCommunication() != null && podStateManager.getLastSuccessfulCommunication().isBefore(podStateManager.getLastFailedCommunication())) ||
podStateManager.isSuspended() ||
rileyLinkServiceData.rileyLinkServiceState.isError() ||
// The below clause is a hack for working around the RL service state forever staying in connecting state on startup if the RL is switched off / unreachable
(rileyLinkServiceData.getRileyLinkServiceState().isConnecting() && rileyLinkServiceData.getLastServiceStateChange() + RILEY_LINK_CONNECT_TIMEOUT < currentTimeMillis);
(rileyLinkServiceData.getRileyLinkServiceState().isConnecting() && rileyLinkServiceData.getLastServiceStateChange() + RILEY_LINK_CONNECT_TIMEOUT_MILLIS < currentTimeMillis);
}
}
@ -771,7 +779,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
}
public void addPodStatusRequest(OmnipodStatusRequestType pumpStatusRequest) {
omnipodStatusRequestList.add(pumpStatusRequest);
statusRequestList.add(pumpStatusRequest);
}
@Override

View file

@ -27,10 +27,10 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateMa
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager
import info.nightscout.androidaps.plugins.pump.omnipod.util.AapsOmnipodUtil
import info.nightscout.androidaps.queue.commands.Command
import info.nightscout.androidaps.queue.events.EventQueueChanged
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.WarnColors
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.extensions.plusAssign
@ -48,6 +48,7 @@ import org.joda.time.Duration
import javax.inject.Inject
class OmnipodFragment : DaggerFragment() {
val REFRESH_INTERVAL_MILLIS = 15 * 1000L; // 15 seconds
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var resourceHelper: ResourceHelper
@ -75,7 +76,7 @@ class OmnipodFragment : DaggerFragment() {
init {
refreshLoop = Runnable {
activity?.runOnUiThread { updateUi() }
loopHandler.postDelayed(refreshLoop, T.mins(1).msecs())
loopHandler.postDelayed(refreshLoop, REFRESH_INTERVAL_MILLIS)
}
}
@ -86,6 +87,11 @@ class OmnipodFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
omnipod_resume_delivery.setOnClickListener {
disablePodActionButtons()
commandQueue.startPump(null)
}
omnipod_pod_mgmt.setOnClickListener {
if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
activity?.let { activity ->
@ -136,7 +142,7 @@ class OmnipodFragment : DaggerFragment() {
override fun onResume() {
super.onResume()
loopHandler.postDelayed(refreshLoop, T.mins(1).msecs())
loopHandler.postDelayed(refreshLoop, REFRESH_INTERVAL_MILLIS)
disposables += rxBus
.toObservable(EventRileyLinkDeviceStatusChange::class.java)
.observeOn(AndroidSchedulers.mainThread())
@ -355,12 +361,14 @@ class OmnipodFragment : DaggerFragment() {
updateRefreshStatusButton()
updateAcknowledgeAlertsButton()
updatePulseLogButton()
updateResumeDeliveryButton()
}
private fun disablePodActionButtons() {
omnipod_pod_active_alerts_ack.isEnabled = false
omnipod_refresh.isEnabled = false
omnipod_pod_debug.isEnabled = false
omnipod_resume_delivery.isEnabled = false
}
private fun updateRefreshStatusButton() {
@ -376,7 +384,7 @@ class OmnipodFragment : DaggerFragment() {
}
}
fun updatePulseLogButton() {
private fun updatePulseLogButton() {
if (aapsOmnipodManager.isPodDebuggingOptionsEnabled) {
omnipod_pod_debug.visibility = View.VISIBLE
omnipod_pod_debug.isEnabled = podStateManager.isPodActivationCompleted && rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty()
@ -385,6 +393,16 @@ class OmnipodFragment : DaggerFragment() {
}
}
private fun updateResumeDeliveryButton() {
val queueEmptyOrStartingPump = isQueueEmpty() || commandQueue.isRunning(Command.CommandType.START_PUMP)
if (podStateManager.isPodActivationCompleted && podStateManager.isSuspended && queueEmptyOrStartingPump) {
omnipod_resume_delivery.visibility = View.VISIBLE
omnipod_resume_delivery.isEnabled = rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty()
} else {
omnipod_resume_delivery.visibility = View.GONE
}
}
private fun displayNotConfiguredDialog() {
context?.let {
OKDialog.show(it, resourceHelper.gs(R.string.omnipod_warning),
@ -393,18 +411,24 @@ class OmnipodFragment : DaggerFragment() {
}
private fun readableDuration(dateTime: DateTime): String {
val minutes = Duration(dateTime, DateTime.now()).standardMinutes.toInt()
val duration = Duration(dateTime, DateTime.now())
val hours = duration.standardHours.toInt()
val minutes = duration.standardMinutes.toInt()
val seconds = duration.standardSeconds.toInt()
when {
minutes == 0 -> {
seconds < 10 -> {
return resourceHelper.gs(R.string.omnipod_moments_ago)
}
minutes < 60 -> {
seconds < 60 -> {
return resourceHelper.gs(R.string.omnipod_less_than_a_minute_ago)
}
seconds < 60 * 60 -> { // < 1 hour
return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_minutes, minutes, minutes))
}
minutes < 1440 -> {
val hours = minutes / 60
seconds < 24 * 60 * 60 -> { // < 1 day
val minutesLeft = minutes % 60
if (minutesLeft > 0)
return resourceHelper.gs(R.string.omnipod_time_ago,
@ -412,8 +436,7 @@ class OmnipodFragment : DaggerFragment() {
return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_hours, hours, hours))
}
else -> {
val hours = minutes / 60
else -> {
val days = hours / 24
val hoursLeft = hours % 24
if (hoursLeft > 0)

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,17.813m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"
android:fillColor="#E93057"/>
<path
android:pathData="M19.357,6.125c0.195,-0.196 0.195,-0.512 0,-0.708c-0.195,-0.195 -0.512,-0.195 -0.707,0l-2.364,2.364C14.916,7.314 13.477,7.062 12,7.062c-2.902,0 -5.674,0.925 -8.015,2.675c-0.222,0.166 -0.267,0.479 -0.102,0.7c0.166,0.222 0.479,0.266 0.7,0.102C6.751,8.919 9.315,8.063 12,8.063c1.198,0 2.369,0.177 3.493,0.512l-1.829,1.829c-0.547,-0.093 -1.1,-0.154 -1.663,-0.154c-2.212,0 -4.325,0.705 -6.111,2.039c-0.222,0.166 -0.267,0.479 -0.102,0.701c0.166,0.221 0.478,0.267 0.7,0.101C8.099,11.887 10.005,11.25 12,11.25c0.26,0 0.516,0.022 0.773,0.044l-2.215,2.215c-1.012,0.201 -1.989,0.596 -2.839,1.231c-0.222,0.165 -0.267,0.479 -0.102,0.7c0.166,0.22 0.479,0.268 0.7,0.101c0.192,-0.143 0.392,-0.271 0.597,-0.389l-2.618,2.618c-0.195,0.195 -0.195,0.512 0,0.707c0.098,0.098 0.226,0.146 0.354,0.146s0.256,-0.049 0.354,-0.146L19.357,6.125z"
android:fillColor="#E93057"/>
<path
android:pathData="M20.016,9.737c-0.562,-0.42 -1.15,-0.789 -1.757,-1.113L17.513,9.37c0.66,0.331 1.299,0.717 1.903,1.169c0.09,0.067 0.195,0.1 0.3,0.1c0.152,0 0.303,-0.069 0.4,-0.201C20.282,10.216 20.236,9.902 20.016,9.737z"
android:fillColor="#E93057"/>
<path
android:pathData="M15.867,11.016l-0.776,0.776c0.856,0.307 1.671,0.739 2.421,1.298c0.09,0.067 0.195,0.1 0.3,0.1c0.152,0 0.303,-0.069 0.4,-0.2c0.166,-0.222 0.12,-0.535 -0.101,-0.701C17.409,11.764 16.656,11.341 15.867,11.016z"
android:fillColor="#E93057"/>
<path
android:pathData="M13.394,13.489l-0.887,0.887c1.13,0.091 2.24,0.467 3.176,1.166c0.09,0.067 0.195,0.1 0.3,0.1c0.152,0 0.303,-0.069 0.4,-0.2c0.166,-0.222 0.12,-0.535 -0.101,-0.7C15.418,14.095 14.424,13.688 13.394,13.489z"
android:fillColor="#E93057"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.578,18.026l0,-11.042l-3.698,0l0,11.042l-9.363,0l0,-1.01l8.354,0l0,-11.041l5.717,0l0,11.041l1.645,0l0,1.01z"
android:fillColor="#E93057"/>
<path
android:pathData="M2.852,13.946c-0.248,0 -0.498,-0.123 -0.74,-0.366c-0.49,-0.49 -0.492,-0.986 -0.004,-1.474l3.125,-3.125C5.106,8.737 5.048,8.435 5.048,8.136c0,-1.336 1.172,-2.508 2.508,-2.508c0.282,0 0.572,0.055 0.864,0.163c0.186,0.069 0.347,0.148 0.493,0.242L8.547,6.484L7.431,7.6l0.731,0.661l1.046,-1.116l0.374,-0.374c0.171,0.154 0.25,0.315 0.319,0.501c0.108,0.292 0.163,0.583 0.163,0.864c0,0.67 -0.261,1.3 -0.734,1.773c-0.473,0.474 -1.103,0.734 -1.773,0.735c-0.298,0 -0.6,-0.058 -0.898,-0.173l-3.072,3.113C3.346,13.824 3.099,13.946 2.852,13.946zM7.556,6.237c-0.507,0 -0.984,0.198 -1.342,0.556C5.855,7.152 5.657,7.629 5.657,8.136c0,0.153 0.024,0.313 0.076,0.5c0.046,0.168 0.114,0.326 0.2,0.47l0.022,0.037c0,0 -0.139,0.115 -0.168,0.146l-3.249,3.248c-0.26,0.26 -0.227,0.381 0.004,0.612c0.134,0.134 0.219,0.186 0.304,0.186c0.085,0 0.184,-0.058 0.308,-0.182l3.249,-3.249c0.03,-0.03 0.104,-0.12 0.104,-0.12l0.078,-0.026c0.146,0.087 0.304,0.155 0.471,0.2c0.186,0.052 0.344,0.076 0.499,0.076c1.009,0 1.896,-0.887 1.898,-1.898c0,-0.134 -0.018,-0.274 -0.054,-0.43L8.342,8.872C8.284,8.929 8.208,8.961 8.126,8.961c-0.081,0 -0.158,-0.032 -0.215,-0.089L6.82,7.78c-0.119,-0.119 -0.119,-0.313 0,-0.431l1.143,-1.142C7.886,6.235 7.807,6.241 7.729,6.241C7.67,6.241 7.556,6.237 7.556,6.237z"
android:fillColor="#E93057"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.069,20.423c-4.644,0 -8.422,-3.779 -8.422,-8.423s3.778,-8.422 8.422,-8.422S20.492,7.355 20.492,12S16.713,20.423 12.069,20.423zM12.069,4.943c-3.891,0 -7.057,3.166 -7.057,7.057c0,3.891 3.166,7.057 7.057,7.057c3.891,0 7.057,-3.166 7.057,-7.057C19.126,8.109 15.961,4.943 12.069,4.943z"
android:fillColor="#67DFE8"/>
<path
android:pathData="M9.945,16.362c-0.113,0 -0.227,-0.028 -0.33,-0.085c-0.217,-0.12 -0.353,-0.349 -0.353,-0.598V8.32c0,-0.249 0.135,-0.478 0.353,-0.598c0.218,-0.12 0.485,-0.112 0.694,0.021l5.827,3.679c0.198,0.125 0.318,0.343 0.318,0.577s-0.12,0.452 -0.318,0.577l-5.827,3.679C10.198,16.326 10.072,16.362 9.945,16.362zM10.627,9.559v4.881L14.493,12L10.627,9.559z"
android:fillColor="#67DFE8"/>
</vector>

View file

@ -714,24 +714,34 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/omnipod_resume_delivery"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_local_activate"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/omnipod_resume_delivery" />
<Button
android:id="@+id/omnipod_refresh"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_actions_refill"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/combo_refresh" />
android:text="@string/omnipod_refresh" />
<Button
android:id="@+id/omnipod_pod_mgmt"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_danarhistory"
android:paddingLeft="0dp"
@ -742,7 +752,7 @@
android:id="@+id/omnipod_pod_active_alerts_ack"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_cp_aaps_offline"
android:paddingLeft="0dp"
@ -753,7 +763,7 @@
android:id="@+id/omnipod_rileylink_stats"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_danarstats"
android:paddingLeft="0dp"
@ -764,7 +774,7 @@
android:id="@+id/omnipod_pod_debug"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_cp_bolus_correction"
android:paddingLeft="0dp"

View file

@ -152,6 +152,10 @@
<string name="omnipod_time_ago">%1$s ago</string>
<string name="omnipod_waiting_for_rileylink_connection">Waiting for RileyLink connection...</string>
<string name="omnipod_bolus_did_not_succeed">Bolus did not succeed</string>
<string name="omnipod_refresh">Refresh</string>
<string name="omnipod_resume_delivery">Resume delivery</string>
<string name="omnipod_error_pod_suspended">Pod suspended</string>
<string name="omnipod_less_than_a_minute_ago">Less than a minute ago</string>
<plurals name="omnipod_minutes">
<item quantity="one">%1$d minute</item>