This commit is contained in:
Milos Kozak 2023-10-11 21:10:36 +02:00
commit ae110dcfe4
14 changed files with 485 additions and 166 deletions

View file

@ -25,7 +25,7 @@ buildscript {
preferencektx_version = '1.2.1' preferencektx_version = '1.2.1'
commonslang3_version = '3.13.0' commonslang3_version = '3.13.0'
commonscodec_version = '1.16.0' commonscodec_version = '1.16.0'
guava_version = '32.1.2-jre' guava_version = '32.1.3-jre'
jodatime_version = '2.12.5' jodatime_version = '2.12.5'
work_version = '2.8.1' work_version = '2.8.1'
tink_version = '1.10.0' tink_version = '1.10.0'
@ -80,7 +80,7 @@ buildscript {
plugins { plugins {
// Test Gradle build, keep disabled under normal circumstances // Test Gradle build, keep disabled under normal circumstances
// id "com.osacky.doctor" version "0.8.1" // id "com.osacky.doctor" version "0.8.1"
id "org.jlleitschuh.gradle.ktlint" version "11.6.0" id "org.jlleitschuh.gradle.ktlint" version "11.6.1"
// Aggregates and/or logs Jacoco test coverage to the Gradle build log // Aggregates and/or logs Jacoco test coverage to the Gradle build log
//id 'org.barfuin.gradle.jacocolog' version '3.1.0' //id 'org.barfuin.gradle.jacocolog' version '3.1.0'
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false

View file

@ -155,7 +155,7 @@
<string name="login">Login</string> <string name="login">Login</string>
<string name="prime_fill">Prime/fylling</string> <string name="prime_fill">Prime/fylling</string>
<string name="overview_insulin_label">Insulin</string> <string name="overview_insulin_label">Insulin</string>
<string name="stoptemptarget">Avbryt midlertidig målverdi</string> <string name="stoptemptarget">Avbryt midlertidig mål</string>
<string name="closedloop">Lukket Loop</string> <string name="closedloop">Lukket Loop</string>
<string name="openloop">Åpen Loop</string> <string name="openloop">Åpen Loop</string>
<string name="lowglucosesuspend">Stopp ved lavt BS</string> <string name="lowglucosesuspend">Stopp ved lavt BS</string>

View file

@ -34,6 +34,5 @@
<string name="do_xdrip_upload_summary">I xDrip+, velg 640G/Eversens som datakilde</string> <string name="do_xdrip_upload_summary">I xDrip+, velg 640G/Eversens som datakilde</string>
<string name="bgsource_upload">Innstillinger for opplasting av BS</string> <string name="bgsource_upload">Innstillinger for opplasting av BS</string>
<string name="dexcom_log_ns_sensor_change_title">Logg sensorbytte til NS</string> <string name="dexcom_log_ns_sensor_change_title">Logg sensorbytte til NS</string>
<string name="dexcom_log_ns_sensor_change_summary">Opprett hendelse \"Sensor bytte\" automatisk i NS ved start av sensoren</string>
<string name="direction">retning</string> <string name="direction">retning</string>
</resources> </resources>

View file

@ -11,6 +11,7 @@ import android.text.format.DateFormat
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import app.aaps.core.interfaces.constraints.ConstraintsChecker import app.aaps.core.interfaces.constraints.ConstraintsChecker
import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.logging.AAPSLogger
import app.aaps.core.interfaces.logging.LTag 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.EventAppExit
import app.aaps.core.interfaces.rx.events.EventDismissNotification import app.aaps.core.interfaces.rx.events.EventDismissNotification
import app.aaps.core.interfaces.rx.events.EventOverviewBolusProgress 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.ui.UiInteraction
import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.interfaces.utils.DateUtil
import app.aaps.core.interfaces.utils.DecimalFormatter import app.aaps.core.interfaces.utils.DecimalFormatter
@ -67,6 +69,7 @@ import kotlin.math.abs
aapsLogger: AAPSLogger, aapsLogger: AAPSLogger,
rh: ResourceHelper, rh: ResourceHelper,
commandQueue: CommandQueue, commandQueue: CommandQueue,
private val sp: SP,
private val constraintChecker: ConstraintsChecker, private val constraintChecker: ConstraintsChecker,
private val aapsSchedulers: AapsSchedulers, private val aapsSchedulers: AapsSchedulers,
private val rxBus: RxBus, private val rxBus: RxBus,
@ -102,6 +105,9 @@ import kotlin.math.abs
.toObservable(EventAppExit::class.java) .toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io) .observeOn(aapsSchedulers.io)
.subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException) .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() { override fun onStop() {
@ -134,6 +140,7 @@ import kotlin.math.abs
preprocessSerialSettings(preferenceFragment) preprocessSerialSettings(preferenceFragment)
preprocessAlarmSettings(preferenceFragment) preprocessAlarmSettings(preferenceFragment)
preprocessMaxInsulinSettings(preferenceFragment) preprocessMaxInsulinSettings(preferenceFragment)
preprocessConnectionAlertSettings(preferenceFragment)
} }
private fun preprocessSerialSettings(preferenceFragment: PreferenceFragmentCompat) { private fun preprocessSerialSettings(preferenceFragment: PreferenceFragmentCompat) {
@ -241,6 +248,21 @@ import kotlin.math.abs
} }
} }
private fun preprocessConnectionAlertSettings(preferenceFragment: PreferenceFragmentCompat) {
val unreachableAlertSetting = preferenceFragment.findPreference<SwitchPreference>(rh.gs(app.aaps.core.utils.R.string.key_enable_pump_unreachable_alert))
val unreachableThresholdSetting = preferenceFragment.findPreference<ValidatingEditTextPreference>(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 { override fun isInitialized(): Boolean {
return medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED return medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED
} }

View file

@ -1,12 +1,32 @@
package info.nightscout.pump.medtrum.comm package info.nightscout.pump.medtrum.comm
import CrcUtils.calcCrc8
class ReadDataPacket(data: ByteArray) { class ReadDataPacket(data: ByteArray) {
private var totalData = data.copyOfRange(0, data.size - 1) // Strip crc private var totalData = data.copyOfRange(0, data.size - 1) // Strip crc
private var failed = false
private var dataSize: Byte = data[0] 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) { fun addData(newData: ByteArray) {
totalData += newData.copyOfRange(4, newData.size - 1) // Strip header and crc 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 { fun allDataReceived(): Boolean {
@ -16,4 +36,8 @@ class ReadDataPacket(data: ByteArray) {
fun getData(): ByteArray { fun getData(): ByteArray {
return totalData return totalData
} }
fun failed(): Boolean {
return failed
}
} }

View file

@ -1,8 +1,8 @@
package info.nightscout.pump.medtrum.comm 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<ByteArray>() private val packages = mutableListOf<ByteArray>()
private var index = 0 private var index = 0
@ -17,7 +17,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) {
) )
var tmp: ByteArray = header + data.copyOfRange(1, data.size) 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) { if ((totalCommand.size - header.size) <= 15) {
packages.add(totalCommand + 0.toByte()) packages.add(totalCommand + 0.toByte())
@ -28,7 +28,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) {
while (remainingCommand.size > 15) { while (remainingCommand.size > 15) {
header[3] = pkgIndex.toByte() header[3] = pkgIndex.toByte()
tmp = header + remainingCommand.copyOfRange(0, 15) 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) remainingCommand = remainingCommand.copyOfRange(15, remainingCommand.size)
pkgIndex = (pkgIndex + 1) % 256 pkgIndex = (pkgIndex + 1) % 256
@ -37,7 +37,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) {
// Add last package // Add last package
header[3] = pkgIndex.toByte() header[3] = pkgIndex.toByte()
tmp = header + remainingCommand 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 { fun allPacketsConsumed(): Boolean {
return index >= packages.size 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
}
} }

View file

@ -53,14 +53,53 @@ class NotificationPacket(val injector: HasAndroidInjector) {
private const val MASK_STORAGE = 0x100 private const val MASK_STORAGE = 0x100
private const val MASK_ALARM = 0x200 private const val MASK_ALARM = 0x200
private const val MASK_AGE = 0x400 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_CGM = 0x1000
private const val MASK_UNUSED_COMMAND_CONFIRM = 0x2000 private const val MASK_UNUSED_COMMAND_CONFIRM = 0x2000
private const val MASK_UNUSED_AUTO_STATUS = 0x4000 private const val MASK_UNUSED_AUTO_STATUS = 0x4000
private const val MASK_UNUSED_LEGACY = 0x8000 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<Int, (ByteArray, Int) -> Int> = 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 { init {
injector.androidInjector().inject(this) injector.androidInjector().inject(this)
} }
@ -74,7 +113,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
medtrumPump.pumpState = state 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)) handleMaskedMessage(notification.copyOfRange(NOTIF_STATE_END, notification.size))
} }
} }
@ -82,164 +121,219 @@ class NotificationPacket(val injector: HasAndroidInjector) {
/** /**
* Handle a message with a field mask, can be used by other packets as well * 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() val fieldMask = data.copyOfRange(0, 2).toInt()
var offset = 2 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") aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask")
if (fieldMask and MASK_SUSPEND != 0) { for ((mask, handler) in maskHandlers) {
aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received") if (fieldMask and mask != 0) {
medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) offset = handler(data, offset)
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<BasalType>()[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)
} }
offset += 12
} }
if (fieldMask and MASK_SETUP != 0) { return true
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) { private fun calculateExpectedLengthBasedOnFieldMask(fieldMask: Int): Int {
aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received") var expectedLength = SIZE_FIELD_MASK
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) { val sizeMap = mapOf(
aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") MASK_SUSPEND to SIZE_SUSPEND,
newPatchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) MASK_NORMAL_BOLUS to SIZE_NORMAL_BOLUS,
if (medtrumPump.patchStartTime != newPatchStartTime) { MASK_EXTENDED_BOLUS to SIZE_EXTENDED_BOLUS,
aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $newPatchStartTime") MASK_BASAL to SIZE_BASAL,
medtrumPump.patchStartTime = newPatchStartTime 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
} }
if (fieldMask and MASK_BATTERY != 0) { return expectedLength
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
}
if (fieldMask and MASK_STORAGE != 0) { private fun handleSuspend(data: ByteArray, offset: Int): Int {
aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received")
val sequence = data.copyOfRange(offset, offset + 2).toInt() medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong())
if (sequence > medtrumPump.currentSequenceNumber) { aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}")
medtrumPump.currentSequenceNumber = sequence 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<BasalType>()[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)
}
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
}
if (fieldMask and MASK_ALARM != 0) { private fun handleAlarm(data: ByteArray, offset: Int): Int {
val alarmFlags = data.copyOfRange(offset, offset + 2).toInt() val alarmFlags = data.copyOfRange(offset, offset + 2).toInt()
val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt()
aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter") aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter")
// If no alarm, clear activeAlarm list // If no alarm, clear activeAlarm list
if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) { if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) {
medtrumPump.clearAlarmState() medtrumPump.clearAlarmState()
} else if (alarmFlags != 0) { } else if (alarmFlags != 0) {
// Check each alarm bit // 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 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] val alarmState = AlarmState.values()[i]
if ((alarmFlags shr i) and 1 != 0) { if ((alarmFlags shr i) and 1 != 0) {
// If the alarm bit is set, add the corresponding alarm to activeAlarms // If the alarm bit is set, add the corresponding alarm to activeAlarms
if (!medtrumPump.activeAlarms.contains(alarmState)) { if (!medtrumPump.activeAlarms.contains(alarmState)) {
aapsLogger.debug(LTag.PUMPCOMM, "Adding alarm $alarmState to active alarms") aapsLogger.debug(LTag.PUMPCOMM, "Adding alarm $alarmState to active alarms")
medtrumPump.addAlarm(alarmState) medtrumPump.addAlarm(alarmState)
medtrumPump.pumpWarning = 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)
} }
} 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
} }
return offset + SIZE_ALARM
}
if (fieldMask and MASK_AGE != 0) { private fun handleAge(data: ByteArray, offset: Int): Int {
aapsLogger.debug(LTag.PUMPCOMM, "Age notification received") aapsLogger.debug(LTag.PUMPCOMM, "Age notification received")
medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong() medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong()
aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}") aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}")
offset += 4 return offset + SIZE_AGE
} }
if (fieldMask and MASK_UNKNOWN_1 != 0) { private fun handleUnknown1(data: ByteArray, offset: Int): Int {
aapsLogger.debug(LTag.PUMPCOMM, "Unknown 1 notification received, not handled!") 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
}
if (fieldMask and MASK_UNUSED_CGM != 0) { private fun handleUnusedCGM(data: ByteArray, offset: Int): Int {
aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!") 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
}
if (fieldMask and MASK_UNUSED_COMMAND_CONFIRM != 0) { private fun handleUnusedCommandConfirm(data: ByteArray, offset: Int): Int {
// 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!")
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
}
if (fieldMask and MASK_UNUSED_AUTO_STATUS != 0) { 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 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
}
if (fieldMask and MASK_UNUSED_LEGACY != 0) { private fun handleUnusedLegacy(data: ByteArray, offset: Int): Int {
aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") 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
} }
} }

View file

@ -31,7 +31,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector)
} }
override fun handleResponse(data: ByteArray): Boolean { override fun handleResponse(data: ByteArray): Boolean {
val success = super.handleResponse(data) var success = super.handleResponse(data)
if (success) { if (success) {
val state = MedtrumPumpState.fromByte(data[RESP_STATE_START]) 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 // 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 return success

View file

@ -40,7 +40,7 @@ interface BLECommCallback {
fun onBLEDisconnected() fun onBLEDisconnected()
fun onNotification(notification: ByteArray) fun onNotification(notification: ByteArray)
fun onIndication(indication: ByteArray) fun onIndication(indication: ByteArray)
fun onSendMessageError(reason: String) fun onSendMessageError(reason: String, isRetryAble: Boolean)
} }
@Singleton @Singleton
@ -258,7 +258,11 @@ class BLEComm @Inject internal constructor(
mReadPacket?.addData(value) mReadPacket?.addData(value)
} }
if (mReadPacket?.allDataReceived() == true) { 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 mReadPacket = null
} }
} }
@ -279,7 +283,7 @@ class BLEComm @Inject internal constructor(
} }
} }
} else { } else {
mCallback?.onSendMessageError("onCharacteristicWrite failure") mCallback?.onSendMessageError("onCharacteristicWrite failure", true)
} }
} }
@ -404,7 +408,7 @@ class BLEComm @Inject internal constructor(
writeCharacteristic(uartWriteBTGattChar, value) writeCharacteristic(uartWriteBTGattChar, value)
} else { } else {
aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage error in writePacket!") 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)}") aapsLogger.debug(LTag.PUMPBTCOMM, "writeCharacteristic: ${Arrays.toString(data)}")
val success = mBluetoothGatt?.writeCharacteristic(characteristic) val success = mBluetoothGatt?.writeCharacteristic(characteristic)
if (success != true) { if (success != true) {
mCallback?.onSendMessageError("Failed to write characteristic") mCallback?.onSendMessageError("Failed to write characteristic", true)
} }
} }
}, WRITE_DELAY_MILLIS) }, WRITE_DELAY_MILLIS)

View file

@ -336,21 +336,22 @@ class MedtrumService : DaggerService(), BLECommCallback {
if (!canSetBolus()) return false if (!canSetBolus()) return false
val insulin = detailedBolusInfo.insulin val insulin = detailedBolusInfo.insulin
medtrumPump.bolusDone = false
medtrumPump.bolusStopped = false
if (!sendBolusCommand(insulin)) { if (!sendBolusCommand(insulin)) {
aapsLogger.error(LTag.PUMPCOMM, "Failed to set bolus") 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
medtrumPump.bolusDone = true
t.insulin = 0.0 t.insulin = 0.0
return false return false
} }
val bolusStart = System.currentTimeMillis() val bolusStart = System.currentTimeMillis()
medtrumPump.bolusDone = false
medtrumPump.bolusingTreatment = t
medtrumPump.bolusAmountToBeDelivered = insulin
medtrumPump.bolusStopped = false
medtrumPump.bolusProgressLastTimeStamp = bolusStart medtrumPump.bolusProgressLastTimeStamp = bolusStart
medtrumPump.bolusStartTime = 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 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 detailedBolusInfoStorage.add(detailedBolusInfo) // will be picked up on reading history
@ -735,9 +736,9 @@ class MedtrumService : DaggerService(), BLECommCallback {
currentState.onIndication(indication) currentState.onIndication(indication)
} }
override fun onSendMessageError(reason: String) { override fun onSendMessageError(reason: String, isRetryAble: Boolean) {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason") aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason")
currentState.onSendMessageError(reason) currentState.onSendMessageError(reason, isRetryAble)
} }
/** Service stuff */ /** Service stuff */
@ -822,10 +823,10 @@ class MedtrumService : DaggerService(), BLECommCallback {
return responseSuccess return responseSuccess
} }
fun onSendMessageError(reason: String) { fun onSendMessageError(reason: String, isRetryAble: Boolean) {
aapsLogger.warn(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason") aapsLogger.warn(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason")
// Retry 3 times // Retry 3 times
if (sendRetryCounter < 3) { if (sendRetryCounter < 3 && isRetryAble) {
sendRetryCounter++ sendRetryCounter++
mPacket?.getRequest()?.let { bleComm.sendMessage(it) } mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
} else { } else {

View file

@ -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()
}
}

View file

@ -80,6 +80,7 @@
<string name="alarm_battery_out">Battery out</string> <string name="alarm_battery_out">Battery out</string>
<string name="alarm_no_calibration">No calibration</string> <string name="alarm_no_calibration">No calibration</string>
<string name="pump_time_update_failed">Failed to update pump timezone, snooze message and refresh manually.</string> <string name="pump_time_update_failed">Failed to update pump timezone, snooze message and refresh manually.</string>
<string name="bolus_error">Bolus error</string>
<!-- wizard--> <!-- wizard-->
<string name="retry">Retry</string> <string name="retry">Retry</string>
@ -134,6 +135,8 @@
<string name="reading_activation_status">Please wait, reading activation status from pump.</string> <string name="reading_activation_status">Please wait, reading activation status from pump.</string>
<!-- settings--> <!-- settings-->
<string name="enable_pump_unreachable_alert_summary">Unreachable alert forced enabled, because Medtrum patch can fail and be unreachable.</string>
<string name="pump_unreachable_threshold_minutes_summary">Advised to set to 30 minutes, because Medtrum patch can fail and be unreachable.</string>
<string name="sn_input_title">Serial Number</string> <string name="sn_input_title">Serial Number</string>
<string name="sn_input_summary">Enter the serial number of your pump base.</string> <string name="sn_input_summary">Enter the serial number of your pump base.</string>
<string name="sn_input_invalid">Invalid serial number!</string> <string name="sn_input_invalid">Invalid serial number!</string>

View file

@ -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()
}
}

View file

@ -87,4 +87,17 @@ class NotificationPacketTest : MedtrumTestBase() {
assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(1.65) assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(1.65)
assertThat(medtrumPump.reservoir).isWithin(0.01).of(161.95) 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)
}
} }