Merge pull request #2322 from dv1/comboctl-dev
Carry over changes from comboctl ; disable pairing UI when BT permissions are not granted
This commit is contained in:
commit
5aacf04038
25 changed files with 704 additions and 324 deletions
|
@ -146,6 +146,10 @@ is much simpler, and builds ComboCtl as a kotlin-android project, not a Kotlin M
|
||||||
This simplifies integration into AndroidAPS, and avoids multiplatform problems (after all,
|
This simplifies integration into AndroidAPS, and avoids multiplatform problems (after all,
|
||||||
Kotlin Multiplatform is still marked as an alpha version feature).
|
Kotlin Multiplatform is still marked as an alpha version feature).
|
||||||
|
|
||||||
|
The `comboctl/src/androidMain/AndroidManifest.xml` file also differs in that the `ComboCtl` version
|
||||||
|
contains `package="info.nightscout.comboctl.android"` in its `<manifest>` tag, while the AndroidAPS
|
||||||
|
version doesn't.
|
||||||
|
|
||||||
When updating ComboCtl, it is important to keep these differences in mind.
|
When updating ComboCtl, it is important to keep these differences in mind.
|
||||||
|
|
||||||
Differences between the copy in `comboctl/` and the original ComboCtl code must be kept as little
|
Differences between the copy in `comboctl/` and the original ComboCtl code must be kept as little
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
package info.nightscout.comboctl.android
|
package info.nightscout.comboctl.android
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter as SystemBluetoothAdapter
|
|
||||||
import android.bluetooth.BluetoothDevice as SystemBluetoothDevice
|
|
||||||
import android.bluetooth.BluetoothSocket as SystemBluetoothSocket
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import info.nightscout.comboctl.base.BluetoothAddress
|
import info.nightscout.comboctl.base.BluetoothAddress
|
||||||
import info.nightscout.comboctl.base.BluetoothDevice
|
import info.nightscout.comboctl.base.BluetoothDevice
|
||||||
|
@ -12,11 +9,14 @@ import info.nightscout.comboctl.base.ComboIOException
|
||||||
import info.nightscout.comboctl.base.LogLevel
|
import info.nightscout.comboctl.base.LogLevel
|
||||||
import info.nightscout.comboctl.base.Logger
|
import info.nightscout.comboctl.base.Logger
|
||||||
import info.nightscout.comboctl.utils.retryBlocking
|
import info.nightscout.comboctl.utils.retryBlocking
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.Dispatchers
|
import android.bluetooth.BluetoothAdapter as SystemBluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothDevice as SystemBluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothSocket as SystemBluetoothSocket
|
||||||
|
|
||||||
private val logger = Logger.get("AndroidBluetoothDevice")
|
private val logger = Logger.get("AndroidBluetoothDevice")
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
package info.nightscout.comboctl.android
|
package info.nightscout.comboctl.android
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.bluetooth.BluetoothAdapter as SystemBluetoothAdapter
|
|
||||||
import android.bluetooth.BluetoothDevice as SystemBluetoothDevice
|
|
||||||
import android.bluetooth.BluetoothManager as SystemBluetoothManager
|
|
||||||
import android.bluetooth.BluetoothServerSocket as SystemBluetoothServerSocket
|
|
||||||
import android.bluetooth.BluetoothSocket as SystemBluetoothSocket
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -20,6 +15,11 @@ import info.nightscout.comboctl.base.toBluetoothAddress
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import android.bluetooth.BluetoothAdapter as SystemBluetoothAdapter
|
||||||
|
import android.bluetooth.BluetoothDevice as SystemBluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothManager as SystemBluetoothManager
|
||||||
|
import android.bluetooth.BluetoothServerSocket as SystemBluetoothServerSocket
|
||||||
|
import android.bluetooth.BluetoothSocket as SystemBluetoothSocket
|
||||||
|
|
||||||
private val logger = Logger.get("AndroidBluetoothInterface")
|
private val logger = Logger.get("AndroidBluetoothInterface")
|
||||||
|
|
||||||
|
|
|
@ -394,21 +394,25 @@ object ApplicationLayer {
|
||||||
) : CMDHistoryEventDetail(isBolusDetail = true)
|
) : CMDHistoryEventDetail(isBolusDetail = true)
|
||||||
data class ExtendedBolusStarted(
|
data class ExtendedBolusStarted(
|
||||||
val totalBolusAmount: Int,
|
val totalBolusAmount: Int,
|
||||||
val totalDurationMinutes: Int
|
val totalDurationMinutes: Int,
|
||||||
|
val manual: Boolean
|
||||||
) : CMDHistoryEventDetail(isBolusDetail = true)
|
) : CMDHistoryEventDetail(isBolusDetail = true)
|
||||||
data class ExtendedBolusEnded(
|
data class ExtendedBolusEnded(
|
||||||
val totalBolusAmount: Int,
|
val totalBolusAmount: Int,
|
||||||
val totalDurationMinutes: Int
|
val totalDurationMinutes: Int,
|
||||||
|
val manual: Boolean
|
||||||
) : CMDHistoryEventDetail(isBolusDetail = true)
|
) : CMDHistoryEventDetail(isBolusDetail = true)
|
||||||
data class MultiwaveBolusStarted(
|
data class MultiwaveBolusStarted(
|
||||||
val totalBolusAmount: Int,
|
val totalBolusAmount: Int,
|
||||||
val immediateBolusAmount: Int,
|
val immediateBolusAmount: Int,
|
||||||
val totalDurationMinutes: Int
|
val totalDurationMinutes: Int,
|
||||||
|
val manual: Boolean
|
||||||
) : CMDHistoryEventDetail(isBolusDetail = true)
|
) : CMDHistoryEventDetail(isBolusDetail = true)
|
||||||
data class MultiwaveBolusEnded(
|
data class MultiwaveBolusEnded(
|
||||||
val totalBolusAmount: Int,
|
val totalBolusAmount: Int,
|
||||||
val immediateBolusAmount: Int,
|
val immediateBolusAmount: Int,
|
||||||
val totalDurationMinutes: Int
|
val totalDurationMinutes: Int,
|
||||||
|
val manual: Boolean
|
||||||
) : CMDHistoryEventDetail(isBolusDetail = true)
|
) : CMDHistoryEventDetail(isBolusDetail = true)
|
||||||
data class NewDateTimeSet(val dateTime: LocalDateTime) : CMDHistoryEventDetail(isBolusDetail = false)
|
data class NewDateTimeSet(val dateTime: LocalDateTime) : CMDHistoryEventDetail(isBolusDetail = false)
|
||||||
}
|
}
|
||||||
|
@ -524,14 +528,18 @@ object ApplicationLayer {
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possible bolus types used in COMMAND mode commands.
|
* Possible immediate bolus types used in COMMAND mode commands.
|
||||||
|
*
|
||||||
|
* "Immediate" means that the bolus gets delivered immediately once the command is sent.
|
||||||
|
* A standard bolus only has an immediate delivery, an extended bolus has none, and
|
||||||
|
* a multiwave bolus is partially made up of an immediate and an extended delivery.
|
||||||
*/
|
*/
|
||||||
enum class CMDBolusType(val id: Int) {
|
enum class CMDImmediateBolusType(val id: Int) {
|
||||||
STANDARD(0x47),
|
STANDARD(0x47),
|
||||||
MULTI_WAVE(0xB7);
|
MULTI_WAVE(0xB7);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val values = CMDBolusType.values()
|
private val values = CMDImmediateBolusType.values()
|
||||||
fun fromInt(value: Int) = values.firstOrNull { it.id == value }
|
fun fromInt(value: Int) = values.firstOrNull { it.id == value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,7 +573,7 @@ object ApplicationLayer {
|
||||||
* "57" means 5.7 IU.
|
* "57" means 5.7 IU.
|
||||||
*/
|
*/
|
||||||
data class CMDBolusDeliveryStatus(
|
data class CMDBolusDeliveryStatus(
|
||||||
val bolusType: CMDBolusType,
|
val bolusType: CMDImmediateBolusType,
|
||||||
val deliveryState: CMDBolusDeliveryState,
|
val deliveryState: CMDBolusDeliveryState,
|
||||||
val remainingAmount: Int
|
val remainingAmount: Int
|
||||||
)
|
)
|
||||||
|
@ -996,8 +1004,18 @@ object ApplicationLayer {
|
||||||
command = Command.CMD_GET_BOLUS_STATUS
|
command = Command.CMD_GET_BOLUS_STATUS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum class CMDDeliverBolusType {
|
||||||
|
STANDARD_BOLUS,
|
||||||
|
EXTENDED_BOLUS,
|
||||||
|
MULTIWAVE_BOLUS
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a CMD_DELIVER_BOLUS packet.
|
* Creates a CMD_DELIVER_BOLUS packet for a standard bolus.
|
||||||
|
*
|
||||||
|
* This is equivalent to calling the full [createCMDDeliverBolusPacket] function
|
||||||
|
* with bolusType set to [CMDDeliverBolusType.STANDARD_BOLUS]. The [bolusAmount]
|
||||||
|
* argument here is passed as the full function's totalBolusAmount argument.
|
||||||
*
|
*
|
||||||
* The command mode must have been activated before this can be sent to the Combo.
|
* The command mode must have been activated before this can be sent to the Combo.
|
||||||
*
|
*
|
||||||
|
@ -1008,45 +1026,124 @@ object ApplicationLayer {
|
||||||
* "57" means 5.7 IU.
|
* "57" means 5.7 IU.
|
||||||
* @return The produced packet.
|
* @return The produced packet.
|
||||||
*/
|
*/
|
||||||
fun createCMDDeliverBolusPacket(bolusAmount: Int): Packet {
|
fun createCMDDeliverBolusPacket(bolusAmount: Int) =
|
||||||
|
createCMDDeliverBolusPacket(
|
||||||
|
totalBolusAmount = bolusAmount,
|
||||||
|
immediateBolusAmount = 0,
|
||||||
|
durationInMinutes = 0,
|
||||||
|
bolusType = CMDDeliverBolusType.STANDARD_BOLUS
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CMD_DELIVER_BOLUS packet.
|
||||||
|
*
|
||||||
|
* The command mode must have been activated before this can be sent to the Combo.
|
||||||
|
*
|
||||||
|
* See the combo-comm-spec.adoc file for details about this packet.
|
||||||
|
*
|
||||||
|
* @param totalBolusAmount Total amount of insulin to use for the bolus.
|
||||||
|
* Note that this is given in 0.1 IU units, so for example, "57" means 5.7 IU.
|
||||||
|
* @param immediateBolusAmount The amount of insulin units to use for the
|
||||||
|
* immediate portion of a multiwave bolus. This value is only used if
|
||||||
|
* [bolusType] is [CMDDeliverBolusType.MULTIWAVE_BOLUS]. This
|
||||||
|
* value must be <= [totalBolusAmount].
|
||||||
|
* @param durationInMinutes The duration of the extended bolus or the
|
||||||
|
* extended portion of the multiwave bolus. If [bolusType] is set to
|
||||||
|
* [CMDDeliverBolusType.STANDARD_BOLUS], this value is ignored.
|
||||||
|
* Otherwise, it must be at least 15.
|
||||||
|
* @param bolusType Type of the bolus.
|
||||||
|
* @return The produced packet.
|
||||||
|
* @throws IllegalArgumentException if [immediateBolusAmount] exceeds
|
||||||
|
* [totalBolusAmount], or if [durationInMinutes] is <15 when [bolusType]
|
||||||
|
* is set to anything other than [CMDDeliverBolusType.STANDARD_BOLUS].
|
||||||
|
*/
|
||||||
|
fun createCMDDeliverBolusPacket(
|
||||||
|
totalBolusAmount: Int,
|
||||||
|
immediateBolusAmount: Int,
|
||||||
|
durationInMinutes: Int,
|
||||||
|
bolusType: CMDDeliverBolusType
|
||||||
|
): Packet {
|
||||||
|
// Values that aren't used for the particular bolus type are set to 0
|
||||||
|
// since we don't know what happens if we transmit a nonzero value to
|
||||||
|
// the Combo with these bolus types.
|
||||||
|
val (effectiveImmediateBolusAmount, effectiveDurationInMinutes) = when (bolusType) {
|
||||||
|
CMDDeliverBolusType.STANDARD_BOLUS -> Pair(0, 0)
|
||||||
|
CMDDeliverBolusType.EXTENDED_BOLUS -> Pair(0, durationInMinutes)
|
||||||
|
CMDDeliverBolusType.MULTIWAVE_BOLUS -> Pair(immediateBolusAmount, durationInMinutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply argument requirement checks, depending on the bolus type.
|
||||||
|
when (bolusType) {
|
||||||
|
CMDDeliverBolusType.STANDARD_BOLUS -> Unit
|
||||||
|
|
||||||
|
CMDDeliverBolusType.EXTENDED_BOLUS ->
|
||||||
|
require(effectiveDurationInMinutes >= 15) {
|
||||||
|
"extended bolus duration must be at least 15; actual duration: $effectiveDurationInMinutes"
|
||||||
|
}
|
||||||
|
|
||||||
|
CMDDeliverBolusType.MULTIWAVE_BOLUS -> {
|
||||||
|
require(effectiveDurationInMinutes >= 15) {
|
||||||
|
"multiwave bolus duration must be at least 15; actual duration: $effectiveDurationInMinutes"
|
||||||
|
}
|
||||||
|
require(immediateBolusAmount <= totalBolusAmount) {
|
||||||
|
"immediate bolus duration must be <= total bolus amount; actual immediate/total amount: " +
|
||||||
|
"$effectiveImmediateBolusAmount / $totalBolusAmount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Need to convert the bolus amount to a 32-bit floating point, and
|
// Need to convert the bolus amount to a 32-bit floating point, and
|
||||||
// then convert that into a form that can be stored below as 4 bytes
|
// then convert that into a form that can be stored below as 4 bytes
|
||||||
// in little-endian order.
|
// in little-endian order.
|
||||||
val bolusAmountAsFloatBits = bolusAmount.toFloat().toBits().toPosLong()
|
val totalBolusAmountAsFloatBits = totalBolusAmount.toFloat().toBits().toPosLong()
|
||||||
|
val effectiveImmediateBolusAmountAsFloatBits = effectiveImmediateBolusAmount.toFloat().toBits().toPosLong()
|
||||||
|
val effectiveDurationInMinutesAsFloatBits = effectiveDurationInMinutes.toFloat().toBits().toPosLong()
|
||||||
|
|
||||||
// TODO: It is currently unknown why the 0x55 and 0x59 bytes encode
|
// TODO: It is currently unknown why the same bolus parameters have to
|
||||||
// a standard bolus, why the same bolus parameters have to be added
|
// be added twice (once as 16-bit integers and once as 32-bit floats).
|
||||||
// twice (once as 16-bit integers and once as 32-bit floats), or
|
|
||||||
// how to program in multi-wave and extended bolus types.
|
// NOTE: The 0x55, 0x59, 0x65 etc. values have been found empirically.
|
||||||
|
val bolusTypeIDBytes = when (bolusType) {
|
||||||
|
CMDDeliverBolusType.STANDARD_BOLUS -> intArrayOf(0x55, 0x59)
|
||||||
|
CMDDeliverBolusType.EXTENDED_BOLUS -> intArrayOf(0x65, 0x69)
|
||||||
|
CMDDeliverBolusType.MULTIWAVE_BOLUS -> intArrayOf(0xA5, 0xA9)
|
||||||
|
}
|
||||||
|
|
||||||
val payload = byteArrayListOfInts(
|
val payload = byteArrayListOfInts(
|
||||||
// This specifies a standard bolus.
|
bolusTypeIDBytes[0], bolusTypeIDBytes[1],
|
||||||
0x55, 0x59,
|
|
||||||
|
|
||||||
// Total bolus amount, encoded as a 16-bit little endian integer.
|
// Total bolus amount, encoded as a 16-bit little endian integer.
|
||||||
(bolusAmount and 0x00FF) ushr 0,
|
(totalBolusAmount and 0x00FF) ushr 0,
|
||||||
(bolusAmount and 0xFF00) ushr 8,
|
(totalBolusAmount and 0xFF00) ushr 8,
|
||||||
// Duration in minutes, encoded as a 16-bit little endian integer.
|
// Duration in minutes, encoded as a 16-bit little endian integer.
|
||||||
// (Only relevant for multi-wave and extended bolus.)
|
// (Only relevant for multi-wave and extended bolus.)
|
||||||
0x00, 0x00,
|
(effectiveDurationInMinutes and 0x00FF) ushr 0,
|
||||||
|
(effectiveDurationInMinutes and 0xFF00) ushr 8,
|
||||||
// Immediate bolus amount encoded as a 16-bit little endian integer.
|
// Immediate bolus amount encoded as a 16-bit little endian integer.
|
||||||
// (Only relevant for multi-wave bolus.)
|
// (Only relevant for multi-wave bolus.)
|
||||||
0x00, 0x00,
|
(effectiveImmediateBolusAmount and 0x00FF) ushr 0,
|
||||||
|
(effectiveImmediateBolusAmount and 0xFF00) ushr 8,
|
||||||
|
|
||||||
// Total bolus amount, encoded as a 32-bit little endian float point.
|
// Total bolus amount, encoded as a 32-bit little endian float point.
|
||||||
((bolusAmountAsFloatBits and 0x000000FFL) ushr 0).toInt(),
|
((totalBolusAmountAsFloatBits and 0x000000FFL) ushr 0).toInt(),
|
||||||
((bolusAmountAsFloatBits and 0x0000FF00L) ushr 8).toInt(),
|
((totalBolusAmountAsFloatBits and 0x0000FF00L) ushr 8).toInt(),
|
||||||
((bolusAmountAsFloatBits and 0x00FF0000L) ushr 16).toInt(),
|
((totalBolusAmountAsFloatBits and 0x00FF0000L) ushr 16).toInt(),
|
||||||
((bolusAmountAsFloatBits and 0xFF000000L) ushr 24).toInt(),
|
((totalBolusAmountAsFloatBits and 0xFF000000L) ushr 24).toInt(),
|
||||||
// Duration in minutes, encoded as a 32-bit little endian float point.
|
// Duration in minutes, encoded as a 32-bit little endian float point.
|
||||||
// (Only relevant for multi-wave and extended bolus.)
|
// (Only relevant for multi-wave and extended bolus.)
|
||||||
0x00, 0x00, 0x00, 0x00,
|
((effectiveDurationInMinutesAsFloatBits and 0x000000FFL) ushr 0).toInt(),
|
||||||
|
((effectiveDurationInMinutesAsFloatBits and 0x0000FF00L) ushr 8).toInt(),
|
||||||
|
((effectiveDurationInMinutesAsFloatBits and 0x00FF0000L) ushr 16).toInt(),
|
||||||
|
((effectiveDurationInMinutesAsFloatBits and 0xFF000000L) ushr 24).toInt(),
|
||||||
// Immediate bolus amount encoded as a 32-bit little endian float point.
|
// Immediate bolus amount encoded as a 32-bit little endian float point.
|
||||||
// (Only relevant for multi-wave bolus.)
|
// (Only relevant for multi-wave bolus.)
|
||||||
0x00, 0x00, 0x00, 0x00
|
((effectiveImmediateBolusAmountAsFloatBits and 0x000000FFL) ushr 0).toInt(),
|
||||||
|
((effectiveImmediateBolusAmountAsFloatBits and 0x0000FF00L) ushr 8).toInt(),
|
||||||
|
((effectiveImmediateBolusAmountAsFloatBits and 0x00FF0000L) ushr 16).toInt(),
|
||||||
|
((effectiveImmediateBolusAmountAsFloatBits and 0xFF000000L) ushr 24).toInt(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add a CRC16 checksum for all of the parameters
|
// Add a CRC16 checksum for all the parameters
|
||||||
// stored in the payload above.
|
// stored in the payload above.
|
||||||
val crcChecksum = calculateCRC16MCRF4XX(payload)
|
val crcChecksum = calculateCRC16MCRF4XX(payload)
|
||||||
payload.add(((crcChecksum and 0x00FF) ushr 0).toByte())
|
payload.add(((crcChecksum and 0x00FF) ushr 0).toByte())
|
||||||
|
@ -1068,7 +1165,7 @@ object ApplicationLayer {
|
||||||
* @param bolusType The type of the bolus to cancel.
|
* @param bolusType The type of the bolus to cancel.
|
||||||
* @return The produced packet.
|
* @return The produced packet.
|
||||||
*/
|
*/
|
||||||
fun createCMDCancelBolusPacket(bolusType: CMDBolusType) = Packet(
|
fun createCMDCancelBolusPacket(bolusType: CMDImmediateBolusType) = Packet(
|
||||||
command = Command.CMD_CANCEL_BOLUS,
|
command = Command.CMD_CANCEL_BOLUS,
|
||||||
payload = byteArrayListOfInts(bolusType.id)
|
payload = byteArrayListOfInts(bolusType.id)
|
||||||
)
|
)
|
||||||
|
@ -1289,6 +1386,11 @@ object ApplicationLayer {
|
||||||
// All bolus amounts are recorded as an integer that got multiplied by 10.
|
// All bolus amounts are recorded as an integer that got multiplied by 10.
|
||||||
// For example, an amount of 3.7 IU is recorded as the 16-bit integer 37.
|
// For example, an amount of 3.7 IU is recorded as the 16-bit integer 37.
|
||||||
|
|
||||||
|
// NOTE: "Manual" means that the user manually administered the bolus
|
||||||
|
// on the pump itself, with the pump's buttons. So, manual == false
|
||||||
|
// specifies that the bolus was given off programmatically (that is,
|
||||||
|
// through the CMD_DELIVER_BOLUS command).
|
||||||
|
|
||||||
val eventDetail = when (eventTypeId) {
|
val eventDetail = when (eventTypeId) {
|
||||||
// Quick bolus.
|
// Quick bolus.
|
||||||
4, 5 -> {
|
4, 5 -> {
|
||||||
|
@ -1313,16 +1415,18 @@ object ApplicationLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extended bolus.
|
// Extended bolus.
|
||||||
8, 9 -> {
|
8, 9, 16, 17 -> {
|
||||||
// Total bolus amount is recorded in the first 2 detail bytes as a 16-bit little endian integer.
|
// Total bolus amount is recorded in the first 2 detail bytes as a 16-bit little endian integer.
|
||||||
val totalBolusAmount = (detailBytes[1].toPosInt() shl 8) or detailBytes[0].toPosInt()
|
val totalBolusAmount = (detailBytes[1].toPosInt() shl 8) or detailBytes[0].toPosInt()
|
||||||
// Total duration in minutes is recorded in the next 2 detail bytes as a 16-bit little endian integer.
|
// Total duration in minutes is recorded in the next 2 detail bytes as a 16-bit little endian integer.
|
||||||
val totalDurationMinutes = (detailBytes[3].toPosInt() shl 8) or detailBytes[2].toPosInt()
|
val totalDurationMinutes = (detailBytes[3].toPosInt() shl 8) or detailBytes[2].toPosInt()
|
||||||
// Event type ID 8 = bolus started. ID 9 = bolus ended.
|
// Event type IDs 8 or 16 = bolus started. IDs 9 or 17 = bolus ended.
|
||||||
val started = (eventTypeId == 8)
|
val started = (eventTypeId == 8) || (eventTypeId == 16)
|
||||||
|
val manual = (eventTypeId == 8) || (eventTypeId == 9)
|
||||||
|
|
||||||
logger(LogLevel.DEBUG) {
|
logger(LogLevel.DEBUG) {
|
||||||
"Detail info: got history event \"extended bolus ${if (started) "started" else "ended"}\" " +
|
"Detail info: got history event \"${if (manual) "manual" else "automatic"} " +
|
||||||
|
"extended bolus ${if (started) "started" else "ended"}\" " +
|
||||||
"with total amount of ${totalBolusAmount.toFloat() / 10} IU and " +
|
"with total amount of ${totalBolusAmount.toFloat() / 10} IU and " +
|
||||||
"total duration of $totalDurationMinutes minutes"
|
"total duration of $totalDurationMinutes minutes"
|
||||||
}
|
}
|
||||||
|
@ -1330,17 +1434,19 @@ object ApplicationLayer {
|
||||||
if (started)
|
if (started)
|
||||||
CMDHistoryEventDetail.ExtendedBolusStarted(
|
CMDHistoryEventDetail.ExtendedBolusStarted(
|
||||||
totalBolusAmount = totalBolusAmount,
|
totalBolusAmount = totalBolusAmount,
|
||||||
totalDurationMinutes = totalDurationMinutes
|
totalDurationMinutes = totalDurationMinutes,
|
||||||
|
manual = manual
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
CMDHistoryEventDetail.ExtendedBolusEnded(
|
CMDHistoryEventDetail.ExtendedBolusEnded(
|
||||||
totalBolusAmount = totalBolusAmount,
|
totalBolusAmount = totalBolusAmount,
|
||||||
totalDurationMinutes = totalDurationMinutes
|
totalDurationMinutes = totalDurationMinutes,
|
||||||
|
manual = manual
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiwave bolus.
|
// Multiwave bolus.
|
||||||
10, 11 -> {
|
10, 11, 18, 19 -> {
|
||||||
// All 8 bits of first byte + 2 LSB of second byte: bolus amount.
|
// All 8 bits of first byte + 2 LSB of second byte: bolus amount.
|
||||||
// 6 MSB of second byte + 4 LSB of third byte: immediate bolus amount.
|
// 6 MSB of second byte + 4 LSB of third byte: immediate bolus amount.
|
||||||
// 4 MSB of third byte + all 8 bits of fourth byte: duration in minutes.
|
// 4 MSB of third byte + all 8 bits of fourth byte: duration in minutes.
|
||||||
|
@ -1349,11 +1455,13 @@ object ApplicationLayer {
|
||||||
((detailBytes[1].toPosInt() and 0b11111100) ushr 2)
|
((detailBytes[1].toPosInt() and 0b11111100) ushr 2)
|
||||||
val totalDurationMinutes = (detailBytes[3].toPosInt() shl 4) or
|
val totalDurationMinutes = (detailBytes[3].toPosInt() shl 4) or
|
||||||
((detailBytes[2].toPosInt() and 0b11110000) ushr 4)
|
((detailBytes[2].toPosInt() and 0b11110000) ushr 4)
|
||||||
// Event type ID 10 = bolus started. ID 11 = bolus ended.
|
// Event type IDs 10 or 18 = bolus started. IDs 11 or 19 = bolus ended.
|
||||||
val started = (eventTypeId == 10)
|
val started = (eventTypeId == 10) || (eventTypeId == 18)
|
||||||
|
val manual = (eventTypeId == 10) || (eventTypeId == 11)
|
||||||
|
|
||||||
logger(LogLevel.DEBUG) {
|
logger(LogLevel.DEBUG) {
|
||||||
"Detail info: got history event \"multiwave bolus ${if (started) "started" else "ended"}\" " +
|
"Detail info: got history event \"${if (manual) "manual" else "automatic"} " +
|
||||||
|
"multiwave bolus ${if (started) "started" else "ended"}\" " +
|
||||||
"with total amount of ${totalBolusAmount.toFloat() / 10} IU, " +
|
"with total amount of ${totalBolusAmount.toFloat() / 10} IU, " +
|
||||||
"immediate amount of ${immediateBolusAmount.toFloat() / 10} IU, " +
|
"immediate amount of ${immediateBolusAmount.toFloat() / 10} IU, " +
|
||||||
"and total duration of $totalDurationMinutes minutes"
|
"and total duration of $totalDurationMinutes minutes"
|
||||||
|
@ -1363,13 +1471,15 @@ object ApplicationLayer {
|
||||||
CMDHistoryEventDetail.MultiwaveBolusStarted(
|
CMDHistoryEventDetail.MultiwaveBolusStarted(
|
||||||
totalBolusAmount = totalBolusAmount,
|
totalBolusAmount = totalBolusAmount,
|
||||||
immediateBolusAmount = immediateBolusAmount,
|
immediateBolusAmount = immediateBolusAmount,
|
||||||
totalDurationMinutes = totalDurationMinutes
|
totalDurationMinutes = totalDurationMinutes,
|
||||||
|
manual = manual
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
CMDHistoryEventDetail.MultiwaveBolusEnded(
|
CMDHistoryEventDetail.MultiwaveBolusEnded(
|
||||||
totalBolusAmount = totalBolusAmount,
|
totalBolusAmount = totalBolusAmount,
|
||||||
immediateBolusAmount = immediateBolusAmount,
|
immediateBolusAmount = immediateBolusAmount,
|
||||||
totalDurationMinutes = totalDurationMinutes
|
totalDurationMinutes = totalDurationMinutes,
|
||||||
|
manual = manual
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1378,10 +1488,6 @@ object ApplicationLayer {
|
||||||
// Bolus amount is recorded in the first 2 detail bytes as a 16-bit little endian integer.
|
// Bolus amount is recorded in the first 2 detail bytes as a 16-bit little endian integer.
|
||||||
val bolusAmount = (detailBytes[1].toPosInt() shl 8) or detailBytes[0].toPosInt()
|
val bolusAmount = (detailBytes[1].toPosInt() shl 8) or detailBytes[0].toPosInt()
|
||||||
// Events with type IDs 6 and 7 indicate manual infusion.
|
// Events with type IDs 6 and 7 indicate manual infusion.
|
||||||
// NOTE: "Manual" means that the user manually administered the bolus
|
|
||||||
// on the pump itself, with the pump's buttons. So, manual == false
|
|
||||||
// specifies that the bolus was given off programmatically (that is,
|
|
||||||
// through the CMD_DELIVER_BOLUS command).
|
|
||||||
val manual = (eventTypeId == 6) || (eventTypeId == 7)
|
val manual = (eventTypeId == 6) || (eventTypeId == 7)
|
||||||
// Events with type IDs 6 and 14 indicate that a bolus was requested, while
|
// Events with type IDs 6 and 14 indicate that a bolus was requested, while
|
||||||
// events with type IDs 7 and 15 indicate that a bolus was infused (= finished).
|
// events with type IDs 7 and 15 indicate that a bolus was infused (= finished).
|
||||||
|
@ -1477,7 +1583,7 @@ object ApplicationLayer {
|
||||||
val payload = packet.payload
|
val payload = packet.payload
|
||||||
|
|
||||||
val bolusTypeInt = payload[2].toPosInt()
|
val bolusTypeInt = payload[2].toPosInt()
|
||||||
val bolusType = CMDBolusType.fromInt(bolusTypeInt)
|
val bolusType = CMDImmediateBolusType.fromInt(bolusTypeInt)
|
||||||
?: throw PayloadDataCorruptionException(
|
?: throw PayloadDataCorruptionException(
|
||||||
packet,
|
packet,
|
||||||
"Invalid bolus type ${bolusTypeInt.toHexString(2, true)}"
|
"Invalid bolus type ${bolusTypeInt.toHexString(2, true)}"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package info.nightscout.comboctl.base
|
package info.nightscout.comboctl.base
|
||||||
|
|
||||||
import kotlin.reflect.KClassifier
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
|
|
||||||
private val logger = Logger.get("Pump")
|
private val logger = Logger.get("Pump")
|
||||||
|
|
||||||
|
|
|
@ -1012,11 +1012,29 @@ class PumpIO(
|
||||||
* of a packet's payload does not match the expected size.
|
* of a packet's payload does not match the expected size.
|
||||||
* @throws ComboIOException if IO with the pump fails.
|
* @throws ComboIOException if IO with the pump fails.
|
||||||
*/
|
*/
|
||||||
suspend fun deliverCMDStandardBolus(bolusAmount: Int): Boolean =
|
suspend fun deliverCMDStandardBolus(totalBolusAmount: Int): Boolean =
|
||||||
|
deliverCMDStandardBolus(
|
||||||
|
totalBolusAmount,
|
||||||
|
immediateBolusAmount = 0,
|
||||||
|
durationInMinutes = 0,
|
||||||
|
bolusType = ApplicationLayer.CMDDeliverBolusType.STANDARD_BOLUS
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun deliverCMDStandardBolus(
|
||||||
|
totalBolusAmount: Int,
|
||||||
|
immediateBolusAmount: Int,
|
||||||
|
durationInMinutes: Int,
|
||||||
|
bolusType: ApplicationLayer.CMDDeliverBolusType
|
||||||
|
): Boolean =
|
||||||
runPumpIOCall("deliver standard bolus", Mode.COMMAND) {
|
runPumpIOCall("deliver standard bolus", Mode.COMMAND) {
|
||||||
|
|
||||||
val packet = sendPacketWithResponse(
|
val packet = sendPacketWithResponse(
|
||||||
ApplicationLayer.createCMDDeliverBolusPacket(bolusAmount),
|
ApplicationLayer.createCMDDeliverBolusPacket(
|
||||||
|
totalBolusAmount,
|
||||||
|
immediateBolusAmount,
|
||||||
|
durationInMinutes,
|
||||||
|
bolusType
|
||||||
|
),
|
||||||
ApplicationLayer.Command.CMD_DELIVER_BOLUS_RESPONSE
|
ApplicationLayer.Command.CMD_DELIVER_BOLUS_RESPONSE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1038,7 +1056,7 @@ class PumpIO(
|
||||||
// TODO: Test that this function does the expected thing
|
// TODO: Test that this function does the expected thing
|
||||||
// when no bolus is actually ongoing.
|
// when no bolus is actually ongoing.
|
||||||
val packet = sendPacketWithResponse(
|
val packet = sendPacketWithResponse(
|
||||||
ApplicationLayer.createCMDCancelBolusPacket(ApplicationLayer.CMDBolusType.STANDARD),
|
ApplicationLayer.createCMDCancelBolusPacket(ApplicationLayer.CMDImmediateBolusType.STANDARD),
|
||||||
ApplicationLayer.Command.CMD_CANCEL_BOLUS_RESPONSE
|
ApplicationLayer.Command.CMD_CANCEL_BOLUS_RESPONSE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package info.nightscout.comboctl.base
|
package info.nightscout.comboctl.base
|
||||||
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.datetime.atTime
|
import kotlinx.datetime.atTime
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
// Utility function for cases when only the time and no date is known.
|
// Utility function for cases when only the time and no date is known.
|
||||||
// monthNumber and dayOfMonth are set to 1 instead of 0 since 0 is
|
// monthNumber and dayOfMonth are set to 1 instead of 0 since 0 is
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package info.nightscout.comboctl.main
|
package info.nightscout.comboctl.main
|
||||||
|
|
||||||
import info.nightscout.comboctl.base.ApplicationLayer
|
import info.nightscout.comboctl.base.ApplicationLayer
|
||||||
|
import info.nightscout.comboctl.base.ApplicationLayer.CMDDeliverBolusType
|
||||||
import info.nightscout.comboctl.base.ApplicationLayer.CMDHistoryEventDetail
|
import info.nightscout.comboctl.base.ApplicationLayer.CMDHistoryEventDetail
|
||||||
import info.nightscout.comboctl.base.BasicProgressStage
|
import info.nightscout.comboctl.base.BasicProgressStage
|
||||||
import info.nightscout.comboctl.base.BluetoothAddress
|
import info.nightscout.comboctl.base.BluetoothAddress
|
||||||
|
@ -29,10 +30,6 @@ import info.nightscout.comboctl.parser.BatteryState
|
||||||
import info.nightscout.comboctl.parser.MainScreenContent
|
import info.nightscout.comboctl.parser.MainScreenContent
|
||||||
import info.nightscout.comboctl.parser.ParsedScreen
|
import info.nightscout.comboctl.parser.ParsedScreen
|
||||||
import info.nightscout.comboctl.parser.ReservoirState
|
import info.nightscout.comboctl.parser.ReservoirState
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.DurationUnit
|
|
||||||
import kotlin.time.toDuration
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
|
@ -53,6 +50,10 @@ import kotlinx.datetime.atStartOfDayIn
|
||||||
import kotlinx.datetime.offsetAt
|
import kotlinx.datetime.offsetAt
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
import kotlinx.datetime.toLocalDateTime
|
import kotlinx.datetime.toLocalDateTime
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.DurationUnit
|
||||||
|
import kotlin.time.toDuration
|
||||||
|
|
||||||
private val logger = Logger.get("Pump")
|
private val logger = Logger.get("Pump")
|
||||||
|
|
||||||
|
@ -101,11 +102,11 @@ object RTCommandProgressStage {
|
||||||
*
|
*
|
||||||
* The amounts are given in 0.1 IU units. For example, "57" means 5.7 IU.
|
* The amounts are given in 0.1 IU units. For example, "57" means 5.7 IU.
|
||||||
*
|
*
|
||||||
* @property deliveredAmount How many units have been delivered so far.
|
* @property deliveredImmediateAmount How many units have been delivered so far.
|
||||||
* This is always <= totalAmount.
|
* This is always <= totalAmount.
|
||||||
* @property totalAmount Total amount of bolus units.
|
* @property totalImmediateAmount Total amount of bolus units.
|
||||||
*/
|
*/
|
||||||
data class DeliveringBolus(val deliveredAmount: Int, val totalAmount: Int) : ProgressStage("deliveringBolus")
|
data class DeliveringBolus(val deliveredImmediateAmount: Int, val totalImmediateAmount: Int) : ProgressStage("deliveringBolus")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TDD fetching history stage.
|
* TDD fetching history stage.
|
||||||
|
@ -297,7 +298,7 @@ class Pump(
|
||||||
BasicProgressStage.Finished,
|
BasicProgressStage.Finished,
|
||||||
is BasicProgressStage.Aborted -> 1.0
|
is BasicProgressStage.Aborted -> 1.0
|
||||||
is RTCommandProgressStage.DeliveringBolus ->
|
is RTCommandProgressStage.DeliveringBolus ->
|
||||||
stage.deliveredAmount.toDouble() / stage.totalAmount.toDouble()
|
stage.deliveredImmediateAmount.toDouble() / stage.totalImmediateAmount.toDouble()
|
||||||
else -> 0.0
|
else -> 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,8 +370,11 @@ class Pump(
|
||||||
val force100Percent: Boolean
|
val force100Percent: Boolean
|
||||||
) : CommandDescription()
|
) : CommandDescription()
|
||||||
class DeliveringBolusCommandDesc(
|
class DeliveringBolusCommandDesc(
|
||||||
val bolusAmount: Int,
|
val totalBolusAmount: Int,
|
||||||
val bolusReason: StandardBolusReason
|
val immediateBolusAmount: Int,
|
||||||
|
val durationInMinutes: Int,
|
||||||
|
val standardBolusReason: StandardBolusReason,
|
||||||
|
val bolusType: ApplicationLayer.CMDDeliverBolusType
|
||||||
) : CommandDescription()
|
) : CommandDescription()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -410,27 +414,27 @@ class Pump(
|
||||||
/**
|
/**
|
||||||
* Exception thrown when the bolus delivery was cancelled.
|
* Exception thrown when the bolus delivery was cancelled.
|
||||||
*
|
*
|
||||||
* @param deliveredAmount Bolus amount that was delivered before the bolus was cancelled. In 0.1 IU units.
|
* @param deliveredImmediateAmount Bolus amount that was delivered before the bolus was cancelled. In 0.1 IU units.
|
||||||
* @param totalAmount Total bolus amount that was supposed to be delivered. In 0.1 IU units.
|
* @param totalImmediateAmount Total bolus amount that was supposed to be delivered. In 0.1 IU units.
|
||||||
*/
|
*/
|
||||||
class BolusCancelledByUserException(val deliveredAmount: Int, totalAmount: Int) :
|
class BolusCancelledByUserException(val deliveredImmediateAmount: Int, totalImmediateAmount: Int) :
|
||||||
BolusDeliveryException(
|
BolusDeliveryException(
|
||||||
totalAmount,
|
totalImmediateAmount,
|
||||||
"Bolus cancelled (delivered amount: ${deliveredAmount.toStringWithDecimal(1)} IU " +
|
"Bolus cancelled (delivered amount: ${deliveredImmediateAmount.toStringWithDecimal(1)} IU " +
|
||||||
"total programmed amount: ${totalAmount.toStringWithDecimal(1)} IU"
|
"total programmed amount: ${totalImmediateAmount.toStringWithDecimal(1)} IU"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown when the bolus delivery was aborted due to an error.
|
* Exception thrown when the bolus delivery was aborted due to an error.
|
||||||
*
|
*
|
||||||
* @param deliveredAmount Bolus amount that was delivered before the bolus was aborted. In 0.1 IU units.
|
* @param deliveredImmediateAmount Bolus amount that was delivered before the bolus was aborted. In 0.1 IU units.
|
||||||
* @param totalAmount Total bolus amount that was supposed to be delivered.
|
* @param totalImmediateAmount Total bolus amount that was supposed to be delivered.
|
||||||
*/
|
*/
|
||||||
class BolusAbortedDueToErrorException(deliveredAmount: Int, totalAmount: Int) :
|
class BolusAbortedDueToErrorException(deliveredImmediateAmount: Int, totalImmediateAmount: Int) :
|
||||||
BolusDeliveryException(
|
BolusDeliveryException(
|
||||||
totalAmount,
|
totalImmediateAmount,
|
||||||
"Bolus aborted due to an error (delivered amount: ${deliveredAmount.toStringWithDecimal(1)} IU " +
|
"Bolus aborted due to an error (delivered amount: ${deliveredImmediateAmount.toStringWithDecimal(1)} IU " +
|
||||||
"total programmed amount: ${totalAmount.toStringWithDecimal(1)} IU"
|
"total programmed amount: ${totalImmediateAmount.toStringWithDecimal(1)} IU"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -454,27 +458,25 @@ class Pump(
|
||||||
*
|
*
|
||||||
* If no TBR is active, [actualTbrDuration] is 0. If no TBR was expected to be active,
|
* If no TBR is active, [actualTbrDuration] is 0. If no TBR was expected to be active,
|
||||||
* [expectedTbrDuration] is 0.
|
* [expectedTbrDuration] is 0.
|
||||||
|
*
|
||||||
|
* [actualTbrPercentage] and [actualTbrDuration] are both null if a multiwave or extended
|
||||||
|
* bolus is active because the exact TBR percentage / duration are not shown on screen then.
|
||||||
*/
|
*/
|
||||||
class UnexpectedTbrStateException(
|
class UnexpectedTbrStateException(
|
||||||
val expectedTbrPercentage: Int,
|
val expectedTbrPercentage: Int,
|
||||||
val expectedTbrDuration: Int,
|
val expectedTbrDuration: Int,
|
||||||
val actualTbrPercentage: Int,
|
val actualTbrPercentage: Int?,
|
||||||
val actualTbrDuration: Int
|
val actualTbrDuration: Int?
|
||||||
) : ComboException(
|
) : ComboException(
|
||||||
|
if (actualTbrPercentage != null)
|
||||||
"Expected TBR: $expectedTbrPercentage% $expectedTbrDuration minutes ; " +
|
"Expected TBR: $expectedTbrPercentage% $expectedTbrDuration minutes ; " +
|
||||||
"actual TBR: $actualTbrPercentage% $actualTbrDuration minutes"
|
"actual TBR: $actualTbrPercentage% $actualTbrDuration minutes"
|
||||||
|
else if (expectedTbrPercentage == 100)
|
||||||
|
"Did not expect a TBR during active extended/multiwave bolus, observed one"
|
||||||
|
else
|
||||||
|
"Expected a TBR during active extended/multiwave bolus, did not observe one"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown when the main screen shows information about an active extended / multiwave bolus.
|
|
||||||
*
|
|
||||||
* These bolus type are currently not supported and cannot be handled properly.
|
|
||||||
*
|
|
||||||
* @property bolusInfo Information about the detected extended / multiwave bolus.
|
|
||||||
*/
|
|
||||||
class ExtendedOrMultiwaveBolusActiveException(val bolusInfo: MainScreenContent.ExtendedOrMultiwaveBolus) :
|
|
||||||
ComboException("Extended or multiwave bolus is active; bolus info: $bolusInfo")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reason for a standard bolus delivery.
|
* Reason for a standard bolus delivery.
|
||||||
*
|
*
|
||||||
|
@ -894,8 +896,6 @@ class Pump(
|
||||||
* @throws SettingPumpDatetimeFailedException if during the checks,
|
* @throws SettingPumpDatetimeFailedException if during the checks,
|
||||||
* the pump's datetime was found to be deviating too much from the
|
* the pump's datetime was found to be deviating too much from the
|
||||||
* actual current datetime, and adjusting the pump's datetime failed.
|
* actual current datetime, and adjusting the pump's datetime failed.
|
||||||
* @throws ExtendedOrMultiwaveBolusActiveException if an extended / multiwave
|
|
||||||
* bolus is active (these are shown on the main screen).
|
|
||||||
*/
|
*/
|
||||||
suspend fun connect(maxNumAttempts: Int? = DEFAULT_MAX_NUM_REGULAR_CONNECT_ATTEMPTS) {
|
suspend fun connect(maxNumAttempts: Int? = DEFAULT_MAX_NUM_REGULAR_CONNECT_ATTEMPTS) {
|
||||||
check(stateFlow.value == State.Disconnected) { "Attempted to connect to pump in the ${stateFlow.value} state" }
|
check(stateFlow.value == State.Disconnected) { "Attempted to connect to pump in the ${stateFlow.value} state" }
|
||||||
|
@ -933,7 +933,6 @@ class Pump(
|
||||||
// failed. That's because these exceptions indicate hard errors that
|
// failed. That's because these exceptions indicate hard errors that
|
||||||
// must be reported ASAP and disallow more connection attempts, at
|
// must be reported ASAP and disallow more connection attempts, at
|
||||||
// least attempts without notifying the user.
|
// least attempts without notifying the user.
|
||||||
is ExtendedOrMultiwaveBolusActiveException,
|
|
||||||
is SettingPumpDatetimeFailedException,
|
is SettingPumpDatetimeFailedException,
|
||||||
is AlertScreenException -> {
|
is AlertScreenException -> {
|
||||||
setState(State.Error(throwable = e, "Connection error"))
|
setState(State.Error(throwable = e, "Connection error"))
|
||||||
|
@ -1233,10 +1232,6 @@ class Pump(
|
||||||
* @throws UnexpectedTbrStateException if the TBR that is actually active
|
* @throws UnexpectedTbrStateException if the TBR that is actually active
|
||||||
* after this function finishes does not match the specified percentage
|
* after this function finishes does not match the specified percentage
|
||||||
* and duration.
|
* and duration.
|
||||||
* @throws ExtendedOrMultiwaveBolusActiveException if an extended / multiwave
|
|
||||||
* bolus is active after setting the TBR. (This should not normally happen,
|
|
||||||
* since it is not possible for users to set such a bolus while also setting
|
|
||||||
* the TBR, but is included for completeness.)
|
|
||||||
* @throws IllegalStateException if the current state is not
|
* @throws IllegalStateException if the current state is not
|
||||||
* [State.ReadyForCommands], or if the pump is suspended after setting the TBR.
|
* [State.ReadyForCommands], or if the pump is suspended after setting the TBR.
|
||||||
* @throws AlertScreenException if alerts occurs during this call, and they
|
* @throws AlertScreenException if alerts occurs during this call, and they
|
||||||
|
@ -1306,7 +1301,7 @@ class Pump(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Current status shows that there is no TBR ongoing. This is
|
// Current status shows that there is no TBR ongoing. This is
|
||||||
// therefore a redunant call. Handle this by expecting a 100%
|
// therefore a redundant call. Handle this by expecting a 100%
|
||||||
// basal rate to make sure the checks below don't throw anything.
|
// basal rate to make sure the checks below don't throw anything.
|
||||||
expectedTbrPercentage = 100
|
expectedTbrPercentage = 100
|
||||||
expectedTbrDuration = 0
|
expectedTbrDuration = 0
|
||||||
|
@ -1337,56 +1332,77 @@ class Pump(
|
||||||
is ParsedScreen.MainScreen -> mainScreen.content
|
is ParsedScreen.MainScreen -> mainScreen.content
|
||||||
else -> throw NoUsableRTScreenException()
|
else -> throw NoUsableRTScreenException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val (actualTbrPercentage, actualTbrDuration) = when (mainScreenContent) {
|
||||||
|
is MainScreenContent.Stopped ->
|
||||||
|
// This should never be reached. The Combo can switch to the Stopped
|
||||||
|
// state on its own, but only if an error occurs, and errors are
|
||||||
|
// already caught by ParsedDisplayFrameStream.getParsedDisplayFrame().
|
||||||
|
throw IllegalStateException("Combo is in the stopped state after setting TBR")
|
||||||
|
|
||||||
|
is MainScreenContent.ExtendedOrMultiwaveBolus -> {
|
||||||
|
if (mainScreenContent.tbrIsActive) {
|
||||||
|
// We have to go into the TBR menu to get details about the active TBR;
|
||||||
|
// the main screen does not show them when an extended/multiwave bolus
|
||||||
|
// is currently ongoing.
|
||||||
|
lookupActiveTbrDetails()
|
||||||
|
} else {
|
||||||
|
Pair(100, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is MainScreenContent.Normal ->
|
||||||
|
Pair(100, 0)
|
||||||
|
|
||||||
|
is MainScreenContent.Tbr ->
|
||||||
|
Pair(mainScreenContent.tbrPercentage, mainScreenContent.remainingTbrDurationInMinutes)
|
||||||
|
}
|
||||||
|
|
||||||
logger(LogLevel.DEBUG) {
|
logger(LogLevel.DEBUG) {
|
||||||
"Main screen content after setting TBR: $mainScreenContent; expected TBR " +
|
"Main screen content after setting TBR: $mainScreenContent; expected TBR " +
|
||||||
"percentage / duration: $expectedTbrPercentage / $expectedTbrDuration"
|
"percentage / duration: $expectedTbrPercentage / $expectedTbrDuration"
|
||||||
}
|
}
|
||||||
when (mainScreenContent) {
|
|
||||||
is MainScreenContent.Stopped ->
|
|
||||||
throw IllegalStateException("Combo is in the stopped state after setting TBR")
|
|
||||||
|
|
||||||
is MainScreenContent.ExtendedOrMultiwaveBolus ->
|
val tbrVisibleOnMainScreen = when (mainScreenContent) {
|
||||||
throw ExtendedOrMultiwaveBolusActiveException(mainScreenContent)
|
is MainScreenContent.Tbr -> true
|
||||||
|
is MainScreenContent.ExtendedOrMultiwaveBolus -> mainScreenContent.tbrIsActive
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
is MainScreenContent.Normal -> {
|
// Verify that the TBR state is OK according to these criteria:
|
||||||
if ((expectedTbrPercentage != 100) && (expectedTbrDuration >= 2)) {
|
//
|
||||||
// We expected a TBR to be active, but there isn't any;
|
// 1. TBR percentages match and the different between expected and actual duration is <= 4 minutes.
|
||||||
// we aren't seen any TBR main screen contents.
|
// (Allow for the 4-minute tolerance since a little while may have passed between setting the
|
||||||
// Only consider this an error if the duration is >2 minutes.
|
// TBR and reaching this location in the code.)
|
||||||
// Otherwise, this was a TBR that was about to end, so it
|
// 2. We expected a TBR to be active, but the main screen shows no TBR. If the expected TBR
|
||||||
// might have ended while these checks here were running.
|
// duration is <2 minutes, then this is still considered OK. That's because the TBR might
|
||||||
|
// have been one that was about to end, so while this code was ongoing, it may have ended.
|
||||||
|
//
|
||||||
|
// In any other case, assume that the TBR that is active is not OK.
|
||||||
|
val tbrStateIsOk =
|
||||||
|
if ((expectedTbrPercentage == actualTbrPercentage) && ((expectedTbrDuration - actualTbrDuration).absoluteValue <= 4)) {
|
||||||
|
logger(LogLevel.DEBUG) { "TBR percentages and durations match" }
|
||||||
|
true
|
||||||
|
} else if (!tbrVisibleOnMainScreen && (expectedTbrPercentage != 100) && (expectedTbrDuration < 2)) {
|
||||||
|
logger(LogLevel.DEBUG) { "Almost-expired TBR no longer visible on screen; assumed to have ended in the meantime" }
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
logger(LogLevel.ERROR) {
|
||||||
|
"Mismatch between expected TBR and actually active TBR; " +
|
||||||
|
"expected TBR percentage / duration: $expectedTbrPercentage / $expectedTbrDuration; " +
|
||||||
|
"actual TBR: percentage / remaining duration: $actualTbrPercentage / $actualTbrDuration"
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tbrStateIsOk) {
|
||||||
throw UnexpectedTbrStateException(
|
throw UnexpectedTbrStateException(
|
||||||
expectedTbrPercentage = expectedTbrPercentage,
|
expectedTbrPercentage = expectedTbrPercentage,
|
||||||
expectedTbrDuration = expectedTbrDuration,
|
expectedTbrDuration = expectedTbrDuration,
|
||||||
actualTbrPercentage = 100,
|
actualTbrPercentage = actualTbrPercentage,
|
||||||
actualTbrDuration = 0
|
actualTbrDuration = actualTbrDuration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
is MainScreenContent.Tbr -> {
|
|
||||||
if (expectedTbrPercentage == 100) {
|
|
||||||
// We expected the TBR to be cancelled, but it isn't.
|
|
||||||
throw UnexpectedTbrStateException(
|
|
||||||
expectedTbrPercentage = 100,
|
|
||||||
expectedTbrDuration = 0,
|
|
||||||
actualTbrPercentage = mainScreenContent.tbrPercentage,
|
|
||||||
actualTbrDuration = mainScreenContent.remainingTbrDurationInMinutes
|
|
||||||
)
|
|
||||||
} else if ((expectedTbrDuration - mainScreenContent.remainingTbrDurationInMinutes) > 2) {
|
|
||||||
// The current TBR duration does not match the programmed one.
|
|
||||||
// We allow a tolerance range of 2 minutes since a little while
|
|
||||||
// may have passed between setting the TBR and reaching this
|
|
||||||
// location in the code.
|
|
||||||
throw UnexpectedTbrStateException(
|
|
||||||
expectedTbrPercentage = expectedTbrPercentage,
|
|
||||||
expectedTbrDuration = expectedTbrDuration,
|
|
||||||
actualTbrPercentage = mainScreenContent.tbrPercentage,
|
|
||||||
actualTbrDuration = mainScreenContent.remainingTbrDurationInMinutes
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return@executeCommand result
|
return@executeCommand result
|
||||||
}
|
}
|
||||||
|
@ -1397,51 +1413,94 @@ class Pump(
|
||||||
val bolusDeliveryProgressFlow = bolusDeliveryProgressReporter.progressFlow
|
val bolusDeliveryProgressFlow = bolusDeliveryProgressReporter.progressFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instructs the pump to deliver the specified bolus amount.
|
* Instructs the pump to deliver a standard bolus with the specified amount.
|
||||||
*
|
*
|
||||||
* This function only delivers a standard bolus, no multi-wave / extended ones.
|
* This is equivalent to calling the full [deliverBolus] function with the bolus
|
||||||
* It is currently not known how to command the Combo to deliver those types.
|
* type set to [ApplicationLayer.CMDDeliverBolusType.STANDARD_BOLUS], a total
|
||||||
|
* bolus amount that is set to [bolusAmount], and the immediate amount and
|
||||||
|
* duration both set to 0.
|
||||||
*
|
*
|
||||||
* The function suspends until the bolus was fully delivered or an error occurred.
|
* @param bolusAmount Amount of insulin units the standard bolus shall deliver.
|
||||||
* In the latter case, an exception is thrown. During the delivery, the current
|
|
||||||
* status is periodically retrieved from the pump. [bolusStatusUpdateIntervalInMs]
|
|
||||||
* controls the status update interval. At each update, the bolus state is checked
|
|
||||||
* (that is, whether it is delivering, or it is done, or an error occurred etc.)
|
|
||||||
* The bolus amount that was delivered by that point is communicated via the
|
|
||||||
* [bolusDeliveryProgressFlow].
|
|
||||||
*
|
|
||||||
* To cancel the bolus, simply cancel the coroutine that is suspended by this function.
|
|
||||||
*
|
|
||||||
* Prior to the delivery, the number of units available in the reservoir is checked
|
|
||||||
* by looking at [statusFlow]. If there aren't enough IU in the reservoir, this
|
|
||||||
* function throws [InsufficientInsulinAvailableException].
|
|
||||||
*
|
|
||||||
* After the delivery, this function looks at the Combo's bolus history delta. That
|
|
||||||
* delta is expected to contain exactly one entry - the bolus that was just delivered.
|
|
||||||
* The details in that history delta entry are then emitted as
|
|
||||||
* [Event.StandardBolusInfused] via [onEvent].
|
|
||||||
* If there is no entry, [BolusNotDeliveredException] is thrown. If more than one
|
|
||||||
* bolus entry is detected, [UnaccountedBolusDetectedException] is thrown (this
|
|
||||||
* second case is not expected to ever happen, but is possible in theory). The
|
|
||||||
* history delta is looked at even if an exception is thrown (unless it is one
|
|
||||||
* of the exceptions that were just mentioned). This is because if there is an
|
|
||||||
* error _during_ a bolus delivery, then some insulin might have still be
|
|
||||||
* delivered, and there will be a [Event.StandardBolusInfused] history entry,
|
|
||||||
* probably just not with the insulin amount that was originally planned.
|
|
||||||
* It is still important to report that (partial) delivery, which is done
|
|
||||||
* via [onEvent] just as described above.
|
|
||||||
*
|
|
||||||
* Once that is completed, this function calls [updateStatus] to make sure the
|
|
||||||
* contents of [statusFlow] are up-to-date. A bolus delivery will at least
|
|
||||||
* change the value of [Status.availableUnitsInReservoir] (unless perhaps it
|
|
||||||
* is a very small bolus like 0.1 IU, since that value is given in whole IU units).
|
|
||||||
*
|
|
||||||
* @param bolusAmount Bolus amount to deliver. Note that this is given
|
|
||||||
* in 0.1 IU units, so for example, "57" means 5.7 IU. Valid range
|
|
||||||
* is 0.0 IU to 25.0 IU (that is, integer values 0-250).
|
|
||||||
* @param bolusReason Reason for this standard bolus.
|
* @param bolusReason Reason for this standard bolus.
|
||||||
* @param bolusStatusUpdateIntervalInMs Interval between status updates,
|
* @param bolusStatusUpdateIntervalInMs Interval between status updates,
|
||||||
* in milliseconds. Must be at least 1
|
* in milliseconds. Must be at least 1
|
||||||
|
*/
|
||||||
|
suspend fun deliverBolus(
|
||||||
|
bolusAmount: Int,
|
||||||
|
bolusReason: StandardBolusReason,
|
||||||
|
bolusStatusUpdateIntervalInMs: Long = 250
|
||||||
|
) = deliverBolus(
|
||||||
|
totalBolusAmount = bolusAmount,
|
||||||
|
immediateBolusAmount = 0,
|
||||||
|
durationInMinutes = 0,
|
||||||
|
standardBolusReason = bolusReason,
|
||||||
|
bolusType = ApplicationLayer.CMDDeliverBolusType.STANDARD_BOLUS,
|
||||||
|
bolusStatusUpdateIntervalInMs = bolusStatusUpdateIntervalInMs
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructs the pump to deliver a bolus.
|
||||||
|
*
|
||||||
|
* The function suspends until the immediate portion of the bolus was fully delivered,
|
||||||
|
* or when an error occurred. In the latter case, an exception is thrown.
|
||||||
|
*
|
||||||
|
* The bolus delivey is split in two parts: the immediate portion and the extended
|
||||||
|
* portion. The immediate portion is infused right away, as fast as the Combo is able
|
||||||
|
* to do so. The extended portion is delivered over the specified [durationInMinutes],
|
||||||
|
* and behaves much like a TBR.
|
||||||
|
*
|
||||||
|
* During the delivery of the immediate portion, the current status is periodically
|
||||||
|
* retrieved from the pump. [bolusStatusUpdateIntervalInMs] controls the status update
|
||||||
|
* interval. At each update, the bolus state is checked (that is, whether it is delivering,
|
||||||
|
* or whethr it is done, or an error occurred etc.) The bolus amount that was delivered
|
||||||
|
* by that point is communicated via the [bolusDeliveryProgressFlow].
|
||||||
|
*
|
||||||
|
* To cancel the immediate delivery of the bolus, simply cancel the coroutine that is
|
||||||
|
* suspended by this function.
|
||||||
|
*
|
||||||
|
* IMPORTANT: The extended portion _cannot_ be canceled that way; there is no way of
|
||||||
|
* doing that other than for the Combo to be stopped and started again.
|
||||||
|
*
|
||||||
|
* Prior to the delivery, the number of units available in the reservoir is checked
|
||||||
|
* by looking at [statusFlow] and compared against [totalBolusAmount]. If there aren't
|
||||||
|
* enough IU in the reservoir, this function throws [InsufficientInsulinAvailableException].
|
||||||
|
*
|
||||||
|
* After the delivery, this function looks at the Combo's bolus history delta. That
|
||||||
|
* delta is expected to contain exactly one entry - the bolus that was just delivered
|
||||||
|
* or started (depending on the bolus type). The details in that history delta entry
|
||||||
|
* are then emitted via [onEvent] as [Event.StandardBolusInfused] for a standard bolus.
|
||||||
|
* Extended boluses generate [Event.ExtendedBolusStarted]. Likewise, multiwave boluses
|
||||||
|
* generate [Event.MultiwaveBolusStarted].
|
||||||
|
*
|
||||||
|
* If there is no entry, [BolusNotDeliveredException] is thrown. If a standard bolus
|
||||||
|
* was delivered, and more than one bolus entry is detected, [UnaccountedBolusDetectedException]
|
||||||
|
* is thrown. The history delta is looked at even if an exception is thrown (unless
|
||||||
|
* it is one of the exceptions that were just mentioned). This is because if there is
|
||||||
|
* an error _during_ an immediate delivery, then some insulin might have still been
|
||||||
|
* delivered, and there will be a corresponding history entry, probably just not with
|
||||||
|
* the insulin amount that was originally planned. It is still important to report
|
||||||
|
* that (partial) delivery, which is done with [onEvent] just as described above.
|
||||||
|
*
|
||||||
|
* Once that is completed, this function calls [updateStatus] to make sure the contents
|
||||||
|
* of [statusFlow] are up-to-date. A bolus delivery will at least change the value of
|
||||||
|
* [Status.availableUnitsInReservoir] (unless perhaps it is a very small bolus like
|
||||||
|
* 0.1 IU, since the reservoir level is given inwhole IU units).
|
||||||
|
*
|
||||||
|
* @param totalBolusAmount Total bolus amount to deliver (that is, the sum of the immediate
|
||||||
|
* and extended portions). Note that this is given in 0.1 IU units, so for example,
|
||||||
|
* "57" means 5.7 IU. Valid range is 0.0 IU to 25.0 IU (that is, integer values 0-250).
|
||||||
|
* @param immediateBolusAmount The amount to deliver immediately. This is only used
|
||||||
|
* [bolusType] is [CMDDeliverBolusType.MULTIWAVE_BOLUS], and is ignored otherwise.
|
||||||
|
* When delivering a multiwave bolus, this value must be >= 1 and < [totalBolusAmount].
|
||||||
|
* @param durationInMinutes The duration of the extended bolus or the extended portion
|
||||||
|
* of the multiwave bolus. If [bolusType] is set to [CMDDeliverBolusType.STANDARD_BOLUS],
|
||||||
|
* this value is ignored. Otherwise, it must be at least 15. Maximum possible value
|
||||||
|
* is 720 (= 12 hours).
|
||||||
|
* @param standardBolusReason Reason for the standard bolus. If [bolusType] is not
|
||||||
|
* [CMDDeliverBolusType.STANDARD_BOLUS], this value is ignored.
|
||||||
|
* @param bolusType Type of the bolus.
|
||||||
|
* @param bolusStatusUpdateIntervalInMs Interval between status updates,
|
||||||
|
* in milliseconds. Must be at least 1.
|
||||||
* @throws BolusNotDeliveredException if the pump did not deliver the bolus.
|
* @throws BolusNotDeliveredException if the pump did not deliver the bolus.
|
||||||
* This typically happens because the pump is currently stopped.
|
* This typically happens because the pump is currently stopped.
|
||||||
* @throws BolusCancelledByUserException when the bolus was cancelled by the user.
|
* @throws BolusCancelledByUserException when the bolus was cancelled by the user.
|
||||||
|
@ -1451,33 +1510,77 @@ class Pump(
|
||||||
* more than one bolus is reported in the Combo's bolus history delta.
|
* more than one bolus is reported in the Combo's bolus history delta.
|
||||||
* @throws InsufficientInsulinAvailableException if the reservoir does not
|
* @throws InsufficientInsulinAvailableException if the reservoir does not
|
||||||
* have enough IUs left for this bolus.
|
* have enough IUs left for this bolus.
|
||||||
* @throws IllegalArgumentException if [bolusAmount] is not in the 0-250 range,
|
* @throws IllegalArgumentException if [totalBolusAmount] is not in the 0-250 range, or
|
||||||
* or if [bolusStatusUpdateIntervalInMs] is less than 1.
|
* if [bolusStatusUpdateIntervalInMs] is less than 1, or if [immediateBolusAmount] exceeds
|
||||||
|
* [totalBolusAmount] when delivering a multiwave bolus, or if [durationInMinutes] is <15
|
||||||
|
* when [bolusType] is set to anything other than [CMDDeliverBolusType.STANDARD_BOLUS].
|
||||||
* @throws IllegalStateException if the current state is not
|
* @throws IllegalStateException if the current state is not
|
||||||
* [State.ReadyForCommands].
|
* [State.ReadyForCommands].
|
||||||
* @throws AlertScreenException if alerts occurs during this call, and they
|
* @throws AlertScreenException if alerts occurs during this call, and they
|
||||||
* aren't a W6 warning (those are handled by this function).
|
* aren't a W6 warning (those are handled by this function).
|
||||||
* @throws ExtendedOrMultiwaveBolusActiveException if an extended / multiwave
|
|
||||||
* bolus is active after delivering this standard bolus. (This should not
|
|
||||||
* normally happen, since it is not possible for users to set such a bolus
|
|
||||||
* while also delivering a standard bolus the TBR, but is included for
|
|
||||||
* completeness.)
|
|
||||||
*/
|
*/
|
||||||
suspend fun deliverBolus(bolusAmount: Int, bolusReason: StandardBolusReason, bolusStatusUpdateIntervalInMs: Long = 250) = executeCommand(
|
suspend fun deliverBolus(
|
||||||
|
totalBolusAmount: Int,
|
||||||
|
immediateBolusAmount: Int,
|
||||||
|
durationInMinutes: Int,
|
||||||
|
standardBolusReason: StandardBolusReason,
|
||||||
|
bolusType: ApplicationLayer.CMDDeliverBolusType,
|
||||||
|
bolusStatusUpdateIntervalInMs: Long = 250
|
||||||
|
) = executeCommand(
|
||||||
// Instruct executeCommand() to not set the mode on its own.
|
// Instruct executeCommand() to not set the mode on its own.
|
||||||
// This function itself switches manually between the
|
// This function itself switches manually between the
|
||||||
// command and remote terminal modes.
|
// command and remote terminal modes.
|
||||||
pumpMode = null,
|
pumpMode = null,
|
||||||
isIdempotent = false,
|
isIdempotent = false,
|
||||||
description = DeliveringBolusCommandDesc(bolusAmount, bolusReason)
|
description = DeliveringBolusCommandDesc(
|
||||||
|
totalBolusAmount,
|
||||||
|
immediateBolusAmount,
|
||||||
|
durationInMinutes,
|
||||||
|
standardBolusReason,
|
||||||
|
bolusType
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
require((bolusAmount > 0) && (bolusAmount <= 250)) {
|
require((totalBolusAmount > 0) && (totalBolusAmount <= 250)) {
|
||||||
"Invalid bolus amount $bolusAmount (${bolusAmount.toStringWithDecimal(1)} IU)"
|
"Invalid bolus amount $totalBolusAmount (${totalBolusAmount.toStringWithDecimal(1)} IU)"
|
||||||
}
|
}
|
||||||
require(bolusStatusUpdateIntervalInMs >= 1) {
|
require(bolusStatusUpdateIntervalInMs >= 1) {
|
||||||
"Invalid bolus status update interval $bolusStatusUpdateIntervalInMs"
|
"Invalid bolus status update interval $bolusStatusUpdateIntervalInMs"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (bolusType) {
|
||||||
|
CMDDeliverBolusType.STANDARD_BOLUS -> Unit
|
||||||
|
|
||||||
|
CMDDeliverBolusType.EXTENDED_BOLUS ->
|
||||||
|
require(
|
||||||
|
(durationInMinutes >= 15) &&
|
||||||
|
(durationInMinutes <= 720) &&
|
||||||
|
((durationInMinutes % 15) == 0)
|
||||||
|
) {
|
||||||
|
"extended bolus duration must be in the 15-720 range and an integer multiple of 15; " +
|
||||||
|
"actual duration: $durationInMinutes"
|
||||||
|
}
|
||||||
|
|
||||||
|
CMDDeliverBolusType.MULTIWAVE_BOLUS -> {
|
||||||
|
require(
|
||||||
|
(durationInMinutes >= 15) &&
|
||||||
|
(durationInMinutes <= 720) &&
|
||||||
|
((durationInMinutes % 15) == 0)
|
||||||
|
) {
|
||||||
|
"multiwave bolus duration must be in the 15-720 range and an integer multiple of 15; " +
|
||||||
|
"actual duration: $durationInMinutes"
|
||||||
|
}
|
||||||
|
require(immediateBolusAmount >= 1) {
|
||||||
|
"immediate bolus portion of multiwave bolus must be at least 0.1 IU; actual" +
|
||||||
|
"amount: ${immediateBolusAmount.toStringWithDecimal(1)}"
|
||||||
|
}
|
||||||
|
require(immediateBolusAmount < totalBolusAmount) {
|
||||||
|
"immediate bolus duration must be < total bolus amount; actual immediate/total " +
|
||||||
|
"amount: ${immediateBolusAmount.toStringWithDecimal(1)}" +
|
||||||
|
" / ${totalBolusAmount.toStringWithDecimal(1)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check that there's enough insulin in the reservoir.
|
// Check that there's enough insulin in the reservoir.
|
||||||
statusFlow.value?.let { status ->
|
statusFlow.value?.let { status ->
|
||||||
// Round the bolus amount. The reservoir fill level is given in whole IUs
|
// Round the bolus amount. The reservoir fill level is given in whole IUs
|
||||||
|
@ -1488,25 +1591,30 @@ class Pump(
|
||||||
// IU units, we'd truncate the 0.3 IU from the bolus, and the check
|
// IU units, we'd truncate the 0.3 IU from the bolus, and the check
|
||||||
// would think that it's OK, because the reservoir has 1 IU. If we instead
|
// would think that it's OK, because the reservoir has 1 IU. If we instead
|
||||||
// round up, any fractional IU will be taken into account correctly.
|
// round up, any fractional IU will be taken into account correctly.
|
||||||
val roundedBolusIU = (bolusAmount + 9) / 10
|
val roundedBolusIU = (totalBolusAmount + 9) / 10
|
||||||
logger(LogLevel.DEBUG) {
|
logger(LogLevel.DEBUG) {
|
||||||
"Checking if there is enough insulin in reservoir; reservoir fill level: " +
|
"Checking if there is enough insulin in reservoir; reservoir fill level: " +
|
||||||
"${status.availableUnitsInReservoir} IU; bolus amount: ${bolusAmount.toStringWithDecimal(1)} IU" +
|
"${status.availableUnitsInReservoir} IU; bolus amount: ${totalBolusAmount.toStringWithDecimal(1)} IU" +
|
||||||
"(rounded: $roundedBolusIU IU)"
|
"(rounded: $roundedBolusIU IU)"
|
||||||
}
|
}
|
||||||
if (status.availableUnitsInReservoir < roundedBolusIU)
|
if (status.availableUnitsInReservoir < roundedBolusIU)
|
||||||
throw InsufficientInsulinAvailableException(bolusAmount, status.availableUnitsInReservoir)
|
throw InsufficientInsulinAvailableException(totalBolusAmount, status.availableUnitsInReservoir)
|
||||||
} ?: throw IllegalStateException("Cannot deliver bolus without a known pump status")
|
} ?: throw IllegalStateException("Cannot deliver bolus without a known pump status")
|
||||||
|
|
||||||
// Switch to COMMAND mode for the actual bolus delivery
|
// Switch to COMMAND mode for the actual bolus delivery
|
||||||
// and for tracking the bolus progress below.
|
// and for tracking the bolus progress below.
|
||||||
pumpIO.switchMode(PumpIO.Mode.COMMAND)
|
pumpIO.switchMode(PumpIO.Mode.COMMAND)
|
||||||
|
|
||||||
logger(LogLevel.DEBUG) { "Beginning bolus delivery of ${bolusAmount.toStringWithDecimal(1)} IU" }
|
logger(LogLevel.DEBUG) { "Beginning bolus delivery of ${totalBolusAmount.toStringWithDecimal(1)} IU" }
|
||||||
val didDeliver = pumpIO.deliverCMDStandardBolus(bolusAmount)
|
val didDeliver = pumpIO.deliverCMDStandardBolus(
|
||||||
|
totalBolusAmount,
|
||||||
|
immediateBolusAmount,
|
||||||
|
durationInMinutes,
|
||||||
|
bolusType
|
||||||
|
)
|
||||||
if (!didDeliver) {
|
if (!didDeliver) {
|
||||||
logger(LogLevel.ERROR) { "Bolus delivery did not commence" }
|
logger(LogLevel.ERROR) { "Bolus delivery did not commence" }
|
||||||
throw BolusNotDeliveredException(bolusAmount)
|
throw BolusNotDeliveredException(totalBolusAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
bolusDeliveryProgressReporter.reset(Unit)
|
bolusDeliveryProgressReporter.reset(Unit)
|
||||||
|
@ -1515,10 +1623,19 @@ class Pump(
|
||||||
|
|
||||||
var bolusFinishedCompletely = false
|
var bolusFinishedCompletely = false
|
||||||
|
|
||||||
// The Combo does not send bolus progress information on its own. Instead,
|
|
||||||
// we have to regularly poll the current bolus status. Do that in this loop.
|
|
||||||
// The bolusStatusUpdateIntervalInMs value controls how often we poll.
|
|
||||||
try {
|
try {
|
||||||
|
// The Combo does not send immediate bolus delivery information on its own.
|
||||||
|
// Instead, we have to regularly poll the current bolus status. Do that in
|
||||||
|
// this loop. The bolusStatusUpdateIntervalInMs value controls how often we
|
||||||
|
// poll. Only do this for standard and multiwave boluses, since the extended
|
||||||
|
// bolus has no immediate delivery portion.
|
||||||
|
|
||||||
|
if (bolusType != CMDDeliverBolusType.EXTENDED_BOLUS) {
|
||||||
|
val expectedImmediateAmount = if (bolusType == CMDDeliverBolusType.STANDARD_BOLUS)
|
||||||
|
totalBolusAmount
|
||||||
|
else
|
||||||
|
immediateBolusAmount
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
delay(bolusStatusUpdateIntervalInMs)
|
delay(bolusStatusUpdateIntervalInMs)
|
||||||
|
|
||||||
|
@ -1527,37 +1644,40 @@ class Pump(
|
||||||
logger(LogLevel.VERBOSE) { "Got current bolus delivery status: $status" }
|
logger(LogLevel.VERBOSE) { "Got current bolus delivery status: $status" }
|
||||||
|
|
||||||
val deliveredAmount = when (status.deliveryState) {
|
val deliveredAmount = when (status.deliveryState) {
|
||||||
ApplicationLayer.CMDBolusDeliveryState.DELIVERING -> bolusAmount - status.remainingAmount
|
ApplicationLayer.CMDBolusDeliveryState.DELIVERING -> expectedImmediateAmount - status.remainingAmount
|
||||||
ApplicationLayer.CMDBolusDeliveryState.DELIVERED -> bolusAmount
|
ApplicationLayer.CMDBolusDeliveryState.DELIVERED -> expectedImmediateAmount
|
||||||
ApplicationLayer.CMDBolusDeliveryState.CANCELLED_BY_USER -> {
|
ApplicationLayer.CMDBolusDeliveryState.CANCELLED_BY_USER -> {
|
||||||
logger(LogLevel.DEBUG) { "Bolus cancelled by user" }
|
logger(LogLevel.DEBUG) { "Bolus cancelled by user" }
|
||||||
throw BolusCancelledByUserException(
|
throw BolusCancelledByUserException(
|
||||||
deliveredAmount = bolusAmount - status.remainingAmount,
|
deliveredImmediateAmount = expectedImmediateAmount - status.remainingAmount,
|
||||||
totalAmount = bolusAmount
|
totalImmediateAmount = expectedImmediateAmount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationLayer.CMDBolusDeliveryState.ABORTED_DUE_TO_ERROR -> {
|
ApplicationLayer.CMDBolusDeliveryState.ABORTED_DUE_TO_ERROR -> {
|
||||||
logger(LogLevel.ERROR) { "Bolus aborted due to a delivery error" }
|
logger(LogLevel.ERROR) { "Bolus aborted due to a delivery error" }
|
||||||
throw BolusAbortedDueToErrorException(
|
throw BolusAbortedDueToErrorException(
|
||||||
deliveredAmount = bolusAmount - status.remainingAmount,
|
deliveredImmediateAmount = expectedImmediateAmount - status.remainingAmount,
|
||||||
totalAmount = bolusAmount
|
totalImmediateAmount = expectedImmediateAmount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> continue
|
else -> continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bolusDeliveryProgressReporter.setCurrentProgressStage(
|
bolusDeliveryProgressReporter.setCurrentProgressStage(
|
||||||
RTCommandProgressStage.DeliveringBolus(
|
RTCommandProgressStage.DeliveringBolus(
|
||||||
deliveredAmount = deliveredAmount,
|
deliveredImmediateAmount = deliveredAmount,
|
||||||
totalAmount = bolusAmount
|
totalImmediateAmount = expectedImmediateAmount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (deliveredAmount >= bolusAmount) {
|
if (deliveredAmount >= expectedImmediateAmount) {
|
||||||
bolusDeliveryProgressReporter.setCurrentProgressStage(BasicProgressStage.Finished)
|
bolusDeliveryProgressReporter.setCurrentProgressStage(BasicProgressStage.Finished)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bolusFinishedCompletely = true
|
bolusFinishedCompletely = true
|
||||||
} catch (e: BolusDeliveryException) {
|
} catch (e: BolusDeliveryException) {
|
||||||
|
@ -1605,19 +1725,21 @@ class Pump(
|
||||||
if (historyDelta.isEmpty()) {
|
if (historyDelta.isEmpty()) {
|
||||||
if (bolusFinishedCompletely) {
|
if (bolusFinishedCompletely) {
|
||||||
logger(LogLevel.ERROR) { "Bolus delivery did not actually occur" }
|
logger(LogLevel.ERROR) { "Bolus delivery did not actually occur" }
|
||||||
throw BolusNotDeliveredException(bolusAmount)
|
throw BolusNotDeliveredException(totalBolusAmount)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var numStandardBolusInfusedEntries = 0
|
var numRelevantBolusEntries = 0
|
||||||
var unexpectedBolusEntriesDetected = false
|
var unexpectedBolusEntriesDetected = false
|
||||||
scanHistoryDeltaForBolusToEmit(
|
scanHistoryDeltaForBolusToEmit(
|
||||||
historyDelta,
|
historyDelta,
|
||||||
reasonForLastStandardBolusInfusion = bolusReason
|
reasonForLastStandardBolusInfusion = standardBolusReason
|
||||||
) { entry ->
|
) { entry ->
|
||||||
|
when (bolusType) {
|
||||||
|
CMDDeliverBolusType.STANDARD_BOLUS ->
|
||||||
when (val detail = entry.detail) {
|
when (val detail = entry.detail) {
|
||||||
is CMDHistoryEventDetail.StandardBolusInfused -> {
|
is CMDHistoryEventDetail.StandardBolusInfused -> {
|
||||||
numStandardBolusInfusedEntries++
|
numRelevantBolusEntries++
|
||||||
if (numStandardBolusInfusedEntries > 1)
|
if (numRelevantBolusEntries > 1)
|
||||||
unexpectedBolusEntriesDetected = true
|
unexpectedBolusEntriesDetected = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1630,12 +1752,41 @@ class Pump(
|
||||||
unexpectedBolusEntriesDetected = true
|
unexpectedBolusEntriesDetected = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CMDDeliverBolusType.EXTENDED_BOLUS ->
|
||||||
|
when (val detail = entry.detail) {
|
||||||
|
is CMDHistoryEventDetail.ExtendedBolusStarted -> {
|
||||||
|
numRelevantBolusEntries++
|
||||||
|
if (numRelevantBolusEntries > 1)
|
||||||
|
unexpectedBolusEntriesDetected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (detail.isBolusDetail)
|
||||||
|
unexpectedBolusEntriesDetected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CMDDeliverBolusType.MULTIWAVE_BOLUS ->
|
||||||
|
when (val detail = entry.detail) {
|
||||||
|
is CMDHistoryEventDetail.MultiwaveBolusStarted -> {
|
||||||
|
numRelevantBolusEntries++
|
||||||
|
if (numRelevantBolusEntries > 1)
|
||||||
|
unexpectedBolusEntriesDetected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (detail.isBolusDetail)
|
||||||
|
unexpectedBolusEntriesDetected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bolusFinishedCompletely) {
|
if (bolusFinishedCompletely) {
|
||||||
if (numStandardBolusInfusedEntries == 0) {
|
if (numRelevantBolusEntries == 0) {
|
||||||
logger(LogLevel.ERROR) { "History delta did not contain an entry about bolus infusion" }
|
logger(LogLevel.ERROR) { "History delta did not contain an entry about bolus delivery" }
|
||||||
throw BolusNotDeliveredException(bolusAmount)
|
throw BolusNotDeliveredException(totalBolusAmount)
|
||||||
} else if (unexpectedBolusEntriesDetected) {
|
} else if (unexpectedBolusEntriesDetected) {
|
||||||
logger(LogLevel.ERROR) { "History delta contained unexpected additional bolus entries" }
|
logger(LogLevel.ERROR) { "History delta contained unexpected additional bolus entries" }
|
||||||
throw UnaccountedBolusDetectedException()
|
throw UnaccountedBolusDetectedException()
|
||||||
|
@ -1747,7 +1898,10 @@ class Pump(
|
||||||
// the button, meaning that it will always press the button at least initially,
|
// the button, meaning that it will always press the button at least initially,
|
||||||
// moving to entry #2 in the TDD history. Thus, if we don't look at the screen now,
|
// moving to entry #2 in the TDD history. Thus, if we don't look at the screen now,
|
||||||
// we miss entry #1, which is the current day.
|
// we miss entry #1, which is the current day.
|
||||||
val firstTDDScreen = navigateToRTScreen(rtNavigationContext, ParsedScreen.MyDataDailyTotalsScreen::class, pumpSuspended) as ParsedScreen.MyDataDailyTotalsScreen
|
val firstTDDScreen = navigateToRTScreen(
|
||||||
|
rtNavigationContext,
|
||||||
|
ParsedScreen.MyDataDailyTotalsScreen::class,
|
||||||
|
pumpSuspended) as ParsedScreen.MyDataDailyTotalsScreen
|
||||||
processTDDScreen(firstTDDScreen)
|
processTDDScreen(firstTDDScreen)
|
||||||
|
|
||||||
longPressRTButtonUntil(rtNavigationContext, RTNavigationButton.DOWN) { parsedScreen ->
|
longPressRTButtonUntil(rtNavigationContext, RTNavigationButton.DOWN) { parsedScreen ->
|
||||||
|
@ -1788,8 +1942,6 @@ class Pump(
|
||||||
* [State.Suspended] or [State.ReadyForCommands].
|
* [State.Suspended] or [State.ReadyForCommands].
|
||||||
* @throws AlertScreenException if alerts occurs during this call, and
|
* @throws AlertScreenException if alerts occurs during this call, and
|
||||||
* they aren't a W6 warning (those are handled by this function).
|
* they aren't a W6 warning (those are handled by this function).
|
||||||
* @throws ExtendedOrMultiwaveBolusActiveException if an extended / multiwave
|
|
||||||
* bolus is active (these are shown on the main screen).
|
|
||||||
*/
|
*/
|
||||||
suspend fun updateStatus() = updateStatusImpl(
|
suspend fun updateStatus() = updateStatusImpl(
|
||||||
allowExecutionWhileSuspended = true,
|
allowExecutionWhileSuspended = true,
|
||||||
|
@ -2523,7 +2675,8 @@ class Pump(
|
||||||
val expectedCurrentTbrPercentage = currentTbrState.tbr.percentage
|
val expectedCurrentTbrPercentage = currentTbrState.tbr.percentage
|
||||||
val actualCurrentTbrPercentage = status.tbrPercentage
|
val actualCurrentTbrPercentage = status.tbrPercentage
|
||||||
val elapsedTimeSinceTbrStart = now - currentTbrState.tbr.timestamp
|
val elapsedTimeSinceTbrStart = now - currentTbrState.tbr.timestamp
|
||||||
val expectedRemainingDurationInMinutes = currentTbrState.tbr.durationInMinutes - elapsedTimeSinceTbrStart.inWholeMinutes.toInt()
|
val expectedRemainingDurationInMinutes =
|
||||||
|
currentTbrState.tbr.durationInMinutes - elapsedTimeSinceTbrStart.inWholeMinutes.toInt()
|
||||||
val actualRemainingDurationInMinutes = status.remainingTbrDurationInMinutes
|
val actualRemainingDurationInMinutes = status.remainingTbrDurationInMinutes
|
||||||
|
|
||||||
// The remaining duration check uses a tolerance range of 10 minutes, since
|
// The remaining duration check uses a tolerance range of 10 minutes, since
|
||||||
|
@ -2765,7 +2918,8 @@ class Pump(
|
||||||
|
|
||||||
numRetrievedFactors++
|
numRetrievedFactors++
|
||||||
logger(LogLevel.DEBUG) {
|
logger(LogLevel.DEBUG) {
|
||||||
"Got basal profile factor #$factorIndexOnScreen : $factor; $numRetrievedFactors factor(s) read and $numObservedScreens screen(s) observed thus far"
|
"Got basal profile factor #$factorIndexOnScreen : $factor; $numRetrievedFactors " +
|
||||||
|
"factor(s) read and $numObservedScreens screen(s) observed thus far"
|
||||||
}
|
}
|
||||||
|
|
||||||
getBasalProfileReporter.setCurrentProgressStage(
|
getBasalProfileReporter.setCurrentProgressStage(
|
||||||
|
@ -3192,6 +3346,23 @@ class Pump(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun lookupActiveTbrDetails(): Pair<Int, Int> {
|
||||||
|
// Go to the TBR percentage screen, which shows both percentage and remaining duration.
|
||||||
|
val tbrPercentageScreen = navigateToRTScreen(
|
||||||
|
rtNavigationContext,
|
||||||
|
ParsedScreen.TemporaryBasalRatePercentageScreen::class,
|
||||||
|
pumpSuspended
|
||||||
|
) as? ParsedScreen.TemporaryBasalRatePercentageScreen ?: throw NoUsableRTScreenException()
|
||||||
|
// The getParsedDisplayFrame() calls inside navigateToRTScreen()
|
||||||
|
// should already filter out blinked-out screens.
|
||||||
|
check(!tbrPercentageScreen.isBlinkedOut)
|
||||||
|
|
||||||
|
// Now get back to the main menu to return back to the initial state.
|
||||||
|
navigateToRTScreen(rtNavigationContext, ParsedScreen.MainScreen::class, pumpSuspended)
|
||||||
|
|
||||||
|
return Pair(tbrPercentageScreen.percentage ?: 100, tbrPercentageScreen.remainingDurationInMinutes ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun updateStatusByReadingMainAndQuickinfoScreens(switchStatesIfNecessary: Boolean) {
|
private suspend fun updateStatusByReadingMainAndQuickinfoScreens(switchStatesIfNecessary: Boolean) {
|
||||||
val mainScreen = navigateToRTScreen(rtNavigationContext, ParsedScreen.MainScreen::class, pumpSuspended)
|
val mainScreen = navigateToRTScreen(rtNavigationContext, ParsedScreen.MainScreen::class, pumpSuspended)
|
||||||
|
|
||||||
|
@ -3265,8 +3436,27 @@ class Pump(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MainScreenContent.ExtendedOrMultiwaveBolus ->
|
is MainScreenContent.ExtendedOrMultiwaveBolus -> {
|
||||||
throw ExtendedOrMultiwaveBolusActiveException(mainScreenContent)
|
val (tbrPercentage, remainingTbrDurationInMinutes) = if (mainScreenContent.tbrIsActive) {
|
||||||
|
lookupActiveTbrDetails()
|
||||||
|
} else {
|
||||||
|
Pair(100, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Status(
|
||||||
|
availableUnitsInReservoir = quickinfo.availableUnits,
|
||||||
|
activeBasalProfileNumber = mainScreenContent.activeBasalProfileNumber,
|
||||||
|
currentBasalRateFactor = if (tbrPercentage != 0)
|
||||||
|
mainScreenContent.currentBasalRateFactor * 100 / tbrPercentage
|
||||||
|
else
|
||||||
|
0,
|
||||||
|
tbrOngoing = (tbrPercentage != 100),
|
||||||
|
remainingTbrDurationInMinutes = remainingTbrDurationInMinutes,
|
||||||
|
tbrPercentage = tbrPercentage,
|
||||||
|
reservoirState = quickinfo.reservoirState,
|
||||||
|
batteryState = mainScreenContent.batteryState
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (switchStatesIfNecessary) {
|
if (switchStatesIfNecessary) {
|
||||||
|
|
|
@ -449,8 +449,7 @@ suspend fun longPressRTButtonUntil(
|
||||||
// Record the screen we just saw so we can return it.
|
// Record the screen we just saw so we can return it.
|
||||||
lastParsedScreen = parsedScreen
|
lastParsedScreen = parsedScreen
|
||||||
return@startLongButtonPress false
|
return@startLongButtonPress false
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
return@startLongButtonPress true
|
return@startLongButtonPress true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1005,7 +1004,9 @@ suspend fun navigateToRTScreen(
|
||||||
// when remaining TBR duration is shown on the main screen and the
|
// when remaining TBR duration is shown on the main screen and the
|
||||||
// duration happens to change during this loop. If this occurs,
|
// duration happens to change during this loop. If this occurs,
|
||||||
// skip the redundant screen.
|
// skip the redundant screen.
|
||||||
if ((previousScreenType != null) && (previousScreenType == parsedScreen::class)) {
|
if ((parsedScreen::class != ParsedScreen.UnrecognizedScreen::class) &&
|
||||||
|
(previousScreenType != null) &&
|
||||||
|
(previousScreenType == parsedScreen::class)) {
|
||||||
logger(LogLevel.DEBUG) { "Got a screen of the same type ${parsedScreen::class}; skipping" }
|
logger(LogLevel.DEBUG) { "Got a screen of the same type ${parsedScreen::class}; skipping" }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import info.nightscout.comboctl.base.ComboException
|
||||||
import info.nightscout.comboctl.base.DisplayFrame
|
import info.nightscout.comboctl.base.DisplayFrame
|
||||||
import info.nightscout.comboctl.base.combinedDateTime
|
import info.nightscout.comboctl.base.combinedDateTime
|
||||||
import info.nightscout.comboctl.base.timeWithoutDate
|
import info.nightscout.comboctl.base.timeWithoutDate
|
||||||
import kotlin.reflect.KClassifier
|
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.datetime.atTime
|
import kotlinx.datetime.atTime
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
|
|
||||||
/*****************************************
|
/*****************************************
|
||||||
*** Screen and screen content classes ***
|
*** Screen and screen content classes ***
|
||||||
|
@ -1416,7 +1416,7 @@ class ExtendedAndMultiwaveBolusMainScreenParser : Parser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val batteryState = batteryStateFromSymbol(
|
val batteryState = batteryStateFromSymbol(
|
||||||
if (parseResult.size >= 7) parseResult.valueAt<Glyph.SmallSymbol>(5).symbol else null
|
if (parseResult.size >= 7) parseResult.valueAt<Glyph.SmallSymbol>(6).symbol else null
|
||||||
)
|
)
|
||||||
|
|
||||||
val remainingBolusDuration = parseResult.valueAt<LocalDateTime>(0)
|
val remainingBolusDuration = parseResult.valueAt<LocalDateTime>(0)
|
||||||
|
|
|
@ -3,11 +3,11 @@ package info.nightscout.comboctl.base
|
||||||
import info.nightscout.comboctl.base.testUtils.TestBluetoothDevice
|
import info.nightscout.comboctl.base.testUtils.TestBluetoothDevice
|
||||||
import info.nightscout.comboctl.base.testUtils.TestPumpStateStore
|
import info.nightscout.comboctl.base.testUtils.TestPumpStateStore
|
||||||
import info.nightscout.comboctl.base.testUtils.runBlockingWithWatchdog
|
import info.nightscout.comboctl.base.testUtils.runBlockingWithWatchdog
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
|
|
||||||
class PairingSessionTest {
|
class PairingSessionTest {
|
||||||
enum class PacketDirection {
|
enum class PacketDirection {
|
||||||
|
|
|
@ -7,12 +7,12 @@ import info.nightscout.comboctl.base.testUtils.TestRefPacketItem
|
||||||
import info.nightscout.comboctl.base.testUtils.checkTestPacketSequence
|
import info.nightscout.comboctl.base.testUtils.checkTestPacketSequence
|
||||||
import info.nightscout.comboctl.base.testUtils.produceTpLayerPacket
|
import info.nightscout.comboctl.base.testUtils.produceTpLayerPacket
|
||||||
import info.nightscout.comboctl.base.testUtils.runBlockingWithWatchdog
|
import info.nightscout.comboctl.base.testUtils.runBlockingWithWatchdog
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlinx.datetime.UtcOffset
|
import kotlinx.datetime.UtcOffset
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class PumpIOTest {
|
class PumpIOTest {
|
||||||
// Common test code.
|
// Common test code.
|
||||||
|
@ -599,23 +599,23 @@ class PumpIOTest {
|
||||||
ApplicationLayer.CMDHistoryEvent(
|
ApplicationLayer.CMDHistoryEvent(
|
||||||
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 0, second = 19),
|
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 0, second = 19),
|
||||||
80666,
|
80666,
|
||||||
ApplicationLayer.CMDHistoryEventDetail.ExtendedBolusStarted(177, 15)
|
ApplicationLayer.CMDHistoryEventDetail.ExtendedBolusStarted(177, 15, true)
|
||||||
),
|
),
|
||||||
ApplicationLayer.CMDHistoryEvent(
|
ApplicationLayer.CMDHistoryEvent(
|
||||||
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 15, second = 18),
|
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 15, second = 18),
|
||||||
80668,
|
80668,
|
||||||
ApplicationLayer.CMDHistoryEventDetail.ExtendedBolusEnded(177, 15)
|
ApplicationLayer.CMDHistoryEventDetail.ExtendedBolusEnded(177, 15, true)
|
||||||
),
|
),
|
||||||
|
|
||||||
ApplicationLayer.CMDHistoryEvent(
|
ApplicationLayer.CMDHistoryEvent(
|
||||||
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 21, second = 31),
|
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 21, second = 31),
|
||||||
80670,
|
80670,
|
||||||
ApplicationLayer.CMDHistoryEventDetail.MultiwaveBolusStarted(193, 37, 30)
|
ApplicationLayer.CMDHistoryEventDetail.MultiwaveBolusStarted(193, 37, 30, true)
|
||||||
),
|
),
|
||||||
ApplicationLayer.CMDHistoryEvent(
|
ApplicationLayer.CMDHistoryEvent(
|
||||||
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 51, second = 8),
|
LocalDateTime(year = 2021, monthNumber = 2, dayOfMonth = 9, hour = 17, minute = 51, second = 8),
|
||||||
80672,
|
80672,
|
||||||
ApplicationLayer.CMDHistoryEventDetail.MultiwaveBolusEnded(193, 37, 30)
|
ApplicationLayer.CMDHistoryEventDetail.MultiwaveBolusEnded(193, 37, 30, true)
|
||||||
),
|
),
|
||||||
|
|
||||||
ApplicationLayer.CMDHistoryEvent(
|
ApplicationLayer.CMDHistoryEvent(
|
||||||
|
|
|
@ -5,6 +5,8 @@ import info.nightscout.comboctl.base.testUtils.TestPumpStateStore
|
||||||
import info.nightscout.comboctl.base.testUtils.WatchdogTimeoutException
|
import info.nightscout.comboctl.base.testUtils.WatchdogTimeoutException
|
||||||
import info.nightscout.comboctl.base.testUtils.coroutineScopeWithWatchdog
|
import info.nightscout.comboctl.base.testUtils.coroutineScopeWithWatchdog
|
||||||
import info.nightscout.comboctl.base.testUtils.runBlockingWithWatchdog
|
import info.nightscout.comboctl.base.testUtils.runBlockingWithWatchdog
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.datetime.UtcOffset
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
@ -13,8 +15,6 @@ import kotlin.test.assertIs
|
||||||
import kotlin.test.assertNotEquals
|
import kotlin.test.assertNotEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.datetime.UtcOffset
|
|
||||||
|
|
||||||
class TransportLayerTest {
|
class TransportLayerTest {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -4,7 +4,6 @@ import info.nightscout.comboctl.base.BluetoothAddress
|
||||||
import info.nightscout.comboctl.base.BluetoothDevice
|
import info.nightscout.comboctl.base.BluetoothDevice
|
||||||
import info.nightscout.comboctl.base.ComboFrameParser
|
import info.nightscout.comboctl.base.ComboFrameParser
|
||||||
import info.nightscout.comboctl.base.ComboIO
|
import info.nightscout.comboctl.base.ComboIO
|
||||||
import info.nightscout.comboctl.base.ProgressReporter
|
|
||||||
import info.nightscout.comboctl.base.byteArrayListOfInts
|
import info.nightscout.comboctl.base.byteArrayListOfInts
|
||||||
import info.nightscout.comboctl.base.toComboFrame
|
import info.nightscout.comboctl.base.toComboFrame
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
|
@ -6,8 +6,8 @@ import info.nightscout.comboctl.base.ComboIO
|
||||||
import info.nightscout.comboctl.base.TransportLayer
|
import info.nightscout.comboctl.base.TransportLayer
|
||||||
import info.nightscout.comboctl.base.byteArrayListOfInts
|
import info.nightscout.comboctl.base.byteArrayListOfInts
|
||||||
import info.nightscout.comboctl.base.toTransportLayerPacket
|
import info.nightscout.comboctl.base.toTransportLayerPacket
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
class TestComboIO : ComboIO {
|
class TestComboIO : ComboIO {
|
||||||
val sentPacketData = newTestPacketSequence()
|
val sentPacketData = newTestPacketSequence()
|
||||||
|
|
|
@ -4,14 +4,14 @@ import info.nightscout.comboctl.base.Cipher
|
||||||
import info.nightscout.comboctl.base.ComboException
|
import info.nightscout.comboctl.base.ComboException
|
||||||
import info.nightscout.comboctl.base.Nonce
|
import info.nightscout.comboctl.base.Nonce
|
||||||
import info.nightscout.comboctl.base.TransportLayer
|
import info.nightscout.comboctl.base.TransportLayer
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
|
||||||
import kotlin.test.fail
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
// Utility function to combine runBlocking() with a watchdog.
|
// Utility function to combine runBlocking() with a watchdog.
|
||||||
// A coroutine is started with runBlocking(), and inside that
|
// A coroutine is started with runBlocking(), and inside that
|
||||||
|
|
|
@ -20,17 +20,17 @@ import info.nightscout.comboctl.parser.testFrameW6CancelTbrWarningScreen
|
||||||
import info.nightscout.comboctl.parser.testTimeAndDateSettingsHourPolishScreen
|
import info.nightscout.comboctl.parser.testTimeAndDateSettingsHourPolishScreen
|
||||||
import info.nightscout.comboctl.parser.testTimeAndDateSettingsHourRussianScreen
|
import info.nightscout.comboctl.parser.testTimeAndDateSettingsHourRussianScreen
|
||||||
import info.nightscout.comboctl.parser.testTimeAndDateSettingsHourTurkishScreen
|
import info.nightscout.comboctl.parser.testTimeAndDateSettingsHourTurkishScreen
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertIs
|
import kotlin.test.assertIs
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.junit.jupiter.api.BeforeAll
|
|
||||||
|
|
||||||
class ParsedDisplayFrameStreamTest {
|
class ParsedDisplayFrameStreamTest {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -13,13 +13,6 @@ import info.nightscout.comboctl.parser.MainScreenContent
|
||||||
import info.nightscout.comboctl.parser.ParsedScreen
|
import info.nightscout.comboctl.parser.ParsedScreen
|
||||||
import info.nightscout.comboctl.parser.Quickinfo
|
import info.nightscout.comboctl.parser.Quickinfo
|
||||||
import info.nightscout.comboctl.parser.ReservoirState
|
import info.nightscout.comboctl.parser.ReservoirState
|
||||||
import kotlin.reflect.KClassifier
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertContentEquals
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertIs
|
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
@ -29,7 +22,14 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertContentEquals
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertIs
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class RTNavigationTest {
|
class RTNavigationTest {
|
||||||
/* RTNavigationContext implementation for testing out RTNavigation functionality.
|
/* RTNavigationContext implementation for testing out RTNavigation functionality.
|
||||||
|
@ -360,7 +360,6 @@ class RTNavigationTest {
|
||||||
decrementButton = RTNavigationButton.DOWN
|
decrementButton = RTNavigationButton.DOWN
|
||||||
)
|
)
|
||||||
assertEquals(Pair(0, RTNavigationButton.CHECK), result)
|
assertEquals(Pair(0, RTNavigationButton.CHECK), result)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -2,12 +2,12 @@ package info.nightscout.comboctl.parser
|
||||||
|
|
||||||
import info.nightscout.comboctl.base.DisplayFrame
|
import info.nightscout.comboctl.base.DisplayFrame
|
||||||
import info.nightscout.comboctl.base.timeWithoutDate
|
import info.nightscout.comboctl.base.timeWithoutDate
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.LocalDateTime
|
|
||||||
|
|
||||||
class ParserTest {
|
class ParserTest {
|
||||||
class TestContext(displayFrame: DisplayFrame, tokenOffset: Int, skipTitleString: Boolean = false, parseTopLeftTime: Boolean = false) {
|
class TestContext(displayFrame: DisplayFrame, tokenOffset: Int, skipTitleString: Boolean = false, parseTopLeftTime: Boolean = false) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ class ComboV2Fragment : DaggerFragment() {
|
||||||
else
|
else
|
||||||
rh.gs(R.string.combov2_cancelling_tbr)
|
rh.gs(R.string.combov2_cancelling_tbr)
|
||||||
is ComboCtlPump.DeliveringBolusCommandDesc ->
|
is ComboCtlPump.DeliveringBolusCommandDesc ->
|
||||||
rh.gs(R.string.combov2_delivering_bolus_cmddesc, desc.bolusAmount.cctlBolusToIU())
|
rh.gs(R.string.combov2_delivering_bolus_cmddesc, desc.immediateBolusAmount.cctlBolusToIU())
|
||||||
is ComboCtlPump.FetchingTDDHistoryCommandDesc ->
|
is ComboCtlPump.FetchingTDDHistoryCommandDesc ->
|
||||||
rh.gs(R.string.combov2_fetching_tdd_history_cmddesc)
|
rh.gs(R.string.combov2_fetching_tdd_history_cmddesc)
|
||||||
is ComboCtlPump.UpdatingPumpDateTimeCommandDesc ->
|
is ComboCtlPump.UpdatingPumpDateTimeCommandDesc ->
|
||||||
|
|
|
@ -1768,8 +1768,8 @@ class ComboV2Plugin @Inject constructor (
|
||||||
is RTCommandProgressStage.DeliveringBolus ->
|
is RTCommandProgressStage.DeliveringBolus ->
|
||||||
rh.gs(
|
rh.gs(
|
||||||
R.string.combov2_delivering_bolus,
|
R.string.combov2_delivering_bolus,
|
||||||
stage.deliveredAmount.cctlBolusToIU(),
|
stage.deliveredImmediateAmount .cctlBolusToIU(),
|
||||||
stage.totalAmount.cctlBolusToIU()
|
stage.totalImmediateAmount.cctlBolusToIU()
|
||||||
)
|
)
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,27 @@ class ComboV2PairingActivity : DaggerAppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val binding: Combov2PairingActivityBinding = DataBindingUtil.setContentView(
|
||||||
|
this, R.layout.combov2_pairing_activity)
|
||||||
|
|
||||||
|
// In the NotInitialized state, the PumpManager is unavailable because it cannot
|
||||||
|
// function without Bluetooth permissions. Several of ComboV2Plugin's functions
|
||||||
|
// such as getPairingProgressFlow() depend on PumpManager though. To prevent UI
|
||||||
|
// controls from becoming active without having a PumpManager, show instead a
|
||||||
|
// view on the activity that explains why pairing is currently not possible.
|
||||||
|
if (combov2Plugin.driverStateUIFlow.value == ComboV2Plugin.DriverState.NotInitialized) {
|
||||||
|
aapsLogger.info(LTag.PUMP, "Cannot pair right now; disabling pairing UI controls, showing message instead")
|
||||||
|
|
||||||
|
binding.combov2PairingSectionInitial.visibility = View.GONE
|
||||||
|
binding.combov2PairingSectionCannotPairDriverNotInitialized.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
binding.combov2CannotPairGoBack.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Install an activity result caller for when the user presses
|
// Install an activity result caller for when the user presses
|
||||||
// "deny" or "reject" in the dialog that pops up when Android
|
// "deny" or "reject" in the dialog that pops up when Android
|
||||||
// asks for permission to enable device discovery. In such a
|
// asks for permission to enable device discovery. In such a
|
||||||
|
@ -54,9 +75,6 @@ class ComboV2PairingActivity : DaggerAppCompatActivity() {
|
||||||
startPairingActivityLauncher.launch(intent)
|
startPairingActivityLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
val binding: Combov2PairingActivityBinding = DataBindingUtil.setContentView(
|
|
||||||
this, R.layout.combov2_pairing_activity)
|
|
||||||
|
|
||||||
binding.combov2PairingFinishedOk.setOnClickListener {
|
binding.combov2PairingFinishedOk.setOnClickListener {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
@ -263,6 +281,10 @@ class ComboV2PairingActivity : DaggerAppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
// In the NotInitialized state, getPairingProgressFlow() crashes because there
|
||||||
|
// is no PumpManager present. But in that state, the pairing progress flow needs
|
||||||
|
// no reset because no pairing can happen in that state anyway.
|
||||||
|
if (combov2Plugin.driverStateUIFlow.value != ComboV2Plugin.DriverState.NotInitialized) {
|
||||||
// Reset the pairing progress reported to allow for future pairing attempts.
|
// Reset the pairing progress reported to allow for future pairing attempts.
|
||||||
// Do this only after pairing was finished or aborted. onDestroy() can be
|
// Do this only after pairing was finished or aborted. onDestroy() can be
|
||||||
// called in the middle of a pairing process, and we do not want to reset
|
// called in the middle of a pairing process, and we do not want to reset
|
||||||
|
@ -276,8 +298,10 @@ class ComboV2PairingActivity : DaggerAppCompatActivity() {
|
||||||
)
|
)
|
||||||
combov2Plugin.resetPairingProgress()
|
combov2Plugin.resetPairingProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,43 @@
|
||||||
|
|
||||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/combov2_pairing_section_cannot_pair_driver_not_initialized"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:singleLine="false"
|
||||||
|
android:text="@string/combov2_cannot_pair_driver_not_initialized_explanation"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/combov2_cannot_pair_go_back"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/combov2_go_back" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:id="@+id/combov2_pairing_section_initial"
|
android:id="@+id/combov2_pairing_section_initial"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -137,4 +137,6 @@ buttons at the same time to cancel pairing)\n
|
||||||
<string name="combov2_dst_ended">Daylight savings time (DST) ended</string>
|
<string name="combov2_dst_ended">Daylight savings time (DST) ended</string>
|
||||||
<string name="combov2_cannot_connect_pump_error_observed">Cannot connect to pump because the pump reported an error. User must handle the error and then either wait 5 minutes or press the Refresh button in the driver tab.</string>
|
<string name="combov2_cannot_connect_pump_error_observed">Cannot connect to pump because the pump reported an error. User must handle the error and then either wait 5 minutes or press the Refresh button in the driver tab.</string>
|
||||||
<string name="combov2_refresh_pump_status_after_error">Refreshing pump status after the pump reported an error</string>
|
<string name="combov2_refresh_pump_status_after_error">Refreshing pump status after the pump reported an error</string>
|
||||||
|
<string name="combov2_go_back">Go back</string>
|
||||||
|
<string name="combov2_cannot_pair_driver_not_initialized_explanation">Cannot perform pairing because the driver is not initialized. This typically happens because the necessary Bluetooth permissions have not been granted. Go back, grant the Bluetooth permissions, then try again to pair.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue