confirm/deny commands

This commit is contained in:
Andrei Vereha 2021-04-18 21:39:20 +02:00
parent feed6a2f63
commit 842f196ae8
19 changed files with 212 additions and 94 deletions

View file

@ -11,18 +11,23 @@ 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.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.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
@ -37,6 +42,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,
@ -147,7 +153,6 @@ class OmnipodDashPumpPlugin @Inject constructor(
it == mapProfileToBasalProgram(profile)
} ?: true
override fun lastDataTime(): Long {
return podStateManager.lastConnection
}
@ -172,7 +177,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
@ -180,11 +184,26 @@ class OmnipodDashPumpPlugin @Inject constructor(
return Single.create<PumpEnactResult> { source ->
val bolusBeeps = sp.getBoolean(R.string.key_omnipod_common_bolus_beeps_enabled, false)
Observable.concat(
history.createRecord(
commandType = OmnipodCommandType.SET_BOLUS,
bolusRecord = BolusRecord(
detailedBolusInfo.insulin,
if (detailedBolusInfo.isSMB) BolusType.SMB else BolusType.DEFAULT
),
).flatMapObservable {
recordId ->
podStateManager.createActiveCommand(recordId).toObservable()
},
omnipodManager.bolus(
detailedBolusInfo.insulin,
bolusBeeps,
bolusBeeps
).subscribeBy(
),
history.updateFromState(podStateManager).toObservable(),
podStateManager.updateActiveCommand().toObservable(),
)
.subscribeBy(
onNext = { podEvent ->
aapsLogger.debug(
LTag.PUMP,

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,11 @@ class OmnipodDashManagerImpl @Inject constructor(
}
is PodEvent.CommandSent -> {
podStateManager.activeCommand?.let {
if (it.sequence == event.command.sequenceNumber) {
it.sentRealtime = SystemClock.elapsedRealtime()
}
}
podStateManager.increaseMessageSequenceNumber()
}

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
import info.nightscout.androidaps.utils.extensions.toHex
import java.nio.ByteBuffer
data class Id(val address: ByteArray) {
init {
require(address.size == 4)

View file

@ -42,7 +42,7 @@ class PayloadJoiner(private val firstPacket: ByteArray) {
oneExtraPacket = lastPacket.oneExtraPacket
}
idx == fullFragments+1 && oneExtraPacket -> {
idx == fullFragments + 1 && oneExtraPacket -> {
fragments.add(LastOptionalPlusOneBlePacket.parse(packet))
}

View file

@ -2,15 +2,12 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Ids
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.PairingException
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageSendErrorSending
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageSendSuccess
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.RandomByteGenerator
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.X25519KeyGenerator
@ -35,7 +32,7 @@ internal class LTKExchanger(
keys = arrayOf(SP1, SP2),
payloads = arrayOf(ids.podId.address, sp2())
)
throwOnSendError(sp1sp2.messagePacket, SP1+SP2)
throwOnSendError(sp1sp2.messagePacket, SP1 + SP2)
seq++
val sps1 = PairMessage(
@ -67,7 +64,7 @@ internal class LTKExchanger(
seq++
// send SP0GP0
val sp0gp0 = PairMessage (
val sp0gp0 = PairMessage(
sequenceNumber = seq,
source = ids.myId,
destination = podAddress,
@ -76,7 +73,7 @@ internal class LTKExchanger(
)
val result = msgIO.sendMessage(sp0gp0.messagePacket)
if (result !is MessageSendSuccess) {
aapsLogger.warn(LTag.PUMPBTCOMM,"Error sending SP0GP0: $result")
aapsLogger.warn(LTag.PUMPBTCOMM, "Error sending SP0GP0: $result")
}
msgIO.receiveMessage()
@ -97,7 +94,6 @@ internal class LTKExchanger(
}
}
private fun processSps1FromPod(msg: MessagePacket) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Received SPS1 from pod: ${msg.payload.toHex()}")

View file

@ -2,7 +2,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Ids
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseResponseException

View file

@ -2,7 +2,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Ids
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.Nonce
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.SessionEstablishmentException

View file

@ -65,4 +65,8 @@ sealed class PodEvent {
return "ResponseReceived(command=$command, response=$response)"
}
}
data class CommandConfirmed(val historyId: String) : PodEvent()
data class CommandDenied(val historyId: String) : 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,14 @@ 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 java.io.Serializable
import java.util.*
@ -18,7 +21,10 @@ interface OmnipodDashPodStateManager {
val isSuspended: Boolean
val isPodRunning: Boolean
var lastConnection: Long
val lastUpdated: Long
val lastUpdatedSystem: Long // System.currentTimeMillis()
// TODO: set lastUpdatedRealtime to 0 on boot
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,15 @@ interface OmnipodDashPodStateManager {
fun updateFromPairing(uniqueId: Id, pairResult: PairResult)
fun reset()
fun createActiveCommand(historyId: String): Completable
fun updateActiveCommand(): Maybe<PodEvent>
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,15 @@ 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 java.io.Serializable
import java.util.*
import javax.inject.Inject
@ -60,8 +64,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 +156,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 +178,41 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
store()
}
override val activeCommand: OmnipodDashPodStateManager.ActiveCommand?
get() = podState.activeCommand
@Synchronized
override fun createActiveCommand(historyId: String): Completable {
return if (activeCommand == null) {
podState.activeCommand = OmnipodDashPodStateManager.ActiveCommand(
podState.messageSequenceNumber,
createdRealtime = SystemClock.elapsedRealtime(),
historyId = historyId
)
Completable.complete()
} else {
Completable.error(
java.lang.IllegalStateException(
"Trying to send a command " +
"and the last command was not confirmed"
)
)
}
}
@Synchronized
override fun updateActiveCommand(): Maybe<PodEvent> {
return podState.activeCommand?.run {
if (createdRealtime >= lastStatusResponseReceived)
Maybe.empty()
else if (sequenceNumberOfLastProgrammingCommand == sequence)
Maybe.just(PodEvent.CommandConfirmed(historyId))
else
Maybe.just(PodEvent.CommandDenied(historyId))
}
?: Maybe.empty() // no active programming command
}
override fun increaseEapAkaSequenceNumber(): ByteArray {
podState.eapAkaSequenceNumber++
return EapSqn(podState.eapAkaSequenceNumber).value
@ -191,7 +233,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 +255,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 +282,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 +339,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 +369,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.CREATED,
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 {
return 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,7 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.history.data
enum class InitialResult {
SUCCESS, FAILURE, UNCONFIRMED
CREATED, 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
}