Merge pull request #2736 from jbr7rr/medtrum-300U

Medtrum: Support for 300U and fixes
This commit is contained in:
Milos Kozak 2023-09-04 22:15:29 +02:00 committed by GitHub
commit d5c7489ecb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 364 additions and 242 deletions

View file

@ -410,10 +410,33 @@ enum class PumpType {
baseBasalSpecialSteps = null,
pumpCapability = PumpCapability.MedtrumCapabilities,
isPatchPump = true,
maxReservoirReading = 400,
maxReservoirReading = 200,
source = Source.Medtrum
),
MEDTRUM_UNTESTED(description = "Medtrum untested", model = "untested", parent = MEDTRUM_NANO);
MEDTRUM_300U(
description = "Medtrum 300U",
manufacturer = ManufacturerType.Medtrum,
model = "300U",
bolusSize = 0.05,
specialBolusSize = null,
extendedBolusSettings = DoseSettings(0.05, 30, 8 * 60, 0.05, 30.0),
pumpTempBasalType = PumpTempBasalType.Absolute,
tbrSettings = DoseSettings(0.05, 30, 12 * 60, 0.0, 30.0),
specialBasalDurations = PumpCapability.BasalRate_Duration30minAllowed,
baseBasalMinValue = 0.05,
baseBasalMaxValue = 30.0,
baseBasalStep = 0.05,
baseBasalSpecialSteps = null,
pumpCapability = PumpCapability.MedtrumCapabilities,
isPatchPump = true,
maxReservoirReading = 300,
source = Source.Medtrum
),
MEDTRUM_UNTESTED(
description = "Medtrum untested",
model = "untested",
parent = MEDTRUM_NANO
);
val description: String
var manufacturer: ManufacturerType? = null
@ -455,8 +478,10 @@ enum class PumpType {
var hasCustomUnreachableAlertCheck = false
private set
var isPatchPump = false
get() = parent?.isPatchPump ?: field
private set
var maxReservoirReading = 50
get() = parent?.maxReservoirReading ?: field
private set
var supportBatteryLevel = true
private set

View file

@ -60,6 +60,7 @@ fun PumpType.Companion.fromDbPumpType(pt: InterfaceIDs.PumpType): PumpType =
InterfaceIDs.PumpType.DIACONN_G8 -> PumpType.DIACONN_G8
InterfaceIDs.PumpType.EOPATCH2 -> PumpType.EOFLOW_EOPATCH2
InterfaceIDs.PumpType.MEDTRUM -> PumpType.MEDTRUM_NANO
InterfaceIDs.PumpType.MEDTRUM_300U -> PumpType.MEDTRUM_300U
InterfaceIDs.PumpType.MEDTRUM_UNTESTED -> PumpType.MEDTRUM_UNTESTED
InterfaceIDs.PumpType.CACHE -> PumpType.CACHE
}
@ -80,6 +81,7 @@ fun PumpType.Source.toDbSource(): UserEntry.Sources =
PumpType.Source.OmnipodEros -> UserEntry.Sources.OmnipodEros
PumpType.Source.OmnipodDash -> UserEntry.Sources.OmnipodDash
PumpType.Source.EOPatch2 -> UserEntry.Sources.EOPatch2
PumpType.Source.Medtrum -> UserEntry.Sources.Medtrum
PumpType.Source.MDI -> UserEntry.Sources.MDI
PumpType.Source.VirtualPump -> UserEntry.Sources.VirtualPump
else -> UserEntry.Sources.Unknown
@ -120,6 +122,7 @@ fun PumpType.toDbPumpType(): InterfaceIDs.PumpType =
PumpType.DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8
PumpType.EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2
PumpType.MEDTRUM_NANO -> InterfaceIDs.PumpType.MEDTRUM
PumpType.MEDTRUM_300U -> InterfaceIDs.PumpType.MEDTRUM_300U
PumpType.MEDTRUM_UNTESTED -> InterfaceIDs.PumpType.MEDTRUM_UNTESTED
PumpType.CACHE -> InterfaceIDs.PumpType.CACHE
}

View file

@ -35,6 +35,14 @@ class ValidatingEditTextPreference(ctx: Context, attrs: AttributeSet, defStyleAt
holder.isDividerAllowedBelow = false
}
fun setMinNumber(min: Int) {
this.validatorParameters.minNumber = min
}
fun setMaxNumber(max: Int) {
this.validatorParameters.maxNumber = max
}
private fun obtainValidatorParameters(attrs: AttributeSet): DefaultEditTextValidator.Parameters {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FormEditText, 0, 0)
return DefaultEditTextValidator.Parameters(

View file

@ -172,6 +172,7 @@ data class UserEntry(
OmnipodEros,
OmnipodDash, //No entry currently
EOPatch2,
Medtrum,
MDI,
VirtualPump,
SMS, //From SMS plugin

View file

@ -44,6 +44,7 @@ data class InterfaceIDs(
DIACONN_G8,
EOPATCH2,
MEDTRUM,
MEDTRUM_300U,
MEDTRUM_UNTESTED,
USER,
CACHE;

View file

@ -97,6 +97,7 @@ class UserEntryPresentationHelperImpl @Inject constructor(
Sources.OmnipodEros -> R.drawable.ic_patch_pump_outline
Sources.OmnipodDash -> R.drawable.ic_patch_pump_outline
Sources.EOPatch2 -> info.nightscout.core.ui.R.drawable.ic_eopatch2_128
Sources.Medtrum -> info.nightscout.core.ui.R.drawable.ic_medtrum_128
Sources.MDI -> R.drawable.ic_ict
Sources.VirtualPump -> R.drawable.ic_virtual_pump
Sources.SMS -> R.drawable.ic_sms

View file

@ -15,6 +15,7 @@ import dagger.android.HasAndroidInjector
import info.nightscout.core.ui.dialogs.OKDialog
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.core.validators.ValidatingEditTextPreference
import info.nightscout.interfaces.constraints.Constraint
import info.nightscout.interfaces.constraints.Constraints
import info.nightscout.interfaces.notifications.Notification
@ -127,33 +128,41 @@ import kotlin.math.abs
override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) {
super.preprocessPreferences(preferenceFragment)
preprocessSerialSettings(preferenceFragment)
preprocessAlarmSettings(preferenceFragment)
preprocessMaxInsulinSettings(preferenceFragment)
}
private fun preprocessSerialSettings(preferenceFragment: PreferenceFragmentCompat) {
val serialSetting = preferenceFragment.findPreference<EditTextPreference>(rh.gs(R.string.key_sn_input))
serialSetting?.isEnabled = !isInitialized()
serialSetting?.setOnBindEditTextListener { editText ->
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(newValue: Editable?) {
val newSN = newValue.toString().toLongOrNull(radix = 16)
val newDeviceType = MedtrumSnUtil().getDeviceTypeFromSerial(newSN ?: 0)
if (newDeviceType == MedtrumSnUtil.INVALID) {
editText.error = rh.gs(R.string.sn_input_invalid)
} else {
editText.error = null
serialSetting?.apply {
isEnabled = !isInitialized()
setOnBindEditTextListener { editText ->
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(newValue: Editable?) {
val newSN = newValue?.toString()?.toLongOrNull(radix = 16) ?: 0
val newDeviceType = MedtrumSnUtil().getDeviceTypeFromSerial(newSN)
editText.error = if (newDeviceType == MedtrumSnUtil.INVALID) {
rh.gs(R.string.sn_input_invalid)
} else {
null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Nothing to do here
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Nothing to do here
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Nothing to do here
}
})
}
setOnPreferenceChangeListener { _, newValue ->
val newSN = (newValue as? String)?.toLongOrNull(radix = 16) ?: 0
val newDeviceType = MedtrumSnUtil().getDeviceTypeFromSerial(newSN)
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// Nothing to do here
}
})
}
serialSetting?.setOnPreferenceChangeListener { _, newValue ->
if (newValue is String) {
val newSN = newValue.toLongOrNull(radix = 16)
val newDeviceType = MedtrumSnUtil().getDeviceTypeFromSerial(newSN ?: 0)
when {
newDeviceType == MedtrumSnUtil.INVALID -> {
preferenceFragment.activity?.let { activity ->
@ -171,30 +180,61 @@ import kotlin.math.abs
else -> true
}
} else {
false
}
}
}
private fun preprocessAlarmSettings(preferenceFragment: PreferenceFragmentCompat) {
val alarmSetting = preferenceFragment.findPreference<ListPreference>(rh.gs(R.string.key_alarm_setting))
val allAlarmEntries = preferenceFragment.resources.getStringArray(R.array.alarmSettings)
val allAlarmValues = preferenceFragment.resources.getStringArray(R.array.alarmSettingsValues)
if (allAlarmEntries.size < 8 || allAlarmValues.size < 8) {
aapsLogger.error(LTag.PUMP, "Alarm settings array is not complete")
return
} else {
when (medtrumPump.pumpType()) {
PumpType.MEDTRUM_NANO, PumpType.MEDTRUM_300U -> {
alarmSetting?.apply {
entries = arrayOf(allAlarmEntries[6], allAlarmEntries[7]) // "Beep", "Silent"
entryValues = arrayOf(allAlarmValues[6], allAlarmValues[7]) // "6", "7"
}
}
else -> {
// Use default
}
}
}
}
private fun preprocessMaxInsulinSettings(preferenceFragment: PreferenceFragmentCompat) {
val hourlyMaxSetting = preferenceFragment.findPreference<ValidatingEditTextPreference>(rh.gs(R.string.key_hourly_max_insulin))
val dailyMaxSetting = preferenceFragment.findPreference<ValidatingEditTextPreference>(rh.gs(R.string.key_daily_max_insulin))
val hourlyMaxValue = hourlyMaxSetting?.text?.toIntOrNull() ?: 0
val newDailyMaxMinValue = if (hourlyMaxValue > 20) hourlyMaxValue else 20
dailyMaxSetting?.setMinNumber(newDailyMaxMinValue)
val pumpTypeSettings = when (medtrumPump.pumpType()) {
PumpType.MEDTRUM_NANO -> Pair(40, 180) // maxHourlyMax, maxDailyMax
PumpType.MEDTRUM_300U -> Pair(60, 270)
else -> Pair(40, 180)
}
when (medtrumPump.pumpType()) {
PumpType.MEDTRUM_NANO -> {
alarmSetting?.entries = arrayOf(allAlarmEntries[6], allAlarmEntries[7]) // "Beep", "Silent"
alarmSetting?.entryValues = arrayOf(allAlarmValues[6], allAlarmValues[7]) // "6", "7"
hourlyMaxSetting?.apply {
setMaxNumber(pumpTypeSettings.first)
val hourlyCurrentValue = text?.toIntOrNull() ?: 0
if (hourlyCurrentValue > pumpTypeSettings.first) {
text = pumpTypeSettings.first.toString()
}
}
else -> {
// Use Nano settings for unknown pumps
alarmSetting?.entries = arrayOf(allAlarmEntries[6], allAlarmEntries[7]) // "Beep", "Silent"
alarmSetting?.entryValues = arrayOf(allAlarmValues[6], allAlarmValues[7]) // "6", "7"
dailyMaxSetting?.apply {
setMaxNumber(pumpTypeSettings.second)
val dailyCurrentValue = text?.toIntOrNull() ?: 0
when {
dailyCurrentValue < newDailyMaxMinValue -> text = newDailyMaxMinValue.toString()
dailyCurrentValue > pumpTypeSettings.second -> text = pumpTypeSettings.second.toString()
}
}
}
@ -220,6 +260,7 @@ import kotlin.math.abs
override fun isHandshakeInProgress(): Boolean = false
override fun finishHandshaking() {
// Unused
}
override fun connect(reason: String) {
@ -417,6 +458,7 @@ import kotlin.math.abs
try {
extended.put("ActiveProfile", profileName)
} catch (ignored: Exception) {
// Ignore
}
pumpJson.put("status", status)
pumpJson.put("extended", extended)
@ -472,6 +514,7 @@ import kotlin.math.abs
}
override fun executeCustomAction(customActionType: CustomActionType) {
// Unused
}
override fun executeCustomCommand(customCommand: CustomCommand): PumpEnactResult? {

View file

@ -2,6 +2,7 @@ package info.nightscout.pump.medtrum
import android.util.Base64
import info.nightscout.interfaces.profile.Profile
import info.nightscout.interfaces.pump.DetailedBolusInfo
import info.nightscout.interfaces.pump.PumpSync
import info.nightscout.interfaces.pump.TemporaryBasalStorage
import info.nightscout.interfaces.pump.defs.PumpType
@ -294,6 +295,7 @@ class MedtrumPump @Inject constructor(
fun pumpType(type: Int): PumpType =
when (type) {
MedtrumSnUtil.MD_0201, MedtrumSnUtil.MD_8201 -> PumpType.MEDTRUM_NANO
MedtrumSnUtil.MD_8301 -> PumpType.MEDTRUM_300U
else -> PumpType.MEDTRUM_UNTESTED
}
@ -333,7 +335,7 @@ class MedtrumPump @Inject constructor(
val date = GregorianCalendar()
date.timeInMillis = timestamp
val hourOfDayMinutes = date.get(GregorianCalendar.HOUR_OF_DAY) * 60 + date.get(GregorianCalendar.MINUTE)
val hourOfDayMinutes = date[GregorianCalendar.HOUR_OF_DAY] * 60 + date[GregorianCalendar.MINUTE]
for (index in 0 until basalCount) {
val currentIndex = 1 + (index * 3)
@ -354,7 +356,6 @@ class MedtrumPump @Inject constructor(
aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: basal: $basal")
break
}
// aapsLogger.debug(LTag.PUMP, "getHourlyBasalFromMedtrumProfileArray: rate: $rate, startMinutes: $startMinutes, endMinutes: $endMinutes")
}
return basal
}
@ -378,59 +379,67 @@ class MedtrumPump @Inject constructor(
)
@Suppress("UNNECESSARY_SAFE_CALL") // Safe call to allow mocks to return null
val expectedTemporaryBasal = pumpSync.expectedPumpState()?.temporaryBasal
if (basalType.isTempBasal() && expectedTemporaryBasal?.pumpId != basalStartTime) {
// Note: temporaryBasalInfo will be removed from temporaryBasalStorage after this call
val temporaryBasalInfo = temporaryBasalStorage.findTemporaryBasal(basalStartTime, basalRate)
when {
basalType.isTempBasal() && expectedTemporaryBasal?.pumpId != basalStartTime -> {
// Note: temporaryBasalInfo will be removed from temporaryBasalStorage after this call
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(FAKE_TBR_LENGTH).msecs()
val adjustedBasalRate = if (basalType == BasalType.ABSOLUTE_TEMP) {
basalRate
} else {
(basalRate / baseBasalRate) * 100 // calculate the percentage of the original basal rate
}
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = adjustedBasalRate,
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} temporaryBasalInfo: $temporaryBasalInfo, expectedTemporaryBasal: $expectedTemporaryBasal"
)
} else if (basalType.isSuspendedByPump() && expectedTemporaryBasal?.pumpId != basalStartTime) {
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = 0.0,
duration = T.mins(FAKE_TBR_LENGTH).msecs(),
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_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) expectedTemporaryBasal: $expectedTemporaryBasal"
)
} else if (basalType == BasalType.NONE && expectedTemporaryBasal?.rate != basalRate && expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs()) {
// Pump suspended, set fake TBR
setFakeTBR()
} else if (basalType == BasalType.STANDARD) {
if (expectedTemporaryBasal != null) {
// Pump resumed, sync end
val success = pumpSync.syncStopTemporaryBasalWithPumpId(
timestamp = basalStartTime + 250, // Time of normal basal start = time of tbr end
endPumpId = basalStartTime + 250, // +250ms Make sure there is time between start and stop of TBR
// If duration is unknown, no way to get it now, set patch lifetime as duration
val duration = temporaryBasalInfo?.duration ?: T.mins(FAKE_TBR_LENGTH).msecs()
val adjustedBasalRate = if (basalType == BasalType.ABSOLUTE_TEMP) {
basalRate
} else {
(basalRate / baseBasalRate) * 100 // calculate the percentage of the original basal rate
}
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = adjustedBasalRate,
duration = duration,
isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP),
type = temporaryBasalInfo?.type,
pumpId = basalStartTime,
pumpType = pumpType(),
pumpSerial = pumpSN.toString(radix = 16)
)
aapsLogger.debug(LTag.PUMPCOMM, "handleBasalStatusUpdate: EVENT TEMP_END ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) success: $success")
aapsLogger.debug(
LTag.PUMPCOMM,
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + "Rate: $basalRate Duration: ${duration} temporaryBasalInfo: $temporaryBasalInfo, expectedTemporaryBasal: $expectedTemporaryBasal"
)
}
basalType.isSuspendedByPump() && expectedTemporaryBasal?.pumpId != basalStartTime -> {
val newRecord = pumpSync.syncTemporaryBasalWithPumpId(
timestamp = basalStartTime,
rate = 0.0,
duration = T.mins(FAKE_TBR_LENGTH).msecs(),
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_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) expectedTemporaryBasal: $expectedTemporaryBasal"
)
}
basalType == BasalType.NONE && expectedTemporaryBasal?.rate != basalRate && expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs() -> {
// Pump suspended, set fake TBR
setFakeTBR()
}
basalType == BasalType.STANDARD -> {
if (expectedTemporaryBasal != null) {
// Pump resumed, sync end
val success = pumpSync.syncStopTemporaryBasalWithPumpId(
timestamp = basalStartTime + 250, // Time of normal basal start = time of tbr end
endPumpId = basalStartTime + 250, // +250ms Make sure there is time between start and stop of TBR
pumpType = pumpType(),
pumpSerial = pumpSN.toString(radix = 16)
)
aapsLogger.debug(LTag.PUMPCOMM, "handleBasalStatusUpdate: EVENT TEMP_END ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) success: $success")
}
}
}
@ -529,6 +538,26 @@ class MedtrumPump @Inject constructor(
return rh.gs(stringId)
}
fun handleNewPatch(newPatchId: Long, sequenceNumber: Int, newStartTime: Long) {
patchId = newPatchId
patchStartTime = newStartTime
currentSequenceNumber = sequenceNumber // We are activated, set the new seq nr
syncedSequenceNumber = 1 // Always start with 1
// Sync cannula change
pumpSync.insertTherapyEventIfNewWithTimestamp(
timestamp = newStartTime,
type = DetailedBolusInfo.EventType.CANNULA_CHANGE,
pumpType = pumpType(),
pumpSerial = pumpSN.toString(radix = 16)
)
pumpSync.insertTherapyEventIfNewWithTimestamp(
timestamp = newStartTime,
type = DetailedBolusInfo.EventType.INSULIN_CHANGE,
pumpType = pumpType(),
pumpSerial = pumpSN.toString(radix = 16)
)
}
private fun saveActiveAlarms() {
val alarmsStr = activeAlarms.joinToString(separator = ",") { it.name }
sp.putString(R.string.key_active_alarms, alarmsStr)

View file

@ -1,7 +1,6 @@
package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
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
@ -93,24 +92,8 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt
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 ()
// Sync cannula change
pumpSync.insertTherapyEventIfNewWithTimestamp(
timestamp = System.currentTimeMillis(),
type = DetailedBolusInfo.EventType.CANNULA_CHANGE,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
pumpSync.insertTherapyEventIfNewWithTimestamp(
timestamp = System.currentTimeMillis(),
type = DetailedBolusInfo.EventType.INSULIN_CHANGE,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
medtrumPump.handleNewPatch(patchId, basalSequence, System.currentTimeMillis())
// Update the actual basal profile
medtrumPump.actualBasalProfile = basalProfile

View file

@ -111,91 +111,99 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int
"$bolusExtendedDuration, " + "bolusExtendedDelivered: $bolusExtendedDelivered, bolusCarb: $bolusCarb, bolusGlucose: $bolusGlucose, bolusIOB: $bolusIOB, unknown1: $unknown1, unknown2: $unknown2, " + "bolusType: $bolusType, bolusWizard: $bolusWizard"
)
if (bolusType == BolusType.NORMAL) {
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
var newRecord = false
if (detailedBolusInfo != null) {
val syncOk = pumpSync.syncBolusWithTempId(
when (bolusType) {
BolusType.NORMAL -> {
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
var newRecord = false
if (detailedBolusInfo != null) {
val syncOk = pumpSync.syncBolusWithTempId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
temporaryId = detailedBolusInfo.timestamp,
type = detailedBolusInfo.bolusType,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
if (!syncOk) {
aapsLogger.warn(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Failed to sync bolus with tempId: ${detailedBolusInfo.timestamp}")
// detailedInfo can be from another similar record. Reinsert
detailedBolusInfoStorage.add(detailedBolusInfo)
}
} else {
newRecord = pumpSync.syncBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
type = null,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
}
aapsLogger.debug(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
)
if (bolusStartTime > medtrumPump.lastBolusTime) {
medtrumPump.lastBolusTime = bolusStartTime
medtrumPump.lastBolusAmount = bolusNormalDelivered
}
}
BolusType.EXTENDED -> {
val newRecord = pumpSync.syncExtendedBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
temporaryId = detailedBolusInfo.timestamp,
type = detailedBolusInfo.bolusType,
amount = bolusExtendedDelivered,
duration = bolusExtendedDuration,
isEmulatingTB = false,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
if (!syncOk) {
aapsLogger.warn(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Failed to sync bolus with tempId: ${detailedBolusInfo.timestamp}")
// detailedInfo can be from another similar record. Reinsert
detailedBolusInfoStorage.add(detailedBolusInfo)
}
} else {
newRecord = pumpSync.syncBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
type = null,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
aapsLogger.debug(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT EXTENDED BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
)
}
aapsLogger.debug(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
)
if (bolusStartTime > medtrumPump.lastBolusTime) {
medtrumPump.lastBolusTime = bolusStartTime
medtrumPump.lastBolusAmount = bolusNormalDelivered
BolusType.COMBI -> {
// Note, this should never happen, as we don't use combo bolus
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
val newRecord = pumpSync.syncBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
type = detailedBolusInfo?.bolusType,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
pumpSync.syncExtendedBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusExtendedDelivered,
duration = bolusExtendedDuration,
isEmulatingTB = false,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.error(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT COMBI BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U Extended: ${bolusExtendedDelivered} THIS SHOULD NOT HAPPEN!!!"
)
if (!newRecord && detailedBolusInfo != null) {
// detailedInfo can be from another similar record. Reinsert
detailedBolusInfoStorage.add(detailedBolusInfo)
}
if (bolusStartTime > medtrumPump.lastBolusTime) {
medtrumPump.lastBolusTime = bolusStartTime
medtrumPump.lastBolusAmount = bolusNormalDelivered
}
}
} else if (bolusType == BolusType.EXTENDED) {
val newRecord = pumpSync.syncExtendedBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusExtendedDelivered,
duration = bolusExtendedDuration,
isEmulatingTB = false,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.debug(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT EXTENDED BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U "
)
} else if (bolusType == BolusType.COMBI) {
// Note, this should never happen, as we don't use combo bolus
val detailedBolusInfo = detailedBolusInfoStorage.findDetailedBolusInfo(bolusStartTime, bolusNormalDelivered)
val newRecord = pumpSync.syncBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusNormalDelivered,
type = detailedBolusInfo?.bolusType,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
pumpSync.syncExtendedBolusWithPumpId(
timestamp = bolusStartTime,
amount = bolusExtendedDelivered,
duration = bolusExtendedDuration,
isEmulatingTB = false,
pumpId = bolusStartTime,
pumpType = medtrumPump.pumpType(),
pumpSerial = medtrumPump.pumpSN.toString(radix = 16)
)
aapsLogger.error(
LTag.PUMPCOMM,
"from record: ${if (newRecord) "**NEW** " else ""}EVENT COMBI BOLUS ${dateUtil.dateAndTimeString(bolusStartTime)} ($bolusStartTime) Bolus: ${bolusNormalDelivered}U Extended: ${bolusExtendedDelivered} THIS SHOULD NOT HAPPEN!!!"
)
if (!newRecord && detailedBolusInfo != null) {
// detailedInfo can be from another similar record. Reinsert
detailedBolusInfoStorage.add(detailedBolusInfo)
else -> {
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Unknown bolus type: $bolusType")
}
if (bolusStartTime > medtrumPump.lastBolusTime) {
medtrumPump.lastBolusTime = bolusStartTime
medtrumPump.lastBolusAmount = bolusNormalDelivered
}
} else {
aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD: Unknown bolus type: $bolusType")
}
}

View file

@ -85,6 +85,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
fun handleMaskedMessage(data: ByteArray) {
val fieldMask = data.copyOfRange(0, 2).toInt()
var offset = 2
var newPatchStartTime: Long? = null
aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask")
@ -148,12 +149,12 @@ class NotificationPacket(val injector: HasAndroidInjector) {
if (fieldMask and MASK_START_TIME != 0) {
aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received")
val patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
if (medtrumPump.patchStartTime != patchStartTime) {
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $patchStartTime")
medtrumPump.patchStartTime = patchStartTime
newPatchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
if (medtrumPump.patchStartTime != newPatchStartTime) {
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $newPatchStartTime")
medtrumPump.patchStartTime = newPatchStartTime
}
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: ${patchStartTime}")
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: $newPatchStartTime")
offset += 4
}
@ -176,6 +177,11 @@ class NotificationPacket(val injector: HasAndroidInjector) {
val patchId = data.copyOfRange(offset + 2, offset + 4).toLong()
if (patchId != medtrumPump.patchId) {
aapsLogger.warn(LTag.PUMPCOMM, "handleMaskedMessage: We got wrong patch id!")
if (newPatchStartTime != null) {
// This is a fallback for when the activate packet did not receive the ack but the patch activated anyway
aapsLogger.error(LTag.PUMPCOMM, "handleMaskedMessage: Also Received start time in this packet, registering new patch id: $patchId")
medtrumPump.handleNewPatch(patchId, sequence, newPatchStartTime)
}
}
aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}")
offset += 4

View file

@ -39,11 +39,11 @@ class Crypt {
}
private fun randomGen(input: Long): Long {
val A = 16807
val Q = 127773
val R = 2836
val tmp1 = input / Q
var ret = (input - (tmp1 * Q)) * A - (tmp1 * R)
val a = 16807
val q = 127773
val r = 2836
val tmp1 = input / q
var ret = (input - (tmp1 * q)) * a - (tmp1 * r)
if (ret < 0) {
ret += 2147483647L
}

View file

@ -310,9 +310,7 @@ class BLEComm @Inject internal constructor(
private fun readDescriptor(descriptor: BluetoothGattDescriptor?) {
aapsLogger.debug(LTag.PUMPBTCOMM, "readDescriptor")
if (mBluetoothAdapter == null || mBluetoothGatt == null || descriptor == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
handleNotInitialized()
return
}
mBluetoothGatt?.readDescriptor(descriptor)
@ -323,9 +321,7 @@ class BLEComm @Inject internal constructor(
aapsLogger.debug(LTag.PUMPBTCOMM, "checkDescriptor")
val service = getGattService()
if (mBluetoothAdapter == null || mBluetoothGatt == null || service == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
handleNotInitialized()
return
}
if (descriptor.value.toInt() > 0) {
@ -352,9 +348,7 @@ class BLEComm @Inject internal constructor(
private fun setCharacteristicNotification(characteristic: BluetoothGattCharacteristic?, enabled: Boolean) {
aapsLogger.debug(LTag.PUMPBTCOMM, "setCharacteristicNotification")
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
handleNotInitialized()
return
}
mBluetoothGatt?.setCharacteristicNotification(characteristic, enabled)
@ -366,7 +360,7 @@ class BLEComm @Inject internal constructor(
it.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
mBluetoothGatt?.writeDescriptor(it)
} else {
// Do nothing
}
}
}
@ -417,9 +411,7 @@ class BLEComm @Inject internal constructor(
private fun getGattService(): BluetoothGattService? {
aapsLogger.debug(LTag.PUMPBTCOMM, "getGattService")
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
handleNotInitialized()
return null
}
return mBluetoothGatt?.getService(UUID.fromString(SERVICE_UUID))
@ -431,9 +423,7 @@ class BLEComm @Inject internal constructor(
private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, data: ByteArray?) {
handler.postDelayed({
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
handleNotInitialized()
} else {
characteristic.value = data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
@ -456,7 +446,7 @@ class BLEComm @Inject internal constructor(
var uuid: String
val gattCharacteristics = gattService.characteristics
for (i in 0 until gattCharacteristics.size) {
val gattCharacteristic = gattCharacteristics.get(i)
val gattCharacteristic = gattCharacteristics[i]
// Check whether read or write properties is set, the pump needs us to enable notifications on all characteristics that have these properties
if (gattCharacteristic.properties and NEEDS_ENABLE > 0) {
handler.postDelayed({
@ -472,4 +462,11 @@ class BLEComm @Inject internal constructor(
}
}
}
private fun handleNotInitialized() {
aapsLogger.error("BluetoothAdapter not initialized_ERROR")
isConnecting = false
isConnected = false
return
}
}

View file

@ -135,20 +135,13 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}, fabricPrivacy::logException)
scope.launch {
medtrumPump.pumpStateFlow.collect { state ->
handlePumpStateUpdate(state)
medtrumPump.pumpStateFlow.collect { pumpState ->
handlePumpStateUpdate(pumpState)
}
}
scope.launch {
medtrumPump.connectionStateFlow.collect { state ->
if (medtrumPlugin.isInitialized()) {
when (state) {
ConnectionState.CONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED))
ConnectionState.DISCONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
ConnectionState.CONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING))
ConnectionState.DISCONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING))
}
}
medtrumPump.connectionStateFlow.collect { connectionState ->
handleConnectionStateChange(connectionState)
}
}
@ -163,23 +156,29 @@ class MedtrumService : DaggerService(), BLECommCallback {
fun connect(from: String): Boolean {
aapsLogger.debug(LTag.PUMP, "connect: called from: $from")
if (currentState is IdleState) {
medtrumPump.connectionState = ConnectionState.CONNECTING
return bleComm.connect(from, medtrumPump.pumpSN)
} else if (currentState is ReadyState) {
aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in ReadyState from: $from")
if (isConnected) {
aapsLogger.debug(LTag.PUMP, "connect: already connected")
return true
} else {
aapsLogger.debug(LTag.PUMP, "connect: not connected, resetting state and trying to connect")
toState(IdleState())
when (currentState) {
is IdleState -> {
medtrumPump.connectionState = ConnectionState.CONNECTING
return bleComm.connect(from, medtrumPump.pumpSN)
}
} else {
aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in state: $currentState from: $from")
return false
is ReadyState -> {
aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in ReadyState from: $from")
return if (isConnected) {
aapsLogger.debug(LTag.PUMP, "connect: already connected")
true
} else {
aapsLogger.debug(LTag.PUMP, "connect: not connected, resetting state and trying to connect")
toState(IdleState())
medtrumPump.connectionState = ConnectionState.CONNECTING
bleComm.connect(from, medtrumPump.pumpSN)
}
}
else -> {
aapsLogger.error(LTag.PUMPCOMM, "Connect attempt when in state: $currentState from: $from")
return false
}
}
}
@ -247,8 +246,8 @@ class MedtrumService : DaggerService(), BLECommCallback {
timeUpdateNotification(result)
}
// Do this here, because TBR can be cancelled due to time change by connect flow
if (needLoadHistory) {
if (result) result = loadEvents()
if (needLoadHistory && result) {
result = loadEvents()
}
if (result) medtrumPump.needCheckTimeUpdate = false
return result
@ -606,6 +605,17 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
private fun handleConnectionStateChange(connectionState: ConnectionState) {
if (medtrumPlugin.isInitialized()) {
when (connectionState) {
ConnectionState.CONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED))
ConnectionState.DISCONNECTED -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED))
ConnectionState.CONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING))
ConnectionState.DISCONNECTING -> rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING))
}
}
}
/** BLECommCallbacks */
override fun onBLEConnected() {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onBLEConnected")
@ -676,7 +686,10 @@ class MedtrumService : DaggerService(), BLECommCallback {
protected var responseSuccess = false
protected var sendRetryCounter = 0
open fun onEnter() {}
open fun onEnter() {
// Used when a state is entered
}
open fun onIndication(data: ByteArray) {
aapsLogger.warn(LTag.PUMPCOMM, "onIndication: " + this.toString() + "Should not be called here!")
}

View file

@ -7,6 +7,7 @@ import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
import info.nightscout.pump.medtrum.ui.event.UIEvent
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.interfaces.queue.CommandQueue
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.ConnectionState
@ -28,6 +29,7 @@ class MedtrumOverviewViewModel @Inject constructor(
private val profileFunction: ProfileFunction,
private val commandQueue: CommandQueue,
private val dateUtil: DateUtil,
private val medtrumPlugin: MedtrumPlugin,
val medtrumPump: MedtrumPump
) : BaseViewModel<MedtrumBaseNavigator>() {
@ -128,7 +130,7 @@ class MedtrumOverviewViewModel @Inject constructor(
scope.launch {
medtrumPump.bolusAmountDeliveredFlow.collect { bolusAmount ->
aapsLogger.debug(LTag.PUMP, "MedtrumViewModel bolusAmountDeliveredFlow: $bolusAmount")
if (!medtrumPump.bolusDone) {
if (!medtrumPump.bolusDone && medtrumPlugin.isInitialized()) {
_activeBolusStatus.postValue(
dateUtil.timeString(medtrumPump.bolusStartTime) + " " + dateUtil.sinceString(medtrumPump.bolusStartTime, rh)
+ " " + rh.gs(info.nightscout.interfaces.R.string.format_insulin_units, bolusAmount) + " / " + rh.gs(
@ -145,6 +147,8 @@ class MedtrumOverviewViewModel @Inject constructor(
kotlinx.coroutines.delay(T.mins(1).msecs())
}
}
// Update gui on init
updateGUI()
}
override fun onCleared() {
@ -189,7 +193,7 @@ class MedtrumOverviewViewModel @Inject constructor(
)
else _lastBolus.postValue("")
}
if (medtrumPump.bolusDone) {
if (medtrumPump.bolusDone || !medtrumPlugin.isInitialized()) {
_activeBolusStatus.postValue("")
}

View file

@ -80,6 +80,7 @@ class MedtrumViewModel @Inject constructor(
}
ConnectionState.CONNECTING, ConnectionState.DISCONNECTING -> {
// Do nothing
}
}
}

View file

@ -16,6 +16,7 @@ internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
@Singleton
class ViewModelFactory @Inject constructor(private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
@ -26,9 +27,7 @@ class ViewModelFactory @Inject constructor(private val creators: Map<Class<out V
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class $modelClass")
}
requireNotNull(creator) { "unknown model class $modelClass" }
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T

View file

@ -25,7 +25,7 @@
<string name="medtrum">Medtrum</string>
<string name="medtrum_pump_shortname">MT</string>
<string name="medtrum_pump_description">Pump integration for Medtrum Nano</string>
<string name="medtrum_pump_description">Pump integration for Medtrum Nano and Medtrum 300U</string>
<string name="medtrum_pump_setting">Medtrum pump settings</string>
<string name="pump_error">Pump error: %1$s !! </string>
<string name="pump_is_suspended">Pump is suspended</string>

View file

@ -44,7 +44,7 @@
android:title="@string/daily_max_insulin_title"
android:dialogMessage="@string/daily_max_insulin_summary"
validate:maxNumber="180"
validate:minNumber="25"
validate:minNumber="20"
validate:testType="numericRange" />
</PreferenceCategory>