Initial TBR implementation and pumpSync for basals etc

This commit is contained in:
jbr7rr 2023-05-20 20:40:55 +02:00
parent 61e873dbcc
commit 0d8f07ad0a
19 changed files with 622 additions and 104 deletions

View file

@ -18,6 +18,7 @@ import info.nightscout.interfaces.pump.Pump
import info.nightscout.interfaces.pump.PumpEnactResult
import info.nightscout.interfaces.pump.PumpPluginBase
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.TemporaryBasalStorage
import info.nightscout.interfaces.pump.actions.CustomAction
import info.nightscout.interfaces.pump.actions.CustomActionType
import info.nightscout.interfaces.pump.defs.ManufacturerType
@ -51,6 +52,7 @@ import org.json.JSONException
import org.json.JSONObject
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round
@Singleton class MedtrumPlugin @Inject constructor(
injector: HasAndroidInjector,
@ -63,10 +65,11 @@ import javax.inject.Singleton
private val context: Context,
private val fabricPrivacy: FabricPrivacy,
private val dateUtil: DateUtil,
private val pumpSync: PumpSync,
private val medtrumPump: MedtrumPump,
private val uiInteraction: UiInteraction,
private val profileFunction: ProfileFunction
private val profileFunction: ProfileFunction,
private val pumpSync: PumpSync,
private val temporaryBasalStorage: TemporaryBasalStorage
) : PumpPluginBase(
PluginDescription()
.mainType(PluginType.PUMP)
@ -205,7 +208,7 @@ import javax.inject.Singleton
}
override val baseBasalRate: Double
get() = 0.0 // TODO
get() = medtrumPump.baseBasalRate
override val reservoirLevel: Double
get() = medtrumPump.reservoir
@ -222,7 +225,24 @@ import javax.inject.Singleton
}
override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
return PumpEnactResult(injector) // TODO
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - absoluteRate: $absoluteRate, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew")
// round rate to 0.05
val pumpRate = round(absoluteRate * 20) / 20 // TODO: Maybe replace by constraints thing
temporaryBasalStorage.add(PumpSync.PumpState.TemporaryBasal(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), pumpRate, true, tbrType, 0L, 0L))
val connectionOk = medtrumService?.setTempBasal(pumpRate, durationInMinutes) ?: false
if (connectionOk
&& medtrumPump.tempBasalInProgress
&& Math.abs(medtrumPump.tempBasalAbsoluteRate - pumpRate) <= 0.05
/*&& Math.abs(medtrumPump.tempBasalRemainingMinutes - durationInMinutes) <= 5*/) {
return PumpEnactResult(injector).success(true).enacted(true).duration(/*medtrumPump.tempBasalRemainingMinutes*/durationInMinutes).absolute(medtrumPump.tempBasalAbsoluteRate).isPercent(false)
.isTempCancel(false)
} else {
aapsLogger.error(LTag.PUMP, "setTempBasalAbsolute failed, connectionOk: $connectionOk, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}, tempBasalAbsoluteRate: ${medtrumPump.tempBasalAbsoluteRate}") //, tempBasalRemainingMinutes: ${medtrumPump.tempBasalRemainingMinutes}")
return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum setTempBasalAbsolute failed")
}
}
override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
@ -236,7 +256,16 @@ import javax.inject.Singleton
}
override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {
return PumpEnactResult(injector) // TODO
if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false)
aapsLogger.info(LTag.PUMP, "cancelTempBasal - enforceNew: $enforceNew")
val connectionOk = medtrumService?.cancelTempBasal() ?: false
if (connectionOk && !medtrumPump.tempBasalInProgress) {
return PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true)
} else {
aapsLogger.error(LTag.PUMP, "cancelTempBasal failed, connectionOk: $connectionOk, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}")
return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum cancelTempBasal failed")
}
}
override fun cancelExtendedBolus(): PumpEnactResult {
@ -289,8 +318,4 @@ import javax.inject.Singleton
override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {
}
private fun readTBR(): PumpSync.PumpState.TemporaryBasal? {
return pumpSync.expectedPumpState().temporaryBasal // TODO
}
}

View file

