Merge pull request #33 from 0pen-dash/avereha/basal

Avereha/basal
This commit is contained in:
Andrei Vereha 2021-06-06 21:28:45 +02:00 committed by GitHub
commit 1c33b84bbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 339 additions and 191 deletions

View file

@ -3,9 +3,11 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.common.ManufacturerType
import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction
import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType
@ -15,7 +17,10 @@ import info.nightscout.androidaps.plugins.pump.omnipod.common.queue.command.*
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ActivationProgress
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BeepType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.DeliveryStatus
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.PodConstants
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.ResponseType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.CommandConfirmed
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
@ -25,6 +30,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusTy
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.TempBasalRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.OmnipodDashOverviewFragment
import info.nightscout.androidaps.plugins.pump.omnipod.dash.util.mapProfileToBasalProgram
import info.nightscout.androidaps.queue.commands.Command
import info.nightscout.androidaps.queue.commands.CustomCommand
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.TimeChangeType
@ -48,6 +54,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
private val profileFunction: ProfileFunction,
private val history: DashHistory,
private val pumpSync: PumpSync,
private val rxBus: RxBusWrapper,
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
resourceHelper: ResourceHelper,
@ -83,23 +91,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun isConnected(): Boolean {
// NOTE: Using connected state for unconfirmed commands
// We are faking connection lost on unconfirmed commands.
// During normal execution, the activeCommand is set to null after a command was executed with success or we
// were not able to send that command.
// If we are not sure if the POD received the command or not, then we answer with "success" but keep this
// activeCommand set until we can confirm/deny it.
// In order to prevent AAPS from sending us other programming commands while the current command was not
// confirmed, we are simulating "connection lost".
// We need to prevent AAPS from sending other commands because they would overwrite the ID of the last
// programming command reported by the POD. And we using that ID to confirm/deny the activeCommand.
// The effect of answering with 'false' here is that AAPS will call connect() and will not sent any new
// commands. On connect(), we are calling getPodStatus where we are always trying to confirm/deny the
// activeCommand.
return podStateManager.activeCommand == null
return true
}
override fun isConnecting(): Boolean {
@ -117,12 +109,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun connect(reason: String) {
// See:
// NOTE: Using connected state for unconfirmed commands
if (podStateManager.activeCommand == null) {
return
}
getPumpStatus("unconfirmed command")
// empty on purpose
}
override fun disconnect(reason: String) {
@ -134,38 +121,81 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun getPumpStatus(reason: String) {
Observable.concat(
omnipodManager.getStatus(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE),
history.updateFromState(podStateManager).toObservable(),
podStateManager.updateActiveCommand().toObservable(),
).blockingSubscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in getPumpStatus: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in getPumpStatus", throwable)
},
onComplete = {
aapsLogger.debug("getPumpStatus completed")
}
)
val throwable = Completable.concat(listOf(
omnipodManager
.getStatus(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE)
.ignoreElements(),
history.updateFromState(podStateManager),
podStateManager.updateActiveCommand()
.map { handleCommandConfirmation(it) }
.ignoreElement(),
)).blockingGet()
if (throwable != null){
aapsLogger.error(LTag.PUMP, "Error in getPumpStatus", throwable)
} else {
aapsLogger.info(LTag.PUMP, "getPumpStatus executed with success")
}
}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
val basalProgram = mapProfileToBasalProgram(profile)
return executeSimpleProgrammingCommand(
history.createRecord(
commandType = OmnipodCommandType.SET_BASAL_PROFILE
),
omnipodManager.setBasalProgram(mapProfileToBasalProgram(profile)).ignoreElements()
pre = suspendDeliveryIfActive(),
historyEntry = history.createRecord(commandType = OmnipodCommandType.SET_BASAL_PROFILE),
command = omnipodManager.setBasalProgram(basalProgram).ignoreElements(),
basalProgram = basalProgram,
post = failWhenUnconfirmed(),
).toPumpEnactResult()
}
override fun isThisProfileSet(profile: Profile): Boolean = podStateManager.basalProgram?.let {
it == mapProfileToBasalProgram(profile)
} ?: true
private fun failWhenUnconfirmed(): Completable = Completable.defer{
if (podStateManager.activeCommand != null) {
Completable.error(java.lang.IllegalStateException("Command not confirmed"))
}else {
Completable.complete()
}
}
private fun suspendDeliveryIfActive(): Completable = Completable.defer {
if (podStateManager.deliveryStatus == DeliveryStatus.SUSPENDED)
Completable.complete()
else
executeSimpleProgrammingCommand(
history.createRecord(OmnipodCommandType.SUSPEND_DELIVERY),
omnipodManager.suspendDelivery()
.filter { podEvent -> podEvent is PodEvent.CommandSent }
.map {
pumpSyncTempBasal(
it,
0.0,
PodConstants.MAX_POD_LIFETIME.standardMinutes,
PumpSync.TemporaryBasalType.PUMP_SUSPEND
)
}
.ignoreElements(),
)
}
private fun observeDeliverySuspended(): Completable = Completable.defer {
if (podStateManager.deliveryStatus == DeliveryStatus.SUSPENDED)
Completable.complete()
else {
Completable.error(java.lang.IllegalStateException("Expected suspended delivery"))
}
}
override fun isThisProfileSet(profile: Profile): Boolean {
if (!podStateManager.isActivationCompleted) {
// prevent setBasal requests
return true
}
// TODO: what do we have to answer here if delivery is suspended?
val running = podStateManager.basalProgram
val equal = (mapProfileToBasalProgram(profile) == running)
aapsLogger.info(LTag.PUMP, "isThisProfileSet: $equal")
return equal
}
override fun lastDataTime(): Long {
return podStateManager.lastUpdatedSystem
@ -270,7 +300,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
tempBasalBeeps
)
.filter { podEvent -> podEvent is PodEvent.CommandSent }
.map { pumpSyncTempBasal(it, tbrType) }
.map { pumpSyncTempBasal(it, absoluteRate, durationInMinutes.toLong(), tbrType) }
.ignoreElements(),
pre = observeNoActiveTempBasal()
).toPumpEnactResult()
@ -278,6 +308,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
private fun pumpSyncTempBasal(
podEvent: PodEvent,
absoluteRate: Double,
durationInMinutes: Long,
tbrType: PumpSync.TemporaryBasalType
): Boolean {
val activeCommand = podStateManager.activeCommand
@ -289,14 +321,11 @@ class OmnipodDashPumpPlugin @Inject constructor(
)
}
val historyEntry = history.getById(activeCommand.historyId)
val record = historyEntry.record
if (record == null || !(record is TempBasalRecord)) {
throw IllegalArgumentException("Illegal recording in history: $record. Expected a temp basal")
}
val ret = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = historyEntry.createdAt,
rate = record.rate,
duration = T.mins(record.duration.toLong()).msecs(),
rate = absoluteRate,
duration = T.mins(durationInMinutes.toLong()).msecs(),
isAbsolute = true,
type = tbrType,
pumpId = historyEntry.pumpId(),
@ -325,8 +354,9 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
private fun observeActiveTempBasal(): Completable {
return Completable.defer {
if (podStateManager.tempBasalActive)
if (podStateManager.tempBasalActive || pumpSync.expectedPumpState().temporaryBasal != null)
Completable.complete()
else
Completable.error(
@ -369,36 +399,6 @@ class OmnipodDashPumpPlugin @Inject constructor(
.blockingGet()
}
private fun handleCommandConfirmation(confirmation: CommandConfirmed) {
val historyEntry = history.getById(confirmation.historyId)
when (historyEntry.commandType) {
OmnipodCommandType.CANCEL_TEMPORARY_BASAL ->
// We can't invalidate this command,
// and this is why it is pumpSync-ed at this point
if (confirmation.success) {
pumpSync.syncStopTemporaryBasalWithPumpId(
historyEntry.createdAt,
historyEntry.pumpId(),
PumpType.OMNIPOD_DASH,
serialNumber()
)
}
OmnipodCommandType.SET_TEMPORARY_BASAL ->
// This treatment was synced before sending the command
if (!confirmation.success) {
// TODO: the ID here is the temp basal id, not the pumpId!!
pumpSync.invalidateTemporaryBasal(historyEntry.pumpId())
}
else ->
aapsLogger.warn(
LTag.PUMP,
"Will not sync confirmed command of type: $historyEntry and " +
"succes: ${confirmation.success}"
)
}
}
override fun cancelExtendedBolus(): PumpEnactResult {
// TODO i18n
return PumpEnactResult(injector).success(false).enacted(false)
@ -515,16 +515,35 @@ class OmnipodDashPumpPlugin @Inject constructor(
private fun suspendDelivery(): PumpEnactResult {
return executeSimpleProgrammingCommand(
history.createRecord(OmnipodCommandType.RESUME_DELIVERY),
omnipodManager.suspendDelivery().ignoreElements()
historyEntry = history.createRecord(OmnipodCommandType.SUSPEND_DELIVERY),
command = omnipodManager.suspendDelivery()
.filter { podEvent -> podEvent is PodEvent.CommandSent }
.map {
pumpSyncTempBasal(
it,
0.0,
PodConstants.MAX_POD_LIFETIME.standardMinutes,
PumpSync.TemporaryBasalType.PUMP_SUSPEND
)
}
.ignoreElements(),
pre = observeDeliveryActive(),
).toPumpEnactResult()
}
private fun observeDeliveryActive(): Completable = Completable.defer {
if (podStateManager.deliveryStatus != DeliveryStatus.SUSPENDED)
Completable.complete()
else
Completable.error(java.lang.IllegalStateException("Expected active delivery"))
}
private fun resumeDelivery(): PumpEnactResult {
return profileFunction.getProfile()?.let {
executeSimpleProgrammingCommand(
history.createRecord(OmnipodCommandType.RESUME_DELIVERY),
omnipodManager.setBasalProgram(mapProfileToBasalProgram(it)).ignoreElements()
omnipodManager.setBasalProgram(mapProfileToBasalProgram(it)).ignoreElements(),
pre = observeDeliverySuspended(),
).toPumpEnactResult()
} ?: PumpEnactResult(injector).success(false).enacted(false).comment("No profile active") // TODO i18n
}
@ -578,13 +597,15 @@ class OmnipodDashPumpPlugin @Inject constructor(
historyEntry: Single<String>,
command: Completable,
pre: Completable = Completable.complete(),
basalProgram: BasalProgram? = null,
post: Completable = Completable.complete(),
): Completable {
return Completable.concat(
listOf(
pre,
podStateManager.observeNoActiveCommand().ignoreElements(),
historyEntry
.flatMap { podStateManager.createActiveCommand(it) }
.flatMap { podStateManager.createActiveCommand(it, basalProgram) }
.ignoreElement(),
command.doOnError {
podStateManager.activeCommand?.sendError = it
@ -593,8 +614,69 @@ class OmnipodDashPumpPlugin @Inject constructor(
history.updateFromState(podStateManager),
podStateManager.updateActiveCommand()
.map { handleCommandConfirmation(it) }
.ignoreElement()
.ignoreElement(),
post,
)
)
}
private fun handleCommandConfirmation(confirmation: CommandConfirmed) {
val command = confirmation.command
val historyEntry = history.getById(command.historyId)
aapsLogger.debug(LTag.PUMPCOMM, "handling command confirmation: $confirmation")
when (historyEntry.commandType) {
OmnipodCommandType.CANCEL_TEMPORARY_BASAL,
OmnipodCommandType.RESUME_DELIVERY ->
// We can't invalidate this command,
// and this is why it is pumpSync-ed at this point
if (confirmation.success) {
pumpSync.syncStopTemporaryBasalWithPumpId(
historyEntry.createdAt,
historyEntry.pumpId(),
PumpType.OMNIPOD_DASH,
serialNumber()
)
}
OmnipodCommandType.SET_BASAL_PROFILE -> {
if (confirmation.success) {
podStateManager.basalProgram = command.basalProgram
if (podStateManager.basalProgram == null) {
aapsLogger.warn(LTag.PUMP, "Saving null basal profile")
}
if (!commandQueue.isRunning(Command.CommandType.BASAL_PROFILE)) {
// we are late-confirming this command. before that, we answered with success:false
rxBus.send(EventProfileSwitchChanged())
}
pumpSync.syncStopTemporaryBasalWithPumpId(
historyEntry.createdAt,
historyEntry.pumpId(),
PumpType.OMNIPOD_DASH,
serialNumber()
)
}
}
OmnipodCommandType.SET_TEMPORARY_BASAL -> {
// This treatment was synced before sending the command
aapsLogger.info(LTag.PUMPCOMM, "temporary basal denied. PumpId: ${historyEntry.pumpId()}")
if (!confirmation.success) {
pumpSync.invalidateTemporaryBasal(historyEntry.pumpId())
}
}
OmnipodCommandType.SUSPEND_DELIVERY -> {
if (!confirmation.success) {
pumpSync.invalidateTemporaryBasal(historyEntry.pumpId())
}
}
else ->
aapsLogger.warn(
LTag.PUMP,
"Will not sync confirmed command of type: $historyEntry and " +
"succes: ${confirmation.success}"
)
}
}
}

View file

@ -4,6 +4,7 @@ import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.Reusable
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.DashHistory
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.DashHistoryDatabase
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.database.HistoryRecordDao
@ -28,6 +29,6 @@ class OmnipodDashHistoryModule {
@Provides
@Singleton
internal fun provideDashHistory(dao: HistoryRecordDao, historyMapper: HistoryMapper) =
DashHistory(dao, historyMapper)
internal fun provideDashHistory(dao: HistoryRecordDao, historyMapper: HistoryMapper, logger: AAPSLogger) =
DashHistory(dao, historyMapper, logger)
}

View file

@ -172,6 +172,7 @@ class OmnipodDashManagerImpl @Inject constructor(
DefaultStatusResponse::class
)
}.doOnComplete {
// TODO: remove podStateManager.basalProgram?
podStateManager.basalProgram = basalProgram
}
}

View file

@ -103,7 +103,6 @@ class OmnipodDashBleManagerImpl @Inject constructor(
?: Connection(podDevice, aapsLogger, context, podState)
connection = conn
if (conn.connectionState() is Connected) {
podState.lastConnection = System.currentTimeMillis()
if (conn.session == null) {
emitter.onNext(PodEvent.EstablishingSession)
establishSession(1.toByte())
@ -116,7 +115,6 @@ class OmnipodDashBleManagerImpl @Inject constructor(
}
conn.connect()
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
podState.lastConnection = System.currentTimeMillis()
emitter.onNext(PodEvent.EstablishingSession)
establishSession(1.toByte())
emitter.onNext(PodEvent.Connected)

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class CouldNotReadResponse

View file

@ -17,7 +17,15 @@ class BasalProgram(
fun isZeroBasal() = segments.sumBy(Segment::basalRateInHundredthUnitsPerHour) == 0
fun rateAt(date: Date): Double = 0.0 // TODO
fun rateAt(date: Date): Double {
val instance = Calendar.getInstance()
instance.time = date
val hourOfDay = instance[Calendar.HOUR_OF_DAY]
val minuteOfHour = instance[Calendar.MINUTE]
val slotIndex = hourOfDay * 2 + minuteOfHour.div(30)
val slot = segments.find { it.startSlotIndex <= slotIndex && slotIndex< it.endSlotIndex }
return (slot?.basalRateInHundredthUnitsPerHour ?: 0).toDouble() / 100
}
class Segment(
val startSlotIndex: Short,

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition
import org.joda.time.Duration
class PodConstants {
companion object {
val MAX_POD_LIFETIME = Duration.standardHours(80)
}
}

View file

@ -1,3 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state
class CommandConfirmed(val historyId: String, val success: Boolean)
data class CommandConfirmed(val command: OmnipodDashPodStateManager.ActiveCommand, val success: Boolean)

View file

@ -14,6 +14,13 @@ import io.reactivex.Single
import java.io.Serializable
import java.util.*
sealed class CommandConfirmationFromState
object CommandSendingFailure : CommandConfirmationFromState()
object CommandSendingNotConfirmed : CommandConfirmationFromState()
object CommandConfirmationDenied : CommandConfirmationFromState()
object CommandConfirmationSuccess : CommandConfirmationFromState()
object NoActiveCommand : CommandConfirmationFromState()
interface OmnipodDashPodStateManager {
var activationProgress: ActivationProgress
@ -21,7 +28,6 @@ interface OmnipodDashPodStateManager {
val isActivationCompleted: Boolean
val isSuspended: Boolean
val isPodRunning: Boolean
var lastConnection: Long
var bluetoothConnectionState: BluetoothConnectionState
val lastUpdatedSystem: Long // System.currentTimeMillis()
@ -67,9 +73,10 @@ interface OmnipodDashPodStateManager {
fun updateFromPairing(uniqueId: Id, pairResult: PairResult)
fun reset()
fun createActiveCommand(historyId: String): Single<ActiveCommand>
fun createActiveCommand(historyId: String, basalProgram: BasalProgram? = null): Single<ActiveCommand>
fun updateActiveCommand(): Maybe<CommandConfirmed>
fun observeNoActiveCommand(): Observable<PodEvent>
fun getCommandConfirmationFromState(): CommandConfirmationFromState
data class ActiveCommand(
val sequence: Short,
@ -77,6 +84,7 @@ interface OmnipodDashPodStateManager {
var sentRealtime: Long = 0,
val historyId: String,
var sendError: Throwable?,
var basalProgram: BasalProgram?
)
// TODO: set created to "now" on boot
data class TempBasal(val startTime: Long, val rate: Double, val durationInMinutes: Short) : Serializable

View file

@ -53,18 +53,11 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
override val isSuspended: Boolean
get() = podState.deliveryStatus?.equals(DeliveryStatus.SUSPENDED)
?: true
?: false
override val isPodRunning: Boolean
get() = podState.podStatus?.isRunning() ?: false
override var lastConnection: Long
get() = podState.lastConnection
set(lastConnection) {
podState.lastConnection = lastConnection
store()
}
override val lastUpdatedSystem: Long
get() = podState.lastUpdatedSystem
@ -148,7 +141,11 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
get() = podState.tempBasal
override val tempBasalActive: Boolean
get() = podState.deliveryStatus in arrayOf(DeliveryStatus.TEMP_BASAL_ACTIVE, DeliveryStatus.BOLUS_AND_TEMP_BASAL_ACTIVE)
get() = podState.deliveryStatus in
arrayOf(
DeliveryStatus.TEMP_BASAL_ACTIVE,
DeliveryStatus.BOLUS_AND_TEMP_BASAL_ACTIVE
)
override var basalProgram: BasalProgram?
get() = podState.basalProgram
@ -191,27 +188,29 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
get() = podState.activeCommand
@Synchronized
override fun createActiveCommand(historyId: String): Single<OmnipodDashPodStateManager.ActiveCommand> {
return Single.create { source ->
if (activeCommand == null) {
val command = OmnipodDashPodStateManager.ActiveCommand(
podState.messageSequenceNumber,
createdRealtime = SystemClock.elapsedRealtime(),
historyId = historyId,
sendError = null,
)
podState.activeCommand = command
source.onSuccess(command)
} else {
source.onError(
java.lang.IllegalStateException(
"Trying to send a command " +
"and the last command was not confirmed"
override fun createActiveCommand(historyId: String, basalProgram: BasalProgram?):
Single<OmnipodDashPodStateManager.ActiveCommand> {
return Single.create { source ->
if (activeCommand == null) {
val command = OmnipodDashPodStateManager.ActiveCommand(
podState.messageSequenceNumber,
createdRealtime = SystemClock.elapsedRealtime(),
historyId = historyId,
sendError = null,
basalProgram = basalProgram,
)
)
podState.activeCommand = command
source.onSuccess(command)
} else {
source.onError(
java.lang.IllegalStateException(
"Trying to send a command " +
"and the last command was not confirmed"
)
)
}
}
}
}
@Synchronized
override fun observeNoActiveCommand(): Observable<PodEvent> {
@ -231,34 +230,72 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
@Synchronized
override fun updateActiveCommand() = Maybe.create<CommandConfirmed> { source ->
podState.activeCommand?.run {
logger.debug(
"Trying to confirm active command with parameters: $activeCommand " +
"lastResponse=$lastStatusResponseReceived " +
"$sequenceNumberOfLastProgrammingCommand $historyId"
)
if (sentRealtime < createdRealtime) { // command was not sent, clear it up
val activeCommand = podState.activeCommand
if (activeCommand == null) {
logger.error("No active command to update")
source.onComplete()
return@create
}
val cmdConfirmation = getCommandConfirmationFromState()
logger.info(LTag.PUMPCOMM, "Update active command with confirmation: $cmdConfirmation")
when (cmdConfirmation) {
CommandSendingFailure -> {
podState.activeCommand = null
source.onError(
this.sendError
activeCommand?.sendError
?: java.lang.IllegalStateException(
"Could not send command and sendError is " +
"missing"
)
)
} else if (createdRealtime >= lastStatusResponseReceived)
// we did not receive a valid response yet
source.onComplete()
else {
podState.activeCommand = null
if (sequenceNumberOfLastProgrammingCommand == sequence)
source.onSuccess(CommandConfirmed(historyId, true))
else
source.onSuccess(CommandConfirmed(historyId, false))
}
} ?: source.onComplete()
// no active programming command
CommandSendingNotConfirmed -> {
// we did not receive a valid response yet
source.onComplete()
}
CommandConfirmationDenied -> {
podState.activeCommand = null
source.onSuccess(CommandConfirmed(activeCommand, false))
}
CommandConfirmationSuccess -> {
podState.activeCommand = null
source.onSuccess(CommandConfirmed(activeCommand, true))
}
NoActiveCommand -> {
source.onComplete()
}
}
}
@Synchronized
override fun getCommandConfirmationFromState(): CommandConfirmationFromState {
return podState.activeCommand?.run {
logger.debug(
"Getting command state with parameters: $activeCommand " +
"lastResponse=$lastStatusResponseReceived " +
"$sequenceNumberOfLastProgrammingCommand $historyId"
)
when {
createdRealtime <= podState.lastStatusResponseReceived &&
sequence == podState.sequenceNumberOfLastProgrammingCommand ->
CommandConfirmationSuccess
createdRealtime <= podState.lastStatusResponseReceived &&
sequence != podState.sequenceNumberOfLastProgrammingCommand ->
CommandConfirmationDenied
// no response received after this point
createdRealtime <= sentRealtime ->
CommandSendingNotConfirmed
createdRealtime > sentRealtime ->
CommandSendingFailure
else -> // this can't happen, see the previous two conditions
NoActiveCommand
}
} ?: NoActiveCommand
}
override fun increaseEapAkaSequenceNumber(): ByteArray {
@ -386,7 +423,6 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
class PodState : Serializable {
var activationProgress: ActivationProgress = ActivationProgress.NOT_STARTED
var lastConnection: Long = 0
var lastUpdatedSystem: Long = 0
var lastStatusResponseReceived: Long = 0
var bluetoothConnectionState: OmnipodDashPodStateManager.BluetoothConnectionState =

View file

@ -1,10 +1,12 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history
import com.github.guepardoapps.kulid.ULID
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_BOLUS
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType.SET_TEMPORARY_BASAL
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.*
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.HistoryRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult
@ -20,7 +22,8 @@ import javax.inject.Inject
class DashHistory @Inject constructor(
private val dao: HistoryRecordDao,
private val historyMapper: HistoryMapper
private val historyMapper: HistoryMapper,
private val logger: AAPSLogger
) {
private fun markSuccess(id: String): Completable = dao.markResolved(
@ -52,29 +55,29 @@ class DashHistory @Inject constructor(
bolusRecord: BolusRecord? = null,
resolveResult: ResolvedResult? = null,
resolvedAt: Long? = null
): Single<String> {
): Single<String> = Single.defer {
val id = ULID.random()
when {
commandType == SET_BOLUS && bolusRecord == null ->
return Single.error(IllegalArgumentException("bolusRecord missing on SET_BOLUS"))
Single.error(IllegalArgumentException("bolusRecord missing on SET_BOLUS"))
commandType == SET_TEMPORARY_BASAL && tempBasalRecord == null ->
return Single.error(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL"))
Single.error(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL"))
else ->
dao.save(
HistoryRecordEntity(
id = id,
date = date,
createdAt = currentTimeMillis(),
commandType = commandType,
tempBasalRecord = tempBasalRecord,
bolusRecord = bolusRecord,
initialResult = initialResult,
resolvedResult = resolveResult,
resolvedAt = resolvedAt
)
).toSingle { id }
}
return dao.save(
HistoryRecordEntity(
id = id,
date = date,
createdAt = currentTimeMillis(),
commandType = commandType,
tempBasalRecord = tempBasalRecord,
bolusRecord = bolusRecord,
initialResult = initialResult,
resolvedResult = resolveResult,
resolvedAt = resolvedAt
)
).toSingle { id }
}
fun getRecords(): Single<List<HistoryRecord>> =
@ -83,26 +86,23 @@ class DashHistory @Inject constructor(
fun getRecordsAfter(time: Long): Single<List<HistoryRecordEntity>> = dao.allSince(time)
fun updateFromState(podState: OmnipodDashPodStateManager) = Completable.defer {
podState.activeCommand?.run {
when {
createdRealtime <= podState.lastStatusResponseReceived &&
sequence == podState.sequenceNumberOfLastProgrammingCommand ->
dao.setInitialResult(historyId, InitialResult.SENT)
.andThen(markSuccess(historyId))
createdRealtime <= podState.lastStatusResponseReceived &&
sequence != podState.sequenceNumberOfLastProgrammingCommand ->
markFailure(historyId)
// no response received after this point
createdRealtime <= sentRealtime ->
dao.setInitialResult(historyId, InitialResult.SENT)
createdRealtime > sentRealtime ->
dao.setInitialResult(historyId, InitialResult.FAILURE_SENDING)
else -> Completable.error(IllegalStateException("This can't happen. Could not update history"))
}
} ?: Completable.complete() // no active programming command
val historyId = podState.activeCommand?.historyId
if (historyId == null) {
logger.error(LTag.PUMP, "HistoryId not found to for updating from state")
return@defer Completable.complete()
}
when (podState.getCommandConfirmationFromState()) {
CommandSendingFailure ->
dao.setInitialResult(historyId, InitialResult.FAILURE_SENDING)
CommandSendingNotConfirmed ->
dao.setInitialResult(historyId, InitialResult.SENT)
CommandConfirmationDenied ->
markFailure(historyId)
CommandConfirmationSuccess ->
dao.setInitialResult(historyId, InitialResult.SENT)
.andThen(markSuccess(historyId))
NoActiveCommand ->
Completable.complete()
}
}
}

View file

@ -298,7 +298,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
*/
// base basal rate
podInfoBinding.baseBasalRate.text = if (podStateManager.basalProgram != null) {
podInfoBinding.baseBasalRate.text = if (podStateManager.basalProgram != null && !podStateManager.isSuspended) {
resourceHelper.gs(
R.string.pump_basebasalrate,
omnipodDashPumpPlugin.model()
@ -357,7 +357,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
private fun updateLastConnection() {
if (podStateManager.isUniqueIdSet) {
podInfoBinding.lastConnection.text = readableDuration(podStateManager.lastConnection)
podInfoBinding.lastConnection.text = readableDuration(podStateManager.lastUpdatedSystem)
val lastConnectionColor =
if (omnipodDashPumpPlugin.isUnreachableAlertTimeoutExceeded(getPumpUnreachableTimeout().millis)) {
Color.RED
@ -367,7 +367,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
podInfoBinding.lastConnection.setTextColor(lastConnectionColor)
} else {
podInfoBinding.lastConnection.setTextColor(Color.WHITE)
podInfoBinding.lastConnection.text = readableDuration(podStateManager.lastConnection)
podInfoBinding.lastConnection.text = readableDuration(podStateManager.lastUpdatedSystem)
}
}
@ -518,7 +518,9 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
private fun updateSuspendDeliveryButton() {
// If the Pod is currently suspended, we show the Resume delivery button instead.
if (isSuspendDeliveryButtonEnabled() &&
// TODO: isSuspendDeliveryButtonEnabled doesn't work
val isSuspendDeliveryButtonEnabled = true
if (isSuspendDeliveryButtonEnabled &&
podStateManager.isPodRunning &&
(!podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(CommandSuspendDelivery::class.java))
) {