From aa4e75c26484d51bbc16cb9f8744e62123232c86 Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Mon, 9 Oct 2023 19:09:30 +0200 Subject: [PATCH 1/4] Medtrum: Refactor Notification packet; Reduce cognitive complexity add check for size of maskedMessage --- .../comm/packets/NotificationPacket.kt | 384 +++++++++++------- .../medtrum/comm/packets/SynchronizePacket.kt | 4 +- .../comm/packets/NotificationPacketTest.kt | 13 + 3 files changed, 255 insertions(+), 146 deletions(-) diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt index 32ed8e39d6..1af3b3b6dd 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt @@ -12,6 +12,8 @@ import info.nightscout.pump.medtrum.extension.toLong import info.nightscout.pump.medtrum.util.MedtrumTimeUtil import javax.inject.Inject +typealias MaskHandler = (ByteArray, Int) -> Int + class NotificationPacket(val injector: HasAndroidInjector) { /** @@ -53,14 +55,53 @@ class NotificationPacket(val injector: HasAndroidInjector) { private const val MASK_STORAGE = 0x100 private const val MASK_ALARM = 0x200 private const val MASK_AGE = 0x400 - private const val MASK_UNKNOWN_1 = 0x800 + private const val MASK_MAGNETO_PLACE = 0x800 private const val MASK_UNUSED_CGM = 0x1000 private const val MASK_UNUSED_COMMAND_CONFIRM = 0x2000 private const val MASK_UNUSED_AUTO_STATUS = 0x4000 private const val MASK_UNUSED_LEGACY = 0x8000 + + private const val SIZE_FIELD_MASK = 2 + private const val SIZE_SUSPEND = 4 + private const val SIZE_NORMAL_BOLUS = 3 + private const val SIZE_EXTENDED_BOLUS = 3 + private const val SIZE_BASAL = 12 + private const val SIZE_SETUP = 1 + private const val SIZE_RESERVOIR = 2 + private const val SIZE_START_TIME = 4 + private const val SIZE_BATTERY = 3 + private const val SIZE_STORAGE = 4 + private const val SIZE_ALARM = 4 + private const val SIZE_AGE = 4 + private const val SIZE_MAGNETO_PLACE = 2 + private const val SIZE_UNUSED_CGM = 5 + private const val SIZE_UNUSED_COMMAND_CONFIRM = 2 + private const val SIZE_UNUSED_AUTO_STATUS = 2 + private const val SIZE_UNUSED_LEGACY = 2 } + val maskHandlers: Map = mapOf( + MASK_SUSPEND to ::handleSuspend, + MASK_NORMAL_BOLUS to ::handleNormalBolus, + MASK_EXTENDED_BOLUS to ::handleExtendedBolus, + MASK_BASAL to ::handleBasal, + MASK_SETUP to ::handleSetup, + MASK_RESERVOIR to ::handleReservoir, + MASK_START_TIME to ::handleStartTime, + MASK_BATTERY to ::handleBattery, + MASK_STORAGE to ::handleStorage, + MASK_ALARM to ::handleAlarm, + MASK_AGE to ::handleAge, + MASK_MAGNETO_PLACE to ::handleUnknown1, + MASK_UNUSED_CGM to ::handleUnusedCGM, + MASK_UNUSED_COMMAND_CONFIRM to ::handleUnusedCommandConfirm, + MASK_UNUSED_AUTO_STATUS to ::handleUnusedAutoStatus, + MASK_UNUSED_LEGACY to ::handleUnusedLegacy + ) + + var newPatchStartTime = 0L + init { injector.androidInjector().inject(this) } @@ -74,7 +115,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { medtrumPump.pumpState = state } - if (notification.size > NOTIF_STATE_END) { + if (notification.size > NOTIF_STATE_END + SIZE_FIELD_MASK) { handleMaskedMessage(notification.copyOfRange(NOTIF_STATE_END, notification.size)) } } @@ -82,164 +123,219 @@ class NotificationPacket(val injector: HasAndroidInjector) { /** * Handle a message with a field mask, can be used by other packets as well */ - fun handleMaskedMessage(data: ByteArray) { + fun handleMaskedMessage(data: ByteArray): Boolean { val fieldMask = data.copyOfRange(0, 2).toInt() var offset = 2 - var newPatchStartTime: Long? = null - + + val expectedLength = calculateExpectedLengthBasedOnFieldMask(fieldMask) + if (data.size < expectedLength) { + aapsLogger.error(LTag.PUMPCOMM, "Incorrect message length. Expected at least $expectedLength bytes.") + return false + } + aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask") - - if (fieldMask and MASK_SUSPEND != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received") - medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) - aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}") - offset += 4 - } - - if (fieldMask and MASK_NORMAL_BOLUS != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") - val bolusData = data.copyOfRange(offset, offset + 1).toInt() - val bolusType = bolusData and 0x7F - val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0 - val bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05 - aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered") - medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered) - offset += 3 - } - - if (fieldMask and MASK_EXTENDED_BOLUS != 0) { - aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!") - offset += 3 - } - - if (fieldMask and MASK_BASAL != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received") - val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()] - val basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt() - val basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong() - val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong()) - val basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt() - val basalRate = (basalRateAndDelivery and 0xFFF) * 0.05 - val basalDelivery = (basalRateAndDelivery shr 12) * 0.05 - aapsLogger.debug( - LTag.PUMPCOMM, - "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalStartTime, basal rate: $basalRate, basal delivery: $basalDelivery" - ) - // Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set - if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) { - medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime) + + for ((mask, handler) in maskHandlers) { + if (fieldMask and mask != 0) { + offset = handler(data, offset) } - offset += 12 } + + return true + } - if (fieldMask and MASK_SETUP != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received") - medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt() - aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}") - offset += 1 - } - - if (fieldMask and MASK_RESERVOIR != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received") - medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05 - aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}") - offset += 2 - } - - if (fieldMask and MASK_START_TIME != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") - 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 + private fun calculateExpectedLengthBasedOnFieldMask(fieldMask: Int): Int { + var expectedLength = SIZE_FIELD_MASK + + val sizeMap = mapOf( + MASK_SUSPEND to SIZE_SUSPEND, + MASK_NORMAL_BOLUS to SIZE_NORMAL_BOLUS, + MASK_EXTENDED_BOLUS to SIZE_EXTENDED_BOLUS, + MASK_BASAL to SIZE_BASAL, + MASK_SETUP to SIZE_SETUP, + MASK_RESERVOIR to SIZE_RESERVOIR, + MASK_START_TIME to SIZE_START_TIME, + MASK_BATTERY to SIZE_BATTERY, + MASK_STORAGE to SIZE_STORAGE, + MASK_ALARM to SIZE_ALARM, + MASK_AGE to SIZE_AGE, + MASK_MAGNETO_PLACE to SIZE_MAGNETO_PLACE, + MASK_UNUSED_CGM to SIZE_UNUSED_CGM, + MASK_UNUSED_COMMAND_CONFIRM to SIZE_UNUSED_COMMAND_CONFIRM, + MASK_UNUSED_AUTO_STATUS to SIZE_UNUSED_AUTO_STATUS, + MASK_UNUSED_LEGACY to SIZE_UNUSED_LEGACY + ) + + for ((mask, size) in sizeMap) { + if (fieldMask and mask != 0) { + expectedLength += size } - aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: $newPatchStartTime") - offset += 4 } + + return expectedLength + } - if (fieldMask and MASK_BATTERY != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received") - val parameter = data.copyOfRange(offset, offset + 3).toInt() - // Precision for voltage A is a guess, voltage B is the important one, threshold: < 2.64 - medtrumPump.batteryVoltage_A = (parameter and 0xFFF) / 512.0 - medtrumPump.batteryVoltage_B = (parameter shr 12) / 512.0 - aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}") - offset += 3 + private fun handleSuspend(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received") + medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) + aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}") + return offset + SIZE_SUSPEND + } + + private fun handleNormalBolus(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") + val bolusData = data.copyOfRange(offset, offset + 1).toInt() + val bolusType = bolusData and 0x7F + val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0 + val bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05 + aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered") + medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered) + return offset + SIZE_NORMAL_BOLUS + } + + private fun handleExtendedBolus(data: ByteArray, offset: Int): Int { + aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!") + aapsLogger.debug(LTag.PUMPCOMM, "Extended bolus data: ${data.copyOfRange(offset, offset + SIZE_EXTENDED_BOLUS).toLong()}") + return offset + SIZE_EXTENDED_BOLUS + } + + private fun handleBasal(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received") + val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()] + val basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt() + val basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong() + val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong()) + val basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt() + val basalRate = (basalRateAndDelivery and 0xFFF) * 0.05 + val basalDelivery = (basalRateAndDelivery shr 12) * 0.05 + aapsLogger.debug( + LTag.PUMPCOMM, + "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalStartTime, basal rate: $basalRate, basal delivery: $basalDelivery" + ) + // Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set + if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) { + medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime) } - - if (fieldMask and MASK_STORAGE != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") - val sequence = data.copyOfRange(offset, offset + 2).toInt() - if (sequence > medtrumPump.currentSequenceNumber) { - medtrumPump.currentSequenceNumber = sequence + return offset + SIZE_BASAL + } + + private fun handleSetup(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received") + medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt() + aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}") + return offset + SIZE_SETUP + } + + private fun handleReservoir(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received") + medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05 + aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}") + return offset + SIZE_RESERVOIR + } + + private fun handleStartTime(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") + 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: $newPatchStartTime") + return offset + SIZE_START_TIME + } + + private fun handleBattery(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received") + val parameter = data.copyOfRange(offset, offset + 3).toInt() + // Precision for voltage A is a guess, voltage B is the important one, threshold: < 2.64 + medtrumPump.batteryVoltage_A = (parameter and 0xFFF) / 512.0 + medtrumPump.batteryVoltage_B = (parameter shr 12) / 512.0 + aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}") + return offset + SIZE_BATTERY + } + + private fun handleStorage(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") + 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.warn(LTag.PUMPCOMM, "handleMaskedMessage: We got wrong patch id!") + if (newPatchStartTime != 0L) { + // 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) } - 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 } + aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}") + return offset + SIZE_STORAGE + } + + private fun handleAlarm(data: ByteArray, offset: Int): Int { + val alarmFlags = data.copyOfRange(offset, offset + 2).toInt() + val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() + aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter") - if (fieldMask and MASK_ALARM != 0) { - val alarmFlags = data.copyOfRange(offset, offset + 2).toInt() - val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() - aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter") - - // If no alarm, clear activeAlarm list - if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) { - medtrumPump.clearAlarmState() - } else if (alarmFlags != 0) { - // Check each alarm bit - for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state - val alarmState = AlarmState.values()[i] - if ((alarmFlags shr i) and 1 != 0) { - // If the alarm bit is set, add the corresponding alarm to activeAlarms - if (!medtrumPump.activeAlarms.contains(alarmState)) { - aapsLogger.debug(LTag.PUMPCOMM, "Adding alarm $alarmState to active alarms") - medtrumPump.addAlarm(alarmState) - medtrumPump.pumpWarning = alarmState - } - } else if (medtrumPump.activeAlarms.contains(alarmState)) { - // If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it - medtrumPump.removeAlarm(alarmState) + // If no alarm, clear activeAlarm list + if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) { + medtrumPump.clearAlarmState() + } else if (alarmFlags != 0) { + // Check each alarm bit + for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state + val alarmState = AlarmState.values()[i] + if ((alarmFlags shr i) and 1 != 0) { + // If the alarm bit is set, add the corresponding alarm to activeAlarms + if (!medtrumPump.activeAlarms.contains(alarmState)) { + aapsLogger.debug(LTag.PUMPCOMM, "Adding alarm $alarmState to active alarms") + medtrumPump.addAlarm(alarmState) + medtrumPump.pumpWarning = alarmState } + } else if (medtrumPump.activeAlarms.contains(alarmState)) { + // If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it + medtrumPump.removeAlarm(alarmState) } } - offset += 4 - } - - if (fieldMask and MASK_AGE != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Age notification received") - medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong() - aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}") - offset += 4 - } - - if (fieldMask and MASK_UNKNOWN_1 != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unknown 1 notification received, not handled!") - } - - if (fieldMask and MASK_UNUSED_CGM != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!") - } - - if (fieldMask and MASK_UNUSED_COMMAND_CONFIRM != 0) { - // This one is a warning, as this happens we need to know about it, and maybe implement - aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!") - } - - if (fieldMask and MASK_UNUSED_AUTO_STATUS != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!") - } - - if (fieldMask and MASK_UNUSED_LEGACY != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") } + return offset + SIZE_ALARM + } + + private fun handleAge(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Age notification received") + medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong() + aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}") + return offset + SIZE_AGE + } + + private fun handleUnknown1(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Magneto placement notification received!") + val magnetoPlacement = data.copyOfRange(offset, offset + 2).toInt() + aapsLogger.debug(LTag.PUMPCOMM, "Magneto placement: $magnetoPlacement") + return offset + SIZE_MAGNETO_PLACE + } + + private fun handleUnusedCGM(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_CGM).toLong()}") + return offset + SIZE_UNUSED_CGM + } + + private fun handleUnusedCommandConfirm(data: ByteArray, offset: Int): Int { + aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused command confirm data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_COMMAND_CONFIRM).toLong()}") + return offset + SIZE_UNUSED_COMMAND_CONFIRM + } + + private fun handleUnusedAutoStatus(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_AUTO_STATUS).toLong()}") + return offset + SIZE_UNUSED_AUTO_STATUS + } + + private fun handleUnusedLegacy(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_LEGACY).toLong()}") + return offset + SIZE_UNUSED_LEGACY } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt index 97e8d15222..8d35793052 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt @@ -31,7 +31,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) } override fun handleResponse(data: ByteArray): Boolean { - val success = super.handleResponse(data) + var success = super.handleResponse(data) if (success) { val state = MedtrumPumpState.fromByte(data[RESP_STATE_START]) @@ -63,7 +63,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) } // Let the notification packet handle the rest of the sync data - NotificationPacket(injector).handleMaskedMessage(fieldMask.toByteArray(2) + syncData) + success = NotificationPacket(injector).handleMaskedMessage(fieldMask.toByteArray(2) + syncData) } return success diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt index 90664ee302..106d0aa892 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt @@ -87,4 +87,17 @@ class NotificationPacketTest : MedtrumTestBase() { assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(1.65) assertThat(medtrumPump.reservoir).isWithin(0.01).of(161.95) } + + @Test fun handleNotificationGivenFieldMaskButMessageTooShortThenNothingSaved() { + // Inputs + val data = byteArrayOf(67, 41, 67, -1, 122, 95, 18, 0, 73, 1, 19, 0, 1, 0, 20, 0, 0, 0, 0, 16) + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.suspendTime).isEqualTo(0) + assertThat(medtrumPump.lastBasalStartTime).isEqualTo(0) + assertThat(medtrumPump.currentSequenceNumber).isEqualTo(0) + } } From 41afd7cb42ee16fa396b4d4dbeb48b0143c5bac0 Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Mon, 9 Oct 2023 20:08:00 +0200 Subject: [PATCH 2/4] Medtrum: Force enable pump unreachable alarm --- .../nightscout/pump/medtrum/MedtrumPlugin.kt | 22 +++++++++++++++++++ pump/medtrum/src/main/res/values/strings.xml | 2 ++ 2 files changed, 24 insertions(+) diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt index e40762b526..2adb563031 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt @@ -11,6 +11,7 @@ import android.text.format.DateFormat import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference import app.aaps.core.interfaces.constraints.ConstraintsChecker import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.logging.LTag @@ -39,6 +40,7 @@ import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventAppExit import app.aaps.core.interfaces.rx.events.EventDismissNotification import app.aaps.core.interfaces.rx.events.EventOverviewBolusProgress +import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.interfaces.utils.DecimalFormatter @@ -67,6 +69,7 @@ import kotlin.math.abs aapsLogger: AAPSLogger, rh: ResourceHelper, commandQueue: CommandQueue, + private val sp: SP, private val constraintChecker: ConstraintsChecker, private val aapsSchedulers: AapsSchedulers, private val rxBus: RxBus, @@ -102,6 +105,9 @@ import kotlin.math.abs .toObservable(EventAppExit::class.java) .observeOn(aapsSchedulers.io) .subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException) + + // Force enable pump unreachable alert due to some failure modes of Medtrum pump + sp.putBoolean(app.aaps.core.utils.R.string.key_enable_pump_unreachable_alert, true) } override fun onStop() { @@ -134,6 +140,7 @@ import kotlin.math.abs preprocessSerialSettings(preferenceFragment) preprocessAlarmSettings(preferenceFragment) preprocessMaxInsulinSettings(preferenceFragment) + preprocessConnectionAlertSettings(preferenceFragment) } private fun preprocessSerialSettings(preferenceFragment: PreferenceFragmentCompat) { @@ -241,6 +248,21 @@ import kotlin.math.abs } } + private fun preprocessConnectionAlertSettings(preferenceFragment: PreferenceFragmentCompat) { + val unreachableAlertSetting = preferenceFragment.findPreference(rh.gs(app.aaps.core.utils.R.string.key_enable_pump_unreachable_alert)) + val unreachableThresholdSetting = preferenceFragment.findPreference(rh.gs(app.aaps.core.utils.R.string.key_pump_unreachable_threshold_minutes)) + + unreachableAlertSetting?.apply { + isSelectable = false + summary = rh.gs(R.string.enable_pump_unreachable_alert_summary) + } + + unreachableThresholdSetting?.apply { + val currentValue = text + summary = "${rh.gs(R.string.pump_unreachable_threshold_minutes_summary)}\n${currentValue}" + } + } + override fun isInitialized(): Boolean { return medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED } diff --git a/pump/medtrum/src/main/res/values/strings.xml b/pump/medtrum/src/main/res/values/strings.xml index b508b7150d..d4f4b4685f 100644 --- a/pump/medtrum/src/main/res/values/strings.xml +++ b/pump/medtrum/src/main/res/values/strings.xml @@ -134,6 +134,8 @@ Please wait, reading activation status from pump. + Unreachable alert forced enabled, because Medtrum patch can fail and be unreachable. + Advised to set to 30 minutes, because Medtrum patch can fail and be unreachable. Serial Number Enter the serial number of your pump base. Invalid serial number! From 100be23cf4427d2a6f9a0b07f0aa8a9160f08d6d Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Tue, 10 Oct 2023 12:36:12 +0200 Subject: [PATCH 3/4] Medtrum: Improve failure modes; - ReadDataPacket: Add crc check - BLEComm: Differentiate between retryable and non-retryable error - setBolus: Use readStatus to wait for bolus if any --- .../pump/medtrum/comm/ReadDataPacket.kt | 24 +++ .../pump/medtrum/comm/WriteCommandPackets.kt | 18 +- .../comm/packets/NotificationPacket.kt | 48 +++--- .../pump/medtrum/services/BLEComm.kt | 14 +- .../pump/medtrum/services/MedtrumService.kt | 10 +- .../nightscout/pump/medtrum/util/CrcUtil.kt | 13 ++ pump/medtrum/src/main/res/values/strings.xml | 1 + .../pump/medtrum/comm/ReadDataPacketTest.kt | 154 ++++++++++++++++++ 8 files changed, 234 insertions(+), 48 deletions(-) create mode 100644 pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt create mode 100644 pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt index e091cb8f73..9eb2e01e89 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt @@ -1,12 +1,32 @@ package info.nightscout.pump.medtrum.comm +import CrcUtils.calcCrc8 + class ReadDataPacket(data: ByteArray) { private var totalData = data.copyOfRange(0, data.size - 1) // Strip crc + private var failed = false private var dataSize: Byte = data[0] + private var sequenceNumber: Byte = data[3] + + init { + val crcInitialChunk = calcCrc8(data.copyOfRange(0, data.size - 1), data.size - 1) + + if (crcInitialChunk != data[data.size - 1]) { + failed = true + } + } fun addData(newData: ByteArray) { totalData += newData.copyOfRange(4, newData.size - 1) // Strip header and crc + sequenceNumber++ + val crcNewChunk = calcCrc8(newData.copyOfRange(0, newData.size - 1), newData.size - 1) + if (crcNewChunk != newData[newData.size - 1]) { + failed = true + } + if (sequenceNumber != newData[3]) { + failed = true + } } fun allDataReceived(): Boolean { @@ -16,4 +36,8 @@ class ReadDataPacket(data: ByteArray) { fun getData(): ByteArray { return totalData } + + fun failed(): Boolean { + return failed + } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt index 31655f3ed9..5d3974e133 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt @@ -1,8 +1,8 @@ package info.nightscout.pump.medtrum.comm -class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { +import CrcUtils.calcCrc8 - private val CRC_8_TABLE: IntArray = intArrayOf(0, 155, 173, 54, 193, 90, 108, 247, 25, 130, 180, 47, 216, 67, 117, 238, 50, 169, 159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 100, 255, 201, 82, 165, 62, 8, 147, 125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, 35, 184, 200, 83, 101, 254, 9, 146, 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 78, 213, 34, 185, 143, 20, 172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52, 218, 65, 119, 236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, 224, 123) +class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { private val packages = mutableListOf() private var index = 0 @@ -17,7 +17,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { ) var tmp: ByteArray = header + data.copyOfRange(1, data.size) - val totalCommand: ByteArray = tmp + calcCrc8(tmp, tmp.size).toByte() + val totalCommand: ByteArray = tmp + calcCrc8(tmp, tmp.size) if ((totalCommand.size - header.size) <= 15) { packages.add(totalCommand + 0.toByte()) @@ -28,7 +28,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { while (remainingCommand.size > 15) { header[3] = pkgIndex.toByte() tmp = header + remainingCommand.copyOfRange(0, 15) - packages.add(tmp + calcCrc8(tmp, tmp.size).toByte()) + packages.add(tmp + calcCrc8(tmp, tmp.size)) remainingCommand = remainingCommand.copyOfRange(15, remainingCommand.size) pkgIndex = (pkgIndex + 1) % 256 @@ -37,7 +37,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { // Add last package header[3] = pkgIndex.toByte() tmp = header + remainingCommand - packages.add(tmp + calcCrc8(tmp, tmp.size).toByte()) + packages.add(tmp + calcCrc8(tmp, tmp.size)) } } @@ -53,12 +53,4 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { fun allPacketsConsumed(): Boolean { return index >= packages.size } - - private fun calcCrc8(value: ByteArray, size: Int): Int { - var crc8 = 0 - for (i in 0 until size) { - crc8 = CRC_8_TABLE[(value[i].toInt() and 255) xor (crc8 and 255)] and 255 - } - return crc8 - } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt index 1af3b3b6dd..929f574165 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt @@ -12,8 +12,6 @@ import info.nightscout.pump.medtrum.extension.toLong import info.nightscout.pump.medtrum.util.MedtrumTimeUtil import javax.inject.Inject -typealias MaskHandler = (ByteArray, Int) -> Int - class NotificationPacket(val injector: HasAndroidInjector) { /** @@ -81,7 +79,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { private const val SIZE_UNUSED_LEGACY = 2 } - val maskHandlers: Map = mapOf( + val maskHandlers: Map Int> = mapOf( MASK_SUSPEND to ::handleSuspend, MASK_NORMAL_BOLUS to ::handleNormalBolus, MASK_EXTENDED_BOLUS to ::handleExtendedBolus, @@ -126,27 +124,27 @@ class NotificationPacket(val injector: HasAndroidInjector) { fun handleMaskedMessage(data: ByteArray): Boolean { val fieldMask = data.copyOfRange(0, 2).toInt() var offset = 2 - + val expectedLength = calculateExpectedLengthBasedOnFieldMask(fieldMask) if (data.size < expectedLength) { aapsLogger.error(LTag.PUMPCOMM, "Incorrect message length. Expected at least $expectedLength bytes.") return false } - + aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask") - + for ((mask, handler) in maskHandlers) { if (fieldMask and mask != 0) { offset = handler(data, offset) } } - + return true } private fun calculateExpectedLengthBasedOnFieldMask(fieldMask: Int): Int { var expectedLength = SIZE_FIELD_MASK - + val sizeMap = mapOf( MASK_SUSPEND to SIZE_SUSPEND, MASK_NORMAL_BOLUS to SIZE_NORMAL_BOLUS, @@ -165,13 +163,13 @@ class NotificationPacket(val injector: HasAndroidInjector) { MASK_UNUSED_AUTO_STATUS to SIZE_UNUSED_AUTO_STATUS, MASK_UNUSED_LEGACY to SIZE_UNUSED_LEGACY ) - + for ((mask, size) in sizeMap) { if (fieldMask and mask != 0) { expectedLength += size } } - + return expectedLength } @@ -181,7 +179,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}") return offset + SIZE_SUSPEND } - + private fun handleNormalBolus(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") val bolusData = data.copyOfRange(offset, offset + 1).toInt() @@ -192,13 +190,13 @@ class NotificationPacket(val injector: HasAndroidInjector) { medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered) return offset + SIZE_NORMAL_BOLUS } - + private fun handleExtendedBolus(data: ByteArray, offset: Int): Int { aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!") aapsLogger.debug(LTag.PUMPCOMM, "Extended bolus data: ${data.copyOfRange(offset, offset + SIZE_EXTENDED_BOLUS).toLong()}") return offset + SIZE_EXTENDED_BOLUS } - + private fun handleBasal(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received") val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()] @@ -218,21 +216,21 @@ class NotificationPacket(val injector: HasAndroidInjector) { } return offset + SIZE_BASAL } - + private fun handleSetup(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received") medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt() aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}") return offset + SIZE_SETUP } - + private fun handleReservoir(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received") medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05 aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}") return offset + SIZE_RESERVOIR } - + private fun handleStartTime(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") newPatchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) @@ -243,7 +241,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: $newPatchStartTime") return offset + SIZE_START_TIME } - + private fun handleBattery(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received") val parameter = data.copyOfRange(offset, offset + 3).toInt() @@ -253,7 +251,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}") return offset + SIZE_BATTERY } - + private fun handleStorage(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") val sequence = data.copyOfRange(offset, offset + 2).toInt() @@ -272,7 +270,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}") return offset + SIZE_STORAGE } - + private fun handleAlarm(data: ByteArray, offset: Int): Int { val alarmFlags = data.copyOfRange(offset, offset + 2).toInt() val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() @@ -300,39 +298,39 @@ class NotificationPacket(val injector: HasAndroidInjector) { } return offset + SIZE_ALARM } - + private fun handleAge(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Age notification received") medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong() aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}") return offset + SIZE_AGE } - + private fun handleUnknown1(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Magneto placement notification received!") val magnetoPlacement = data.copyOfRange(offset, offset + 2).toInt() aapsLogger.debug(LTag.PUMPCOMM, "Magneto placement: $magnetoPlacement") return offset + SIZE_MAGNETO_PLACE } - + private fun handleUnusedCGM(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!") aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_CGM).toLong()}") return offset + SIZE_UNUSED_CGM } - + private fun handleUnusedCommandConfirm(data: ByteArray, offset: Int): Int { aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!") aapsLogger.debug(LTag.PUMPCOMM, "Unused command confirm data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_COMMAND_CONFIRM).toLong()}") return offset + SIZE_UNUSED_COMMAND_CONFIRM } - + private fun handleUnusedAutoStatus(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!") aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_AUTO_STATUS).toLong()}") return offset + SIZE_UNUSED_AUTO_STATUS } - + private fun handleUnusedLegacy(data: ByteArray, offset: Int): Int { aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_LEGACY).toLong()}") diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt index cc5283b9dd..c2b76b71fa 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt @@ -40,7 +40,7 @@ interface BLECommCallback { fun onBLEDisconnected() fun onNotification(notification: ByteArray) fun onIndication(indication: ByteArray) - fun onSendMessageError(reason: String) + fun onSendMessageError(reason: String, isRetryAble: Boolean) } @Singleton @@ -258,7 +258,11 @@ class BLEComm @Inject internal constructor( mReadPacket?.addData(value) } if (mReadPacket?.allDataReceived() == true) { - mReadPacket?.getData()?.let { mCallback?.onIndication(it) } + if (mReadPacket?.failed() == true) { + mCallback?.onSendMessageError("ReadDataPacket failed", false) + } else { + mReadPacket?.getData()?.let { mCallback?.onIndication(it) } + } mReadPacket = null } } @@ -279,7 +283,7 @@ class BLEComm @Inject internal constructor( } } } else { - mCallback?.onSendMessageError("onCharacteristicWrite failure") + mCallback?.onSendMessageError("onCharacteristicWrite failure", true) } } @@ -404,7 +408,7 @@ class BLEComm @Inject internal constructor( writeCharacteristic(uartWriteBTGattChar, value) } else { aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage error in writePacket!") - mCallback?.onSendMessageError("error in writePacket!") + mCallback?.onSendMessageError("error in writePacket!", false) } } @@ -430,7 +434,7 @@ class BLEComm @Inject internal constructor( aapsLogger.debug(LTag.PUMPBTCOMM, "writeCharacteristic: ${Arrays.toString(data)}") val success = mBluetoothGatt?.writeCharacteristic(characteristic) if (success != true) { - mCallback?.onSendMessageError("Failed to write characteristic") + mCallback?.onSendMessageError("Failed to write characteristic", true) } } }, WRITE_DELAY_MILLIS) diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt index c04f5e9f1d..d3b85463e1 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt @@ -339,7 +339,7 @@ class MedtrumService : DaggerService(), BLECommCallback { if (!sendBolusCommand(insulin)) { aapsLogger.error(LTag.PUMPCOMM, "Failed to set bolus") - commandQueue.loadEvents(null) // make sure if anything is delivered (which is highly unlikely at this point) we get it + commandQueue.readStatus(rh.gs(R.string.bolus_error), null) // make sure if anything is delivered (which is highly unlikely at this point) we get it t.insulin = 0.0 return false } @@ -735,9 +735,9 @@ class MedtrumService : DaggerService(), BLECommCallback { currentState.onIndication(indication) } - override fun onSendMessageError(reason: String) { + override fun onSendMessageError(reason: String, isRetryAble: Boolean) { aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason") - currentState.onSendMessageError(reason) + currentState.onSendMessageError(reason, isRetryAble) } /** Service stuff */ @@ -822,10 +822,10 @@ class MedtrumService : DaggerService(), BLECommCallback { return responseSuccess } - fun onSendMessageError(reason: String) { + fun onSendMessageError(reason: String, isRetryAble: Boolean) { aapsLogger.warn(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason") // Retry 3 times - if (sendRetryCounter < 3) { + if (sendRetryCounter < 3 && isRetryAble) { sendRetryCounter++ mPacket?.getRequest()?.let { bleComm.sendMessage(it) } } else { diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt new file mode 100644 index 0000000000..211f35da72 --- /dev/null +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt @@ -0,0 +1,13 @@ +object CrcUtils { + + private val lookupTable: UByteArray = ubyteArrayOf(0u, 155u, 173u, 54u, 193u, 90u, 108u, 247u, 25u, 130u, 180u, 47u, 216u, 67u, 117u, 238u, 50u, 169u, 159u, 4u, 243u, 104u, 94u, 197u, 43u, 176u, 134u, 29u, 234u, 113u, 71u, 220u, 100u, 255u, 201u, 82u, 165u, 62u, 8u, 147u, 125u, 230u, 208u, 75u, 188u, 39u, 17u, 138u, 86u, 205u, 251u, 96u, 151u, 12u, 58u, 161u, 79u, 212u, 226u, 121u, 142u, 21u, 35u, 184u, 200u, 83u, 101u, 254u, 9u, 146u, 164u, 63u, 209u, 74u, 124u, 231u, 16u, 139u, 189u, 38u, 250u, 97u, 87u, 204u, 59u, 160u, 150u, 13u, 227u, 120u, 78u, 213u, 34u, 185u, 143u, 20u, 172u, 55u, 1u, 154u, 109u, 246u, 192u, 91u, 181u, 46u, 24u, 131u, 116u, 239u, 217u, 66u, 158u, 5u, 51u, 168u, 95u, 196u, 242u, 105u, 135u, 28u, 42u, 177u, 70u, 221u, 235u, 112u, 11u, 144u, 166u, 61u, 202u, 81u, 103u, 252u, 18u, 137u, 191u, 36u, 211u, 72u, 126u, 229u, 57u, 162u, 148u, 15u, 248u, 99u, 85u, 206u, 32u, 187u, 141u, 22u, 225u, 122u, 76u, 215u, 111u, 244u, 194u, 89u, 174u, 53u, 3u, 152u, 118u, 237u, 219u, 64u, 183u, 44u, 26u, 129u, 93u, 198u, 240u, 107u, 156u, 7u, 49u, 170u, 68u, 223u, 233u, 114u, 133u, 30u, 40u, 179u, 195u, 88u, 110u, 245u, 2u, 153u, 175u, 52u, 218u, 65u, 119u, 236u, 27u, 128u, 182u, 45u, 241u, 106u, 92u, 199u, 48u, 171u, 157u, 6u, 232u, 115u, 69u, 222u, 41u, 178u, 132u, 31u, 167u, 60u, 10u, 145u, 102u, 253u, 203u, 80u, 190u, 37u, 19u, 136u, 127u, 228u, 210u, 73u, 149u, 14u, 56u, 163u, 84u, 207u, 249u, 98u, 140u, 23u, 33u, 186u, 77u, 214u, 224u, 123u) + + fun calcCrc8(value: ByteArray, size: Int): Byte { + var crc8: UByte = 0u + for (i in 0 until size) { + val tableIndex: UByte = (value[i].toUByte() xor crc8) + crc8 = lookupTable[tableIndex.toInt()] + } + return crc8.toByte() + } +} diff --git a/pump/medtrum/src/main/res/values/strings.xml b/pump/medtrum/src/main/res/values/strings.xml index d4f4b4685f..01bbe48505 100644 --- a/pump/medtrum/src/main/res/values/strings.xml +++ b/pump/medtrum/src/main/res/values/strings.xml @@ -80,6 +80,7 @@ Battery out No calibration Failed to update pump timezone, snooze message and refresh manually. + Bolus error Retry diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt new file mode 100644 index 0000000000..9b61869cff --- /dev/null +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt @@ -0,0 +1,154 @@ +package info.nightscout.pump.medtrum.comm + +import com.google.common.truth.Truth.assertThat +import org.junit.jupiter.api.Test + +class ReadDataPacketTest { + + @Test + fun givenCorrectBytesExpectPacketNotFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.getData()).isEqualTo( + byteArrayOf( + 51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 0, 0, 0, -80, + -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 19, -82 + ) + ) + assertThat(packet.failed()).isFalse() + } + + @Test + fun givenIncorrectCRCInFirstChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -1, -62, -1, -1, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectCRCInSecondChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -1, 84, 18, 10, 0, 10, -1, -1, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectCRCInThirdChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectCRCInLastChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, -1, -1, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectSequenceExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk3 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenCorrectBytesOneChunkExpectPacketNotFailed() { + // arrange + val chunk1 = byteArrayOf(14, 5, 0, 0, 0, 0, 2, 80, 1, 74, 64, 4, 0, -16, 0) + + // act + val packet = ReadDataPacket(chunk1) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.getData()).isEqualTo(byteArrayOf(14, 5, 0, 0, 0, 0, 2, 80, 1, 74, 64, 4, 0, -16)) + assertThat(packet.failed()).isFalse() + } + + @Test + fun givenIncorrectBytesOneChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(14, 5, 0, -1, -1, -1, 2, 80, 1, 74, 64, 4, 0, -16, 0) + + // act + val packet = ReadDataPacket(chunk1) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } +} From ed5dd463ed9c44b82977f3660673c1cba08d1846 Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Tue, 10 Oct 2023 14:40:37 +0200 Subject: [PATCH 4/4] Medtrum: Fix waiting too long for small bolus status --- .../nightscout/pump/medtrum/services/MedtrumService.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt index d3b85463e1..ae596414e9 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt @@ -336,21 +336,22 @@ class MedtrumService : DaggerService(), BLECommCallback { if (!canSetBolus()) return false val insulin = detailedBolusInfo.insulin + medtrumPump.bolusDone = false + medtrumPump.bolusStopped = false if (!sendBolusCommand(insulin)) { aapsLogger.error(LTag.PUMPCOMM, "Failed to set bolus") commandQueue.readStatus(rh.gs(R.string.bolus_error), null) // make sure if anything is delivered (which is highly unlikely at this point) we get it + medtrumPump.bolusDone = true t.insulin = 0.0 return false } val bolusStart = System.currentTimeMillis() - medtrumPump.bolusDone = false - medtrumPump.bolusingTreatment = t - medtrumPump.bolusAmountToBeDelivered = insulin - medtrumPump.bolusStopped = false medtrumPump.bolusProgressLastTimeStamp = bolusStart medtrumPump.bolusStartTime = bolusStart + medtrumPump.bolusingTreatment = t + medtrumPump.bolusAmountToBeDelivered = insulin detailedBolusInfo.timestamp = bolusStart // Make sure the timestamp is set to the start of the bolus detailedBolusInfoStorage.add(detailedBolusInfo) // will be picked up on reading history