@ -1,16 +1,22 @@
package info.nightscout.pump.medtrum
import android.util.Base64
import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.TemporaryBasalStorage
import info.nightscout.interfaces.pump.defs.PumpType
import info.nightscout.pump.medtrum.code.ConnectionState
import info.nightscout.pump.medtrum.comm.enums.AlarmSetting
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ -21,7 +27,9 @@ import kotlin.math.round
class MedtrumPump @Inject constructor(
private val aapsLogger: AAPSLogger,
private val sp: SP,
private val dateUtil: DateUtil
private val dateUtil: DateUtil,
private val pumpSync: PumpSync,
private val temporaryBasalStorage: TemporaryBasalStorage
) {
// Connection state flow
@ -33,6 +41,14 @@ class MedtrumPump @Inject constructor(
_connectionState.value = value
}
/** Patch activated state, mainly for UI, but also controls the connection flow,
* if patch is not activated, AAPS cannot connect to the pump, we can then connect trough the activation flow.
* Note: this is also saved in SP, by the set functions
*/
private var _patchActivated = false
val patchActivated: Boolean
get() = _patchActivated
// Pump state flow
private val _pumpState = MutableStateFlow(MedtrumPumpState.NONE)
val pumpStateFlow: StateFlow<MedtrumPumpState> = _pumpState
@ -42,10 +58,6 @@ class MedtrumPump @Inject constructor(
_pumpState.value = value
}
var _patchActivated = false
val patchActivated: Boolean
get() = _patchActivated
// Prime progress as state flow
private val _primeProgress = MutableStateFlow(0)
val primeProgressFlow: StateFlow<Int> = _primeProgress
@ -55,14 +67,62 @@ class MedtrumPump @Inject constructor(
_primeProgress.value = value
}
var pumpSN = 0L
val pumpType: PumpType = PumpType.MEDTRUM_NANO // TODO, type based on pumpSN or pump activation/connection
var patchSessionToken = 0L
/** Stuff stored in SP */
private var _patchSessionToken = 0L
var patchSessionToken: Long
get() = _patchSessionToken
set(value) {
_patchSessionToken = value
sp.putLong(R.string.key_session_token, value)
}
private var _patchId = 0L
var patchId: Long
get() = _patchId
set(value) {
_patchId = value
sp.putLong(R.string.key_patch_id, value)
}
private var _currentSequenceNumber = 0
var currentSequenceNumber: Int
get() = _currentSequenceNumber
set(value) {
_currentSequenceNumber = value
sp.putInt(R.string.key_current_sequence_number, value)
}
private var _syncedSequenceNumber = 0
var syncedSequenceNumber: Int
get() = _syncedSequenceNumber
set(value) {
_syncedSequenceNumber = value
sp.putInt(R.string.key_synced_sequence_number, value)
}
private var _actualBasalProfile = byteArrayOf(0)
var actualBasalProfile: ByteArray
get() = _actualBasalProfile
set(value) {
_actualBasalProfile = value
val encodedString = Base64.encodeToString(value, Base64.DEFAULT)
sp.putString(R.string.key_actual_basal_profile, encodedString)
}
private var _lastBasalType: BasalType = BasalType.NONE
var lastBasalType: BasalType
get() = _lastBasalType
set(value) {
_lastBasalType = value
sp.putInt(R.string.key_last_basal_type, value.ordinal)
}
private var _pumpSN = 0L
val pumpSN: Long
get() = _pumpSN
val pumpType: PumpType = PumpType.MEDTRUM_NANO // TODO, type based on pumpSN or pump activation/connection
// TODO: Save this in SP? This might be a bit tricky as we only know what we have set, not what the pump has set but the pump should not change it, addtionally we should track the active basal profile in pump e.g. Basal patern A, B etc
var actualBasalProfile = byteArrayOf(0)
var patchId = 0L
var lastKnownSequenceNumber = 0
var lastTimeReceivedFromPump = 0L // Time in seconds!
var suspendTime = 0L // Time in seconds!
var patchStartTime = 0L // Time in seconds!
@ -76,16 +136,25 @@ class MedtrumPump @Inject constructor(
var alarmFlags = 0
var alarmParameter = 0
// Last basal status update
var lastBasalType = 0
// Last basal status update
// TODO: Save this in SP?
var lastBasalRate = 0.0
var lastBasalSequence = 0
var lastBasalPatchId = 0
var lastBasalPatchId = 0L
var lastBasalStartTime = 0L
val baseBasalRate: Double
get() = getCurrentHourlyBasalFromMedtrumProfileArray(actualBasalProfile)
// TBR status
val tempBasalInProgress: Boolean
get() = lastBasalType == BasalType.ABSOLUTE_TEMP || lastBasalType == BasalType.RELATIVE_TEMP
val tempBasalAbsoluteRate: Double
get() = if (tempBasalInProgress) lastBasalRate else 0.0
// Last stop status update
var lastStopSequence = 0
var lastStopPatchId = 0
var lastStopPatchId = 0L
// TODO set these setting on init
// User settings (desired values, to be set on pump)
@ -94,6 +163,45 @@ class MedtrumPump @Inject constructor(
var desiredHourlyMaxInsulin: Int = 40
var desiredDailyMaxInsulin: Int = 180
init {
// Load stuff from SP
_patchActivated = sp.getBoolean(R.string.key_patch_activated, false)
_patchSessionToken = sp.getLong(R.string.key_session_token, 0L)
_currentSequenceNumber = sp.getInt(R.string.key_current_sequence_number, 0)
_patchId = sp.getLong(R.string.key_patch_id, 0L)
_syncedSequenceNumber = sp.getInt(R.string.key_synced_sequence_number, 0)
_lastBasalType = enumValues<BasalType>()[sp.getInt(R.string.key_last_basal_type, 0)]
val encodedString = sp.getString(R.string.key_actual_basal_profile, "0")
try {
_actualBasalProfile = Base64.decode(encodedString, Base64.DEFAULT)
} catch (e: Exception) {
aapsLogger.error(LTag.PUMP, "Error decoding basal profile from SP: $encodedString")
}
if (patchActivated) {
aapsLogger.debug(LTag.PUMP, "changePump: Patch is already activated, setting as ACTIVE")
// Set inital status as active will be updated on first connection
pumpState = MedtrumPumpState.ACTIVE
}
loadUserSettingsFromSP()
}
fun loadUserSettingsFromSP() {
// TODO
// desiredPatchExpiration = sp.getBoolean(R.string.key_patch_expiration, false)
// desiredAlarmSetting = sp.getInt(R.string.key_alarm_setting, AlarmSetting.LIGHT_VIBRATE_AND_BEEP.code)
// desiredHourlyMaxInsulin = sp.getInt(R.string.key_hourly_max_insulin, 40)
// desiredDailyMaxInsulin = sp.getInt(R.string.key_daily_max_insulin, 180)
try {
_pumpSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16)
} catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!")
}
}
fun setPatchActivatedState(activated: Boolean) {
aapsLogger.debug(LTag.PUMP, "setPatchActivatedState: $activated")
_patchActivated = activated
@ -101,7 +209,8 @@ class MedtrumPump @Inject constructor(
}
/** When the activation/deactivation screen, and the connection flow needs to be controlled,
* this can be used to set the ActivatedState without saving to SP, So when app is force closed the state is still maintained */
* this can be used to set the ActivatedState without saving to SP, So when app is force closed the state is still maintained
*/
fun setPatchActivatedStateTemp(activated: Boolean) {
aapsLogger.debug(LTag.PUMP, "setPatchActivatedStateTemp: $activated")
_patchActivated = activated
@ -123,29 +232,109 @@ class MedtrumPump @Inject constructor(
return (list.size).toByteArray(1) + basals
}
fun handleBasalStatusUpdate(basalType: Int, basalValue: Double, basalSequence: Int, basalPatchId: Int, basalStartTime: Long) {
fun getCurrentHourlyBasalFromMedtrumProfileArray(basalProfile: ByteArray): Double {
val basalCount = basalProfile[0].toInt()
var basal = 0.0
if (basalProfile.size < 4 || (basalProfile.size - 1) % 3 != 0 || basalCount > 24) {
aapsLogger.debug(LTag.PUMP, "getCurrentHourlyBasalFromMedtrumProfileArray: No valid basal profile set")
return basal
}
val hourOfDayMinutes = dateUtil.dateAndTimeString(dateUtil.now()).substring(11, 13).toInt() * 60 + dateUtil.dateAndTimeString(dateUtil.now()).substring(14, 16).toInt()
for (index in 0 until basalCount) {
val currentIndex = 1 + (index * 3)
val nextIndex = currentIndex + 3
val rateAndTime = basalProfile.copyOfRange(currentIndex, nextIndex).toInt()
val rate = (rateAndTime shr 12) * 0.05
val startMinutes = rateAndTime and 0xFFF
val endMinutes = if (nextIndex < basalProfile.size) {
val nextRateAndTime = basalProfile.copyOfRange(nextIndex, nextIndex + 3).toInt()
nextRateAndTime and 0xFFF
} else {
24 * 60
}
if (hourOfDayMinutes in startMinutes until endMinutes) {
basal = rate
aapsLogger.debug(LTag.PUMP, "getCurrentHourlyBasalFromMedtrumProfileArray: basal: $basal")
break
}
aapsLogger.debug(LTag.PUMP, "getCurrentHourlyBasalFromMedtrumProfileArray: rate: $rate, startMinutes: $startMinutes, endMinutes: $endMinutes")
}
return basal
}
fun handleBasalStatusUpdate(basalType: BasalType, basalValue: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long) {
handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, dateUtil.now())
}
fun handleBasalStatusUpdate(basalType: Int, basalRate: Double, basalSequence: Int, basalPatchId: Int, basalStartTime: Long, receivedTime: Long) {
fun handleBasalStatusUpdate(basalType: BasalType, basalRate: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long, receivedTime: Long) {
aapsLogger.debug(
LTag.PUMP, "handleBasalStatusUpdate: basalType: $basalType basalValue: $basalRate basalSequence: $basalSequence basalPatchId: $basalPatchId basalStartTime: $basalStartTime " +
"receivedTime: $receivedTime"
LTag.PUMP,
"handleBasalStatusUpdate: basalType: $basalType basalValue: $basalRate basalSequence: $basalSequence basalPatchId: $basalPatchId basalStartTime: $basalStartTime " + "receivedTime: $receivedTime"
)
if (basalType.isTempBasal()) {
// TODO: Is this the correct place to sync temporaryBasalInfo? Note: it will be removed after getting it once, So this would only apply when called in setTempBasalPacket, maybe first check if basal entry already exists and leave this here, then we can also let the onNotification stuff sync basal?
val temporaryBasalInfo = temporaryBasalStorage.findTemporaryBasal(basalStartTime, basalRate)
// If duration is unknown, no way to get it now, set patch lifetime as duration
val duration = temporaryBasalInfo?.duration ?: T.mins(4800).msecs()
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = basalRate, // TODO: Support percent here, this will break things? Check if this is correct
duration = duration,
isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
type = temporaryBasalInfo?.type,
pumpId = basalStartTime,
pumpType = pumpType,
pumpSerial = pumpSN.toString(radix = 16)
)
aapsLogger.debug(
LTag.PUMPCOMM,
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + "Rate: $basalRate Duration: ${duration}min"
)
} else if (basalType.isSuspendedByPump()) {
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = 0.0,
duration = T.mins(4800).msecs(), // TODO MAGIC NUMBER
isAbsolute = true,
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
pumpId = basalStartTime,
pumpType = pumpType,
pumpSerial = pumpSN.toString(radix = 16)
)
aapsLogger.debug(
LTag.PUMPCOMM,
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_END ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime)"
)
}
// Update medtrum pump state
lastBasalType = basalType
lastBasalRate = basalRate
lastBasalSequence = basalSequence
lastKnownSequenceNumber = basalSequence
if (basalSequence > currentSequenceNumber) {
currentSequenceNumber = basalSequence
}
lastBasalPatchId = basalPatchId
if (basalPatchId != patchId) {
aapsLogger.error(LTag.PUMP, "handleBasalStatusUpdate: WTF? PatchId in status update does not match current patchId!")
}
lastBasalStartTime = basalStartTime
// TODO Handle history
}
fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Int) {
fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Long) {
aapsLogger.debug(LTag.PUMP, "handleStopStatusUpdate: stopSequence: $stopSequence stopPatchId: $stopPatchId")
lastStopSequence = stopSequence
lastKnownSequenceNumber = stopSequence
if (stopSequence > currentSequenceNumber) {
currentSequenceNumber = stopSequence
}
lastStopPatchId = stopPatchId
// TODO Handle history
if (stopPatchId != patchId) {
aapsLogger.error(LTag.PUMP, "handleStopStatusUpdate: WTF? PatchId in status update does not match current patchId!")
}
}
}

