update overview fragment. set basal if pod is not activated

This commit is contained in:
Andrei Vereha 2021-06-10 08:22:29 +02:00
parent fa846bdd65
commit 1910ac7b60
6 changed files with 360 additions and 127 deletions

View file

@ -39,11 +39,9 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import org.json.JSONObject import org.json.JSONObject
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.ceil import kotlin.math.ceil
@ -68,6 +66,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
resourceHelper: ResourceHelper, resourceHelper: ResourceHelper,
commandQueue: CommandQueueProvider commandQueue: CommandQueueProvider
) : PumpPluginBase(pluginDescription, injector, aapsLogger, resourceHelper, commandQueue), Pump { ) : PumpPluginBase(pluginDescription, injector, aapsLogger, resourceHelper, commandQueue), Pump {
@Volatile var bolusCanceled = false
companion object { companion object {
@ -128,18 +127,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
} }
override fun getPumpStatus(reason: String) { override fun getPumpStatus(reason: String) {
val throwable = Completable.concat( val throwable = getPodStatus().blockingGet()
listOf(
omnipodManager
.getStatus(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE)
.ignoreElements(),
history.updateFromState(podStateManager),
podStateManager.updateActiveCommand()
.map { handleCommandConfirmation(it) }
.ignoreElement(),
checkPodKaput()
)
).blockingGet()
if (throwable != null) { if (throwable != null) {
aapsLogger.error(LTag.PUMP, "Error in getPumpStatus", throwable) aapsLogger.error(LTag.PUMP, "Error in getPumpStatus", throwable)
} else { } else {
@ -147,26 +135,58 @@ class OmnipodDashPumpPlugin @Inject constructor(
} }
} }
private fun getPodStatus(): Completable = Completable.concat(
listOf(
omnipodManager
.getStatus(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE)
.ignoreElements(),
history.updateFromState(podStateManager),
podStateManager.updateActiveCommand()
.map { handleCommandConfirmation(it) }
.ignoreElement(),
checkPodKaput(),
)
)
private fun checkPodKaput(): Completable = Completable.defer { private fun checkPodKaput(): Completable = Completable.defer {
val tbr = pumpSync.expectedPumpState().temporaryBasal val tbr = pumpSync.expectedPumpState().temporaryBasal
if (podStateManager.isPodKaput && if (podStateManager.isPodKaput) {
(tbr == null || tbr.rate != 0.0) if (tbr == null || tbr.rate != 0.0) {
) { pumpSync.syncTemporaryBasalWithPumpId(
pumpSync.syncTemporaryBasalWithPumpId( timestamp = System.currentTimeMillis(),
timestamp = System.currentTimeMillis(), rate = 0.0,
rate = 0.0, duration = T.mins(PodConstants.MAX_POD_LIFETIME.standardMinutes).msecs(),
duration = T.mins(PodConstants.MAX_POD_LIFETIME.standardMinutes).msecs(), isAbsolute = true,
isAbsolute = true, type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND, pumpId = Random.Default.nextLong(), // we don't use this, just make sure it's unique
pumpId = Random.Default.nextLong(), // we don't use this, just make sure it's unique pumpType = PumpType.OMNIPOD_DASH,
pumpType = PumpType.OMNIPOD_DASH, pumpSerial = serialNumber()
pumpSerial = serialNumber() )
) }
podStateManager.lastBolus?.run {
if (!complete) {
val deliveredUnits = markComplete()
complete = true
val bolusHistoryEntry = history.getById(historyId)
val sync = pumpSync.syncBolusWithPumpId(
timestamp = bolusHistoryEntry.createdAt,
amount = deliveredUnits,
pumpId = bolusHistoryEntry.pumpId(),
pumpType = PumpType.OMNIPOD_DASH,
pumpSerial = serialNumber(),
type = bolusType
)
aapsLogger.info(LTag.PUMP, "syncBolusWithPumpId on CANCEL_BOLUS returned: $sync")
}
}
} }
Completable.complete() Completable.complete()
} }
override fun setNewBasalProfile(profile: Profile): PumpEnactResult { override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
if (!podStateManager.isActivationCompleted) {
return PumpEnactResult().success(true).enacted(false)
}
val basalProgram = mapProfileToBasalProgram(profile) val basalProgram = mapProfileToBasalProgram(profile)
return executeProgrammingCommand( return executeProgrammingCommand(
pre = suspendDeliveryIfActive(), pre = suspendDeliveryIfActive(),
@ -244,7 +264,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
get() { get() {
val date = Date() val date = Date()
val ret = podStateManager.basalProgram?.rateAt(date) ?: 0.0 val ret = podStateManager.basalProgram?.rateAt(date) ?: 0.0
aapsLogger.info(LTag.PUMP, "baseBasalRate: %ret at $date}") aapsLogger.info(LTag.PUMP, "baseBasalRate: $ret at $date}")
return if (podStateManager.alarmType != null) { return if (podStateManager.alarmType != null) {
0.0 0.0
} else } else
@ -268,74 +288,136 @@ class OmnipodDashPumpPlugin @Inject constructor(
get() = 0 get() = 0
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult { override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
// TODO report actual delivered amount after Pod Alarm and bolus cancellation try {
val bolusBeeps = sp.getBoolean(R.string.key_omnipod_common_bolus_beeps_enabled, false) aapsLogger.info(LTag.PUMP, "Delivering treatment: $detailedBolusInfo")
val bolusBeeps = sp.getBoolean(R.string.key_omnipod_common_bolus_beeps_enabled, false)
if (detailedBolusInfo.carbs > 0 || detailedBolusInfo.insulin == 0.0) { if (detailedBolusInfo.carbs > 0 ||
return PumpEnactResult(injector) detailedBolusInfo.insulin == 0.0
.success(false) ) {
.enacted(false) return PumpEnactResult(injector)
.bolusDelivered(0.0) .success(false)
.carbsDelivered(0.0) .enacted(false)
.comment("Invalid input") .bolusDelivered(0.0)
.carbsDelivered(0.0)
.comment("Invalid input")
}
val requestedBolusAmount = detailedBolusInfo.insulin
if (requestedBolusAmount > reservoirLevel) {
return PumpEnactResult(injector)
.success(false)
.enacted(false)
.bolusDelivered(0.0)
.carbsDelivered(0.0)
.comment("Not enough insulin in the reservoir")
}
var deliveredBolusAmount = 0.0
aapsLogger.info(
LTag.PUMP,
"deliverTreatment: requestedBolusAmount=$requestedBolusAmount"
)
val ret = executeProgrammingCommand(
pre = observeDeliveryNotCanceled(),
historyEntry = history.createRecord(
commandType = OmnipodCommandType.SET_BOLUS,
bolusRecord = BolusRecord(
requestedBolusAmount,
BolusType.fromBolusInfoBolusType(detailedBolusInfo.bolusType)
)
),
activeCommandEntry = { historyId ->
podStateManager.createActiveCommand(
historyId,
requestedBolus = requestedBolusAmount
)
},
command = omnipodManager.bolus(
detailedBolusInfo.insulin,
bolusBeeps,
bolusBeeps
).filter { podEvent -> podEvent is PodEvent.CommandSent }
.map { pumpSyncBolusStart(it, requestedBolusAmount, detailedBolusInfo.bolusType) }
.ignoreElements(),
post = waitForBolusDeliveryToComplete(5, requestedBolusAmount, detailedBolusInfo.bolusType)
.map {
deliveredBolusAmount = it
aapsLogger.info(LTag.PUMP, "deliverTreatment: deliveredBolusAmount=$deliveredBolusAmount")
}
.ignoreElement()
).toSingleDefault(
PumpEnactResult(injector).success(true).enacted(true).bolusDelivered(deliveredBolusAmount)
)
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false))
.blockingGet()
aapsLogger.info(LTag.PUMP, "deliverTreatment result: $ret")
return ret
} finally {
bolusCanceled = false
} }
val requestedBolusAmount = detailedBolusInfo.insulin }
var delieveredBolusAmount = 0.0
aapsLogger.info( private fun observeDeliveryNotCanceled(): Completable = Completable.defer {
LTag.PUMP, if (bolusCanceled) {
"deliverTreatment: units: $requestedBolusAmount" Completable.error(java.lang.IllegalStateException("Bolus canceled"))
) } else {
return executeProgrammingCommand( Completable.complete()
pre = observeNoActiveTempBasal(true), }
historyEntry = history.createRecord(
commandType = OmnipodCommandType.SET_BOLUS,
bolusRecord = BolusRecord(
requestedBolusAmount,
BolusType.fromBolusInfoBolusType(detailedBolusInfo.bolusType)
)
),
command = omnipodManager.bolus(
detailedBolusInfo.insulin,
bolusBeeps,
bolusBeeps
).filter { podEvent -> podEvent is PodEvent.CommandSent }
.map { pumpSyncBolusStart(it, requestedBolusAmount, detailedBolusInfo.bolusType) }
.ignoreElements(),
post = waitForBolusDeliveryToComplete(5, requestedBolusAmount, detailedBolusInfo.bolusType)
).toSingleDefault(PumpEnactResult(injector).success(true).enacted(true).bolusDelivered(delieveredBolusAmount))
.onErrorReturnItem(PumpEnactResult(injector).success(false).enacted(false))
.blockingGet()
} }
private fun waitForBolusDeliveryToComplete( private fun waitForBolusDeliveryToComplete(
maxRetriesAtTheEnd: Int, maxRetries: Int,
requestedBolusAmount: Double, requestedBolusAmount: Double,
bolusType: DetailedBolusInfo.BolusType bolusType: DetailedBolusInfo.BolusType
): Completable { ): Single<Double> = Single.defer {
// TODO: wait for bolus delivery to start! // wait for bolus delivery confirmation, if possible
// For now, we assume it started with success val estimatedDeliveryTimeSeconds = estimateBolusDeliverySeconds(requestedBolusAmount)
val estimatedDeliveryTimeSeconds = ceil(requestedBolusAmount / 0.05).toLong() * 2
aapsLogger.info(LTag.PUMP, "estimatedDeliveryTimeSeconds: $estimatedDeliveryTimeSeconds") aapsLogger.info(LTag.PUMP, "estimatedDeliveryTimeSeconds: $estimatedDeliveryTimeSeconds")
return Completable.concat( var waited = 0
listOf( while (waited < estimatedDeliveryTimeSeconds && !bolusCanceled) {
Observable.interval(1, TimeUnit.SECONDS) waited += 1
.take(estimatedDeliveryTimeSeconds) Thread.sleep(1000)
.doOnNext { if (bolusType == DetailedBolusInfo.BolusType.SMB) {
if (bolusType == DetailedBolusInfo.BolusType.SMB) { continue
return@doOnNext }
} val progressUpdateEvent = EventOverviewBolusProgress
val progressUpdateEvent = EventOverviewBolusProgress val percent = (100 * waited) / estimatedDeliveryTimeSeconds
val percent = (100 * it) / estimatedDeliveryTimeSeconds progressUpdateEvent.status = resourceHelper.gs(R.string.bolusdelivering, requestedBolusAmount)
progressUpdateEvent.status = resourceHelper.gs(R.string.bolusdelivering, requestedBolusAmount) progressUpdateEvent.percent = percent.toInt()
progressUpdateEvent.percent = percent.toInt() rxBus.send(progressUpdateEvent)
rxBus.send(progressUpdateEvent) }
}.ignoreElements(),
Observable.interval(5, TimeUnit.SECONDS).take(1).ignoreElements() for (tries in 1..maxRetries) {
// TODO check delivery status. for now, we are just sleeping for 5 sec val errorGettingStatus = getPodStatus().blockingGet()
) if (errorGettingStatus != null) {
) Thread.sleep(3000) // retry every 3 sec
continue
}
if (podStateManager.deliveryStatus in
arrayOf(
DeliveryStatus.BOLUS_AND_TEMP_BASAL_ACTIVE,
DeliveryStatus.BOLUS_AND_BASAL_ACTIVE
) &&
!bolusCanceled
) {
// delivery not complete yet
val remainingUnits = podStateManager.lastBolus!!.bolusUnitsRemaining
val progressUpdateEvent = EventOverviewBolusProgress
val percent = ((requestedBolusAmount - remainingUnits) / requestedBolusAmount) * 100
progressUpdateEvent.status = "Remaining: $remainingUnits units"
progressUpdateEvent.percent = percent.toInt()
Thread.sleep(estimateBolusDeliverySeconds(remainingUnits) * 1000.toLong())
} else {
// delivery is complete. If pod is Kaput, we are handling this in getPodStatus
// TODO: race with cancel!!
return@defer Single.just(podStateManager.lastBolus!!.deliveredUnits()!!)
}
}
Single.just(requestedBolusAmount) // will be updated later!
}
private fun estimateBolusDeliverySeconds(requestedBolusAmount: Double): Long {
return ceil(requestedBolusAmount / 0.05).toLong() * 2 + 3
} }
private fun pumpSyncBolusStart( private fun pumpSyncBolusStart(
@ -365,14 +447,20 @@ class OmnipodDashPumpPlugin @Inject constructor(
} }
override fun stopBolusDelivering() { override fun stopBolusDelivering() {
// TODO update Treatments (?)
aapsLogger.info(LTag.PUMP, "stopBolusDelivering called") aapsLogger.info(LTag.PUMP, "stopBolusDelivering called")
val ret = executeProgrammingCommand( bolusCanceled = true
historyEntry = history.createRecord(OmnipodCommandType.CANCEL_BOLUS), for (tries in 1..10) {
command = omnipodManager.stopBolus().ignoreElements() val ret = executeProgrammingCommand(
).subscribeOn(aapsSchedulers.io) // stopBolusDelivering is executed on the main thread historyEntry = history.createRecord(OmnipodCommandType.CANCEL_BOLUS),
.toPumpEnactResult() command = omnipodManager.stopBolus().ignoreElements()
aapsLogger.info(LTag.PUMP, "stopBolusDelivering finished with result: $ret") ).subscribeOn(aapsSchedulers.io) // stopBolusDelivering is executed on the main thread
.toPumpEnactResult()
aapsLogger.info(LTag.PUMP, "stopBolusDelivering finished with result: $ret")
if (ret.success) {
return
}
Thread.sleep(500)
}
} }
override fun setTempBasalAbsolute( override fun setTempBasalAbsolute(
@ -385,11 +473,11 @@ class OmnipodDashPumpPlugin @Inject constructor(
val tempBasalBeeps = sp.getBoolean(R.string.key_omnipod_common_tbr_beeps_enabled, false) val tempBasalBeeps = sp.getBoolean(R.string.key_omnipod_common_tbr_beeps_enabled, false)
aapsLogger.info( aapsLogger.info(
LTag.PUMP, LTag.PUMP,
"setTempBasalAbsolute: $durationInMinutes min :: $absoluteRate U/h :: " + "setTempBasalAbsolute: duration=$durationInMinutes min, rate=$absoluteRate U/h :: " +
"enforce: $enforceNew :: tbrType: $tbrType" "enforce=$enforceNew, tbrType=$tbrType"
) )
return executeProgrammingCommand( val ret = executeProgrammingCommand(
pre = observeNoActiveTempBasal(true), pre = observeNoActiveTempBasal(true),
historyEntry = history.createRecord( historyEntry = history.createRecord(
commandType = OmnipodCommandType.SET_TEMPORARY_BASAL, commandType = OmnipodCommandType.SET_TEMPORARY_BASAL,
@ -414,6 +502,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
.map { pumpSyncTempBasal(it, absoluteRate, durationInMinutes.toLong(), tbrType) } .map { pumpSyncTempBasal(it, absoluteRate, durationInMinutes.toLong(), tbrType) }
.ignoreElements(), .ignoreElements(),
).toPumpEnactResult() ).toPumpEnactResult()
aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute: result=$ret")
return ret
} }
private fun pumpSyncTempBasal( private fun pumpSyncTempBasal(
@ -718,6 +808,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
podStateManager.updateActiveCommand() podStateManager.updateActiveCommand()
.map { handleCommandConfirmation(it) } .map { handleCommandConfirmation(it) }
.ignoreElement(), .ignoreElement(),
checkPodKaput(),
post, post,
) )
) )
@ -763,8 +854,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
OmnipodCommandType.SET_TEMPORARY_BASAL -> { OmnipodCommandType.SET_TEMPORARY_BASAL -> {
// This treatment was synced before sending the command // This treatment was synced before sending the command
aapsLogger.info(LTag.PUMPCOMM, "temporary basal denied. PumpId: ${historyEntry.pumpId()}")
if (!confirmation.success) { if (!confirmation.success) {
aapsLogger.info(LTag.PUMPCOMM, "temporary basal denied. PumpId: ${historyEntry.pumpId()}")
pumpSync.invalidateTemporaryBasal(historyEntry.pumpId()) pumpSync.invalidateTemporaryBasal(historyEntry.pumpId())
} else { } else {
podStateManager.tempBasal = command.tempBasal podStateManager.tempBasal = command.tempBasal
@ -780,7 +871,26 @@ class OmnipodDashPumpPlugin @Inject constructor(
} }
OmnipodCommandType.SET_BOLUS -> { OmnipodCommandType.SET_BOLUS -> {
if (!confirmation.success) { if (confirmation.success) {
if (command.requestedBolus == null) {
aapsLogger.error(LTag.PUMP, "Requested bolus not found: $command")
}
val record = historyEntry.record
if (record !is BolusRecord) {
aapsLogger.error(
LTag.PUMP,
"Expected SET_BOLUS history record to be a BolusRecord, found " +
"$record"
)
}
record as BolusRecord
podStateManager.createLastBolus(
record.amout,
command.historyId,
record.bolusType.toBolusInfoBolusType()
)
} else {
pumpSync.syncBolusWithPumpId( pumpSync.syncBolusWithPumpId(
timestamp = historyEntry.createdAt, timestamp = historyEntry.createdAt,
amount = 0.0, amount = 0.0,
@ -792,6 +902,25 @@ class OmnipodDashPumpPlugin @Inject constructor(
} }
} }
OmnipodCommandType.CANCEL_BOLUS -> {
if (confirmation.success) {
podStateManager.lastBolus?.run {
val deliveredUnits = markComplete()
val bolusHistoryEntry = history.getById(historyId)
val sync = pumpSync.syncBolusWithPumpId(
timestamp = bolusHistoryEntry.createdAt,
amount = deliveredUnits, // we just marked this bolus as complete
pumpId = bolusHistoryEntry.pumpId(),
pumpType = PumpType.OMNIPOD_DASH,
pumpSerial = serialNumber(),
type = bolusType
)
aapsLogger.info(LTag.PUMP, "syncBolusWithPumpId on CANCEL_BOLUS returned: $sync")
} ?: aapsLogger.error(LTag.PUMP, "Cancelled bolus that does not exist")
}
}
else -> else ->
aapsLogger.warn( aapsLogger.warn(
LTag.PUMP, LTag.PUMP,

View file

@ -218,7 +218,7 @@ class OmnipodDashManagerImpl @Inject constructor(
return Observable.concat( return Observable.concat(
observePodReadyForActivationPart1, observePodReadyForActivationPart1,
observePairNewPod, observePairNewPod,
observeConnectToPod, // FIXME needed after disconnect; observePairNewPod does not connect in that case. observeConnectToPod,
observeActivationPart1Commands(lowReservoirAlertTrigger) observeActivationPart1Commands(lowReservoirAlertTrigger)
).doOnComplete(ActivationProgressUpdater(ActivationProgress.PHASE_1_COMPLETED)) ).doOnComplete(ActivationProgressUpdater(ActivationProgress.PHASE_1_COMPLETED))
// TODO these would be common for any observable returned in a public function in this class // TODO these would be common for any observable returned in a public function in this class
@ -246,6 +246,7 @@ class OmnipodDashManagerImpl @Inject constructor(
) )
} }
if (podStateManager.activationProgress.isBefore(ActivationProgress.PRIMING)) { if (podStateManager.activationProgress.isBefore(ActivationProgress.PRIMING)) {
observables.add(observeConnectToPod) // connection can time out while waiting
observables.add( observables.add(
Observable.defer { Observable.defer {
Observable.timer(podStateManager.firstPrimeBolusVolume!!.toLong(), TimeUnit.SECONDS) Observable.timer(podStateManager.firstPrimeBolusVolume!!.toLong(), TimeUnit.SECONDS)
@ -644,6 +645,7 @@ class OmnipodDashManagerImpl @Inject constructor(
} }
is PodEvent.CommandSent -> { is PodEvent.CommandSent -> {
logger.debug(LTag.PUMP, "Command sent: ${event.command.commandType}")
podStateManager.activeCommand?.let { podStateManager.activeCommand?.let {
if (it.sequence == event.command.sequenceNumber) { if (it.sequence == event.command.sequenceNumber) {
it.sentRealtime = SystemClock.elapsedRealtime() it.sentRealtime = SystemClock.elapsedRealtime()

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
@ -64,6 +65,7 @@ interface OmnipodDashPodStateManager {
val tempBasalActive: Boolean val tempBasalActive: Boolean
var basalProgram: BasalProgram? var basalProgram: BasalProgram?
val activeCommand: ActiveCommand? val activeCommand: ActiveCommand?
val lastBolus: LastBolus?
fun increaseMessageSequenceNumber() fun increaseMessageSequenceNumber()
fun increaseEapAkaSequenceNumber(): ByteArray fun increaseEapAkaSequenceNumber(): ByteArray
@ -75,12 +77,19 @@ interface OmnipodDashPodStateManager {
fun updateFromPairing(uniqueId: Id, pairResult: PairResult) fun updateFromPairing(uniqueId: Id, pairResult: PairResult)
fun reset() fun reset()
fun createActiveCommand(historyId: String, basalProgram: BasalProgram? = null, tempBasal: TempBasal? = null): fun createActiveCommand(
Single<ActiveCommand> historyId: String,
basalProgram: BasalProgram? = null,
tempBasal: TempBasal? = null,
requestedBolus: Double? = null
): Single<ActiveCommand>
fun updateActiveCommand(): Maybe<CommandConfirmed> fun updateActiveCommand(): Maybe<CommandConfirmed>
fun observeNoActiveCommand(): Observable<PodEvent> fun observeNoActiveCommand(): Observable<PodEvent>
fun getCommandConfirmationFromState(): CommandConfirmationFromState fun getCommandConfirmationFromState(): CommandConfirmationFromState
fun createLastBolus(requestedUnits: Double, historyId: String, bolusType: DetailedBolusInfo.BolusType)
fun markLastBolusComplete(): LastBolus?
data class ActiveCommand( data class ActiveCommand(
val sequence: Short, val sequence: Short,
val createdRealtime: Long, val createdRealtime: Long,
@ -88,11 +97,36 @@ interface OmnipodDashPodStateManager {
val historyId: String, val historyId: String,
var sendError: Throwable?, var sendError: Throwable?,
var basalProgram: BasalProgram?, var basalProgram: BasalProgram?,
val tempBasal: TempBasal? val tempBasal: TempBasal?,
val requestedBolus: Double?
) )
// TODO: set created to "now" on boot // TODO: set created to "now" on boot
data class TempBasal(val startTime: Long, val rate: Double, val durationInMinutes: Short) : Serializable data class TempBasal(val startTime: Long, val rate: Double, val durationInMinutes: Short) : Serializable
data class LastBolus(
val startTime: Long,
val requestedUnits: Double,
var bolusUnitsRemaining: Double,
var complete: Boolean,
val historyId: String,
val bolusType: DetailedBolusInfo.BolusType
) {
fun deliveredUnits(): Double? {
return if (complete) {
requestedUnits - bolusUnitsRemaining
} else {
null
}
}
fun markComplete(): Double {
this.complete = true
return requestedUnits - bolusUnitsRemaining
}
}
enum class BluetoothConnectionState { enum class BluetoothConnectionState {
CONNECTING, CONNECTED, DISCONNECTED CONNECTING, CONNECTED, DISCONNECTED
} }

View file

@ -2,6 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state
import android.os.SystemClock import android.os.SystemClock
import com.google.gson.Gson import com.google.gson.Gson
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
@ -151,6 +152,10 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
store() store()
} }
override val lastBolus: OmnipodDashPodStateManager.LastBolus?
@Synchronized
get() = podState.lastBolus
override val tempBasalActive: Boolean override val tempBasalActive: Boolean
get() = !isSuspended && tempBasal?.let { get() = !isSuspended && tempBasal?.let {
it.startTime + it.durationInMinutes * 60 * 1000 > System.currentTimeMillis() it.startTime + it.durationInMinutes * 60 * 1000 > System.currentTimeMillis()
@ -197,11 +202,46 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
override val activeCommand: OmnipodDashPodStateManager.ActiveCommand? override val activeCommand: OmnipodDashPodStateManager.ActiveCommand?
get() = podState.activeCommand get() = podState.activeCommand
@Synchronized
override fun createLastBolus(requestedUnits: Double, historyId: String, bolusType: DetailedBolusInfo.BolusType) {
podState.lastBolus = OmnipodDashPodStateManager.LastBolus(
startTime = System.currentTimeMillis(),
requestedUnits = requestedUnits,
bolusUnitsRemaining = requestedUnits,
complete = false, // cancelled, delivered 100% or pod failure
historyId = historyId,
bolusType = bolusType
)
}
@Synchronized
override fun markLastBolusComplete(): OmnipodDashPodStateManager.LastBolus? {
val lastBolus = podState.lastBolus
lastBolus?.run {
this.complete = true
}
?: logger.error(LTag.PUMP, "Trying to mark null bolus as complete")
return lastBolus
}
private fun updateLastBolusFromResponse(bolusPulsesRemaining: Short) {
podState.lastBolus?.run {
val remainingUnits = bolusPulsesRemaining.toDouble() * 0.05
this.bolusUnitsRemaining = remainingUnits
if (remainingUnits == 0.0) {
this.complete = true
}
}
}
@Synchronized @Synchronized
override fun createActiveCommand( override fun createActiveCommand(
historyId: String, historyId: String,
basalProgram: BasalProgram?, basalProgram: BasalProgram?,
tempBasal: OmnipodDashPodStateManager.TempBasal? tempBasal: OmnipodDashPodStateManager.TempBasal?,
requestedBolus: Double?
): ):
Single<OmnipodDashPodStateManager.ActiveCommand> { Single<OmnipodDashPodStateManager.ActiveCommand> {
return Single.create { source -> return Single.create { source ->
@ -213,6 +253,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
sendError = null, sendError = null,
basalProgram = basalProgram, basalProgram = basalProgram,
tempBasal = tempBasal, tempBasal = tempBasal,
requestedBolus = requestedBolus
) )
podState.activeCommand = command podState.activeCommand = command
source.onSuccess(command) source.onSuccess(command)
@ -233,6 +274,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
if (activeCommand == null) { if (activeCommand == null) {
Observable.empty() Observable.empty()
} else { } else {
logger.warn(LTag.PUMP, "Active command already existing: $activeCommand")
Observable.error( Observable.error(
java.lang.IllegalStateException( java.lang.IllegalStateException(
"Trying to send a command " + "Trying to send a command " +
@ -257,7 +299,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
CommandSendingFailure -> { CommandSendingFailure -> {
podState.activeCommand = null podState.activeCommand = null
source.onError( source.onError(
activeCommand?.sendError activeCommand.sendError
?: java.lang.IllegalStateException( ?: java.lang.IllegalStateException(
"Could not send command and sendError is " + "Could not send command and sendError is " +
"missing" "missing"
@ -323,11 +365,10 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
} }
override fun updateFromDefaultStatusResponse(response: DefaultStatusResponse) { override fun updateFromDefaultStatusResponse(response: DefaultStatusResponse) {
logger.debug(LTag.PUMPBTCOMM, "Default status response :$response") logger.debug(LTag.PUMPCOMM, "Default status response :$response")
podState.deliveryStatus = response.deliveryStatus podState.deliveryStatus = response.deliveryStatus
podState.podStatus = response.podStatus podState.podStatus = response.podStatus
podState.pulsesDelivered = response.totalPulsesDelivered podState.pulsesDelivered = response.totalPulsesDelivered
podState.bolusPulsesRemaining = response.bolusPulsesRemaining
if (response.reservoirPulsesRemaining < 1023) { if (response.reservoirPulsesRemaining < 1023) {
podState.pulsesRemaining = response.reservoirPulsesRemaining podState.pulsesRemaining = response.reservoirPulsesRemaining
} }
@ -337,6 +378,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
podState.lastUpdatedSystem = System.currentTimeMillis() podState.lastUpdatedSystem = System.currentTimeMillis()
podState.lastStatusResponseReceived = SystemClock.elapsedRealtime() podState.lastStatusResponseReceived = SystemClock.elapsedRealtime()
updateLastBolusFromResponse(response.bolusPulsesRemaining)
store() store()
rxBus.send(EventOmnipodDashPumpValuesChanged()) rxBus.send(EventOmnipodDashPumpValuesChanged())
@ -398,7 +440,6 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
podState.deliveryStatus = response.deliveryStatus podState.deliveryStatus = response.deliveryStatus
podState.podStatus = response.podStatus podState.podStatus = response.podStatus
podState.pulsesDelivered = response.totalPulsesDelivered podState.pulsesDelivered = response.totalPulsesDelivered
podState.bolusPulsesRemaining = response.bolusPulsesRemaining
if (response.reservoirPulsesRemaining < 1023) { if (response.reservoirPulsesRemaining < 1023) {
podState.pulsesRemaining = response.reservoirPulsesRemaining podState.pulsesRemaining = response.reservoirPulsesRemaining
@ -410,6 +451,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
podState.lastUpdatedSystem = System.currentTimeMillis() podState.lastUpdatedSystem = System.currentTimeMillis()
podState.lastStatusResponseReceived = SystemClock.elapsedRealtime() podState.lastStatusResponseReceived = SystemClock.elapsedRealtime()
updateLastBolusFromResponse(response.bolusPulsesRemaining)
store() store()
rxBus.send(EventOmnipodDashPumpValuesChanged()) rxBus.send(EventOmnipodDashPumpValuesChanged())
@ -488,5 +530,6 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
var basalProgram: BasalProgram? = null var basalProgram: BasalProgram? = null
var tempBasal: OmnipodDashPodStateManager.TempBasal? = null var tempBasal: OmnipodDashPodStateManager.TempBasal? = null
var activeCommand: OmnipodDashPodStateManager.ActiveCommand? = null var activeCommand: OmnipodDashPodStateManager.ActiveCommand? = null
var lastBolus: OmnipodDashPodStateManager.LastBolus? = null
} }
} }

View file

@ -11,6 +11,13 @@ data class TempBasalRecord(val duration: Int, val rate: Double) : Record()
enum class BolusType { enum class BolusType {
DEFAULT, SMB; DEFAULT, SMB;
fun toBolusInfoBolusType(): DetailedBolusInfo.BolusType {
return when (this) {
DEFAULT -> DetailedBolusInfo.BolusType.NORMAL
SMB -> DetailedBolusInfo.BolusType.SMB
}
}
companion object { companion object {
fun fromBolusInfoBolusType(type: DetailedBolusInfo.BolusType): BolusType { fun fromBolusInfoBolusType(type: DetailedBolusInfo.BolusType): BolusType {
return when (type) { return when (type) {

View file

@ -5,6 +5,7 @@ import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -367,7 +368,8 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
private fun updateLastConnection() { private fun updateLastConnection() {
if (podStateManager.isUniqueIdSet) { if (podStateManager.isUniqueIdSet) {
podInfoBinding.lastConnection.text = readableDuration(podStateManager.lastUpdatedSystem) podInfoBinding.lastConnection.text = readableDuration(Duration(podStateManager.lastUpdatedSystem, System
.currentTimeMillis()))
val lastConnectionColor = val lastConnectionColor =
if (omnipodDashPumpPlugin.isUnreachableAlertTimeoutExceeded(getPumpUnreachableTimeout().millis)) { if (omnipodDashPumpPlugin.isUnreachableAlertTimeoutExceeded(getPumpUnreachableTimeout().millis)) {
Color.RED Color.RED
@ -377,7 +379,8 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
podInfoBinding.lastConnection.setTextColor(lastConnectionColor) podInfoBinding.lastConnection.setTextColor(lastConnectionColor)
} else { } else {
podInfoBinding.lastConnection.setTextColor(Color.WHITE) podInfoBinding.lastConnection.setTextColor(Color.WHITE)
podInfoBinding.lastConnection.text = readableDuration(podStateManager.lastUpdatedSystem) podInfoBinding.lastConnection.text = readableDuration(
Duration(podStateManager.lastUpdatedSystem, System.currentTimeMillis()))
} }
} }
@ -416,7 +419,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
} }
val podStatusColor = val podStatusColor =
if (!podStateManager.isActivationCompleted || /* TODO podStateManager.isPodDead || */ podStateManager.isSuspended) { if (!podStateManager.isActivationCompleted || podStateManager.isPodKaput || podStateManager.isSuspended) {
Color.RED Color.RED
} else { } else {
Color.WHITE Color.WHITE
@ -425,27 +428,43 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
} }
private fun updateLastBolus() { private fun updateLastBolus() {
// TODO
/*
if (podStateManager.isActivationCompleted && podStateManager.hasLastBolus()) {
var text = resourceHelper.gs(R.string.omnipod_common_overview_last_bolus_value, omnipodDashPumpPlugin.model().determineCorrectBolusSize(podStateManager.lastBolusAmount), resourceHelper.gs(R.string.insulin_unit_shortname), readableDuration(podStateManager.lastBolusStartTime))
val textColor: Int
if (podStateManager.isLastBolusCertain) { var textColor = Color.WHITE
textColor = Color.WHITE podStateManager.activeCommand?.let {
} else { val requestedBolus = it.requestedBolus
if (requestedBolus != null) {
var text = resourceHelper.gs(
R.string.omnipod_common_overview_last_bolus_value,
omnipodDashPumpPlugin.model().determineCorrectBolusSize(requestedBolus),
resourceHelper.gs(R.string.insulin_unit_shortname),
readableDuration(Duration(it.createdRealtime, SystemClock.elapsedRealtime()))
)
text += " (uncertain) "
textColor = Color.RED textColor = Color.RED
text += " (" + resourceHelper.gs(R.string.omnipod_eros_uncertain) + ")" podInfoBinding.lastBolus.text = text
podInfoBinding.lastBolus.setTextColor(textColor)
return
} }
}
podInfoBinding.lastBolus.setTextColor(textColor)
podStateManager.lastBolus?.let {
// display requested units if delivery is in progress
var bolusSize = it.deliveredUnits()
?: it.requestedUnits
var text = resourceHelper.gs(
R.string.omnipod_common_overview_last_bolus_value,
omnipodDashPumpPlugin.model().determineCorrectBolusSize(bolusSize),
resourceHelper.gs(R.string.insulin_unit_shortname),
readableDuration(Duration(it.startTime, System.currentTimeMillis()))
)
if (!it.complete) {
textColor = Color.YELLOW
}
podInfoBinding.lastBolus.text = text podInfoBinding.lastBolus.text = text
podInfoBinding.lastBolus.setTextColor(textColor) podInfoBinding.lastBolus.setTextColor(textColor)
} else {
podInfoBinding.lastBolus.text = PLACEHOLDER
podInfoBinding.lastBolus.setTextColor(Color.WHITE)
} }
*/
} }
private fun updateTempBasal() { private fun updateTempBasal() {
@ -592,8 +611,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
} }
*/ */
private fun readableDuration(dateTime: Long): String { private fun readableDuration(duration: Duration): String {
val duration = Duration(dateTime, System.currentTimeMillis())
val hours = duration.standardHours.toInt() val hours = duration.standardHours.toInt()
val minutes = duration.standardMinutes.toInt() val minutes = duration.standardMinutes.toInt()
val seconds = duration.standardSeconds.toInt() val seconds = duration.standardSeconds.toInt()