commit
1c33b84bbf
|
@ -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 ->
|
||||
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)
|
||||
},
|
||||
onComplete = {
|
||||
aapsLogger.debug("getPumpStatus completed")
|
||||
} 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}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -172,6 +172,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
DefaultStatusResponse::class
|
||||
)
|
||||
}.doOnComplete {
|
||||
// TODO: remove podStateManager.basalProgram?
|
||||
podStateManager.basalProgram = basalProgram
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||
|
||||
class CouldNotReadResponse
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,7 +188,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
get() = podState.activeCommand
|
||||
|
||||
@Synchronized
|
||||
override fun createActiveCommand(historyId: String): Single<OmnipodDashPodStateManager.ActiveCommand> {
|
||||
override fun createActiveCommand(historyId: String, basalProgram: BasalProgram?):
|
||||
Single<OmnipodDashPodStateManager.ActiveCommand> {
|
||||
return Single.create { source ->
|
||||
if (activeCommand == null) {
|
||||
val command = OmnipodDashPodStateManager.ActiveCommand(
|
||||
|
@ -199,6 +197,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
createdRealtime = SystemClock.elapsedRealtime(),
|
||||
historyId = historyId,
|
||||
sendError = null,
|
||||
basalProgram = basalProgram,
|
||||
)
|
||||
podState.activeCommand = command
|
||||
source.onSuccess(command)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
CommandSendingNotConfirmed -> {
|
||||
// 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
|
||||
|
||||
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 =
|
||||
|
|
|
@ -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,17 +55,16 @@ 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"))
|
||||
}
|
||||
|
||||
return dao.save(
|
||||
Single.error(IllegalArgumentException("tempBasalRecord missing on SET_TEMPORARY_BASAL"))
|
||||
else ->
|
||||
dao.save(
|
||||
HistoryRecordEntity(
|
||||
id = id,
|
||||
date = date,
|
||||
|
@ -76,6 +78,7 @@ class DashHistory @Inject constructor(
|
|||
)
|
||||
).toSingle { id }
|
||||
}
|
||||
}
|
||||
|
||||
fun getRecords(): Single<List<HistoryRecord>> =
|
||||
dao.all().map { list -> list.map(historyMapper::entityToDomain) }
|
||||
|
@ -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 ->
|
||||
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))
|
||||
|
||||
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"))
|
||||
NoActiveCommand ->
|
||||
Completable.complete()
|
||||
}
|
||||
} ?: Completable.complete() // no active programming command
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
) {
|
||||
|
|
Loading…
Reference in a new issue