View file

@ -0,0 +1,58 @@
package info.nightscout.pump.medtrum.comm.enums
enum class BasalType {
NONE,
STANDARD,
EXERCISE,
HOLIDAY,
PROGRAM_A,
PROGRAM_B,
ABSOLUTE_TEMP,
RELATIVE_TEMP,
PROGRAM_C,
PROGRAM_D,
SICK,
AUTO,
NEW,
SUSPEND_LOW_GLUCOSE,
SUSPEND_PREDICT_LOW_GLUCOSE,
SUSPEND_AUTO,
SUSPEND_MORE_THAN_MAX_PER_HOUR,
SUSPEND_MORE_THAN_MAX_PER_DAY,
SUSPEND_MANUAL,
SUSPEND_KEY_LOST,
STOP_OCCLUSION,
STOP_EXPIRED,
STOP_EMPTY,
STOP_PATCH_FAULT,
STOP_PATCH_FAULT2,
STOP_BASE_FAULT,
STOP_DISCARD,
STOP_BATTERY_EXHAUSTED,
STOP,
PAUSE_INTERRUPT,
PRIME,
AUTO_MODE_START,
AUTO_MODE_EXIT,
AUTO_MODE_TARGET_100,
AUTO_MODE_TARGET_110,
AUTO_MODE_TARGET_120,
AUTO_MODE_BREAKFAST,
AUTO_MODE_LUNCH,
AUTO_MODE_DINNER,
AUTO_MODE_SNACK,
AUTO_MODE_EXERCISE_START,
AUTO_MODE_EXERCISE_EXIT;
fun getValue(): Int {
return ordinal
}
fun isTempBasal(): Boolean {
return this == ABSOLUTE_TEMP || this == RELATIVE_TEMP
}
fun isSuspendedByPump(): Boolean {
return this in SUSPEND_LOW_GLUCOSE..STOP
}
}

