This commit is contained in:
Andrei Vereha 2021-05-02 14:02:10 +02:00
commit dcfd9bcdbf
14 changed files with 338 additions and 217 deletions

View file

@ -31,7 +31,8 @@ class DeactivatePodFragment : ActionFragmentBase() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.omnipod_wizard_button_discard_pod)?.setOnClickListener {
buttonDiscardPod = view.findViewById(R.id.omnipod_wizard_button_discard_pod)
buttonDiscardPod.setOnClickListener {
context?.let {
AlertDialog.Builder(it)
.setIcon(android.R.drawable.ic_dialog_alert)

View file

@ -10,18 +10,25 @@ 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
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType
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.BeepType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.ResponseType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.DashHistory
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusRecord
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.BolusType
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.CustomCommand
import info.nightscout.androidaps.utils.TimeChangeType
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.rxkotlin.blockingSubscribeBy
import io.reactivex.rxkotlin.subscribeBy
@ -36,6 +43,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
private val podStateManager: OmnipodDashPodStateManager,
private val sp: SP,
private val profileFunction: ProfileFunction,
private val history: DashHistory,
injector: HasAndroidInjector,
aapsLogger: AAPSLogger,
resourceHelper: ResourceHelper,
@ -71,8 +79,23 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun isConnected(): Boolean {
// TODO
return true
// 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
}
override fun isConnecting(): Boolean {
@ -90,7 +113,12 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun connect(reason: String) {
// TODO
// See:
// NOTE: Using connected state for unconfirmed commands
if (podStateManager.activeCommand == null) {
return
}
getPumpStatus("unconfirmed command")
}
override fun disconnect(reason: String) {
@ -102,8 +130,11 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun getPumpStatus(reason: String) {
// TODO history
omnipodManager.getStatus(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE).blockingSubscribeBy(
Observable.concat(
omnipodManager.getStatus(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE),
history.updateFromState(podStateManager).toObservable(),
podStateManager.updateActiveCommand().toObservable(),
).blockingSubscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
@ -120,30 +151,12 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
// TODO history
return Single.create<PumpEnactResult> { source ->
omnipodManager.setBasalProgram(mapProfileToBasalProgram(profile)).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in setNewBasalProfile: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in setNewBasalProfile", throwable)
source.onSuccess(
PumpEnactResult(injector).success(false).enacted(false).comment(
throwable.toString()
)
)
},
onComplete = {
aapsLogger.debug("setNewBasalProfile completed")
source.onSuccess(PumpEnactResult(injector).success(true).enacted(true))
}
)
}.blockingGet()
return executeProgrammingCommand(
history.createRecord(
commandType = OmnipodCommandType.SET_BASAL_PROFILE
),
omnipodManager.setBasalProgram(mapProfileToBasalProgram(profile))
)
}
override fun isThisProfileSet(profile: Profile): Boolean = podStateManager.basalProgram?.let {
@ -151,7 +164,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
} ?: true
override fun lastDataTime(): Long {
return podStateManager.lastConnection
return podStateManager.lastUpdatedSystem
}
override val baseBasalRate: Double
@ -174,7 +187,6 @@ class OmnipodDashPumpPlugin @Inject constructor(
get() = 0
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
// TODO history
// TODO update Treatments (?)
// TODO bolus progress
// TODO report actual delivered amount after Pod Alarm and bolus cancellation
@ -182,10 +194,23 @@ class OmnipodDashPumpPlugin @Inject constructor(
return Single.create<PumpEnactResult> { source ->
val bolusBeeps = sp.getBoolean(R.string.key_omnipod_common_bolus_beeps_enabled, false)
omnipodManager.bolus(
detailedBolusInfo.insulin,
bolusBeeps,
bolusBeeps
Observable.concat(
history.createRecord(
commandType = OmnipodCommandType.SET_BOLUS,
bolusRecord = BolusRecord(
detailedBolusInfo.insulin,
BolusType.fromBolusInfoBolusType(detailedBolusInfo.bolusType),
),
).flatMapObservable { recordId ->
podStateManager.createActiveCommand(recordId).toObservable()
},
omnipodManager.bolus(
detailedBolusInfo.insulin,
bolusBeeps,
bolusBeeps
),
history.updateFromState(podStateManager).toObservable(),
podStateManager.updateActiveCommand().toObservable(),
).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
@ -204,7 +229,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
onComplete = {
aapsLogger.debug("deliverTreatment completed")
source.onSuccess(
PumpEnactResult(injector).success(true).enacted(true).bolusDelivered(detailedBolusInfo.insulin)
PumpEnactResult(injector).success(true).enacted(true)
.bolusDelivered(detailedBolusInfo.insulin)
.carbsDelivered(detailedBolusInfo.carbs)
)
}
@ -213,22 +239,10 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun stopBolusDelivering() {
// TODO history
// TODO update Treatments (?)
omnipodManager.stopBolus().blockingSubscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in stopBolusDelivering: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in stopBolusDelivering", throwable)
},
onComplete = {
aapsLogger.debug("stopBolusDelivering completed")
}
executeProgrammingCommand(
history.createRecord(OmnipodCommandType.CANCEL_BOLUS),
omnipodManager.stopBolus(),
)
}
@ -239,37 +253,24 @@ class OmnipodDashPumpPlugin @Inject constructor(
enforceNew: Boolean,
tbrType: PumpSync.TemporaryBasalType
): PumpEnactResult {
// TODO history
// TODO update Treatments
return Single.create<PumpEnactResult> { source ->
// TODO check for existing basal
// check existing basal(locally and maybe? get status)
// if enforceNew -> cancel it()
// else -> return error that existing basal is running
// set new temp basal
// update treatments
// profit
return executeProgrammingCommand(
history.createRecord(
commandType = OmnipodCommandType.SET_TEMPORARY_BASAL,
tempBasalRecord = TempBasalRecord(duration = durationInMinutes, rate = absoluteRate)
),
omnipodManager.setTempBasal(
absoluteRate,
durationInMinutes.toShort()
).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in setTempBasalAbsolute: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in setTempBasalAbsolute", throwable)
source.onSuccess(
PumpEnactResult(injector).success(false).enacted(false).comment(
throwable.toString()
)
)
},
onComplete = {
aapsLogger.debug("setTempBasalAbsolute completed")
source.onSuccess(
PumpEnactResult(injector).success(true).enacted(true).absolute(absoluteRate)
.duration(durationInMinutes)
)
}
)
}.blockingGet()
)
}
override fun setTempBasalPercent(
@ -291,33 +292,11 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
// TODO history
// TODO update Treatments
return Single.create<PumpEnactResult> { source ->
omnipodManager.stopTempBasal().subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in cancelTempBasal: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in cancelTempBasal", throwable)
source.onSuccess(
PumpEnactResult(injector).success(false).enacted(false).comment(
throwable.toString()
)
)
},
onComplete = {
aapsLogger.debug("cancelTempBasal completed")
source.onSuccess(
PumpEnactResult(injector).success(true).enacted(true)
)
}
)
}.blockingGet()
return executeProgrammingCommand(
history.createRecord(OmnipodCommandType.CANCEL_TEMPORARY_BASAL),
omnipodManager.stopTempBasal()
)
}
override fun cancelExtendedBolus(): PumpEnactResult {
@ -342,11 +321,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
override fun serialNumber(): String {
return if (podStateManager.uniqueId == null) {
"n/a" // TODO i18n
} else {
podStateManager.uniqueId.toString()
}
return podStateManager.uniqueId?.toString()
?: "n/a" // TODO i18n
}
override fun shortStatus(veryShort: Boolean): String {
@ -405,12 +381,15 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
private fun silenceAlerts(): PumpEnactResult {
// TODO history
// TODO filter alert types
return podStateManager.activeAlerts?.let {
Single.create<PumpEnactResult> { source ->
omnipodManager.silenceAlerts(it).subscribeBy(
Observable.concat(
// TODO: is this a programming command? if yes, save to history
omnipodManager.silenceAlerts(it),
history.updateFromState(podStateManager).toObservable(),
podStateManager.updateActiveCommand().toObservable(),
).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
@ -435,75 +414,26 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
private fun suspendDelivery(): PumpEnactResult {
// TODO history
return Single.create<PumpEnactResult> { source ->
omnipodManager.suspendDelivery().subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in suspendDelivery: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in suspendDelivery", throwable)
source.onSuccess(PumpEnactResult(injector).success(false).comment(throwable.toString()))
},
onComplete = {
aapsLogger.debug("suspendDelivery completed")
source.onSuccess(PumpEnactResult(injector).success(true))
}
)
}.blockingGet()
return executeProgrammingCommand(
history.createRecord(OmnipodCommandType.RESUME_DELIVERY),
omnipodManager.suspendDelivery()
)
}
private fun resumeDelivery(): PumpEnactResult {
// TODO history
return profileFunction.getProfile()?.let {
Single.create<PumpEnactResult> { source ->
omnipodManager.setBasalProgram(mapProfileToBasalProgram(it)).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in resumeDelivery: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in resumeDelivery", throwable)
source.onSuccess(PumpEnactResult(injector).success(false).comment(throwable.toString()))
},
onComplete = {
aapsLogger.debug("resumeDelivery completed")
source.onSuccess(PumpEnactResult(injector).success(true))
}
)
}.blockingGet()
executeProgrammingCommand(
history.createRecord(OmnipodCommandType.RESUME_DELIVERY),
omnipodManager.setBasalProgram(mapProfileToBasalProgram(it))
)
} ?: PumpEnactResult(injector).success(false).enacted(false).comment("No profile active") // TODO i18n
}
private fun deactivatePod(): PumpEnactResult {
// TODO history
return Single.create<PumpEnactResult> { source ->
omnipodManager.deactivatePod().subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in deactivatePod: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in deactivatePod", throwable)
source.onSuccess(PumpEnactResult(injector).success(false).comment(throwable.toString()))
},
onComplete = {
aapsLogger.debug("deactivatePod completed")
source.onSuccess(PumpEnactResult(injector).success(true))
}
)
}.blockingGet()
return executeProgrammingCommand(
history.createRecord(OmnipodCommandType.DEACTIVATE_POD),
omnipodManager.deactivatePod()
)
}
private fun handleTimeChange(): PumpEnactResult {
@ -517,31 +447,10 @@ class OmnipodDashPumpPlugin @Inject constructor(
}
private fun playTestBeep(): PumpEnactResult {
// TODO history
return Single.create<PumpEnactResult> { source ->
omnipodManager.playBeep(BeepType.LONG_SINGLE_BEEP).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent in playTestBeep: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error in playTestBeep", throwable)
source.onSuccess(
PumpEnactResult(injector).success(false).enacted(false)
.comment(throwable.toString())
)
},
onComplete = {
aapsLogger.debug("playTestBeep completed")
source.onSuccess(
PumpEnactResult(injector).success(true).enacted(true)
)
}
)
}.blockingGet()
return executeProgrammingCommand(
history.createRecord(OmnipodCommandType.PLAY_TEST_BEEP),
omnipodManager.playBeep(BeepType.LONG_SINGLE_BEEP)
)
}
override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {
@ -564,4 +473,51 @@ class OmnipodDashPumpPlugin @Inject constructor(
commandQueue.customCommand(CommandHandleTimeChange(false), null)
}
private fun observeAddNewActiveCommandToHistory(observeCreateHistoryEntry: Single<String>): Observable<PodEvent> {
return observeCreateHistoryEntry.flatMapObservable {
podStateManager.createActiveCommand(it).toObservable<PodEvent>()
}
}
private fun executeProgrammingCommand(
observeCreateHistoryEntry: Single<String>,
command: Observable<PodEvent>
): PumpEnactResult {
return Single.create<PumpEnactResult> { source ->
Observable.concat(
listOf(
podStateManager.observeNoActiveCommand(),
observeAddNewActiveCommandToHistory(observeCreateHistoryEntry),
command,
history.updateFromState(podStateManager).toObservable(),
podStateManager.updateActiveCommand().toObservable(),
)
).subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,
"Received PodEvent: $podEvent"
)
},
onError = { throwable ->
aapsLogger.error(LTag.PUMP, "Error executing command", throwable)
// Here we assume that onError will be called only BEFORE we manage to send a command
// If it gets called later, we will have the command as "not sent" in history and will not try to
// get it's final status, even if it was send
podStateManager.maybeMarkActiveCommandFailed()
source.onSuccess(
PumpEnactResult(injector).success(false).enacted(false).comment(throwable.toString())
)
},
onComplete = {
aapsLogger.debug("Command completed")
source.onSuccess(
PumpEnactResult(injector).success(true).enacted(true)
)
}
)
}.blockingGet()
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver
import android.os.SystemClock
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManager
@ -643,6 +644,20 @@ class OmnipodDashManagerImpl @Inject constructor(
}
is PodEvent.CommandSent -> {
podStateManager.activeCommand?.let {
if (it.sequence == event.command.sequenceNumber) {
it.sentRealtime = SystemClock.elapsedRealtime()
}
}
podStateManager.increaseMessageSequenceNumber()
}
is PodEvent.CommandSendNotConfirmed -> {
podStateManager.activeCommand?.let {
if (it.sequence == event.command.sequenceNumber) {
it.sentRealtime = SystemClock.elapsedRealtime()
}
}
podStateManager.increaseMessageSequenceNumber()
}

View file

@ -86,8 +86,10 @@ class Connection(
val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO)
fun connect() {
disconnect()
if (session != null) {
disconnect()
}
aapsLogger.debug("Connecting")
if (!gattConnection.connect()) {
throw FailedToConnectException("connect() returned false")
}
@ -167,6 +169,6 @@ class Connection(
companion object {
private const val CONNECT_TIMEOUT_MS = 7000
private const val CONNECT_TIMEOUT_MS = 12000
}
}

View file

@ -65,4 +65,6 @@ sealed class PodEvent {
return "ResponseReceived(command=$command, response=$response)"
}
}
data class CommandConfirmed(val historyId: String, val success: Boolean) : PodEvent()
}

View file

@ -31,7 +31,7 @@ class ProgramBasalCommand private constructor(
private val delayUntilNextTenthPulseInUsec: Int
val length: Short
get() = (insulinProgramElements.size * 6 + 10).toShort()
val bodyLength: Byte
private val bodyLength: Byte
get() = (insulinProgramElements.size * 6 + 8).toByte()
override val encoded: ByteArray

View file

@ -6,4 +6,5 @@ import java.io.Serializable
interface Command : Encodable, Serializable {
val commandType: CommandType
val sequenceNumber: Short
}

View file

@ -6,7 +6,7 @@ import java.nio.ByteBuffer
abstract class HeaderEnabledCommand protected constructor(
override val commandType: CommandType,
protected val uniqueId: Int,
protected val sequenceNumber: Short,
override val sequenceNumber: Short,
protected val multiCommandFlag: Boolean
) : Command {

View file

@ -2,11 +2,15 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state
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.event.PodEvent
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.DefaultStatusResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.SetUniqueIdResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.VersionResponse
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Observable
import java.io.Serializable
import java.util.*
@ -18,7 +22,9 @@ interface OmnipodDashPodStateManager {
val isSuspended: Boolean
val isPodRunning: Boolean
var lastConnection: Long
val lastUpdated: Long
val lastUpdatedSystem: Long // System.currentTimeMillis()
val lastStatusResponseReceived: Long
val messageSequenceNumber: Short
val sequenceNumberOfLastProgrammingCommand: Short?
@ -48,6 +54,7 @@ interface OmnipodDashPodStateManager {
val tempBasal: TempBasal?
val tempBasalActive: Boolean
var basalProgram: BasalProgram?
val activeCommand: ActiveCommand?
fun increaseMessageSequenceNumber()
fun increaseEapAkaSequenceNumber(): ByteArray
@ -59,5 +66,17 @@ interface OmnipodDashPodStateManager {
fun updateFromPairing(uniqueId: Id, pairResult: PairResult)
fun reset()
fun createActiveCommand(historyId: String): Completable
fun updateActiveCommand(): Maybe<PodEvent>
fun observeNoActiveCommand(): Observable<PodEvent>
fun maybeMarkActiveCommandFailed()
data class ActiveCommand(
val sequence: Short,
val createdRealtime: Long,
var sentRealtime: Long = 0,
val historyId: String
)
// TODO: set created to "now" on boot
data class TempBasal(val startTime: Long, val rate: Double, val durationInMinutes: Short) : Serializable
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state
import android.os.SystemClock
import com.google.gson.Gson
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
@ -9,12 +10,16 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
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.session.EapSqn
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.DefaultStatusResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.SetUniqueIdResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.VersionResponse
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Observable
import java.io.Serializable
import java.util.*
import javax.inject.Inject
@ -60,8 +65,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
store()
}
override val lastUpdated: Long
get() = podState.lastUpdated
override val lastUpdatedSystem: Long
get() = podState.lastUpdatedSystem
override val messageSequenceNumber: Short
get() = podState.messageSequenceNumber
@ -152,6 +157,9 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
store()
}
override val lastStatusResponseReceived: Long
get() = podState.lastStatusResponseReceived
override fun increaseMessageSequenceNumber() {
podState.messageSequenceNumber = ((podState.messageSequenceNumber.toInt() + 1) and 0x0f).toShort()
store()
@ -171,6 +179,76 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
store()
}
override val activeCommand: OmnipodDashPodStateManager.ActiveCommand?
get() = podState.activeCommand
@Synchronized
override fun createActiveCommand(historyId: String) = Completable.create { source ->
if (activeCommand == null) {
podState.activeCommand = OmnipodDashPodStateManager.ActiveCommand(
podState.messageSequenceNumber,
createdRealtime = SystemClock.elapsedRealtime(),
historyId = historyId
)
source.onComplete()
} else {
source.onError(
java.lang.IllegalStateException(
"Trying to send a command " +
"and the last command was not confirmed"
)
)
}
}
@Synchronized
override fun observeNoActiveCommand(): Observable<PodEvent> {
return Observable.defer {
if (activeCommand == null) {
Observable.empty()
} else {
Observable.error(
java.lang.IllegalStateException(
"Trying to send a command " +
"and the last command was not confirmed"
)
)
}
}
}
@Synchronized
override fun maybeMarkActiveCommandFailed() {
podState.activeCommand?.run {
if (sentRealtime < createdRealtime) {
// command was not sent
podState.activeCommand = null
}
}
}
@Synchronized
override fun updateActiveCommand() = Maybe.create<PodEvent> { source ->
podState.activeCommand?.run {
logger.debug(
"Trying to confirm active command with parameters: $activeCommand " +
"lastResponse=$lastStatusResponseReceived " +
"$sequenceNumberOfLastProgrammingCommand $historyId"
)
if (createdRealtime >= lastStatusResponseReceived)
// we did not receive a valid response yet
source.onComplete()
else {
podState.activeCommand = null
if (sequenceNumberOfLastProgrammingCommand == sequence)
source.onSuccess(PodEvent.CommandConfirmed(historyId, true))
else
source.onSuccess(PodEvent.CommandConfirmed(historyId, false))
}
}
?: source.onComplete() // no active programming command
}
override fun increaseEapAkaSequenceNumber(): ByteArray {
podState.eapAkaSequenceNumber++
return EapSqn(podState.eapAkaSequenceNumber).value
@ -191,7 +269,9 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
podState.minutesSinceActivation = response.minutesSinceActivation
podState.activeAlerts = response.activeAlerts
podState.lastUpdated = System.currentTimeMillis()
podState.lastUpdatedSystem = System.currentTimeMillis()
podState.lastStatusResponseReceived = SystemClock.elapsedRealtime()
store()
rxBus.send(EventOmnipodDashPumpValuesChanged())
}
@ -211,7 +291,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
podState.lotNumber = response.lotNumber
podState.podSequenceNumber = response.podSequenceNumber
podState.lastUpdated = System.currentTimeMillis()
podState.lastUpdatedSystem = System.currentTimeMillis()
store()
rxBus.send(EventOmnipodDashPumpValuesChanged())
}
@ -237,7 +318,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
podState.podSequenceNumber = response.podSequenceNumber
podState.uniqueId = response.uniqueIdReceivedInCommand
podState.lastUpdated = System.currentTimeMillis()
podState.lastUpdatedSystem = System.currentTimeMillis()
store()
rxBus.send(EventOmnipodDashPumpValuesChanged())
}
@ -293,7 +375,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
var activationProgress: ActivationProgress = ActivationProgress.NOT_STARTED
var lastConnection: Long = 0
var lastUpdated: Long = 0
var lastUpdatedSystem: Long = 0
var lastStatusResponseReceived: Long = 0
var messageSequenceNumber: Short = 0
var sequenceNumberOfLastProgrammingCommand: Short? = null
@ -322,5 +405,6 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
var basalProgram: BasalProgram? = null
var tempBasal: OmnipodDashPodStateManager.TempBasal? = null
var activeCommand: OmnipodDashPodStateManager.ActiveCommand? = null
}
}

View file

@ -4,6 +4,7 @@ import com.github.guepardoapps.kulid.ULID
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.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
@ -22,13 +23,13 @@ class DashHistory @Inject constructor(
private val historyMapper: HistoryMapper
) {
fun markSuccess(id: String, date: Long): Completable = dao.markResolved(
private fun markSuccess(id: String): Completable = dao.markResolved(
id,
ResolvedResult.SUCCESS,
currentTimeMillis()
)
fun markFailure(id: String, date: Long): Completable = dao.markResolved(
private fun markFailure(id: String): Completable = dao.markResolved(
id,
ResolvedResult.FAILURE,
currentTimeMillis()
@ -37,8 +38,8 @@ class DashHistory @Inject constructor(
@Suppress("ReturnCount")
fun createRecord(
commandType: OmnipodCommandType,
date: Long,
initialResult: InitialResult = InitialResult.UNCONFIRMED,
date: Long = System.currentTimeMillis(),
initialResult: InitialResult = InitialResult.NOT_SENT,
tempBasalRecord: TempBasalRecord? = null,
bolusRecord: BolusRecord? = null,
resolveResult: ResolvedResult? = null,
@ -72,4 +73,29 @@ class DashHistory @Inject constructor(
dao.all().map { list -> list.map(historyMapper::entityToDomain) }
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
}
}

View file

@ -1,11 +1,22 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data
import info.nightscout.androidaps.data.DetailedBolusInfo
sealed class Record
data class BolusRecord(val amout: Double, val bolusType: BolusType) : Record()
data class TempBasalRecord(val duration: Long, val rate: Double) : Record()
data class TempBasalRecord(val duration: Int, val rate: Double) : Record()
enum class BolusType {
DEFAULT, SMB
DEFAULT, SMB;
companion object {
fun fromBolusInfoBolusType(type: DetailedBolusInfo.BolusType): BolusType {
return when (type) {
DetailedBolusInfo.BolusType.SMB -> SMB;
else -> DEFAULT
}
}
}
}

View file

@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data
enum class InitialResult {
SUCCESS, FAILURE, UNCONFIRMED
NOT_SENT, FAILURE_SENDING, UNCONFIRMED, SENT
}
enum class ResolvedResult {

View file

@ -5,6 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.InitialResult
import info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data.ResolvedResult
import io.reactivex.Completable
import io.reactivex.Single
@ -32,4 +33,7 @@ abstract class HistoryRecordDao {
@Query("UPDATE historyrecords SET resolvedResult = :resolvedResult, resolvedAt = :resolvedAt WHERE id = :id ")
abstract fun markResolved(id: String, resolvedResult: ResolvedResult, resolvedAt: Long): Completable
@Query("UPDATE historyrecords SET initialResult = :initialResult WHERE id = :id ")
abstract fun setInitialResult(id: String, initialResult: InitialResult): Completable
}