View file

@ -5,6 +5,7 @@ import info.nightscout.interfaces.pump.DetailedBolusInfo
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.ACTIVATE
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toByte
import info.nightscout.interfaces.stats.TddCalculator
@ -85,15 +86,17 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt
val medtrumTimeUtil = MedtrumTimeUtil()
val patchId = data.copyOfRange(RESP_PATCH_ID_START, RESP_PATCH_ID_END).toLong()
val time = medtrumTimeUtil.convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()
val time = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toInt()
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
medtrumPump.patchId = patchId
medtrumPump.lastTimeReceivedFromPump = time
medtrumPump.currentSequenceNumber = basalSequence // We are activated, set the new seq nr
medtrumPump.syncedSequenceNumber = basalSequence // We are activated, reset the synced seq nr ()
// Update the actual basal profile
medtrumPump.actualBasalProfile = basalProfile
// TODO: Handle history entry

View file

@ -3,9 +3,11 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.CANCEL_TEMP_BASAL
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
class CancelTempBasalPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
@ -34,11 +36,11 @@ class CancelTempBasalPacket(injector: HasAndroidInjector) : MedtrumPacket(inject
override fun handleResponse(data: ByteArray): Boolean {
val success = super.handleResponse(data)
if (success) {
val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_END).toInt() * 0.05
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toInt()
val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
}

View file

@ -1,16 +1,26 @@
package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.TemporaryBasalStorage
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_RECORD
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import javax.inject.Inject
class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int) : MedtrumPacket(injector) {
@Inject lateinit var medtrumPump: MedtrumPump
@Inject lateinit var pumpSync: PumpSync
@Inject lateinit var temporaryBasalStorage: TemporaryBasalStorage
@Inject lateinit var dateUtil: DateUtil
companion object {
@ -20,11 +30,29 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int
private const val RESP_RECORD_UNKNOWN_END = RESP_RECORD_UNKNOWN_START + 1
private const val RESP_RECORD_TYPE_START = RESP_RECORD_UNKNOWN_END
private const val RESP_RECORD_TYPE_END = RESP_RECORD_TYPE_START + 1
private const val RESP_RECORD_SERIAL_START = RESP_RECORD_TYPE_END
private const val RESP_RECORD_UNKNOWN1_START = RESP_RECORD_TYPE_END
private const val RESP_RECORD_UNKNOWN1_END = RESP_RECORD_UNKNOWN1_START + 1
private const val RESP_RECORD_SERIAL_START = RESP_RECORD_UNKNOWN1_END
private const val RESP_RECORD_SERIAL_END = RESP_RECORD_SERIAL_START + 4
private const val RESP_RECORD_PATCHID_START = RESP_RECORD_SERIAL_END
private const val RESP_RECORD_PATCHID_END = RESP_RECORD_PATCHID_START + 2
private const val RESP_RECORD_DATA_START = RESP_RECORD_PATCHID_END
private const val RESP_RECORD_SEQUENCE_START = RESP_RECORD_PATCHID_END
private const val RESP_RECORD_SEQUENCE_END = RESP_RECORD_SEQUENCE_START + 2
private const val RESP_RECORD_DATA_START = RESP_RECORD_SEQUENCE_END
private const val VALID_HEADER = 170
private const val BOLUS_RECORD = 1
private const val BOLUS_RECORD_ALT = 65
private const val BASAL_RECORD = 2
private const val BASAL_RECORD_ALT = 66
private const val ALARM_RECORD = 3
private const val AUTO_RECORD = 4
private const val TIME_SYNC_RECORD = 5
private const val AUTO1_RECORD = 6
private const val AUTO2_RECORD = 7
private const val AUTO3_RECORD = 8
private const val TDD_RECORD = 9
}
init {
@ -44,8 +72,137 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int
val recordType = data.copyOfRange(RESP_RECORD_TYPE_START, RESP_RECORD_TYPE_END).toInt()
val recordSerial = data.copyOfRange(RESP_RECORD_SERIAL_START, RESP_RECORD_SERIAL_END).toLong()
val recordPatchId = data.copyOfRange(RESP_RECORD_PATCHID_START, RESP_RECORD_PATCHID_END).toInt()
val recordSequence = data.copyOfRange(RESP_RECORD_SEQUENCE_START, RESP_RECORD_SEQUENCE_END).toInt()
// TODO Handle history records
aapsLogger.debug(
LTag.PUMPCOMM,
"GetRecordPacket HandleResponse: Record header: $recordHeader, unknown: $recordUnknown, type: $recordType, serial: $recordSerial, patchId: $recordPatchId, " + "sequence: $recordSequence"
)
medtrumPump.syncedSequenceNumber = recordSequence // Assume sync upwards
if (recordHeader == VALID_HEADER) {
when (recordType) {
BOLUS_RECORD, BOLUS_RECORD_ALT -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD")
}
BASAL_RECORD, BASAL_RECORD_ALT -> {
val medtrumTimeUtil = MedtrumTimeUtil()
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 4).toLong())
val basalEndTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong())
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 9).toInt()]
val basalEndReason = data.copyOfRange(RESP_RECORD_DATA_START + 9, RESP_RECORD_DATA_START + 10).toInt()
val basalRate = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05
val basalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05
val basalPercent = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toInt()
aapsLogger.debug(
LTag.PUMPCOMM,
"GetRecordPacket HandleResponse: BASAL_RECORD: Start: $basalStartTime, End: $basalEndTime, Type: $basalType, EndReason: $basalEndReason, Rate: $basalRate, Delivered: $basalDelivered, Percent: $basalPercent"
)
when (basalType) {
BasalType.STANDARD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Standard basal")
// If we are here it means the basal has ended
}
BasalType.ABSOLUTE_TEMP, BasalType.RELATIVE_TEMP -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Absolute temp basal")
val duration = (basalEndTime - basalStartTime)
if (duration > 0) { // Sync start and end
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = if (basalType == BasalType.ABSOLUTE_TEMP) basalRate else basalPercent.toDouble(),
duration = duration,
isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
type = PumpSync.TemporaryBasalType.NORMAL,
pumpId = basalStartTime,
pumpType = medtrumPump.pumpType,
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.debug(
LTag.PUMPCOMM,
"handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_SYNC: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " +
"Rate: $basalRate Duration: ${duration}min"
)
} else { // Sync only end ?
pumpSync.syncStopTemporaryBasalWithPumpId(
timestamp = basalEndTime,
endPumpId = basalEndTime,
pumpType = medtrumPump.pumpType,
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.warn(
LTag.PUMPCOMM,
"handleBasalStatusUpdate from record: EVENT TEMP_END ($basalType) ${dateUtil.dateAndTimeString(basalEndTime)} ($basalEndTime) " + "Rate: $basalRate Duration: ${duration}min"
)
}
}
in BasalType.SUSPEND_LOW_GLUCOSE..BasalType.STOP -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Suspend basal")
val duration = (basalEndTime - basalStartTime)
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalEndTime,
rate = 0.0,
duration = duration,
isAbsolute = true,
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
pumpId = basalStartTime,
pumpType = medtrumPump.pumpType,
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.debug(
LTag.PUMPCOMM,
"handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT SUSPEND: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " +
"Rate: $basalRate Duration: ${duration}min"
)
}
else -> {
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Unknown basal type: $basalType")
// TODO: Error warning
}
}
}
ALARM_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: ALARM_RECORD")
}
AUTO_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO_RECORD")
}
TIME_SYNC_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TIME_SYNC_RECORD")
}
AUTO1_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO1_RECORD")
}
AUTO2_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO2_RECORD")
}
AUTO3_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO3_RECORD")
}
TDD_RECORD -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TDD_RECORD")
}
else -> {
aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Unknown record type: $recordType")
}
}
} else {
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Invalid record header")
}
}
return success

View file

@ -25,7 +25,7 @@ class GetTimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
override fun handleResponse(data: ByteArray): Boolean {
val success = super.handleResponse(data)
if (success) {
val time = MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
val time = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong())
medtrumPump.lastTimeReceivedFromPump = time
}

View file

@ -2,6 +2,7 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toInt
@ -98,7 +99,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
var bolusData = data.copyOfRange(offset, offset + 1).toInt()
var bolusType = bolusData and 0x7F
var bolusCompleted = (bolusData shr 7) and 0x01 // TODO: Check for other flags here :)
var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() / 0.05
var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05
// TODO Sync bolus flow:
// If bolus is known add status
// If bolus is not known start read bolus
@ -115,18 +116,20 @@ class NotificationPacket(val injector: HasAndroidInjector) {
if (fieldMask and MASK_BASAL != 0) {
aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received")
var basalType = data.copyOfRange(offset, offset + 1).toInt()
val basalType = enumValues<BasalType>()[data.copyOfRange(offset, offset + 1).toInt()]
var basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt()
var basalPatchId = data.copyOfRange(offset + 3, offset + 5).toInt()
var basalInterval = data.copyOfRange(offset + 5, offset + 9).toInt()
var basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong()
var basalTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong())
var basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt()
var basalRate = (basalRateAndDelivery and 0xFFF) * 0.05
var basalDelivery = (basalRateAndDelivery shr 12) * 0.05
aapsLogger.debug(
LTag.PUMPCOMM,
"Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal interval: $basalInterval, basal rate: $basalRate, basal delivery: $basalDelivery"
"Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalTime, basal rate: $basalRate, basal delivery: $basalDelivery"
)
// TODO Sync basal flow
// TODO: Check if basal is known, if not add it
// medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalTime)
// TODO: Handle basal delivery
offset += 12
}
@ -165,9 +168,16 @@ class NotificationPacket(val injector: HasAndroidInjector) {
if (fieldMask and MASK_STORAGE != 0) {
aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received")
// TODO, trigger check for new sequence?
medtrumPump.lastKnownSequenceNumber = data.copyOfRange(offset, offset + 2).toInt()
medtrumPump.patchId = data.copyOfRange(offset + 2, offset + 4).toLong()
aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.lastKnownSequenceNumber}, patch id: ${medtrumPump.patchId}")
val sequence = data.copyOfRange(offset, offset + 2).toInt()
if (sequence > medtrumPump.currentSequenceNumber) {
medtrumPump.currentSequenceNumber = sequence
}
val patchId = data.copyOfRange(offset + 2, offset + 4).toLong()
if (patchId != medtrumPump.patchId) {
aapsLogger.error(LTag.PUMPCOMM, "handleMaskedMessage: WTF? We got wrong patch id!")
// TODO: We should terminate session or stop patch here? or at least throw error? THis can be thrown during activation process though
}
aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}")
offset += 4
}
@ -182,7 +192,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
if (fieldMask and MASK_START_TIME != 0) {
aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received")
medtrumPump.patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(data.copyOfRange(offset, offset + 4).toLong())
medtrumPump.patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: ${medtrumPump.patchStartTime}")
offset += 4
}

View file

@ -3,6 +3,7 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BASAL_PROFILE
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
@ -41,11 +42,11 @@ class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfi
val success = super.handleResponse(data)
if (success) {
val medtrumTimeUtil = MedtrumTimeUtil()
val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toInt()
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
// Update the actual basal profile
medtrumPump.actualBasalProfile = basalProfile

View file

@ -3,10 +3,12 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_TEMP_BASAL
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.extension.toByteArray
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
import kotlin.math.round
@ -49,11 +51,19 @@ class SetTempBasalPacket(injector: HasAndroidInjector, private val absoluteRate:
override fun handleResponse(data: ByteArray): Boolean {
val success = super.handleResponse(data)
if (success) {
val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()
val basalType = enumValues<BasalType>()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()]
val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_END).toInt() * 0.05
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toInt()
val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong()
val rawTime = data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()
val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
aapsLogger.debug(LTag.PUMPCOMM, "Basal status update: type=$basalType, rate=$basalRate, sequence=$basalSequence, patchId=$basalPatchId, startTime=$basalStartTime, rawTime=$rawTime")
// TODO: For debugging remove later
val pumpTime = MedtrumTimeUtil().getCurrentTimePumpSeconds()
val systemTime = System.currentTimeMillis()
aapsLogger.debug(LTag.PUMPCOMM, "Pump time: $pumpTime, System time: $systemTime")
medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
}

View file

@ -4,6 +4,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.comm.enums.CommandType.STOP_PATCH
import info.nightscout.pump.medtrum.extension.toLong
import javax.inject.Inject
class StopPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
@ -27,7 +28,7 @@ class StopPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) {
val success = super.handleResponse(data)
if (success) {
val stopSequence = data.copyOfRange(RESP_STOP_SEQUENCE_START, RESP_STOP_SEQUENCE_END).toInt()
val stopPatchId = data.copyOfRange(RESP_STOP_PATCH_ID_START, RESP_STOP_PATCH_ID_END).toInt()
val stopPatchId = data.copyOfRange(RESP_STOP_PATCH_ID_START, RESP_STOP_PATCH_ID_END).toLong()
medtrumPump.handleStopStatusUpdate(stopSequence, stopPatchId)
}

View file

@ -116,8 +116,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
fun startPrime(): Boolean {
val packet = PrimePacket(injector)
return sendPacketAndGetResponse(packet)
return sendPacketAndGetResponse(PrimePacket(injector))
}
fun startActivate(): Boolean {
@ -141,18 +140,19 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
fun readPumpStatus() {
// TODO read pump history
}
// TODO decide what we need to do here
var result = false
fun loadEvents(): PumpEnactResult {
if (!medtrumPlugin.isInitialized()) {
val result = PumpEnactResult(injector).success(false)
result.comment = "pump not initialized"
return result
// Most of these things are already done when a connection is setup, but wo dont know how long the pump was connected for?
// So just do a syncronize to make sure we have the latest data
result = sendPacketAndGetResponse(SynchronizePacket(injector))
// Sync records (based on the info we have from the sync)
if (result) result = syncRecords()
if (!result) {
aapsLogger.error(LTag.PUMPCOMM, "Failed to sync records")
return
}
// TODO need this? Check
val result = PumpEnactResult(injector)
return result
}
fun bolus(insulin: Double, carbs: Int, carbTime: Long, t: EventOverviewBolusProgress.Treatment): Boolean {
@ -165,24 +165,59 @@ class MedtrumService : DaggerService(), BLECommCallback {
// TODO
}
fun setTempBasal(absoluteRate: Double, durationInMinutes: Int): Boolean {
var result = true
if (medtrumPump.tempBasalInProgress) {
result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
}
if (result) result = sendPacketAndGetResponse(SetTempBasalPacket(injector, absoluteRate, durationInMinutes))
// Get history records, this will update the pump state
if (result) result = syncRecords()
return result
}
fun cancelTempBasal(): Boolean {
var result = false
result = sendPacketAndGetResponse(CancelTempBasalPacket(injector))
// Get history records, this will update the pump state
if (result) result = syncRecords()
return result
}
fun updateBasalsInPump(profile: Profile): Boolean {
var result = false
val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
return packet?.let { sendPacketAndGetResponse(it) } == true
result = packet?.let { sendPacketAndGetResponse(it) } == true
// TODO: We might want to get rid of this and cancel the TBR before we set the basal profile
// Update basal affects the TBR records (the pump will cancel the TBR, set our basal profile, and resume the TBR in a new record)
// Get history records, this will update the pump state and add changes in TBR to AAPS history
if (result) result = syncRecords()
return result
}
fun changePump() {
aapsLogger.debug(LTag.PUMP, "changePump: called!")
try {
medtrumPump.pumpSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16)
} catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!")
}
medtrumPump.setPatchActivatedState(sp.getBoolean(R.string.key_patch_activated, false))
medtrumPump.patchSessionToken = sp.getLong(R.string.key_session_token, 0)
if (medtrumPump.patchActivated) {
aapsLogger.debug(LTag.PUMP, "changePump: Patch is already activated, setting as ACTIVE")
medtrumPump.pumpState = MedtrumPumpState.ACTIVE // Set inital status as active will be updated on first connection
medtrumPump.loadUserSettingsFromSP()
}
/** This gets the history records from the pump */
private fun syncRecords(): Boolean {
aapsLogger.debug(LTag.PUMP, "syncRecords: called!, syncedSequenceNumber: ${medtrumPump.syncedSequenceNumber}, currentSequenceNumber: ${medtrumPump.currentSequenceNumber}")
var result = false
// TODO: Check if we need to sync older records as well
// Note: medtrum app fetches all records when they sync?
for (sequence in medtrumPump.syncedSequenceNumber..medtrumPump.currentSequenceNumber) {
result = sendPacketAndGetResponse(GetRecordPacket(injector, sequence))
if (!result) break
}
return result
}
/** BLECommCallbacks */
@ -206,7 +241,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onSendMessageError(reason: String) {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason")
// TODO
currentState.onSendMessageError(reason)
}
/** Service stuff */
@ -270,6 +305,10 @@ class MedtrumService : DaggerService(), BLECommCallback {
open fun waitForResponse(): Boolean {
return false
}
open fun onSendMessageError(reason: String) {
aapsLogger.debug(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason")
}
}
private inner class IdleState : State() {
@ -350,16 +389,18 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onIndication(data: ByteArray) {
if (mPacket?.handleResponse(data) == true) {
// Succes!
val currTimeSec = dateUtil.nowWithoutMilliseconds() / 1000
if (abs(timeUtil.convertPumpTimeToSystemTimeSeconds(medtrumPump.lastTimeReceivedFromPump) - currTimeSec) <= 5) { // Allow 5 sec deviation
val currTime = dateUtil.nowWithoutMilliseconds()
if (abs(medtrumPump.lastTimeReceivedFromPump - currTime) <= T.secs(5).msecs()) { // Allow 5 sec deviation
toState(SynchronizeState())
} else {
aapsLogger.debug(
LTag.PUMPCOMM,
"GetTimeState.onIndication need to set time. systemTime: $currTimeSec PumpTime: ${medtrumPump.lastTimeReceivedFromPump} Pump Time to system time: " + timeUtil.convertPumpTimeToSystemTimeSeconds(
"GetTimeState.onIndication need to set time. systemTime: $currTime PumpTime: ${medtrumPump.lastTimeReceivedFromPump} Pump Time to system time: " + timeUtil
.convertPumpTimeToSystemTimeMillis(
medtrumPump.lastTimeReceivedFromPump
)
)
// TODO: Setting time cancels any TBR, so we need to handle that and cancel? or let AAPS handle time syncs?
toState(SetTimeState())
}
} else if (mPacket?.failed == true) {
@ -497,6 +538,12 @@ class MedtrumService : DaggerService(), BLECommCallback {
responseSuccess = false
}
override fun onSendMessageError(reason: String) {
super.onSendMessageError(reason)
responseHandled = true
responseSuccess = false
}
override fun waitForResponse(): Boolean {
val startTime = System.currentTimeMillis()
val timeoutMillis = T.secs(45).msecs()

View file

@ -46,9 +46,9 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBindi
EventType.ACTIVATION_CLICKED -> requireContext().apply {
val step = convertToPatchStep(medtrumPump.pumpState)
// TODO is stil needed?
if (step != PatchStep.PREPARE_PATCH) {
aapsLogger.warn(LTag.PUMP, "MedtrumOverviewFragment: Patch already in activation process, going to $step")
}
// if (step != PatchStep.PREPARE_PATCH) {
// aapsLogger.warn(LTag.PUMP, "MedtrumOverviewFragment: Patch already in activation process, going to $step")
// }
startActivity(MedtrumActivity.createIntentFromMenu(this, step))
}

View file

@ -173,8 +173,7 @@ class MedtrumViewModel @Inject constructor(
}
// New session, generate new session token
aapsLogger.info(LTag.PUMP, "preparePatch: new session")
medtrumPump.patchSessionToken = Crypt().generateRandomToken()
sp.putLong(R.string.key_session_token, medtrumPump.patchSessionToken)
medtrumPump.patchSessionToken = Crypt().generateRandomToken()
// Connect to pump
medtrumService?.connect("PreparePatch")
}

View file

@ -11,10 +11,16 @@ class MedtrumTimeUtil {
return Duration.between(startInstant, currentInstant).seconds
}
fun convertPumpTimeToSystemTimeSeconds(pumpTime: Long) : Long {
fun getCurrentTimePumpMillis() : Long {
val startInstant = Instant.parse("2014-01-01T00:00:00Z")
val currentInstant = Instant.now()
return Duration.between(startInstant, currentInstant).seconds * 1000
}
fun convertPumpTimeToSystemTimeMillis(pumpTime: Long) : Long {
val startInstant = Instant.parse("2014-01-01T00:00:00Z")
val pumpInstant = startInstant.plusSeconds(pumpTime)
val epochInstant = Instant.EPOCH
return Duration.between(epochInstant, pumpInstant).seconds
return Duration.between(epochInstant, pumpInstant).seconds * 1000
}
}

View file

@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- MedtrumPump -->
<string name="key_snInput" translatable="false">snInput</string>
<string name="key_medtrumpump_settings" translatable="false">medtrumpump_settings</string>
<string name="key_patch_activated" translatable="false">medtrum_patch_activated</string>
<string name="key_session_token" translatable="false">medtrum_session_token</string>
<string name="key_snInput" translatable="false">snInput</string>
<string name="key_medtrumpump_settings" translatable="false">medtrumpump_settings</string>
<string name="key_patch_activated" translatable="false">medtrum_patch_activated</string>
<string name="key_session_token" translatable="false">medtrum_session_token</string>
<string name="key_patch_id" translatable="false">patch_id</string>
<string name="key_actual_basal_profile" translatable="false">actual_basal_profile</string>
<string name="key_last_basal_type" translatable="false">last_basal_type</string>
<string name="key_current_sequence_number" translatable="false">current_sequence_number</string>
<string name="key_synced_sequence_number" translatable="false">synced_sequence_number</string>
<string name="medtrum">Medtrum</string>
<string name="medtrum_pump_shortname">MEDTRUM</string>

View file

@ -4,6 +4,7 @@ import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.TemporaryBasalStorage
import info.nightscout.interfaces.stats.TddCalculator
import org.junit.jupiter.api.BeforeEach
import org.mockito.Mock
@ -14,11 +15,12 @@ open class MedtrumTestBase: TestBaseWithProfile() {
@Mock lateinit var instantiator: Instantiator
@Mock lateinit var tddCalculator: TddCalculator
@Mock lateinit var pumpSync: PumpSync
@Mock lateinit var temporaryBasalStorage: TemporaryBasalStorage
lateinit var medtrumPump: MedtrumPump
@BeforeEach
fun setup() {
medtrumPump = MedtrumPump(aapsLogger, sp, dateUtil)
medtrumPump = MedtrumPump(aapsLogger, sp, dateUtil, pumpSync, temporaryBasalStorage)
}
}

View file

@ -2,6 +2,7 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.MedtrumTestBase
import info.nightscout.pump.medtrum.extension.toByteArray
import org.junit.jupiter.api.Test
@ -23,7 +24,9 @@ class AuthorizePacketTest : MedtrumTestBase() {
@Test fun getRequestGivenPacketAndSNWhenCalledThenReturnAuthorizePacket() {
// Inputs
val opCode = 5
medtrumPump.pumpSN = 2859923929
val _pumpSN = MedtrumPump::class.java.getDeclaredField("_pumpSN")
_pumpSN.isAccessible = true
_pumpSN.setLong(medtrumPump, 2859923929)
medtrumPump.patchSessionToken = 667
// Call

View file

@ -48,7 +48,7 @@ class GetTimePacketTest : MedtrumTestBase() {
// Expected values
assertEquals(true, result)
assertEquals(false, packet.failed)
assertEquals(MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(time), medtrumPump.lastTimeReceivedFromPump)
assertEquals(MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(time), medtrumPump.lastTimeReceivedFromPump)
}
@Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {